src/esys/repo/libgit2/gitimpl_libgit2.cpp

1 error line(s), 12 error(s)

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