Line data Source code
1 : /*!
2 : * \file esys/repo/gitbase.h
3 : * \brief Base class for git client implementation
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 : #pragma once
19 :
20 : #include "esys/repo/esysrepo_defs.h"
21 : #include "esys/repo/git/aheadbehind.h"
22 : #include "esys/repo/git/branches.h"
23 : #include "esys/repo/git/cloneoptions.h"
24 : #include "esys/repo/git/commit.h"
25 : #include "esys/repo/git/diff.h"
26 : #include "esys/repo/git/fetchstep.h"
27 : #include "esys/repo/git/mergeanalysisresult.h"
28 : #include "esys/repo/git/noteid.h"
29 : #include "esys/repo/git/person.h"
30 : #include "esys/repo/git/progress.h"
31 : #include "esys/repo/git/remote.h"
32 : #include "esys/repo/git/repostatus.h"
33 : #include "esys/repo/git/resettype.h"
34 : #include "esys/repo/git/sshbackend.h"
35 : #include "esys/repo/git/walkcommit.h"
36 : #include "esys/repo/result.h"
37 : #include "esys/repo/result_t.h"
38 :
39 : #include "esys/repo/progresscallbackbase.h"
40 :
41 : #include <esys/log/user.h>
42 :
43 : #include <chrono>
44 : #include <map>
45 : #include <memory>
46 : #include <mutex>
47 : #include <string>
48 : #include <vector>
49 :
50 : //<swig_inc/>
51 :
52 : //<swig>%shared_ptr(esys::repo::GitBase);</swig>
53 :
54 : namespace esys::repo
55 : {
56 :
57 : /*! \class GitBase esys/repo/gitbase.h "esys/repo/gitbase.h"
58 : * \brief Base class for git client implementation
59 : */
60 : class ESYSREPO_API GitBase : public log::User
61 : {
62 : public:
63 : using GeneratorType = std::shared_ptr<GitBase> (*)();
64 :
65 : //! Default constructor
66 : GitBase();
67 :
68 : //! Destructor
69 : ~GitBase() override;
70 :
71 : //! Set the author to be used for commit and notes
72 : /*!
73 : * \param[in] person the person to use as author
74 : */
75 : void set_author(std::shared_ptr<git::Person> person);
76 :
77 : //! Get the author to be used for commit and notes
78 : /*!
79 : * \return the person used as Author
80 : */
81 : std::shared_ptr<git::Person> get_author() const;
82 :
83 : //! Set the committer to be used for commit and notes
84 : /*!
85 : * \param[in] person the person to use as committer
86 : */
87 : void set_committer(std::shared_ptr<git::Person> person);
88 :
89 : //! Get the committer to be used for commit and notes
90 : /*!
91 : * \return the person used as committer
92 : */
93 : std::shared_ptr<git::Person> get_committer() const;
94 :
95 : //! Open a git repository for inspection
96 : /*!
97 : * \param[in] folder the folder where is the git repository
98 : * \return 0 if successful, < 0 otherwise
99 : */
100 : //<swig>%rename(open_folder) open;</swig>
101 : virtual Result open(const std::string &folder) = 0;
102 :
103 : //! Tells if the git repository is open or not
104 : /*!
105 : * \return true if open, false otherwise
106 : */
107 : virtual bool is_open() = 0;
108 :
109 : //! Initialize a bare git repository in a given folder
110 : /*!
111 : * \param[in] folder_path the folder where to create the bare git repository
112 : * \return 0 if successful, < 0 otherwise
113 : */
114 : virtual Result init_bare(const std::string &folder_path) = 0;
115 :
116 : //! Initialize a non-bare git repository in a given folder
117 : /*!
118 : * \param[in] folder_path the folder where to create the git repository
119 : * \return 0 if successful, < 0 otherwise
120 : */
121 : virtual Result init(const std::string &folder_path) = 0;
122 :
123 : //! Close a git repository
124 : /*!
125 : * \return 0 if successful, < 0 otherwise
126 : */
127 : virtual Result close() = 0;
128 :
129 : //! Close a git repository when an error occured
130 : virtual void close_on_error() = 0;
131 :
132 : //! Get all remotes of an open git repository
133 : /*!
134 : * \param[out] remotes vector with all the remotes of the open git repository
135 : * \return 0 if successful, < 0 otherwise
136 : */
137 : virtual Result get_remotes(std::vector<git::Remote> &remotes) = 0;
138 :
139 : //! Get the branch and the remote of the head
140 : /*!
141 : * \param[out] branch the branch of the head
142 : * \param[out] remote the remote of the head
143 : * \return 0 if successful, < 0 otherwise
144 : */
145 : virtual Result get_head_branch_remote(git::Branch &branch, git::Remote &remote) = 0;
146 :
147 : //! Add a remote to the local git repository
148 : /*!
149 : * \param[in] name the name of the remote
150 : * \param[in] url the url of the remote
151 : * \return 0 if successful, < 0 otherwise
152 : */
153 : virtual Result add_remote(const std::string &name, const std::string &url) = 0;
154 :
155 : //! Get all branches of an open git repository
156 : /*!
157 : * \param[out] branches vector with all the branches of the open git repository
158 : * \param[in] branch_type tells if the local or remote branches are requested
159 : * \return 0 if successful, < 0 otherwise
160 : */
161 : virtual Result get_branches(git::Branches &branches, git::BranchType branch_type = git::BranchType::LOCAL) = 0;
162 :
163 : //! Clone a remote git repository given it's address and path where to clone it
164 : /*!
165 : * \param[in] url the url for the repository to clone
166 : * \param[in] path where to clone the repository
167 : * \param[in] rev optional named tip (branch or tag); honored only if
168 : * ``options.checkout_rev``. Not a raw commit SHA.
169 : * \param[in] options checkout_rev / single_branch (defaults = legacy clone)
170 : * \return 0 if successful, < 0 otherwise
171 : */
172 : virtual Result clone(const std::string &url, const std::string &path, const std::string &rev,
173 : const git::CloneOptions &options) = 0;
174 :
175 : //! Clone with default options (legacy: ``rev`` ignored; remote default tip only)
176 37 : Result clone(const std::string &url, const std::string &path, const std::string &rev = "")
177 : {
178 37 : return clone(url, path, rev, git::CloneOptions());
179 : }
180 :
181 : //! Checkout a given branch of an open git repository
182 : /*!
183 : * \param[in] branch the name of the branch to checkout
184 : * \param[in] force if the cloning will be forced or not
185 : * \return 0 if successful, < 0 otherwise
186 : */
187 : virtual Result checkout(const std::string &branch, bool force = false) = 0;
188 :
189 : //! Reset the git repo to a given commit
190 : /*!
191 : * \param[in] commit the commit to reset the git repo to
192 : * \param[in] type the type of reset to do
193 : * \return 0 if successful, < 0 otherwise
194 : */
195 : virtual Result reset(const git::CommitHash &commit, git::ResetType type = git::ResetType::SOFT) = 0;
196 :
197 : //! Fast foward git repo to a given commit
198 : /*!
199 : * \param[in] commit the commit to reset the git repo to
200 : * \return 0 if successful, < 0 otherwise
201 : */
202 : virtual Result fastforward(const git::CommitHash &commit) = 0;
203 :
204 : //! Get the last commit of the HEAD
205 : /*!
206 : * \param[out] commit the commit hash
207 : * \return 0 if successful, < 0 otherwise
208 : */
209 : virtual Result get_last_commit(git::CommitHash &commit) = 0;
210 :
211 : //! Get the last commit of the HEAD
212 : /*!
213 : * \param[out] commit the commit data
214 : * \return 0 if successful, < 0 otherwise
215 : */
216 : virtual Result get_last_commit(git::Commit &commit, bool get_all_notes = false) = 0;
217 :
218 : //! Get the nth parent of a given commit
219 : /*!
220 : * If nth_parent is zero, the parent commit is simply the given commit.
221 : * If nth_parent is 1, it will return the parent of the commit.
222 : * If nth_parent is 2, it will return the grandparent of the commit.
223 : *
224 : * In the case of an octopus merge, it will take the first parent of the list.
225 : *
226 : * \param[in] commit the commit data
227 : * \param[out] parent the parent commit data
228 : * \param[in] nth_parent the nth parent starting
229 : * \return 0 if successful, < 0 otherwise
230 : */
231 : virtual Result get_parent_commit(const git::CommitHash &commit, git::CommitHash &parent, int nth_parent = 1) = 0;
232 :
233 : //! Tells if there are changes in the git repo
234 : /*!
235 : * \param[out] dirty true if the git repo is dirty, there are modification; false otherwise
236 : * \return 0 if successful, < 0 otherwise
237 : */
238 : virtual Result is_dirty(bool &dirty) = 0;
239 :
240 : //! Tells if git repo is detached or not
241 : /*!
242 : * \param[out] detached true if the git repo is detached; false otherwise
243 : * \return 0 if successful, < 0 otherwise
244 : */
245 : virtual Result is_detached(bool &detached) = 0;
246 :
247 : //! Get the status of the repository
248 : /*!
249 : * \param[out] status the status of the repository
250 : * \return 0 if successful, < 0 otherwise
251 : */
252 : virtual Result get_status(git::RepoStatus &status) = 0;
253 :
254 : //! Tells if a SSH agent is running or not
255 : /*!
256 : * \param[in] log_once if true, log only once if the SSH is detected
257 : * \return true if a SSH agent is running, false otherwise
258 : */
259 : virtual Result_t<bool> is_ssh_agent_running(bool log_once = true) = 0;
260 :
261 : //! Detect and optionally log if a SSH agent is found
262 : /*!
263 : * \param[in] log_once if true, log only once if the SSH is detected
264 : */
265 : virtual void detect_ssh_agent(bool log_once = true) = 0;
266 :
267 : //! Tell if an SSH backend was compiled into the active Git implementation
268 : /*!
269 : * Process-wide (not per Git instance). Dispatches to the registered
270 : * implementation from GitMngr.
271 : *
272 : * \param[in] backend the SSH backend
273 : * \return true if compiled in / supported by the implementation
274 : */
275 : static bool is_ssh_backend_supported(git::SshBackend backend);
276 :
277 : //! Tell if an SSH backend can be used at runtime
278 : /*!
279 : * Process-wide (not per Git instance). For LibSSH2 this matches
280 : * is_ssh_backend_supported. For Exec, also checks that an OpenSSH-compatible
281 : * client is configured or findable (GIT_SSH_COMMAND, GIT_SSH, or ssh on PATH).
282 : *
283 : * \param[in] backend the SSH backend
284 : * \param[in] force if true, refresh the Exec client probe cache
285 : * \return true if the backend can be selected for SSH operations
286 : */
287 : static bool is_ssh_backend_available(git::SshBackend backend, bool force = false);
288 :
289 : //! Select the process-wide SSH backend
290 : /*!
291 : * \param[in] backend the SSH backend to use for subsequent SSH operations
292 : * \return OK if successful, error if unsupported or the underlying library failed
293 : */
294 : static Result set_ssh_backend(git::SshBackend backend);
295 :
296 : //! Get the process-wide SSH backend currently selected
297 : /*!
298 : * \return the active backend, or an error if SSH is unavailable
299 : */
300 : static Result_t<git::SshBackend> get_ssh_backend();
301 :
302 : //! Check what kind of merge would required to merge the given branch to the git repo
303 : /*!
304 : * \param[in] refs the list of references/branches which would be merged
305 : * \param[out] merge_analysis_result the type of merge which would be required
306 : * \param[out] commits the list of commits which would be merged
307 : * \return 0 if successful, < 0 otherwise
308 : */
309 : virtual Result merge_analysis(const std::vector<std::string> &refs, git::MergeAnalysisResult &merge_analysis_result,
310 : std::vector<git::CommitHash> &commits) = 0;
311 :
312 : //! Do a fetch on a git repo from the given remote
313 : /*!
314 : * \param[in] remote the remote to use, or uses the default one if not given
315 : * \return 0 if successful, < 0 otherwise
316 : */
317 : virtual Result fetch(const std::string &remote = "") = 0;
318 :
319 : //! Init and update Git submodules of an existing work tree (ADR-0032)
320 : /*!
321 : * \param[in] path absolute path to the parent repository work tree
322 : * \param[in] recursive also update nested submodules
323 : * \return OK if successful
324 : */
325 : virtual Result update_submodules(const std::string &path, bool recursive = true) = 0;
326 :
327 : //! fetch all notes
328 : /*!
329 : * \param[in] arg the remote
330 : */
331 : virtual Result fetch_all_notes(const std::string &remote = "") = 0;
332 :
333 : //! reset to parent
334 : /*!
335 : * \param[in] 1 the nth parent
336 : */
337 : virtual Result reset_to_parent(int nth_parent = 1);
338 :
339 : virtual Result_t<bool> has_branch(const std::string &name,
340 : git::BranchType branch_type = git::BranchType::LOCAL) = 0;
341 :
342 : virtual Result get_hash(const std::string &revision, std::string &hash,
343 : git::BranchType branch_type = git::BranchType::REMOTE) = 0;
344 :
345 : //! Walks through all the commits starting from the HEAD
346 : /*!
347 : * \param[in] walk_commit the instance used to handle the commits
348 : * \return 0 if successful, < 0 otherwise
349 : */
350 : virtual Result walk_commits(std::shared_ptr<git::WalkCommit> walk_commit) = 0;
351 :
352 : //! Get the diff between a commit and its parent
353 : /*!
354 : * \param[in] commit_hash the commit to get the diff from
355 : * \param[out] diff the diff with the parent if any
356 : * \return 0 if successful, < 0 otherwise
357 : */
358 : virtual Result diff(const git::CommitHash &commit_hash, std::shared_ptr<git::Diff> diff) = 0;
359 :
360 : //! Get the the number of commits ahead or behind between a branch and its upstream
361 : /*!
362 : * \param[out] ahead_behind number of commits ahead or behind
363 : * \param[in] local_branch the local_branch
364 : * \return 0 if successful, < 0 otherwise
365 : */
366 : virtual Result get_ahead_behind(git::AheadBehind &ahead_behind, const git::Branch &local_branch) = 0;
367 :
368 : //! Get the the number of commits ahead or behind between 2 references
369 : /*!
370 : * \param[out] ahead_behind number of commits ahead or behind
371 : * \param[in] first_ref the first reference
372 : * \param[in] second_ref the second reference
373 : * \return 0 if successful, < 0 otherwise
374 : */
375 : virtual Result get_ahead_behind(git::AheadBehind &ahead_behind, const std::string &first_ref,
376 : const std::string &second_ref) = 0;
377 :
378 : //! Tells if a folder is a git repository
379 : /*!
380 : * \param[in] path the path of the folder
381 : * \return true if it's a git repository, false otherwise
382 : */
383 : static bool is_repo(const std::string &path);
384 :
385 : //! Set the id of this instance
386 : /*!
387 : * \param[in] id the id of this instance
388 : */
389 : void set_id(std::size_t id);
390 :
391 : //! Get the id of this instance
392 : /*!
393 : * \return the id of this instance
394 : */
395 : std::size_t get_id() const;
396 :
397 : //! Set if debug information should be printed
398 : /*!
399 : * \param[in] debug true if debug information should be printed
400 : */
401 : void set_debug(bool debug);
402 :
403 : //! Get if debug information should be printed
404 : /*!
405 : * \return true if debug information should be printed, false otherwise
406 : */
407 : bool get_debug() const;
408 :
409 : //! Get the version of the library used to implement the functionality
410 : /*!
411 : * \return the version of the library used to implement the functionality
412 : */
413 : virtual const std::string &get_version() = 0;
414 :
415 : //! Get the name of the library used to implement the functionality
416 : /*!
417 : * \return the name of the library used to implement the functionality
418 : */
419 : virtual const std::string &get_lib_name() = 0;
420 :
421 : //! handle sideband progress
422 : /*!
423 : * \param[in] text the text
424 : */
425 : int handle_sideband_progress(const std::string &text);
426 :
427 : //! handle transfer progress
428 : /*!
429 : * \param[in] progress the progress
430 : */
431 : int handle_transfer_progress(const git::Progress &progress);
432 :
433 : //! Set the progress
434 : /*!
435 : * \param[in] progress the progress
436 : */
437 : void set_progress(const git::Progress &progress);
438 :
439 : //! Get the progress
440 : /*!
441 : * \return the progress
442 : */
443 : void get_progress(git::Progress &progress);
444 :
445 : //! Set the progress callback
446 : /*!
447 : * \param[in] progress_callback the progress callback
448 : */
449 : void set_progress_callback(ProgressCallbackBase *progress_callback);
450 : ProgressCallbackBase *get_progress_callback();
451 :
452 : virtual Result add_note(git::NoteId ¬e_id, const std::string &reference, const std::string &message,
453 : bool overwrite = false) = 0;
454 :
455 : //! remove note
456 : /*!
457 : * \param[in] node_id the node id
458 : */
459 : virtual Result remove_note(const git::NoteId &node_id) = 0;
460 :
461 : //! remove note
462 : /*!
463 : * \param[in] commit_hash the commit hash
464 : * \param[in] reference the reference
465 : */
466 : virtual Result remove_note(const git::CommitHash &commit_hash, const std::string &reference) = 0;
467 :
468 : //! remove note last commit
469 : /*!
470 : * \param[in] reference the reference
471 : */
472 : virtual Result remove_note_last_commit(const std::string &reference) = 0;
473 :
474 : virtual Result push(const std::string &remote, const std::string &src_ref = "",
475 : const std::string &dst_ref = "") = 0;
476 : //! push notes
477 : /*!
478 : * \param[in] remote the remote
479 : * \param[in] arg the reference
480 : */
481 : virtual Result push_notes(const std::string &remote, const std::string &reference = "") = 0;
482 :
483 : //! Clear all note references
484 : /*!
485 : * \return the name of the library used to implement the functionality
486 : */
487 : void clear_notes_ref();
488 :
489 : //! Add a notes reference
490 : /*!
491 : * \param[in] notes_ref the notes reference to add
492 : */
493 : int add_notes_ref(const std::string ¬es_ref);
494 :
495 : //! Get all notes reference
496 : /*!
497 : * \return the notes references
498 : */
499 : std::vector<std::string> &get_notes_references();
500 :
501 : //! Get all notes reference
502 : /*!
503 : * \return the notes references
504 : */
505 : const std::vector<std::string> &get_notes_references() const;
506 :
507 : //! Decode the information sent by the remote git server
508 : /*!
509 : * \param[in] txt the text received
510 : * \param[out] fetch_step the fetch step we are at
511 : * \param[out] done true if the detected fetch step is done
512 : * \param[out] percentage the percentange done of the detected fetch step
513 : * \return 0 if the decoding was successful, < 0 if an error occurred
514 : */
515 : static int decode_sideband_progress(const std::string &txt, git::Progress &progress);
516 :
517 : //! Notify the progress callback (rate-limited).
518 : /*!
519 : * Throttle decision first (no lock). On emit: cache progress and invoke the
520 : * ProgressCallback. At most about every 50 ms, plus on started/done/fetch-step
521 : * changes — so libgit2 transfer ticks do not flood TaskBase / TUI.
522 : */
523 : void notify_progress_throttled(const git::Progress &progress);
524 :
525 : //! cmd start
526 : virtual void cmd_start();
527 :
528 : //! cmd end
529 : virtual void cmd_end();
530 :
531 : //! open time
532 : virtual void open_time();
533 :
534 : //! close time
535 : virtual void close_time();
536 :
537 : //! Get the open time
538 : /*!
539 : * \return the open time
540 : */
541 : virtual uint64_t get_open_time();
542 :
543 : //! Get the last cmd elapsed time
544 : /*!
545 : * \return the last cmd elapsed time
546 : */
547 : virtual uint64_t get_last_cmd_elapsed_time();
548 :
549 : //! Hybrid: ms spent reopening libgit2 after clone/fetch; others return 0
550 : /*!
551 : * Clears the stored value. Nested inside the clone/fetch wall time.
552 : */
553 : virtual uint64_t take_last_refresh_ms();
554 :
555 : protected:
556 : //!< \cond DOXY_IMPL
557 : virtual bool do_is_ssh_backend_supported(git::SshBackend backend) const = 0;
558 : virtual bool do_is_ssh_backend_available(git::SshBackend backend, bool force) = 0;
559 : virtual Result do_set_ssh_backend(git::SshBackend backend) = 0;
560 : virtual Result_t<git::SshBackend> do_get_ssh_backend() const = 0;
561 :
562 : //! One probe instance for the active Git implementation (GitMngr)
563 : static GitBase &ssh_probe();
564 : //!< \endcond
565 :
566 : private:
567 : //!< \cond DOXY_IMPL
568 :
569 : using Time = std::chrono::time_point<std::chrono::steady_clock>;
570 :
571 : std::shared_ptr<git::Person> m_person;
572 : std::shared_ptr<git::Person> m_committer;
573 : std::size_t m_id = 0; //!< The id of the git repository handled
574 : bool m_debug = false; //!< Tell if debug information should be printed
575 : git::Progress m_progress; //!< The progress info
576 : ProgressCallbackBase *m_progress_callback = nullptr; //!< The progress callback
577 : std::mutex m_progress_mutex; //!< The mutex for all access to the progress data
578 : Time m_last_progress_emit{}; //!< Last ProgressCallback emit (throttle)
579 : git::FetchStep m_last_emitted_progress_step = git::FetchStep::NOT_SET;
580 : bool m_progress_emit_armed = false; //!< True after first throttled emit
581 : Time m_open_time; //!< The time when the git repo was opened
582 : Time m_last_cmd_start_time; //!< The time when the last command was started
583 : Time m_last_cmd_end_time; //!< The time when the last command was started
584 : Time m_close_time; //!< The time when the git repo was closed
585 : std::vector<std::string> m_notes_references; //!< The vector of notes references
586 : std::map<std::string, bool, std::less<>> m_map_notes_references; //!< The map of already added notes references
587 : //!< \endcond
588 : };
589 :
590 : } // namespace esys::repo
|