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