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