Line data Source code
1 : /*!
2 : * \file esys/repo/libgit2/gitimpl_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/gitimpl.h"
20 : #include "esys/repo/libgit2/guard.h"
21 : #include "esys/repo/libgit2/guards.h"
22 : #include "esys/repo/git/sshbackend.h"
23 : #include "esys/repo/git/sshenv.h"
24 : #include "esys/repo/git/updatetip.h"
25 : #include "esys/repo/libssh2/ssh.h"
26 :
27 : #include <esys/trace/call.h>
28 : #include <esys/trace/macros.h>
29 :
30 : #include <git2.h>
31 : #include <libssh2.h>
32 :
33 : #include <boost/algorithm/string.hpp>
34 : #include <boost/filesystem.hpp>
35 :
36 : #include <cstring>
37 : #include <sstream>
38 : #include <cassert>
39 : #include <algorithm>
40 : #include <cstdlib>
41 : #include <mutex>
42 :
43 : #include <iostream>
44 :
45 : namespace esys::repo::libgit2
46 : {
47 :
48 : std::unique_ptr<LibGit2> GitImpl::s_libgt2 = nullptr;
49 :
50 260 : GitImpl::GitImpl(Git *self)
51 260 : : m_self(self)
52 260 : , m_notes(self)
53 : {
54 261 : if (s_libgt2 == nullptr) s_libgt2 = std::make_unique<LibGit2>();
55 260 : }
56 :
57 260 : GitImpl::~GitImpl()
58 : {
59 260 : if (m_repo != nullptr) close_on_error();
60 260 : }
61 :
62 389 : Result GitImpl::open(const std::string &folder)
63 : {
64 389 : Result result;
65 389 : ETRC_CALL_RET(result, folder);
66 :
67 389 : self()->open_time();
68 :
69 389 : int result_int = git_repository_open(&m_repo, folder.c_str());
70 389 : if (result_int < 0)
71 : {
72 0 : result = ESYSREPO_RESULT(ResultCode::GIT_ERROR_OPENING_REPO, "open git repo " + folder);
73 0 : return ETRC_RET(ESYSREPO_RESULT(check_error(result)));
74 : }
75 :
76 778 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
77 389 : }
78 :
79 1003 : bool GitImpl::is_open()
80 : {
81 1003 : return (m_repo != nullptr);
82 : }
83 :
84 3 : Result GitImpl::init_bare(const std::string &folder_path)
85 : {
86 3 : Result result;
87 3 : ETRC_CALL_RET(result, folder_path);
88 :
89 3 : if (is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_ALREADY_OPENED));
90 :
91 3 : git_repository_init_options initopts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
92 3 : initopts.flags = GIT_REPOSITORY_INIT_MKPATH;
93 3 : initopts.flags |= GIT_REPOSITORY_INIT_BARE;
94 :
95 3 : int result_int = git_repository_init_ext(&m_repo, folder_path.c_str(), &initopts);
96 3 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
97 :
98 6 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
99 3 : }
100 :
101 1 : Result GitImpl::init(const std::string &folder_path)
102 : {
103 1 : Result result;
104 1 : ETRC_CALL_RET(result, folder_path);
105 :
106 1 : if (is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_ALREADY_OPENED));
107 :
108 1 : self()->cmd_start();
109 :
110 1 : git_repository_init_options initopts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
111 1 : initopts.flags = GIT_REPOSITORY_INIT_MKPATH;
112 :
113 1 : int result_int = git_repository_init_ext(&m_repo, folder_path.c_str(), &initopts);
114 1 : if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
115 :
116 2 : return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
117 1 : }
118 :
119 427 : Result GitImpl::close()
120 : {
121 427 : Result result;
122 427 : ETRC_CALL_RET_NP(result);
123 :
124 427 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
125 :
126 427 : git_repository_free(m_repo);
127 :
128 427 : m_repo = nullptr;
129 :
130 427 : self()->close_time();
131 :
132 854 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
133 427 : }
134 :
135 1 : void GitImpl::close_on_error()
136 : {
137 1 : ETRC_CALL_NP();
138 1 : self()->close_time();
139 1 : if (m_repo == nullptr) return;
140 :
141 1 : git_repository_free(m_repo);
142 1 : m_repo = nullptr;
143 1 : }
144 :
145 57 : Result GitImpl::get_remotes(std::vector<git::Remote> &remotes)
146 : {
147 57 : Result result;
148 57 : ETRC_CALL_RET_BEGIN(result, remotes);
149 57 : ETRC_CALL_RET_OUT_END(remotes);
150 :
151 57 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
152 :
153 57 : self()->cmd_start();
154 :
155 57 : remotes.clear();
156 57 : GuardS<git_strarray> data;
157 :
158 57 : int result_int = git_remote_list(data.get(), m_repo);
159 57 : if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
160 :
161 57 : char **p = data.get()->strings;
162 :
163 57 : if (data.get()->count == 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
164 :
165 57 : if (p == nullptr) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "No data")));
166 :
167 117 : for (auto idx = 0; idx < data.get()->count; ++idx)
168 : {
169 60 : char *name = *p;
170 60 : if (name == nullptr) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "No name")));
171 :
172 60 : git::Remote remote;
173 :
174 60 : remote.set_name(name);
175 :
176 60 : remotes.push_back(remote);
177 60 : p += 1;
178 60 : }
179 :
180 117 : for (auto &remote_item : remotes)
181 : {
182 60 : Guard<git_remote> remote; // This will automically release the git_remote
183 :
184 60 : const git_remote_head **refs = nullptr;
185 60 : size_t refs_len = 0;
186 60 : size_t i = 0;
187 60 : git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
188 :
189 : // Find the remote by name
190 60 : result_int = git_remote_lookup(remote.get_p(), m_repo, remote_item.get_name().c_str());
191 60 : if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
192 :
193 60 : const char *url = git_remote_url(remote.get());
194 120 : remote_item.set_url(url);
195 :
196 : /*result = git_remote_ls(&refs, &refs_len, remote.get());
197 : if (result < 0) return check_error(result, ""); */
198 60 : }
199 :
200 114 : return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
201 57 : }
202 :
203 17 : Result GitImpl::get_head_branch_remote(git::Branch &branch, git::Remote &remote)
204 : {
205 17 : Result result;
206 17 : ETRC_CALL_RET_BEGIN(result, branch, remote);
207 17 : ETRC_CALL_RET_OUT_END(branch, remote);
208 :
209 17 : if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
210 :
211 17 : Guard<git_reference> head_ref;
212 :
213 17 : auto result_int = git_repository_head(head_ref.get_p(), m_repo);
214 17 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
215 17 : auto head_ref_name = git_reference_name(head_ref.get());
216 17 : auto head_ref_short = git_reference_shorthand(head_ref.get());
217 :
218 17 : branch.set_is_head(true);
219 17 : branch.set_ref_name(head_ref_name);
220 17 : branch.set_name(head_ref_short);
221 17 : branch.set_type(git::BranchType::LOCAL);
222 :
223 17 : result_int = git_repository_head_detached(m_repo);
224 17 : if (result_int == 1)
225 0 : branch.set_detached(true);
226 17 : else if (result_int == 0)
227 17 : branch.set_detached(false);
228 0 : else if (result_int < 0)
229 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
230 :
231 17 : git_buf remote_name_buf = {nullptr};
232 17 : result_int = git_branch_upstream_remote(&remote_name_buf, m_repo, head_ref_name);
233 17 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
234 :
235 34 : std::string remote_name(remote_name_buf.ptr, remote_name_buf.ptr + remote_name_buf.size);
236 17 : git_buf_dispose(&remote_name_buf);
237 :
238 17 : branch.set_remote_name(remote_name);
239 17 : remote.set_name(remote_name);
240 :
241 17 : Guard<git_remote> remote_git;
242 17 : result = find_remote(remote_git, remote_name.c_str());
243 17 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
244 :
245 17 : std::string remote_url = git_remote_url(remote_git.get());
246 17 : remote.set_url(remote_url);
247 :
248 17 : Guard<git_reference> upstream_ref;
249 17 : result_int = git_branch_upstream(upstream_ref.get_p(), head_ref.get());
250 17 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
251 :
252 17 : auto up_ref_name = git_reference_name(upstream_ref.get());
253 17 : branch.set_remote_branch(up_ref_name);
254 17 : auto up_ref_short = git_reference_shorthand(upstream_ref.get());
255 17 : branch.set_remote_branch_name(up_ref_short);
256 34 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
257 68 : }
258 :
259 6 : Result GitImpl::add_remote(const std::string &name, const std::string &url)
260 : {
261 6 : Result result;
262 6 : ETRC_CALL_RET(result, name, url);
263 :
264 6 : if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
265 :
266 6 : self()->cmd_start();
267 :
268 6 : Guard<git_remote> remote;
269 :
270 6 : int result_int = git_remote_create(remote.get_p(), m_repo, name.c_str(), url.c_str());
271 6 : if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
272 :
273 12 : return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
274 6 : }
275 :
276 131 : Result GitImpl::get_branches(git::Branches &branches, git::BranchType branch_type)
277 : {
278 131 : Result result;
279 131 : ETRC_CALL_RET_BEGIN(result, branches, branch_type);
280 131 : ETRC_CALL_RET_OUT_END(branches);
281 :
282 131 : if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
283 :
284 131 : self()->cmd_start();
285 :
286 131 : Guard<git_branch_iterator> branch_it;
287 131 : git_branch_t list_flags = GIT_BRANCH_ALL;
288 :
289 131 : convert(branch_type, list_flags);
290 :
291 131 : int result_int = git_branch_iterator_new(branch_it.get_p(), m_repo, list_flags);
292 131 : if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
293 :
294 271 : while (true)
295 : {
296 271 : Guard<git_reference> ref;
297 271 : git_branch_t git_branch_type = GIT_BRANCH_ALL;
298 :
299 271 : result_int = git_branch_next(ref.get_p(), &git_branch_type, branch_it.get());
300 402 : if (result_int == GIT_ITEROVER) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
301 140 : if (result_int < 0) ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
302 :
303 140 : std::shared_ptr<git::Branch> branch = std::make_shared<git::Branch>();
304 140 : const char *branch_name = nullptr;
305 140 : result_int = git_branch_name(&branch_name, ref.get());
306 140 : if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
307 :
308 140 : std::string ref_name = git_reference_name(ref.get());
309 :
310 140 : branch->set_name(branch_name);
311 140 : branch->set_ref_name(ref_name);
312 :
313 140 : switch (git_branch_type)
314 : {
315 0 : case GIT_BRANCH_ALL: branch_type = git::BranchType::ALL; break;
316 140 : case GIT_BRANCH_LOCAL: branch_type = git::BranchType::LOCAL; break;
317 0 : case GIT_BRANCH_REMOTE: branch_type = git::BranchType::REMOTE; break;
318 0 : default: branch_type = git::BranchType::NOT_SET;
319 : }
320 :
321 140 : branch->set_type(branch_type);
322 :
323 140 : result_int = git_branch_is_head(ref.get());
324 140 : if (result_int == 1) branch->set_is_head(true);
325 :
326 140 : if (branch_type == git::BranchType::LOCAL)
327 : {
328 140 : git_buf buf_out = {nullptr};
329 140 : result_int = git_branch_upstream_remote(&buf_out, m_repo, ref_name.c_str());
330 140 : if (result_int == 0)
331 : {
332 280 : std::string remote_name(buf_out.ptr, buf_out.ptr + buf_out.size);
333 140 : branch->set_remote_name(remote_name);
334 140 : git_buf_dispose(&buf_out);
335 140 : }
336 140 : result_int = git_branch_upstream_name(&buf_out, m_repo, ref_name.c_str());
337 140 : if (result_int == 0)
338 : {
339 280 : std::string remote_branch(buf_out.ptr, buf_out.ptr + buf_out.size);
340 140 : branch->set_remote_branch(remote_branch);
341 140 : git_buf_dispose(&buf_out);
342 140 : }
343 : }
344 : else
345 0 : check_error(result, ""); //! \TODO why??
346 420 : branches.add(branch);
347 411 : }
348 : return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
349 131 : }
350 :
351 33 : Result_t<bool> GitImpl::has_branch(const std::string &name, git::BranchType branch_type)
352 : {
353 33 : Result_t<bool> result;
354 33 : ETRC_CALL_RET(result, name, branch_type);
355 :
356 33 : Guard<git_reference> ref;
357 33 : git_branch_t git_branch_type = GIT_BRANCH_ALL;
358 33 : Guard<git_annotated_commit> annotated_commit;
359 : // Guard<git_reference> branch_ref;
360 33 : Guard<git_reference> input_branch_ref;
361 33 : std::string new_ref = name;
362 :
363 33 : result = resolve_ref(input_branch_ref.get_p(), annotated_commit.get_p(), name);
364 33 : if (result.error())
365 : {
366 31 : self()->debug(0, "branch not found : " + name);
367 : // In the case where the content of branch doesn't have the full qualifier,
368 : // try to to guess
369 :
370 31 : result = find_ref(input_branch_ref.get_p(), annotated_commit.get_p(), name, new_ref);
371 51 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT_T(result, false));
372 : }
373 :
374 26 : return ETRC_RET(ESYSREPO_RESULT_T(result, true));
375 33 : }
376 :
377 27 : Result GitImpl::get_hash(const std::string &revision, std::string &hash, git::BranchType branch_type)
378 : {
379 27 : Result result;
380 27 : ETRC_CALL_RET_BEGIN(result, revision, hash, branch_type);
381 27 : ETRC_CALL_RET_OUT_END(hash);
382 :
383 27 : Guard<git_annotated_commit> annotated_commit;
384 27 : Guard<git_reference> revision_ref;
385 27 : std::string new_ref = revision;
386 :
387 27 : result = resolve_ref(revision_ref.get_p(), annotated_commit.get_p(), revision);
388 27 : if (result.error())
389 : {
390 0 : self()->debug(0, "revision not found : " + revision);
391 : // In the case where the content of branch doesn't have the full qualifier,
392 : // try to to guess
393 :
394 0 : result = find_ref(revision_ref.get_p(), annotated_commit.get_p(), revision, new_ref);
395 0 : if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
396 : }
397 :
398 27 : const git_oid *oid = git_annotated_commit_id(annotated_commit.get());
399 27 : if (oid == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_GET_HASH_FAILED));
400 :
401 27 : result = convert_bin_hex(*oid, hash);
402 54 : return ETRC_RET(ESYSREPO_RESULT(result));
403 27 : }
404 :
405 7 : Result GitImpl::treeish_to_tree(Guard<git_tree> &tree, git_repository *repo, const char *treeish)
406 : {
407 7 : Guard<git_object> obj;
408 :
409 7 : int result = git_revparse_single(obj.get_p(), repo, treeish);
410 7 : if (result < 0) return ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result);
411 :
412 7 : result = git_object_peel((git_object **)tree.get_p(), obj.get(), GIT_OBJECT_TREE);
413 7 : if (result < 0) return ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result);
414 7 : return ESYSREPO_RESULT(ResultCode::OK);
415 7 : }
416 :
417 2 : Result GitImpl::walk_commits(std::shared_ptr<git::WalkCommit> walk_commit)
418 : {
419 2 : Result result;
420 2 : ETRC_CALL_RET_NP(result);
421 :
422 2 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
423 2 : if (walk_commit == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
424 :
425 2 : git::CommitHash hash;
426 2 : Result rresult = get_last_commit(hash);
427 2 : if (rresult.error()) return ETRC_RET(ESYSREPO_RESULT(rresult));
428 :
429 2 : git_oid oid;
430 :
431 2 : rresult = convert_hex_bin(hash.get_hash(), oid);
432 2 : if (rresult.error()) return ETRC_RET(ESYSREPO_RESULT(rresult));
433 :
434 2 : Guard<git_revwalk> walker;
435 2 : Guard<git_commit> commit;
436 :
437 2 : git_revwalk_new(walker.get_p(), m_repo);
438 2 : git_revwalk_sorting(walker.get(), GIT_SORT_TOPOLOGICAL);
439 2 : git_revwalk_push(walker.get(), &oid);
440 :
441 : const char *commit_message;
442 : const git_signature *commit_signature;
443 : git_time_t commit_time;
444 10 : int result_int = 0;
445 :
446 10 : while (git_revwalk_next(&oid, walker.get()) == 0)
447 : {
448 8 : result_int = git_commit_lookup(commit.get_p(), m_repo, &oid);
449 8 : if (result_int != 0)
450 : {
451 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
452 : }
453 :
454 8 : commit_message = git_commit_message(commit.get());
455 8 : commit_signature = git_commit_committer(commit.get());
456 8 : commit_time = git_commit_time(commit.get());
457 :
458 8 : std::time_t commit_time_t = static_cast<std::time_t>(commit_time);
459 8 : std::string commit_time_str = std::asctime(std::localtime(&commit_time_t));
460 :
461 8 : auto commit_info = std::make_shared<git::Commit>();
462 :
463 8 : commit_info->set_message(commit_message);
464 8 : commit_info->set_author(commit_signature->name);
465 8 : commit_info->set_email(commit_signature->email);
466 8 : commit_info->set_date_time(std::chrono::system_clock::from_time_t(commit_time_t));
467 :
468 8 : rresult = convert_bin_hex(oid, commit_info->get_hash().get_hash());
469 8 : if (rresult.error()) return ETRC_RET(ESYSREPO_RESULT(rresult));
470 :
471 16 : walk_commit->new_commit(self(), commit_info);
472 16 : commit.reset();
473 8 : }
474 :
475 4 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
476 4 : }
477 :
478 4 : Result GitImpl::diff(const git::CommitHash &commit_hash, std::shared_ptr<git::Diff> diff)
479 : {
480 4 : Result result;
481 4 : ETRC_CALL_RET_BEGIN(result, commit_hash, diff);
482 4 : ETRC_CALL_RET_OUT_END(diff);
483 :
484 4 : Guard<git_tree> commit_tree;
485 4 : Guard<git_tree> parent_tree;
486 4 : bool has_parent = true;
487 :
488 4 : result = treeish_to_tree(commit_tree, m_repo, commit_hash.get_hash().c_str());
489 4 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
490 :
491 4 : git::CommitHash parent_hash;
492 4 : result = get_parent_commit(commit_hash, parent_hash);
493 4 : if (result.error())
494 : has_parent = false;
495 : else
496 : {
497 3 : result = treeish_to_tree(parent_tree, m_repo, parent_hash.get_hash().c_str());
498 3 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
499 : }
500 :
501 4 : Guard<git_diff> the_diff;
502 4 : git_diff_options diffopts;
503 :
504 4 : int result_int = git_diff_options_init(&diffopts, GIT_DIFF_OPTIONS_VERSION);
505 4 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
506 :
507 : // diffopts.id_abbrev = 40;
508 :
509 4 : if (has_parent)
510 3 : result_int = git_diff_tree_to_tree(the_diff.get_p(), m_repo, parent_tree.get(), commit_tree.get(), &diffopts);
511 : else
512 1 : result_int = git_diff_tree_to_tree(the_diff.get_p(), m_repo, nullptr, commit_tree.get(), &diffopts);
513 :
514 4 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
515 :
516 4 : Guard<git_diff_stats> diff_stats;
517 :
518 4 : result_int = git_diff_get_stats(diff_stats.get_p(), the_diff.get());
519 4 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
520 :
521 : /*diff_file_stats *filestats;
522 :
523 : size_t files_changed;
524 : size_t insertions;
525 : size_t deletions;
526 : size_t renames;
527 :
528 : size_t max_name;
529 : size_t max_filestat;
530 : int max_digits; */
531 4 : diff->set_files_changed(static_cast<unsigned int>(git_diff_stats_files_changed(diff_stats.get())));
532 4 : diff->set_insertions(static_cast<unsigned int>(git_diff_stats_insertions(diff_stats.get())));
533 4 : diff->set_deletions(static_cast<unsigned int>(git_diff_stats_deletions(diff_stats.get())));
534 4 : diff->set_renames(static_cast<unsigned int>(git_diff_stats_renames(diff_stats.get())));
535 8 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
536 12 : }
537 :
538 0 : Result GitImpl::get_ahead_behind(git::AheadBehind &ahead_behind, const git::Branch &local_branch)
539 : {
540 0 : Result result;
541 0 : ETRC_CALL_RET_BEGIN(result, ahead_behind, local_branch);
542 0 : ETRC_CALL_RET_OUT_END(ahead_behind);
543 :
544 0 : size_t ahead = 0;
545 0 : size_t behind = 0;
546 0 : git_oid local;
547 0 : git_oid upstream;
548 :
549 0 : ahead_behind.set_ahead(0);
550 0 : ahead_behind.set_behind(0);
551 :
552 0 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
553 0 : if (local_branch.get_remote_branch().empty()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
554 :
555 0 : result = get_commit_hash_from_branch(local_branch.get_name(), local);
556 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
557 :
558 0 : result = get_commit_hash_from_branch(local_branch.get_remote_branch(), upstream);
559 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
560 :
561 0 : int result_int = git_graph_ahead_behind(&ahead, &behind, m_repo, &local, &upstream);
562 0 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR));
563 :
564 0 : ahead_behind.set_ahead(ahead);
565 0 : ahead_behind.set_behind(behind);
566 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
567 0 : }
568 :
569 20 : Result GitImpl::get_ahead_behind(git::AheadBehind &ahead_behind, const std::string &first_ref,
570 : const std::string &second_ref)
571 : {
572 20 : Result result;
573 20 : ETRC_CALL_RET_BEGIN(result, ahead_behind, first_ref, second_ref);
574 20 : ETRC_CALL_RET_OUT_END(ahead_behind);
575 :
576 20 : size_t ahead = 0;
577 20 : size_t behind = 0;
578 20 : git_oid first_ref_oid;
579 20 : git_oid second_ref_oid;
580 :
581 20 : ahead_behind.set_ahead(0);
582 20 : ahead_behind.set_behind(0);
583 :
584 20 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
585 :
586 20 : result = get_commit_hash_from_branch(first_ref, first_ref_oid);
587 20 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
588 :
589 20 : result = get_commit_hash_from_branch(second_ref, second_ref_oid);
590 20 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
591 :
592 20 : int result_int = git_graph_ahead_behind(&ahead, &behind, m_repo, &first_ref_oid, &second_ref_oid);
593 20 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR));
594 :
595 20 : ahead_behind.set_ahead(ahead);
596 20 : ahead_behind.set_behind(behind);
597 40 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
598 20 : }
599 :
600 37 : Result GitImpl::clone(const std::string &url, const std::string &path, const std::string &rev,
601 : const git::CloneOptions &options)
602 : {
603 37 : Result rresult;
604 37 : ETRC_CALL_RET(rresult, url, path, rev);
605 :
606 37 : int result = 0;
607 :
608 37 : self()->debug(1, "[GitImpl::clone] begin ...");
609 :
610 37 : const bool https = (url.find("https:") == 0) || (url.find("http:") == 0);
611 37 : const bool ssh = is_ssh_url(url);
612 :
613 : // Payload must outlive git_clone when single_branch remote_cb is set.
614 74 : struct CloneRemotePayload
615 : {
616 : std::string rev;
617 37 : } remote_payload;
618 :
619 74 : auto make_opts = [&](git_clone_options &opts) {
620 37 : opts = GIT_CLONE_OPTIONS_INIT;
621 : // Default CloneOptions: ignore rev (legacy remote-default tip only).
622 37 : if (options.checkout_rev && !rev.empty()) opts.checkout_branch = rev.c_str();
623 37 : if (options.checkout_rev && options.single_branch && !rev.empty())
624 : {
625 0 : remote_payload.rev = rev;
626 0 : opts.remote_cb = [](git_remote **out, git_repository *repo, const char *name, const char *url,
627 : void *payload) -> int {
628 0 : const auto *p = static_cast<const CloneRemotePayload *>(payload);
629 0 : const char *remote_name = (name != nullptr && name[0] != '\0') ? name : "origin";
630 0 : std::string branch_spec =
631 0 : "+refs/heads/" + p->rev + ":refs/remotes/" + remote_name + "/" + p->rev;
632 0 : int error = git_remote_create_with_fetchspec(out, repo, name, url, branch_spec.c_str());
633 0 : if (error < 0) return error;
634 : // Named tip may be a tag (clone --branch accepts both).
635 0 : std::string tag_spec = "+refs/tags/" + p->rev + ":refs/tags/" + p->rev;
636 0 : return git_remote_add_fetch(repo, name, tag_spec.c_str());
637 0 : };
638 0 : opts.remote_cb_payload = &remote_payload;
639 : }
640 74 : };
641 :
642 37 : if (ssh)
643 : {
644 27 : self()->debug(1, "[GitImpl::clone] ssh");
645 27 : rresult = ensure_ssh_backend();
646 27 : if (rresult.error()) return ETRC_RET(check_error(rresult, false));
647 :
648 27 : self()->cmd_start();
649 27 : self()->open_time();
650 27 : m_ssh_cred_attempt = 0;
651 27 : m_ssh_agent_tried = false;
652 27 : m_ssh_explicit_tried = false;
653 :
654 27 : git_clone_options opts;
655 27 : make_opts(opts);
656 27 : setup_remote_callbacks(opts.fetch_opts.callbacks);
657 :
658 27 : result = git_clone(&m_repo, url.c_str(), path.c_str(), &opts);
659 : }
660 10 : else if (https)
661 : {
662 7 : self()->debug(1, "[GitImpl::clone] https");
663 7 : self()->cmd_start();
664 7 : self()->open_time();
665 :
666 7 : git_clone_options opts;
667 7 : make_opts(opts);
668 7 : result = git_clone(&m_repo, url.c_str(), path.c_str(), &opts);
669 : }
670 : else
671 : {
672 3 : self()->debug(1, "[GitImpl::clone] no protocol / local");
673 3 : self()->cmd_start();
674 3 : self()->open_time();
675 :
676 3 : git_clone_options opts;
677 3 : make_opts(opts);
678 3 : result = git_clone(&m_repo, url.c_str(), path.c_str(), &opts);
679 : }
680 :
681 73 : if (result == 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
682 1 : rresult = ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result, "Clone failed");
683 2 : return ETRC_RET(check_error(rresult));
684 37 : }
685 :
686 62 : Result GitImpl::checkout(const std::string &branch, bool force)
687 : {
688 62 : Result result;
689 62 : ETRC_CALL_RET(result, branch, force);
690 :
691 62 : Guard<git_annotated_commit> annotated_commit;
692 62 : Guard<git_object> treeish;
693 62 : Guard<git_commit> target_commit;
694 62 : Guard<git_reference> ref;
695 62 : Guard<git_reference> branch_ref;
696 62 : Guard<git_reference> input_branch_ref;
697 62 : std::string new_ref = branch;
698 :
699 62 : self()->cmd_start();
700 :
701 62 : result = resolve_ref(input_branch_ref.get_p(), annotated_commit.get_p(), branch);
702 62 : if (result.error())
703 : {
704 22 : self()->debug(0, "branch not found : " + branch);
705 : // In the case where the content of branch doesn't have the full qualifier,
706 : // try to to guess
707 :
708 22 : result = find_ref(input_branch_ref.get_p(), annotated_commit.get_p(), branch, new_ref);
709 22 : if (result.error())
710 : {
711 0 : return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
712 : }
713 : }
714 :
715 62 : int result_int = git_commit_lookup(target_commit.get_p(), m_repo, git_annotated_commit_id(annotated_commit.get()));
716 62 : const git_oid *oid = git_commit_id(target_commit.get());
717 62 : std::string oid_str;
718 62 : result = convert_bin_hex(*oid, oid_str);
719 :
720 62 : git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
721 62 : if (force)
722 0 : opts.checkout_strategy = GIT_CHECKOUT_FORCE;
723 : else
724 : opts.checkout_strategy = GIT_CHECKOUT_SAFE;
725 :
726 62 : auto t_commit = static_cast<git_object *>(static_cast<void *>(target_commit.get()));
727 62 : result_int = git_checkout_tree(m_repo, t_commit, &opts);
728 62 : if (result_int < 0)
729 : {
730 0 : return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
731 : }
732 :
733 62 : if (git_annotated_commit_ref(annotated_commit.get()))
734 : {
735 39 : const char *target_head = nullptr;
736 :
737 39 : result_int = git_reference_lookup(ref.get_p(), m_repo, git_annotated_commit_ref(annotated_commit.get()));
738 39 : if (result_int < 0)
739 : {
740 0 : return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
741 : }
742 :
743 39 : if (git_reference_is_remote(ref.get()))
744 : {
745 22 : result_int =
746 22 : git_branch_create_from_annotated(branch_ref.get_p(), m_repo, branch.c_str(), annotated_commit.get(), 0);
747 22 : if (result_int < 0)
748 : {
749 0 : return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
750 : }
751 :
752 22 : target_head = git_reference_name(branch_ref.get());
753 :
754 : // if (git_reference_is_branch(input_branch_ref.get()))
755 : // git_
756 22 : const char *branch_name = nullptr;
757 22 : result_int = git_branch_name(&branch_name, input_branch_ref.get());
758 22 : if (result_int < 0)
759 0 : check_error(result, "can't get the name of the branch"); //! \TODO check if this is correct
760 : else
761 : {
762 22 : result_int = git_branch_set_upstream(branch_ref.get(), branch_name);
763 22 : if (result_int < 0) check_error(result, "set branch upstream"); //! \TODO check if this is correct
764 : }
765 : }
766 : else
767 : {
768 17 : target_head = git_annotated_commit_ref(annotated_commit.get());
769 : }
770 :
771 39 : result_int = git_repository_set_head(m_repo, target_head);
772 39 : Result rresult;
773 :
774 39 : if (result_int < 0)
775 0 : rresult = ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int);
776 : else
777 39 : rresult = ESYSREPO_RESULT(ResultCode::OK);
778 78 : return ETRC_RET(check_error(rresult));
779 39 : }
780 : else
781 : {
782 23 : result_int = git_repository_set_head_detached_from_annotated(m_repo, annotated_commit.get());
783 23 : Result rresult;
784 23 : if (result_int < 0)
785 0 : rresult = ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int);
786 : else
787 23 : rresult = ESYSREPO_RESULT(ResultCode::OK);
788 :
789 46 : return ETRC_RET(check_error(rresult));
790 23 : }
791 62 : }
792 :
793 2 : Result GitImpl::reset(const git::CommitHash &commit, git::ResetType type)
794 : {
795 2 : Result result;
796 2 : ETRC_CALL_RET(result, commit, type);
797 :
798 2 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
799 :
800 2 : git_reset_t reset_type = GIT_RESET_SOFT;
801 :
802 2 : switch (type)
803 : {
804 0 : case git::ResetType::NOT_SET: return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RESET_TYPE_NOT_SET));
805 : case git::ResetType::HARD: reset_type = GIT_RESET_HARD; break;
806 0 : case git::ResetType::MIXED: reset_type = GIT_RESET_MIXED; break;
807 0 : case git::ResetType::SOFT: reset_type = GIT_RESET_SOFT; break;
808 0 : default: return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RESET_TYPE_UNKNOWN));
809 : }
810 :
811 2 : Guard<git_commit> g_commit;
812 2 : git_oid oid_commit;
813 :
814 2 : result = convert_hex_bin(commit.get_hash(), oid_commit);
815 2 : if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
816 :
817 : // get the actual commit structure
818 2 : int result_int = git_commit_lookup(g_commit.get_p(), m_repo, &oid_commit);
819 2 : if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
820 :
821 2 : auto t_commit = static_cast<git_object *>(static_cast<void *>(g_commit.get()));
822 2 : result_int = git_reset(m_repo, t_commit, reset_type, nullptr);
823 4 : if (result_int == GIT_OK) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
824 0 : return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
825 2 : }
826 :
827 2 : Result GitImpl::fastforward(const git::CommitHash &commit)
828 : {
829 2 : Result result;
830 2 : ETRC_CALL_RET(result, commit);
831 :
832 2 : git_checkout_options ff_checkout_options = GIT_CHECKOUT_OPTIONS_INIT;
833 2 : Guard<git_reference> target_ref;
834 2 : Guard<git_reference> new_target_ref;
835 2 : Guard<git_object> target;
836 2 : git_oid target_oid;
837 :
838 2 : assert(m_repo != nullptr);
839 :
840 2 : result = convert_hex_bin(commit.get_hash(), target_oid);
841 2 : if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
842 :
843 : // HEAD exists, just lookup and resolve
844 2 : int result_int = git_repository_head(target_ref.get_p(), m_repo);
845 2 : if (result_int != GIT_OK)
846 0 : return ETRC_RET(
847 : check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, "failed to get HEAD reference")));
848 :
849 : // Lookup the target object
850 2 : result_int = git_object_lookup(target.get_p(), m_repo, &target_oid, GIT_OBJECT_COMMIT);
851 2 : if (result_int != GIT_OK)
852 0 : return ETRC_RET(check_error(
853 : ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, "failed to lookup OID " + commit.get_hash())));
854 :
855 : // Checkout the result so the workdir is in the expected state
856 2 : ff_checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE;
857 2 : result_int = git_checkout_tree(m_repo, target.get(), &ff_checkout_options);
858 2 : if (result_int != 0)
859 0 : return ETRC_RET(check_error(
860 : ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, "failed to checkout HEAD reference")));
861 :
862 : // Move the target reference to the target OID
863 2 : result_int = git_reference_set_target(new_target_ref.get_p(), target_ref.get(), &target_oid, nullptr);
864 2 : if (result_int != 0)
865 0 : return ETRC_RET(
866 : check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, "failed to move HEAD reference")));
867 :
868 4 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
869 2 : }
870 :
871 63 : Result GitImpl::get_commit_hash(const git_oid &oid_commit, git::CommitHash &commit_hash)
872 : {
873 63 : Result result;
874 63 : ETRC_CALL_RET_BEGIN(result, commit_hash);
875 63 : ETRC_CALL_RET_OUT_END(commit_hash);
876 :
877 63 : std::string hash;
878 :
879 63 : result = convert_bin_hex(oid_commit, hash);
880 63 : if (result.ok())
881 : {
882 63 : commit_hash.set_hash(hash);
883 126 : return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
884 : }
885 :
886 0 : return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
887 63 : }
888 :
889 19 : Result GitImpl::get_commit(const git_oid &oid_commit, git::Commit &commit, bool get_all_notes)
890 : {
891 19 : Result result;
892 19 : ETRC_CALL_RET_BEGIN(result, commit, get_all_notes);
893 19 : ETRC_CALL_RET_OUT_END(commit);
894 :
895 19 : Guard<git_commit> g_commit;
896 :
897 : // get the actual commit structure
898 19 : auto result_int = git_commit_lookup(g_commit.get_p(), m_repo, &oid_commit);
899 19 : if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
900 :
901 19 : result = get_commit_hash(oid_commit, commit.get_hash());
902 19 : if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
903 :
904 19 : auto message = git_commit_message(g_commit.get());
905 38 : if (message != nullptr) commit.set_message(message);
906 :
907 19 : auto summary = git_commit_summary(g_commit.get());
908 38 : if (summary != nullptr) commit.set_summary(summary);
909 :
910 19 : auto body = git_commit_body(g_commit.get());
911 24 : if (body != nullptr) commit.set_body(body);
912 :
913 19 : auto author = git_commit_author(g_commit.get());
914 19 : result = get_signature(author, commit.get_author_sign());
915 19 : if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
916 :
917 19 : auto committer = git_commit_committer(g_commit.get());
918 19 : result = get_signature(committer, commit.get_committer_sign());
919 19 : if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
920 :
921 28 : if (!get_all_notes) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
922 :
923 10 : result = m_notes.load_all();
924 10 : if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
925 :
926 10 : std::vector<std::shared_ptr<git::Note>> notes;
927 :
928 10 : result = m_notes.get_note(oid_commit, notes);
929 10 : if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
930 :
931 17 : for (auto note : notes)
932 : {
933 21 : commit.add_note(note);
934 7 : }
935 :
936 20 : return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
937 29 : }
938 :
939 34 : Result GitImpl::get_last_commit(git::CommitHash &commit_hash)
940 : {
941 34 : Result result;
942 34 : ETRC_CALL_RET_BEGIN(result, commit_hash);
943 34 : ETRC_CALL_RET_OUT_END(commit_hash);
944 :
945 34 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
946 :
947 34 : git_oid oid_last_commit;
948 :
949 34 : self()->cmd_start();
950 :
951 : // resolve HEAD into a SHA1
952 34 : auto result_int = git_reference_name_to_id(&oid_last_commit, m_repo, "HEAD");
953 34 : if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
954 :
955 34 : result = get_commit_hash(oid_last_commit, commit_hash);
956 68 : return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
957 34 : }
958 :
959 19 : Result GitImpl::get_last_commit(git::Commit &commit, bool get_all_notes)
960 : {
961 19 : Result result;
962 19 : ETRC_CALL_RET_BEGIN(result, commit, get_all_notes);
963 19 : ETRC_CALL_RET_OUT_END(commit);
964 :
965 19 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
966 :
967 19 : git_oid oid_last_commit;
968 :
969 19 : self()->cmd_start();
970 :
971 19 : commit.clear();
972 :
973 : // resolve HEAD into a SHA1
974 19 : auto result_int = git_reference_name_to_id(&oid_last_commit, m_repo, "HEAD");
975 19 : if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
976 :
977 19 : result = get_commit(oid_last_commit, commit, get_all_notes);
978 38 : return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
979 19 : }
980 :
981 0 : Result GitImpl::get_notes(git_commit *commit_obj, git::Commit &commit, const std::string ¬es_ref)
982 : {
983 0 : Guard<git_iterator> it;
984 :
985 0 : auto result_int = git_note_iterator_new(it.get_p(), m_repo, notes_ref.c_str());
986 :
987 0 : return check_error(ESYSREPO_RESULT(ResultCode::OK));
988 0 : }
989 :
990 52 : Result GitImpl::get_signature(const git_signature *signature_obj, git::Signature &signature)
991 : {
992 52 : signature.set_name(signature_obj->name);
993 52 : signature.set_email(signature_obj->email);
994 :
995 52 : git_time_t commit_time = signature_obj->when.time;
996 :
997 52 : std::time_t commit_time_t = static_cast<std::time_t>(commit_time);
998 52 : std::string commit_time_str = std::asctime(std::localtime(&commit_time_t));
999 :
1000 52 : signature.set_date_time(std::chrono::system_clock::from_time_t(commit_time_t));
1001 52 : signature.set_offset(signature_obj->when.offset);
1002 :
1003 52 : return ESYSREPO_RESULT(ResultCode::OK);
1004 52 : }
1005 :
1006 9 : Result GitImpl::get_parent_commit(const git::CommitHash &commit, git::CommitHash &parent, int nth_parent)
1007 : {
1008 9 : Result result;
1009 9 : ETRC_CALL_RET_BEGIN(result, commit, parent, nth_parent);
1010 9 : ETRC_CALL_RET_OUT_END(parent);
1011 :
1012 9 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
1013 :
1014 9 : Guard<git_commit> g_commit;
1015 9 : Guard<git_commit> cur_commit;
1016 9 : Guard<git_commit> parent_commit;
1017 9 : git_oid oid_commit;
1018 :
1019 9 : self()->cmd_start();
1020 :
1021 9 : if (nth_parent == 0)
1022 : {
1023 0 : parent = commit;
1024 0 : return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
1025 : }
1026 :
1027 9 : result = convert_hex_bin(commit.get_hash(), oid_commit);
1028 9 : if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
1029 :
1030 : // get the actual commit structure
1031 9 : int result_int = git_commit_lookup(cur_commit.get_p(), m_repo, &oid_commit);
1032 9 : if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
1033 :
1034 20 : unsigned int count = 0;
1035 :
1036 20 : for (int idx = 0; idx < nth_parent; ++idx)
1037 : {
1038 12 : count = git_commit_parentcount(cur_commit.get());
1039 12 : if (count <= 0)
1040 : {
1041 2 : return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_NO_PARENT)));
1042 : }
1043 :
1044 11 : parent_commit.reset();
1045 11 : result_int = git_commit_parent(parent_commit.get_p(), cur_commit.get(), 0);
1046 11 : if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
1047 :
1048 22 : cur_commit = parent_commit;
1049 : }
1050 :
1051 8 : const git_oid *parent_commit_id = git_commit_id(parent_commit.get());
1052 8 : if (parent_commit_id == nullptr) check_error(-3);
1053 :
1054 8 : std::string hash;
1055 8 : result = convert_bin_hex(*parent_commit_id, hash);
1056 8 : if (result.ok())
1057 : {
1058 8 : parent.set_hash(hash);
1059 16 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
1060 : }
1061 :
1062 0 : return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
1063 18 : }
1064 :
1065 42 : Result GitImpl::is_dirty(bool &dirty)
1066 : {
1067 42 : Result result;
1068 42 : ETRC_CALL_RET_BEGIN(result, dirty);
1069 42 : ETRC_CALL_RET_OUT_END(dirty);
1070 :
1071 42 : if (m_repo == nullptr) return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
1072 :
1073 42 : Guard<git_status_list> status;
1074 42 : git_status_options statusopt = GIT_STATUS_OPTIONS_INIT;
1075 :
1076 42 : self()->cmd_start();
1077 :
1078 42 : statusopt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
1079 42 : statusopt.flags = GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
1080 :
1081 42 : int result_int = git_status_list_new(status.get_p(), m_repo, &statusopt);
1082 42 : if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
1083 :
1084 42 : std::size_t count = git_status_list_entrycount(status.get());
1085 42 : dirty = (count != 0);
1086 84 : return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
1087 42 : }
1088 :
1089 54 : Result GitImpl::is_detached(bool &detached)
1090 : {
1091 54 : Result result;
1092 54 : ETRC_CALL_RET_BEGIN(result, detached);
1093 54 : ETRC_CALL_RET_OUT_END(detached);
1094 :
1095 54 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
1096 :
1097 54 : int result_int = git_repository_head_detached(m_repo);
1098 54 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
1099 :
1100 54 : if (result_int == 1)
1101 11 : detached = true;
1102 : else
1103 43 : detached = false;
1104 108 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
1105 54 : }
1106 :
1107 31 : Result GitImpl::get_status(git::RepoStatus &repo_status)
1108 : {
1109 31 : Result result;
1110 31 : ETRC_CALL_RET_BEGIN(result, repo_status);
1111 31 : ETRC_CALL_RET_OUT_END(repo_status);
1112 :
1113 31 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
1114 :
1115 31 : const git_status_entry *status_entry = nullptr;
1116 31 : Guard<git_status_list> status_list;
1117 31 : git_status_options statusopt = GIT_STATUS_OPTIONS_INIT;
1118 :
1119 31 : self()->cmd_start();
1120 :
1121 31 : statusopt.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
1122 31 : statusopt.flags =
1123 : GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX | GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
1124 :
1125 31 : int result_int = git_status_list_new(status_list.get_p(), m_repo, &statusopt);
1126 31 : if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
1127 :
1128 31 : std::size_t count = git_status_list_entrycount(status_list.get());
1129 :
1130 37 : for (std::size_t idx = 0; idx < count; ++idx)
1131 : {
1132 6 : status_entry = git_status_byindex(status_list.get(), idx);
1133 :
1134 6 : handle_status_entry(repo_status, status_entry);
1135 : }
1136 62 : return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::OK)));
1137 31 : }
1138 :
1139 6 : Result GitImpl::handle_status_entry(git::RepoStatus &repo_status, const git_status_entry *status_entry)
1140 : {
1141 6 : std::shared_ptr<git::Status> status = std::make_shared<git::Status>();
1142 6 : Result result;
1143 :
1144 6 : if (status_entry->status == GIT_STATUS_CURRENT)
1145 0 : result = handle_status_entry_current(repo_status, status, status_entry);
1146 6 : else if (status_entry->status < GIT_STATUS_WT_NEW)
1147 0 : result = handle_status_entry_index(repo_status, status, status_entry);
1148 6 : else if (status_entry->status < GIT_STATUS_IGNORED)
1149 18 : result = handle_status_entry_work_dir(repo_status, status, status_entry);
1150 0 : else if (status_entry->status == GIT_STATUS_IGNORED)
1151 0 : result = handle_status_entry_ignored(repo_status, status, status_entry);
1152 0 : else if (status_entry->status == GIT_STATUS_CONFLICTED)
1153 0 : result = handle_status_entry_conflicted(repo_status, status, status_entry);
1154 : else
1155 0 : return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
1156 :
1157 12 : repo_status.add(status);
1158 12 : return ESYSREPO_RESULT(result); // It was 0?!
1159 12 : }
1160 :
1161 0 : Result GitImpl::handle_status_entry_current(git::RepoStatus &repo_status, std::shared_ptr<git::Status> status,
1162 : const git_status_entry *status_entry)
1163 : {
1164 0 : status->set_type(git::StatusType::CURRENT);
1165 :
1166 0 : return ESYSREPO_RESULT(ResultCode::OK);
1167 : }
1168 :
1169 0 : Result GitImpl::handle_status_entry_index(git::RepoStatus &repo_status, std::shared_ptr<git::Status> status,
1170 : const git_status_entry *status_entry)
1171 : {
1172 0 : status->set_type(git::StatusType::INDEX);
1173 :
1174 0 : return ESYSREPO_RESULT(ResultCode::OK);
1175 : }
1176 :
1177 6 : Result GitImpl::handle_status_entry_work_dir(git::RepoStatus &repo_status, std::shared_ptr<git::Status> status,
1178 : const git_status_entry *status_entry)
1179 : {
1180 6 : status->set_type(git::StatusType::WORKING_DIR);
1181 :
1182 6 : Result result = from_to(status_entry->index_to_workdir, status->get_diff_delta());
1183 6 : if (result.error()) return ESYSREPO_RESULT(result);
1184 :
1185 12 : result = from_to(status_entry->status, status);
1186 6 : if (result.error()) return ESYSREPO_RESULT(result);
1187 :
1188 6 : return ESYSREPO_RESULT(ResultCode::OK);
1189 6 : }
1190 :
1191 0 : Result GitImpl::handle_status_entry_conflicted(git::RepoStatus &repo_status, std::shared_ptr<git::Status> status,
1192 : const git_status_entry *status_entry)
1193 : {
1194 0 : status->set_type(git::StatusType::CONFLICTED);
1195 :
1196 0 : return ESYSREPO_RESULT(ResultCode::OK);
1197 : }
1198 :
1199 0 : Result GitImpl::handle_status_entry_ignored(git::RepoStatus &repo_status, std::shared_ptr<git::Status> status,
1200 : const git_status_entry *status_entry)
1201 : {
1202 0 : status->set_type(git::StatusType::IGNORED);
1203 :
1204 0 : return ESYSREPO_RESULT(ResultCode::OK);
1205 : }
1206 :
1207 6 : Result GitImpl::from_to(git_status_t status, std::shared_ptr<git::Status> status_ptr)
1208 : {
1209 6 : switch (status)
1210 : {
1211 5 : case GIT_STATUS_INDEX_NEW:
1212 5 : case GIT_STATUS_WT_NEW: status_ptr->set_sub_type(git::StatusSubType::NEW); break;
1213 :
1214 1 : case GIT_STATUS_INDEX_MODIFIED:
1215 1 : case GIT_STATUS_WT_MODIFIED: status_ptr->set_sub_type(git::StatusSubType::MODIFIED); break;
1216 :
1217 0 : case GIT_STATUS_INDEX_DELETED:
1218 0 : case GIT_STATUS_WT_DELETED: status_ptr->set_sub_type(git::StatusSubType::DELETED); break;
1219 :
1220 0 : case GIT_STATUS_INDEX_RENAMED:
1221 0 : case GIT_STATUS_WT_RENAMED: status_ptr->set_sub_type(git::StatusSubType::RENAMED); break;
1222 :
1223 0 : case GIT_STATUS_INDEX_TYPECHANGE:
1224 0 : case GIT_STATUS_WT_TYPECHANGE: status_ptr->set_sub_type(git::StatusSubType::TYPECHANGE); break;
1225 :
1226 0 : case GIT_STATUS_WT_UNREADABLE: status_ptr->set_sub_type(git::StatusSubType::UNREADABLE); break;
1227 :
1228 0 : default: return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
1229 : }
1230 6 : return ESYSREPO_RESULT(ResultCode::OK);
1231 : }
1232 :
1233 6 : Result GitImpl::from_to(const git_diff_delta *delta, git::DiffDelta &diff_delta)
1234 : {
1235 6 : diff_delta.set_file_count(delta->nfiles);
1236 6 : diff_delta.set_similatiry(delta->similarity);
1237 :
1238 6 : Result result = from_to(delta->old_file, diff_delta.get_old_file());
1239 6 : if (result.error()) return ESYSREPO_RESULT(result);
1240 :
1241 6 : result = from_to(delta->new_file, diff_delta.get_new_file());
1242 6 : if (result.error()) return ESYSREPO_RESULT(result);
1243 :
1244 6 : return ESYSREPO_RESULT(ResultCode::OK);
1245 6 : }
1246 :
1247 12 : Result GitImpl::from_to(const git_diff_file &file, git::DiffFile &diff_file)
1248 : {
1249 12 : std::string id;
1250 12 : Result result = convert_bin_hex(file.id, id);
1251 12 : if (result.ok()) diff_file.set_id(id);
1252 :
1253 12 : diff_file.set_path(file.path);
1254 12 : diff_file.set_size(file.size);
1255 :
1256 12 : git::FileMode mode = git::FileMode::NOT_SET;
1257 :
1258 12 : switch (file.mode)
1259 : {
1260 : case GIT_FILEMODE_UNREADABLE: mode = git ::FileMode::NEW; break;
1261 3 : case GIT_FILEMODE_TREE: mode = git ::FileMode::TREE; break;
1262 4 : case GIT_FILEMODE_BLOB: mode = git ::FileMode::BLOB; break;
1263 0 : case GIT_FILEMODE_BLOB_EXECUTABLE: mode = git ::FileMode::BLOB_EXECUTABLE; break;
1264 0 : case GIT_FILEMODE_LINK: mode = git ::FileMode::LINK; break;
1265 0 : case GIT_FILEMODE_COMMIT: mode = git ::FileMode::COMMIT; break;
1266 0 : default: mode = git::FileMode::NOT_SET;
1267 : }
1268 :
1269 12 : diff_file.set_mode(mode);
1270 :
1271 12 : return result;
1272 12 : }
1273 :
1274 0 : int GitImpl::check_error(int result, const std::string &action, bool show_result)
1275 : {
1276 0 : self()->cmd_end();
1277 :
1278 0 : if (result == 0) return 0;
1279 :
1280 0 : const git_error *error = git_error_last();
1281 :
1282 0 : std::ostringstream oss;
1283 : // oss << "ERROR " << result << " : " << action;
1284 0 : oss << action;
1285 0 : if (show_result)
1286 : {
1287 0 : oss << " (" << result << ").";
1288 0 : if (error && error->message)
1289 : {
1290 0 : oss << " - " << error->message;
1291 : }
1292 : }
1293 0 : self()->error(oss.str());
1294 :
1295 0 : return result;
1296 0 : }
1297 :
1298 507 : Result GitImpl::check_error(Result result, bool show_result)
1299 : {
1300 507 : self()->cmd_end();
1301 :
1302 507 : if (result.ok()) return result;
1303 :
1304 2 : const git_error *error = git_error_last();
1305 :
1306 2 : std::ostringstream oss;
1307 : // oss << "ERROR " << result << " : " << action;
1308 6 : if (result.get_error_info() != nullptr) oss << result.get_error_info()->get_text();
1309 2 : if (show_result)
1310 : {
1311 2 : oss << " (" << result.get_result_code_int() << ").";
1312 2 : if (error && error->message)
1313 : {
1314 2 : oss << " - " << error->message;
1315 : }
1316 : }
1317 2 : self()->error(oss.str());
1318 :
1319 2 : return result;
1320 2 : }
1321 :
1322 2881 : Git *GitImpl::self() const
1323 : {
1324 2881 : return m_self;
1325 : }
1326 :
1327 65 : int GitImpl::libgit2_credentials_cb(git_credential **out, const char *url, const char *name, unsigned int types,
1328 : void *payload)
1329 : {
1330 65 : (void)url;
1331 :
1332 65 : const git_error *error = git_error_last();
1333 65 : if (error && error->klass == GIT_ERROR_SSH) return -1;
1334 :
1335 64 : auto self = static_cast<GitImpl *>(payload);
1336 64 : self->m_ssh_cred_attempt++;
1337 :
1338 64 : const char *username = (name != nullptr && name[0] != '\0') ? name : "git";
1339 :
1340 64 : Result_t<git::SshBackend> backend = GitBase::get_ssh_backend();
1341 64 : const bool use_exec = backend.ok() && backend.get() == git::SshBackend::Exec;
1342 :
1343 : // OpenSSH exec: the external ssh client owns key/agent auth. Only supply username.
1344 64 : if (use_exec)
1345 : {
1346 0 : if (types & GIT_CREDENTIAL_USERNAME) return git_credential_username_new(out, username);
1347 0 : if (types & GIT_CREDENTIAL_DEFAULT) return git_credential_default_new(out);
1348 0 : self->self()->debug(0, "[GitImpl::libgit2_credentials_cb] exec backend; no further credentials needed");
1349 0 : return 1; // no credential acquired
1350 : }
1351 :
1352 : // libssh2: explicit CI/env key, then agent, then default identity files.
1353 64 : if ((types & GIT_CREDENTIAL_SSH_KEY) || (types & GIT_CREDENTIAL_SSH_MEMORY))
1354 : {
1355 64 : if (!self->m_ssh_explicit_tried)
1356 : {
1357 63 : self->m_ssh_explicit_tried = true;
1358 63 : std::string key_path;
1359 63 : const int explicit_result = try_explicit_ssh_key(out, username, &key_path);
1360 63 : if (explicit_result == 0)
1361 : {
1362 63 : log_ssh_auth_info_once(self->self(),
1363 126 : "SSH credentials: libssh2 explicit identity '" + key_path + "'");
1364 63 : return 0;
1365 : }
1366 63 : }
1367 :
1368 2 : if (!self->m_ssh_agent_tried && self->is_ssh_agent_running())
1369 : {
1370 1 : self->m_ssh_agent_tried = true;
1371 1 : self->self()->debug(0, "[GitImpl::libgit2_credentials_cb] using ssh agent");
1372 :
1373 1 : std::string agent_id_path = libssh2::SSH::get_dflt_agent_identity_path();
1374 1 : if (agent_id_path.empty())
1375 : {
1376 0 : log_ssh_auth_info_once(self->self(), "SSH credentials: libssh2 via ssh-agent");
1377 0 : return git_credential_ssh_key_from_agent(out, username);
1378 : }
1379 :
1380 1 : std::ostringstream oss;
1381 1 : oss << "[GitImpl::libgit2_credentials_cb] use custom agent with path '" << agent_id_path << "'";
1382 1 : self->self()->debug(0, oss.str());
1383 1 : log_ssh_auth_info_once(self->self(),
1384 2 : "SSH credentials: libssh2 via custom ssh-agent ('" + agent_id_path + "')");
1385 1 : return git_credential_ssh_key_from_custom_agent(out, username, agent_id_path.c_str());
1386 2 : }
1387 :
1388 : // Attempts: optional explicit (already done), optional agent (already done), then defaults.
1389 0 : const int prior = 1 + (self->m_ssh_agent_tried ? 1 : 0);
1390 0 : const int key_index = self->m_ssh_cred_attempt - prior;
1391 0 : self->self()->debug(0, "[GitImpl::libgit2_credentials_cb] trying default ssh identity files");
1392 0 : std::string key_path;
1393 0 : int key_result = try_default_ssh_key(out, username, key_index, &key_path);
1394 0 : if (key_result == 0)
1395 : {
1396 0 : log_ssh_auth_info_once(self->self(), "SSH credentials: libssh2 identity file '" + key_path + "'");
1397 0 : return 0;
1398 : }
1399 64 : }
1400 :
1401 0 : if (types & GIT_CREDENTIAL_USERNAME) return git_credential_username_new(out, username);
1402 :
1403 0 : self->self()->debug(0, "[GitImpl::libgit2_credentials_cb] no usable credentials");
1404 0 : return -1;
1405 65 : }
1406 :
1407 37 : bool GitImpl::is_ssh_url(const std::string &url)
1408 : {
1409 37 : if (url.rfind("ssh://", 0) == 0) return true;
1410 10 : if (url.rfind("ssh:", 0) == 0) return true;
1411 :
1412 : // scp-like: git@host:path (no ://)
1413 10 : if (url.find("://") != std::string::npos) return false;
1414 3 : const auto at = url.find('@');
1415 3 : const auto colon = url.find(':');
1416 3 : return at != std::string::npos && colon != std::string::npos && colon > at;
1417 : }
1418 :
1419 69 : void GitImpl::setup_remote_callbacks(git_remote_callbacks &callbacks)
1420 : {
1421 69 : callbacks.sideband_progress = &GitImpl::libgit2_sideband_progress_cb;
1422 69 : callbacks.credentials = &GitImpl::libgit2_credentials_cb;
1423 69 : callbacks.transfer_progress = &GitImpl::libgit2_transfer_progress_cb;
1424 69 : callbacks.update_tips = &GitImpl::libgit2_update_tips_cb;
1425 69 : callbacks.pack_progress = &GitImpl::libgit2_pack_progress_cb;
1426 69 : callbacks.payload = this;
1427 69 : }
1428 :
1429 69 : Result GitImpl::ensure_ssh_backend()
1430 : {
1431 69 : static std::mutex mutex;
1432 69 : static bool done = false;
1433 69 : static Result cached = Result::OK;
1434 :
1435 69 : std::lock_guard<std::mutex> lock(mutex);
1436 69 : if (done) return cached;
1437 :
1438 1 : done = true;
1439 :
1440 : // Optional override: ESYSREPO_SSH_BACKEND=libssh2|exec (CI unit tests force
1441 : // libssh2 so the GitLab ssh-agent deploy key works; OpenSSH exec is the
1442 : // default preference on developer machines when available).
1443 1 : const char *override_env = std::getenv("ESYSREPO_SSH_BACKEND");
1444 1 : if (override_env != nullptr)
1445 : {
1446 1 : const std::string override_name = override_env;
1447 1 : if (override_name == "libssh2")
1448 : {
1449 1 : if (GitBase::is_ssh_backend_supported(git::SshBackend::LibSSH2))
1450 : {
1451 1 : cached = GitBase::set_ssh_backend(git::SshBackend::LibSSH2);
1452 1 : if (cached.ok())
1453 : {
1454 1 : self()->info("SSH backend: libssh2 (ESYSREPO_SSH_BACKEND)");
1455 1 : return cached;
1456 : }
1457 : }
1458 0 : self()->warn("ESYSREPO_SSH_BACKEND=libssh2 requested but unavailable");
1459 : }
1460 0 : else if (override_name == "exec")
1461 : {
1462 0 : if (GitBase::is_ssh_backend_available(git::SshBackend::Exec))
1463 : {
1464 0 : cached = GitBase::set_ssh_backend(git::SshBackend::Exec);
1465 0 : if (cached.ok())
1466 : {
1467 0 : self()->info("SSH backend: OpenSSH (exec) (ESYSREPO_SSH_BACKEND)");
1468 0 : return cached;
1469 : }
1470 : }
1471 0 : self()->warn("ESYSREPO_SSH_BACKEND=exec requested but unavailable");
1472 : }
1473 0 : else if (!override_name.empty())
1474 : {
1475 0 : self()->warn("Unknown ESYSREPO_SSH_BACKEND='" + override_name
1476 0 : + "' (expected libssh2 or exec); using auto selection");
1477 : }
1478 1 : }
1479 :
1480 0 : if (GitBase::is_ssh_backend_available(git::SshBackend::Exec))
1481 : {
1482 0 : cached = GitBase::set_ssh_backend(git::SshBackend::Exec);
1483 0 : if (cached.ok())
1484 : {
1485 0 : self()->info("SSH backend: OpenSSH (exec); credentials via ssh "
1486 : "(agent/keys as configured, e.g. omniSSHAgent / ssh-agent)");
1487 0 : return cached;
1488 : }
1489 0 : self()->warn("Failed to select OpenSSH exec backend; trying libssh2");
1490 : }
1491 :
1492 0 : if (GitBase::is_ssh_backend_supported(git::SshBackend::LibSSH2))
1493 : {
1494 0 : cached = GitBase::set_ssh_backend(git::SshBackend::LibSSH2);
1495 0 : if (cached.ok())
1496 : {
1497 0 : self()->info("SSH backend: libssh2");
1498 0 : return cached;
1499 : }
1500 : }
1501 :
1502 0 : cached = ESYSREPO_RESULT(ResultCode::GIT_SSH_BACKEND_NOT_SUPPORTED, "No usable SSH backend");
1503 0 : return cached;
1504 69 : }
1505 :
1506 64 : void GitImpl::log_ssh_auth_info_once(Git *self, const std::string &msg)
1507 : {
1508 64 : static std::mutex mutex;
1509 64 : static bool logged = false;
1510 :
1511 64 : std::lock_guard<std::mutex> lock(mutex);
1512 64 : if (logged) return;
1513 1 : logged = true;
1514 1 : if (self != nullptr) self->info(msg);
1515 64 : }
1516 :
1517 63 : int GitImpl::try_explicit_ssh_key(git_credential **out, const char *username, std::string *used_private_key)
1518 : {
1519 63 : const std::string private_str = git::explicit_ssh_private_key_from_env();
1520 63 : if (private_str.empty()) return -1;
1521 :
1522 63 : boost::filesystem::path private_key(private_str);
1523 63 : boost::filesystem::path public_key = private_key;
1524 63 : public_key += ".pub";
1525 63 : const std::string public_str = public_key.string();
1526 63 : const char *public_path = boost::filesystem::exists(public_key) ? public_str.c_str() : nullptr;
1527 :
1528 63 : if (used_private_key != nullptr) *used_private_key = private_str;
1529 :
1530 63 : return git_credential_ssh_key_new(out, username, public_path, private_str.c_str(), "");
1531 126 : }
1532 :
1533 0 : int GitImpl::try_default_ssh_key(git_credential **out, const char *username, int key_index,
1534 : std::string *used_private_key)
1535 : {
1536 0 : if (key_index < 0) return -1;
1537 :
1538 : #ifdef _WIN32
1539 : const char *home_env = "USERPROFILE";
1540 : #else
1541 0 : const char *home_env = "HOME";
1542 : #endif
1543 :
1544 0 : std::string home;
1545 : #ifdef _WIN32
1546 : char *buffer = nullptr;
1547 : size_t length = 0;
1548 : if (_dupenv_s(&buffer, &length, home_env) == 0 && buffer != nullptr)
1549 : {
1550 : home = buffer;
1551 : free(buffer);
1552 : }
1553 : #else
1554 0 : if (const char *value = std::getenv(home_env)) home = value;
1555 : #endif
1556 0 : if (home.empty()) return -1;
1557 :
1558 : static const char *key_names[] = {"id_ed25519", "id_ecdsa", "id_rsa", "id_dsa"};
1559 : int found = 0;
1560 0 : for (const char *key_name : key_names)
1561 : {
1562 0 : boost::filesystem::path private_key = boost::filesystem::path(home) / ".ssh" / key_name;
1563 0 : if (!boost::filesystem::exists(private_key)) continue;
1564 :
1565 0 : if (found != key_index)
1566 : {
1567 0 : ++found;
1568 0 : continue;
1569 : }
1570 :
1571 0 : boost::filesystem::path public_key = private_key;
1572 0 : public_key += ".pub";
1573 :
1574 0 : const std::string private_str = private_key.string();
1575 0 : const std::string public_str = public_key.string();
1576 0 : const char *public_path = boost::filesystem::exists(public_key) ? public_str.c_str() : nullptr;
1577 :
1578 0 : if (used_private_key != nullptr) *used_private_key = private_str;
1579 :
1580 0 : return git_credential_ssh_key_new(out, username, public_path, private_str.c_str(), "");
1581 0 : }
1582 :
1583 : return -1;
1584 0 : }
1585 :
1586 185 : int GitImpl::libgit2_sideband_progress_cb(const char *str, int len, void *data)
1587 : {
1588 185 : auto impl = static_cast<GitImpl *>(data);
1589 :
1590 185 : if (impl == nullptr) return 0;
1591 :
1592 185 : std::string txt(str, str + len);
1593 :
1594 185 : int result = impl->self()->handle_sideband_progress(txt);
1595 185 : return result;
1596 185 : }
1597 :
1598 645 : int GitImpl::libgit2_transfer_progress_cb(const git_indexer_progress *stats, void *payload)
1599 : {
1600 645 : auto impl = static_cast<GitImpl *>(payload);
1601 :
1602 645 : if (impl == nullptr) return 0;
1603 :
1604 645 : git::Progress progress;
1605 :
1606 : // std::cout << "[libgit2_transfer_progress_cb]" << std::endl;
1607 :
1608 645 : progress.set_total_objects(static_cast<int>(stats->total_objects));
1609 645 : progress.set_received_objects(static_cast<int>(stats->received_objects));
1610 645 : progress.set_indexed_objects(static_cast<int>(stats->indexed_objects));
1611 645 : progress.set_total_deltas(static_cast<int>(stats->total_deltas));
1612 645 : progress.set_indexed_deltas(static_cast<int>(stats->indexed_deltas));
1613 645 : progress.set_received_bytes(static_cast<std::int64_t>(stats->received_bytes));
1614 :
1615 645 : if (stats->received_objects == stats->total_objects)
1616 : {
1617 143 : progress.set_fetch_step(git::FetchStep::RESOLVING);
1618 :
1619 143 : if (stats->indexed_deltas == stats->total_deltas)
1620 : {
1621 85 : progress.set_done(true);
1622 85 : progress.set_percentage(git::Progress::MAX_PERCENTAGE);
1623 : }
1624 : else
1625 : {
1626 58 : double percentage = (100.0 * stats->indexed_deltas) / stats->total_deltas;
1627 :
1628 58 : progress.set_done(false);
1629 58 : progress.set_percentage(static_cast<int>(percentage));
1630 : }
1631 : }
1632 502 : else if (stats->total_objects > 0)
1633 : {
1634 502 : progress.set_fetch_step(git::FetchStep::RECEIVING);
1635 502 : if (stats->received_objects == stats->total_objects)
1636 : {
1637 0 : progress.set_done(true);
1638 0 : progress.set_percentage(git::Progress::MAX_PERCENTAGE);
1639 : }
1640 : else
1641 : {
1642 502 : double percentage = (100.0 * stats->received_objects) / stats->total_objects;
1643 :
1644 502 : progress.set_done(false);
1645 502 : progress.set_percentage(static_cast<int>(percentage));
1646 : }
1647 : }
1648 :
1649 645 : impl->self()->notify_progress_throttled(progress);
1650 :
1651 : return 0;
1652 : }
1653 :
1654 114 : int GitImpl::libgit2_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data)
1655 : {
1656 114 : std::string a_str;
1657 114 : std::string b_str;
1658 114 : auto self = static_cast<GitImpl *>(data);
1659 114 : std::ostringstream oss;
1660 114 : git::UpdateTip update_tip;
1661 :
1662 114 : auto result = self->convert_bin_hex(*b, b_str);
1663 114 : if (result.error()) return -1;
1664 :
1665 114 : update_tip.set_ref_name(refname);
1666 :
1667 114 : if (git_oid_is_zero(a))
1668 : {
1669 : // oss << "[new] " << b_str << " " << refname;
1670 : // std::cout << oss.str() << std::endl;
1671 114 : update_tip.set_type(git::UpdateTipType::NEW);
1672 114 : update_tip.set_new_oid(b_str);
1673 : }
1674 : else
1675 : {
1676 0 : update_tip.set_type(git::UpdateTipType::UPDATE);
1677 0 : result = self->convert_bin_hex(*a, a_str);
1678 0 : if (result.error()) return -1;
1679 :
1680 0 : update_tip.set_new_oid(b_str);
1681 0 : update_tip.set_new_oid(b_str);
1682 : // oss << "[updated] " << a_str << ".." << b_str << " " << refname;
1683 : // std::cout << oss.str() << std::endl;
1684 : }
1685 : return 0;
1686 114 : }
1687 :
1688 12 : int GitImpl::libgit2_pack_progress_cb(int /*stage*/, uint32_t /*current*/, uint32_t /*total*/, void * /*payload*/)
1689 : {
1690 12 : return 0;
1691 : }
1692 :
1693 2 : Result_t<bool> GitImpl::is_ssh_agent_running()
1694 : {
1695 2 : bool present = m_ssh.is_agent_present();
1696 :
1697 4 : if (present) self()->debug(0, "SSH agent detected");
1698 :
1699 2 : return present;
1700 : }
1701 :
1702 27 : Result GitImpl::merge_analysis(const std::vector<std::string> &refs, git::MergeAnalysisResult &merge_analysis_result,
1703 : std::vector<git::CommitHash> &commits)
1704 : {
1705 27 : Result result;
1706 27 : ETRC_CALL_RET_BEGIN(result, refs, merge_analysis_result, commits);
1707 27 : ETRC_CALL_RET_OUT_END(merge_analysis_result, commits);
1708 :
1709 27 : git_repository_state_t state = GIT_REPOSITORY_STATE_NONE;
1710 27 : git_merge_analysis_t analysis = GIT_MERGE_ANALYSIS_NONE;
1711 27 : git_merge_preference_t preference = GIT_MERGE_PREFERENCE_NONE;
1712 27 : git_annotated_commit *annotated = nullptr;
1713 27 : git::CommitHash commit;
1714 :
1715 27 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
1716 :
1717 27 : state = static_cast<git_repository_state_t>(git_repository_state(m_repo));
1718 27 : if (state != GIT_REPOSITORY_STATE_NONE)
1719 : {
1720 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
1721 : }
1722 :
1723 27 : std::vector<git_annotated_commit *> annotated_vec;
1724 27 : std::vector<git_oid> oids;
1725 27 : const git_oid *target_oid = nullptr;
1726 27 : std::string hash;
1727 :
1728 54 : for (auto &ref : refs)
1729 : {
1730 27 : result = resolve_ref(&annotated, ref);
1731 27 : if (result.ok())
1732 : {
1733 27 : target_oid = git_annotated_commit_id(annotated);
1734 27 : oids.push_back(*target_oid);
1735 27 : annotated_vec.push_back(annotated);
1736 :
1737 27 : result = convert_bin_hex(*target_oid, hash);
1738 27 : if (result.ok())
1739 : {
1740 27 : commit.set_hash(hash);
1741 27 : commits.push_back(commit);
1742 : }
1743 : }
1744 : }
1745 :
1746 27 : int result_int = git_merge_analysis(&analysis, &preference, m_repo,
1747 27 : (const git_annotated_commit **)annotated_vec.data(), annotated_vec.size());
1748 : //! \TODO handling error is missing
1749 54 : for (auto annotated : annotated_vec) git_annotated_commit_free(annotated);
1750 :
1751 27 : if (analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE)
1752 25 : merge_analysis_result = git::MergeAnalysisResult::UP_TO_DATE;
1753 2 : else if (analysis & GIT_MERGE_ANALYSIS_NORMAL)
1754 : {
1755 2 : if (analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)
1756 2 : merge_analysis_result = git::MergeAnalysisResult::FASTFORWARD;
1757 : else
1758 0 : merge_analysis_result = git::MergeAnalysisResult::NORMAL;
1759 : }
1760 0 : else if (analysis & GIT_MERGE_ANALYSIS_FASTFORWARD)
1761 0 : merge_analysis_result = git::MergeAnalysisResult::FASTFORWARD;
1762 0 : else if (analysis & GIT_MERGE_ANALYSIS_UNBORN)
1763 0 : merge_analysis_result = git::MergeAnalysisResult::UNBORN;
1764 : else
1765 0 : merge_analysis_result = git::MergeAnalysisResult::NONE;
1766 54 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
1767 54 : }
1768 :
1769 56 : Result GitImpl::find_remote(Guard<git_remote> &remote, const std::string &remote_name)
1770 : {
1771 56 : Result result;
1772 56 : ETRC_CALL_RET_BEGIN(result, remote, remote_name);
1773 56 : ETRC_CALL_RET_OUT_END(remote);
1774 :
1775 56 : if (m_repo == nullptr) return ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
1776 :
1777 56 : std::string remote_name_str;
1778 :
1779 56 : if (!remote_name.empty())
1780 : {
1781 19 : int result_int = git_remote_lookup(remote.get_p(), m_repo, remote_name.c_str());
1782 19 : if (result_int < 0)
1783 : {
1784 0 : std::string err_str = "The given remote is not known : " + remote_name;
1785 : // The remote is not known
1786 0 : self()->error(err_str);
1787 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, err_str));
1788 0 : }
1789 :
1790 19 : remote_name_str = git_remote_name(remote.get());
1791 : }
1792 : else
1793 : {
1794 37 : git::Branches branches;
1795 :
1796 37 : result = get_branches(branches);
1797 37 : if (result.error())
1798 : {
1799 0 : std::string err_str = "Couldn't get the branches for this git repo";
1800 0 : self()->error(err_str);
1801 0 : return ETRC_RET(ESYSREPO_RESULT(result, err_str));
1802 0 : }
1803 :
1804 37 : if (branches.size() == 0)
1805 : {
1806 : // After init+fetch with no local checkout yet, fall back to "origin"
1807 0 : int result_int = git_remote_lookup(remote.get_p(), m_repo, "origin");
1808 0 : if (result_int == 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
1809 :
1810 0 : self()->error("No branches found for this git repo");
1811 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_NO_BRANCH_FOUND));
1812 : }
1813 :
1814 37 : branches.sort();
1815 :
1816 37 : remote_name_str = branches.get()[0]->get_remote_name();
1817 :
1818 37 : int result_int = git_remote_lookup(remote.get_p(), m_repo, remote_name_str.c_str());
1819 37 : if (result_int < 0)
1820 : {
1821 0 : std::string err_str = "The given remote is not known : " + remote_name_str;
1822 : // The remote is not known
1823 0 : self()->error(err_str);
1824 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, err_str));
1825 0 : }
1826 37 : }
1827 :
1828 112 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
1829 56 : }
1830 :
1831 40 : Result GitImpl::get_commit_hash_from_branch(const std::string &branch, git_oid &oid)
1832 : {
1833 40 : Guard<git_annotated_commit> annotated_commit;
1834 40 : Guard<git_reference> input_branch_ref;
1835 40 : std::string new_ref = branch;
1836 40 : Guard<git_commit> target_commit;
1837 :
1838 : // Guard<git_object> treeish;
1839 :
1840 : // Guard<git_reference> ref;
1841 : // Guard<git_reference> branch_ref;
1842 :
1843 40 : Result result = resolve_ref(input_branch_ref.get_p(), annotated_commit.get_p(), branch);
1844 40 : if (result.error())
1845 : {
1846 0 : self()->debug(0, "branch not found : " + branch);
1847 : // In the case where the content of branch doesn't have the full qualifier,
1848 : // try to to guess
1849 :
1850 0 : result = find_ref(input_branch_ref.get_p(), annotated_commit.get_p(), branch, new_ref);
1851 0 : if (result.error())
1852 : {
1853 0 : return check_error(ESYSREPO_RESULT(result));
1854 : }
1855 : }
1856 :
1857 40 : int result_int = git_commit_lookup(target_commit.get_p(), m_repo, git_annotated_commit_id(annotated_commit.get()));
1858 40 : if (result_int < 0) return check_error(ESYSREPO_RESULT(ResultCode::GIT_FIND_REF_FAILED));
1859 :
1860 40 : const git_oid *target_oid = git_commit_id(target_commit.get());
1861 40 : if (target_oid == nullptr) ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR);
1862 40 : oid = *target_oid;
1863 40 : return ESYSREPO_RESULT(ResultCode::OK);
1864 40 : }
1865 :
1866 36 : Result GitImpl::fetch(const std::string &remote_str)
1867 : {
1868 36 : Result result;
1869 36 : ETRC_CALL_RET(result, remote_str);
1870 :
1871 36 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
1872 :
1873 36 : result = ensure_ssh_backend();
1874 36 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
1875 :
1876 36 : Guard<git_remote> remote;
1877 :
1878 36 : result = find_remote(remote, remote_str);
1879 36 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
1880 :
1881 36 : git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
1882 36 : m_ssh_cred_attempt = 0;
1883 36 : m_ssh_agent_tried = false;
1884 36 : m_ssh_explicit_tried = false;
1885 36 : setup_remote_callbacks(fetch_opts.callbacks);
1886 :
1887 36 : int result_int = git_remote_fetch(remote.get(), nullptr, &fetch_opts, "fetch");
1888 36 : if (result_int < 0)
1889 : {
1890 0 : std::string err_str = "Fetch failed";
1891 0 : self()->error(err_str);
1892 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, err_str));
1893 0 : }
1894 :
1895 36 : const git_indexer_progress *stats = git_remote_stats(remote.get());
1896 :
1897 : //! \TODO what to do with the stats;
1898 :
1899 72 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
1900 36 : }
1901 :
1902 0 : Result GitImpl::fetch_refspec(const std::string &refspec, const std::string &remote_str)
1903 : {
1904 0 : Result result;
1905 0 : ETRC_CALL_RET(result, refspec, remote_str);
1906 :
1907 0 : if (refspec.empty()) return ETRC_RET(fetch(remote_str));
1908 0 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
1909 :
1910 0 : result = ensure_ssh_backend();
1911 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
1912 :
1913 0 : Guard<git_remote> remote;
1914 0 : result = find_remote(remote, remote_str);
1915 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
1916 :
1917 0 : git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
1918 0 : m_ssh_cred_attempt = 0;
1919 0 : m_ssh_agent_tried = false;
1920 0 : m_ssh_explicit_tried = false;
1921 0 : setup_remote_callbacks(fetch_opts.callbacks);
1922 :
1923 0 : std::string spec = refspec;
1924 0 : char *strings[] = {spec.data()};
1925 0 : git_strarray specs = {strings, 1};
1926 :
1927 0 : int result_int = git_remote_fetch(remote.get(), &specs, &fetch_opts, "fetch");
1928 0 : if (result_int < 0)
1929 : {
1930 0 : std::string err_str = "Fetch refspec failed";
1931 0 : self()->error(err_str);
1932 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, err_str));
1933 0 : }
1934 :
1935 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
1936 0 : }
1937 :
1938 : namespace
1939 : {
1940 :
1941 0 : struct CollectSubmodules
1942 : {
1943 : std::vector<std::string> names;
1944 : };
1945 :
1946 0 : int collect_submodule_cb(git_submodule * /*sm*/, const char *name, void *payload)
1947 : {
1948 0 : auto *data = static_cast<CollectSubmodules *>(payload);
1949 0 : if (name != nullptr) data->names.emplace_back(name);
1950 0 : return 0;
1951 : }
1952 :
1953 : } // namespace
1954 :
1955 0 : Result GitImpl::update_submodules(const std::string &path, bool recursive)
1956 : {
1957 0 : Result result;
1958 0 : ETRC_CALL_RET(result, path, recursive);
1959 :
1960 0 : if (path.empty()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR, "empty path"));
1961 :
1962 0 : git_repository *repo = nullptr;
1963 0 : int result_int = git_repository_open(&repo, path.c_str());
1964 0 : if (result_int < 0)
1965 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, "failed to open repo for submodules"));
1966 :
1967 0 : CollectSubmodules collected;
1968 0 : result_int = git_submodule_foreach(repo, collect_submodule_cb, &collected);
1969 0 : if (result_int < 0)
1970 : {
1971 0 : git_repository_free(repo);
1972 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, "git_submodule_foreach failed"));
1973 : }
1974 :
1975 0 : git_submodule_update_options update_opts = GIT_SUBMODULE_UPDATE_OPTIONS_INIT;
1976 0 : result = ensure_ssh_backend();
1977 0 : if (result.error())
1978 : {
1979 0 : git_repository_free(repo);
1980 0 : return ETRC_RET(ESYSREPO_RESULT(result));
1981 : }
1982 0 : m_ssh_cred_attempt = 0;
1983 0 : m_ssh_agent_tried = false;
1984 0 : m_ssh_explicit_tried = false;
1985 0 : setup_remote_callbacks(update_opts.fetch_opts.callbacks);
1986 :
1987 0 : for (const auto &name : collected.names)
1988 : {
1989 0 : git_submodule *sm = nullptr;
1990 0 : result_int = git_submodule_lookup(&sm, repo, name.c_str());
1991 0 : if (result_int < 0)
1992 : {
1993 0 : git_repository_free(repo);
1994 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, "git_submodule_lookup failed"));
1995 : }
1996 :
1997 0 : result_int = git_submodule_update(sm, 1 /* init */, &update_opts);
1998 0 : if (result_int < 0)
1999 : {
2000 0 : git_submodule_free(sm);
2001 0 : git_repository_free(repo);
2002 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, "git_submodule_update failed"));
2003 : }
2004 :
2005 0 : if (recursive)
2006 : {
2007 0 : const char *rel = git_submodule_path(sm);
2008 0 : boost::filesystem::path child = path;
2009 0 : if (rel != nullptr) child /= rel;
2010 0 : git_submodule_free(sm);
2011 0 : sm = nullptr;
2012 0 : result = update_submodules(child.generic_string(), true);
2013 0 : if (result.error())
2014 : {
2015 0 : git_repository_free(repo);
2016 0 : return ETRC_RET(ESYSREPO_RESULT(result));
2017 : }
2018 0 : }
2019 : else
2020 : {
2021 0 : git_submodule_free(sm);
2022 : }
2023 : }
2024 :
2025 0 : git_repository_free(repo);
2026 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
2027 0 : }
2028 :
2029 3 : Result GitImpl::fetch_all_notes(const std::string &remote_str)
2030 : {
2031 3 : Result result;
2032 3 : ETRC_CALL_RET(result, remote_str);
2033 :
2034 3 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
2035 :
2036 3 : result = ensure_ssh_backend();
2037 3 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
2038 :
2039 3 : m_notes.clear();
2040 :
2041 3 : Guard<git_remote> remote;
2042 :
2043 3 : result = find_remote(remote, remote_str);
2044 3 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
2045 :
2046 3 : Guard<git_refspec> ref_spec;
2047 3 : int result_int = git_refspec_parse(ref_spec.get_p(), "refs/notes/*:refs/notes/*", 1);
2048 3 : if (result_int != 0) ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR);
2049 :
2050 3 : git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
2051 3 : m_ssh_cred_attempt = 0;
2052 3 : m_ssh_agent_tried = false;
2053 3 : m_ssh_explicit_tried = false;
2054 3 : setup_remote_callbacks(fetch_opts.callbacks);
2055 :
2056 3 : git_strarray refspecs;
2057 3 : char refspecs_str[] = "refs/notes/*:refs/notes/*";
2058 3 : char *refspecs_array[1];
2059 3 : refspecs_array[0] = refspecs_str;
2060 3 : refspecs.count = 1;
2061 3 : refspecs.strings = refspecs_array;
2062 :
2063 3 : result_int = git_remote_fetch(remote.get(), &refspecs, &fetch_opts, "fetch");
2064 3 : if (result_int < 0)
2065 : {
2066 0 : std::string err_str = "Fetch failed";
2067 0 : self()->error(err_str);
2068 0 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int, err_str));
2069 0 : }
2070 :
2071 6 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
2072 6 : }
2073 :
2074 5 : Result GitImpl::add_note(git::NoteId ¬e_id, const std::string &reference, const std::string &message, bool overwrite)
2075 : {
2076 5 : Result result;
2077 5 : ETRC_CALL_RET_BEGIN(result, note_id, reference, message, overwrite);
2078 5 : ETRC_CALL_RET_OUT_END(note_id);
2079 :
2080 5 : if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
2081 5 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
2082 10 : if (self()->get_author() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::NO_AUTHOR_GIVEN));
2083 :
2084 5 : std::string the_ref = reference;
2085 5 : Guard<git_signature> author_sig;
2086 5 : Guard<git_signature> committer_sig;
2087 :
2088 5 : the_ref = normalize_notes_ref(reference);
2089 :
2090 5 : result = get_sigs(author_sig.get_p(), committer_sig.get_p());
2091 5 : if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
2092 :
2093 5 : git_oid note_iod;
2094 5 : git_oid oid_last_commit;
2095 :
2096 : // resolve HEAD into a SHA1
2097 5 : auto result_int = git_reference_name_to_id(&oid_last_commit, m_repo, "HEAD");
2098 5 : if (result_int < 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
2099 :
2100 5 : result_int = git_note_create(¬e_iod, m_repo, the_ref.c_str(), author_sig.get(), committer_sig.get(),
2101 : &oid_last_commit, message.c_str(), overwrite);
2102 5 : if (result_int != 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
2103 :
2104 5 : m_notes.clear();
2105 :
2106 5 : result = get_commit_hash(note_iod, note_id);
2107 5 : if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
2108 :
2109 5 : result = get_commit_hash(oid_last_commit, note_id.get_commit_hash());
2110 5 : if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
2111 :
2112 5 : note_id.set_reference(reference);
2113 :
2114 10 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
2115 10 : }
2116 :
2117 1 : Result GitImpl::remove_note(const git::NoteId ¬e_id)
2118 : {
2119 1 : Result result;
2120 1 : ETRC_CALL_RET(result, note_id);
2121 :
2122 1 : if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
2123 1 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
2124 2 : if (self()->get_author() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::NO_AUTHOR_GIVEN));
2125 :
2126 1 : git_oid note_oid;
2127 :
2128 1 : int result_int = git_oid_fromstr(¬e_oid, note_id.get_hash().c_str());
2129 1 : if (result_int != 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
2130 :
2131 2 : return ETRC_RET(remove_note(note_id.get_commit_hash(), note_id.get_reference()));
2132 1 : }
2133 :
2134 2 : Result GitImpl::remove_note(const git::CommitHash &commit_hash, const std::string &reference)
2135 : {
2136 2 : Result result;
2137 2 : ETRC_CALL_RET(result, commit_hash, reference);
2138 :
2139 2 : if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
2140 2 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
2141 4 : if (self()->get_author() == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::NO_AUTHOR_GIVEN));
2142 :
2143 2 : Guard<git_signature> author_sig;
2144 2 : Guard<git_signature> committer_sig;
2145 2 : git_oid target_oid;
2146 :
2147 2 : result = get_sigs(author_sig.get_p(), committer_sig.get_p());
2148 2 : if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
2149 :
2150 2 : int result_int = git_oid_fromstr(&target_oid, commit_hash.get_hash().c_str());
2151 2 : if (result_int != 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
2152 :
2153 2 : std::string note_ref = normalize_notes_ref(reference);
2154 2 : result_int = git_note_remove(m_repo, note_ref.c_str(), author_sig.get(), committer_sig.get(), &target_oid);
2155 :
2156 2 : if (result_int != 0) return ETRC_RET(check_error(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int)));
2157 :
2158 2 : m_notes.clear();
2159 4 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
2160 4 : }
2161 :
2162 1 : Result GitImpl::remove_note_last_commit(const std::string &reference)
2163 : {
2164 1 : Result result;
2165 1 : ETRC_CALL_RET(result, reference);
2166 :
2167 1 : git::CommitHash commit_hash;
2168 1 : result = get_last_commit(commit_hash);
2169 1 : if (result.error()) return ETRC_RET(check_error(ESYSREPO_RESULT(result)));
2170 :
2171 2 : return ETRC_RET(remove_note(commit_hash, reference));
2172 1 : }
2173 :
2174 2 : Result GitImpl::get_refspec(std::string &refspec_str, const std::string &src_ref, const std::string &dst_ref)
2175 : {
2176 2 : Result result;
2177 2 : ETRC_CALL_RET_BEGIN(result, refspec_str, src_ref, dst_ref);
2178 2 : ETRC_CALL_RET_OUT_END(refspec_str);
2179 :
2180 2 : if (!is_open()) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_REPO_NOT_OPEN));
2181 2 : if (m_repo == nullptr) return ETRC_RET(ESYSREPO_RESULT(ResultCode::INTERNAL_ERROR));
2182 :
2183 2 : std::string src_ref_name;
2184 2 : std::string dst_ref_name;
2185 :
2186 2 : if (src_ref.empty() && dst_ref.empty())
2187 : {
2188 : // Push the head
2189 0 : Guard<git_reference> head_ref;
2190 :
2191 0 : int result_int = git_repository_head(head_ref.get_p(), m_repo);
2192 0 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
2193 :
2194 0 : src_ref_name = git_reference_name(head_ref.get());
2195 0 : }
2196 2 : if (!src_ref.empty())
2197 : {
2198 2 : Guard<git_reference> git_src_ref;
2199 2 : result = resolve_ref(git_src_ref.get_p(), src_ref);
2200 2 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
2201 :
2202 2 : src_ref_name = git_reference_name(git_src_ref.get());
2203 2 : }
2204 2 : if (!dst_ref.empty())
2205 : {
2206 0 : Guard<git_reference> git_dst_ref;
2207 0 : result = resolve_ref(git_dst_ref.get_p(), src_ref);
2208 0 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
2209 :
2210 0 : dst_ref_name = git_reference_name(git_dst_ref.get());
2211 0 : }
2212 : else
2213 2 : dst_ref_name = src_ref_name;
2214 :
2215 2 : refspec_str = src_ref_name;
2216 2 : refspec_str += ":";
2217 2 : refspec_str += dst_ref_name;
2218 4 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
2219 4 : }
2220 :
2221 2 : Result GitImpl::push(const std::string &remote, const std::string &src_ref, const std::string &dst_ref)
2222 : {
2223 2 : Result result;
2224 2 : ETRC_CALL_RET(result, src_ref, dst_ref);
2225 :
2226 2 : result = ensure_ssh_backend();
2227 2 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
2228 :
2229 2 : git_push_options options;
2230 2 : Guard<git_remote> remote_git;
2231 2 : std::string refspec_str;
2232 :
2233 2 : result = get_refspec(refspec_str, src_ref, dst_ref);
2234 2 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
2235 :
2236 2 : char *refspec = (char *)refspec_str.c_str();
2237 2 : const git_strarray refspecs = {&refspec, 1};
2238 :
2239 2 : int result_int = git_remote_lookup(remote_git.get_p(), m_repo, remote.c_str());
2240 2 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
2241 :
2242 2 : result_int = git_push_options_init(&options, GIT_PUSH_OPTIONS_VERSION);
2243 2 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
2244 :
2245 2 : m_ssh_cred_attempt = 0;
2246 2 : m_ssh_agent_tried = false;
2247 2 : m_ssh_explicit_tried = false;
2248 2 : setup_remote_callbacks(options.callbacks);
2249 :
2250 2 : result_int = git_remote_push(remote_git.get(), &refspecs, &options);
2251 2 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
2252 :
2253 4 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
2254 4 : }
2255 :
2256 1 : Result GitImpl::push_notes(const std::string &remote, const std::string &reference)
2257 : {
2258 1 : Result result;
2259 1 : ETRC_CALL_RET(result, remote, reference);
2260 :
2261 1 : result = ensure_ssh_backend();
2262 1 : if (result.error()) return ETRC_RET(ESYSREPO_RESULT(result));
2263 :
2264 1 : git_push_options options;
2265 1 : Guard<git_remote> remote_git;
2266 1 : std::string note_ref = normalize_notes_ref(reference);
2267 1 : char *refspec = (char *)note_ref.c_str();
2268 1 : const git_strarray refspecs = {&refspec, 1};
2269 :
2270 1 : int result_int = git_remote_lookup(remote_git.get_p(), m_repo, remote.c_str());
2271 1 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
2272 :
2273 1 : result_int = git_push_options_init(&options, GIT_PUSH_OPTIONS_VERSION);
2274 1 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
2275 :
2276 1 : m_ssh_cred_attempt = 0;
2277 1 : m_ssh_agent_tried = false;
2278 1 : m_ssh_explicit_tried = false;
2279 1 : setup_remote_callbacks(options.callbacks);
2280 :
2281 1 : result_int = git_remote_push(remote_git.get(), &refspecs, &options);
2282 1 : if (result_int < 0) return ETRC_RET(ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result_int));
2283 :
2284 2 : return ETRC_RET(ESYSREPO_RESULT(ResultCode::OK));
2285 2 : }
2286 :
2287 2 : Result GitImpl::resolve_ref(git_reference **ref, const std::string &ref_str)
2288 : {
2289 2 : Guard<git_object> git_obj;
2290 2 : int result = 0;
2291 :
2292 2 : assert(ref != nullptr);
2293 2 : assert(m_repo != nullptr);
2294 :
2295 2 : result = git_reference_dwim(ref, m_repo, ref_str.c_str());
2296 2 : if (result == GIT_OK) return ESYSREPO_RESULT(ResultCode::OK);
2297 :
2298 0 : result = git_revparse_single(git_obj.get_p(), m_repo, ref_str.c_str());
2299 0 : if (result == GIT_OK) return ESYSREPO_RESULT(ResultCode::OK);
2300 :
2301 0 : return ESYSREPO_RESULT(ResultCode::GENERIC_ERROR);
2302 2 : }
2303 :
2304 27 : Result GitImpl::resolve_ref(git_annotated_commit **commit, const std::string &ref)
2305 : {
2306 27 : git_reference *git_ref = nullptr;
2307 27 : git_object *git_obj = nullptr;
2308 27 : int result = 0;
2309 :
2310 27 : assert(commit != nullptr);
2311 27 : assert(m_repo != nullptr);
2312 :
2313 27 : result = git_reference_dwim(&git_ref, m_repo, ref.c_str());
2314 27 : if (result == GIT_OK)
2315 : {
2316 27 : const char *name = git_reference_name(git_ref);
2317 :
2318 : //! \TODO remote this
2319 27 : const char *branch_name = nullptr;
2320 27 : git_branch_name(&branch_name, git_ref);
2321 :
2322 27 : git_annotated_commit_from_ref(commit, m_repo, git_ref);
2323 27 : git_reference_free(git_ref);
2324 27 : return ESYSREPO_RESULT(ResultCode::OK);
2325 : }
2326 :
2327 0 : result = git_revparse_single(&git_obj, m_repo, ref.c_str());
2328 0 : if (result == GIT_OK)
2329 : {
2330 0 : result = git_annotated_commit_lookup(commit, m_repo, git_object_id(git_obj));
2331 0 : git_object_free(git_obj);
2332 0 : return ESYSREPO_RESULT(ResultCode::OK);
2333 : }
2334 :
2335 0 : return ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result);
2336 : }
2337 :
2338 217 : Result GitImpl::resolve_ref(git_reference **ref, git_annotated_commit **commit, const std::string &ref_str)
2339 : {
2340 217 : Guard<git_object> git_obj;
2341 217 : int result = 0;
2342 :
2343 217 : assert(commit != nullptr);
2344 217 : assert(ref != nullptr);
2345 217 : assert(m_repo != nullptr);
2346 :
2347 217 : result = git_reference_dwim(ref, m_repo, ref_str.c_str());
2348 217 : if (result == GIT_OK)
2349 : {
2350 101 : git_annotated_commit_from_ref(commit, m_repo, *ref);
2351 101 : return ESYSREPO_RESULT(ResultCode::OK);
2352 : }
2353 :
2354 116 : result = git_revparse_single(git_obj.get_p(), m_repo, ref_str.c_str());
2355 116 : if (result == GIT_OK)
2356 : {
2357 41 : result = git_annotated_commit_lookup(commit, m_repo, git_object_id(git_obj.get()));
2358 : }
2359 :
2360 116 : if (result == GIT_OK) return ESYSREPO_RESULT(ResultCode::OK);
2361 75 : return ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result);
2362 217 : }
2363 :
2364 0 : Result GitImpl::find_ref(git_annotated_commit **commit, const std::string &ref, std::string &new_ref)
2365 : {
2366 0 : std::vector<git::Remote> remotes;
2367 0 : int found_remotes = 0;
2368 :
2369 0 : Result rresult = get_remotes(remotes);
2370 0 : if (rresult.error()) return ESYSREPO_RESULT(rresult);
2371 :
2372 0 : int result = 0;
2373 :
2374 0 : for (auto &remote : remotes)
2375 : {
2376 0 : new_ref = "refs/remotes/" + remote.get_name() + "/" + ref;
2377 :
2378 0 : rresult = resolve_ref(commit, new_ref);
2379 0 : if (rresult.ok())
2380 : {
2381 0 : self()->debug(0, "found branch : " + new_ref);
2382 0 : ++found_remotes;
2383 : }
2384 : }
2385 0 : if (found_remotes == 1) return ESYSREPO_RESULT(ResultCode::OK);
2386 0 : return ESYSREPO_RESULT(ResultCode::GIT_FIND_REF_FAILED);
2387 0 : }
2388 :
2389 53 : Result GitImpl::find_ref(git_reference **ref, git_annotated_commit **commit, const std::string &ref_str,
2390 : std::string &new_ref)
2391 : {
2392 53 : std::vector<git::Remote> remotes;
2393 53 : int found_remotes = 0;
2394 :
2395 53 : Result rresult = get_remotes(remotes);
2396 53 : if (rresult.error()) return ESYSREPO_RESULT(rresult);
2397 :
2398 53 : int result = 0;
2399 108 : for (auto &remote : remotes)
2400 : {
2401 55 : new_ref = "refs/remotes/" + remote.get_name() + "/" + ref_str;
2402 :
2403 55 : rresult = resolve_ref(ref, commit, new_ref);
2404 55 : if (rresult.ok())
2405 : {
2406 33 : self()->debug(0, "found branch : " + new_ref);
2407 33 : ++found_remotes;
2408 : }
2409 : }
2410 53 : if (found_remotes == 1) return ESYSREPO_RESULT(ResultCode::OK);
2411 20 : return ESYSREPO_RESULT(ResultCode::GIT_FIND_REF_FAILED);
2412 53 : }
2413 :
2414 321 : Result GitImpl::convert_bin_hex(const git_oid &oid, std::string &hex_str)
2415 : {
2416 321 : char temp[GIT_OID_HEXSZ + 1];
2417 :
2418 321 : int result = git_oid_fmt(temp, &oid);
2419 321 : if (result < 0) return ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result);
2420 321 : temp[GIT_OID_HEXSZ] = 0;
2421 321 : hex_str = std::string(temp);
2422 321 : return ESYSREPO_RESULT(ResultCode::OK);
2423 : }
2424 :
2425 15 : Result GitImpl::convert_hex_bin(const std::string &hex_str, git_oid &oid)
2426 : {
2427 15 : int result = git_oid_fromstrp(&oid, hex_str.c_str());
2428 15 : if (result == GIT_OK) return ESYSREPO_RESULT(ResultCode::OK);
2429 0 : return ESYSREPO_RESULT(ResultCode::GIT_RAW_INT_ERROR, result);
2430 : }
2431 :
2432 0 : const std::string &GitImpl::get_version()
2433 : {
2434 0 : return s_get_version();
2435 : }
2436 :
2437 0 : const std::string &GitImpl::get_lib_name()
2438 : {
2439 0 : return s_get_lib_name();
2440 : }
2441 :
2442 131 : void GitImpl::convert(git::BranchType branch_type, git_branch_t &list_flags)
2443 : {
2444 131 : switch (branch_type)
2445 : {
2446 0 : case git::BranchType::ALL: list_flags = GIT_BRANCH_ALL; break;
2447 131 : case git::BranchType::LOCAL: list_flags = GIT_BRANCH_LOCAL; break;
2448 0 : case git::BranchType::REMOTE: list_flags = GIT_BRANCH_REMOTE; break;
2449 0 : default: list_flags = GIT_BRANCH_LOCAL;
2450 : }
2451 131 : }
2452 :
2453 0 : void GitImpl::set_agent_identity_path(const std::string &agent_identity_path)
2454 : {
2455 0 : m_agent_identity_path = agent_identity_path;
2456 0 : }
2457 :
2458 0 : const std::string &GitImpl::get_agent_identity_path() const
2459 : {
2460 0 : return m_agent_identity_path;
2461 : }
2462 :
2463 542 : void GitImpl::set_logger_if(std::shared_ptr<log::Logger_if> logger_if)
2464 : {
2465 1084 : m_ssh.set_logger_if(logger_if);
2466 542 : }
2467 :
2468 17 : git_repository *GitImpl::get_repo()
2469 : {
2470 17 : return m_repo;
2471 : }
2472 :
2473 7 : Result GitImpl::get_sigs(git_signature **author, git_signature **committer)
2474 : {
2475 7 : std::shared_ptr<git::Person> person = self()->get_author();
2476 :
2477 7 : int result_int = git_signature_now(author, person->get_name().c_str(), person->get_email().c_str());
2478 7 : if (result_int < 0) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR);
2479 :
2480 7 : if (self()->get_committer() != nullptr) person = self()->get_committer();
2481 :
2482 7 : result_int = git_signature_now(committer, person->get_name().c_str(), person->get_email().c_str());
2483 7 : if (result_int < 0) return ESYSREPO_RESULT(ResultCode::GIT_GENERIC_ERROR);
2484 :
2485 7 : return ESYSREPO_RESULT(ResultCode::OK);
2486 7 : }
2487 :
2488 25 : std::string GitImpl::normalize_notes_ref(const std::string &reference)
2489 : {
2490 25 : std::string the_ref = reference;
2491 :
2492 25 : if (reference.find("refs/notes/") == std::string::npos) the_ref = "refs/notes/" + reference;
2493 :
2494 25 : return the_ref;
2495 0 : }
2496 :
2497 2 : const std::string &GitImpl::s_get_version()
2498 : {
2499 2 : static std::string s_version = LIBGIT2_VERSION;
2500 2 : return s_version;
2501 : }
2502 :
2503 2 : const std::string &GitImpl::s_get_lib_name()
2504 : {
2505 2 : static std::string s_lib_name = "libgit2";
2506 2 : return s_lib_name;
2507 : }
2508 :
2509 2 : const std::string &GitImpl::s_get_ssh_version()
2510 : {
2511 2 : static std::string s_version = LIBSSH2_VERSION;
2512 2 : return s_version;
2513 : }
2514 :
2515 2 : const std::string &GitImpl::s_get_ssh_lib_name()
2516 : {
2517 2 : static std::string s_lib_name = "libssh2";
2518 2 : return s_lib_name;
2519 : }
2520 :
2521 : } // namespace esys::repo::libgit2
|