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