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 35 : Result clone(const std::string &url, const std::string &path, const std::string &rev = "")
177 : {
178 35 : 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 : //! fetch all notes
320 : /*!
321 : * \param[in] arg the remote
322 : */
323 : virtual Result fetch_all_notes(const std::string &remote = "") = 0;
324 :
325 : //! reset to parent
326 : /*!
327 : * \param[in] 1 the nth parent
328 : */
329 : virtual Result reset_to_parent(int nth_parent = 1);
330 :
331 : virtual Result_t<bool> has_branch(const std::string &name,
332 : git::BranchType branch_type = git::BranchType::LOCAL) = 0;
333 :
334 : virtual Result get_hash(const std::string &revision, std::string &hash,
335 : git::BranchType branch_type = git::BranchType::REMOTE) = 0;
336 :
337 : //! Walks through all the commits starting from the HEAD
338 : /*!
339 : * \param[in] walk_commit the instance used to handle the commits
340 : * \return 0 if successful, < 0 otherwise
341 : */
342 : virtual Result walk_commits(std::shared_ptr<git::WalkCommit> walk_commit) = 0;
343 :
344 : //! Get the diff between a commit and its parent
345 : /*!
346 : * \param[in] commit_hash the commit to get the diff from
347 : * \param[out] diff the diff with the parent if any
348 : * \return 0 if successful, < 0 otherwise
349 : */
350 : virtual Result diff(const git::CommitHash &commit_hash, std::shared_ptr<git::Diff> diff) = 0;
351 :
352 : //! Get the the number of commits ahead or behind between a branch and its upstream
353 : /*!
354 : * \param[out] ahead_behind number of commits ahead or behind
355 : * \param[in] local_branch the local_branch
356 : * \return 0 if successful, < 0 otherwise
357 : */
358 : virtual Result get_ahead_behind(git::AheadBehind &ahead_behind, const git::Branch &local_branch) = 0;
359 :
360 : //! Get the the number of commits ahead or behind between 2 references
361 : /*!
362 : * \param[out] ahead_behind number of commits ahead or behind
363 : * \param[in] first_ref the first reference
364 : * \param[in] second_ref the second reference
365 : * \return 0 if successful, < 0 otherwise
366 : */
367 : virtual Result get_ahead_behind(git::AheadBehind &ahead_behind, const std::string &first_ref,
368 : const std::string &second_ref) = 0;
369 :
370 : //! Tells if a folder is a git repository
371 : /*!
372 : * \param[in] path the path of the folder
373 : * \return true if it's a git repository, false otherwise
374 : */
375 : static bool is_repo(const std::string &path);
376 :
377 : //! Set the id of this instance
378 : /*!
379 : * \param[in] id the id of this instance
380 : */
381 : void set_id(std::size_t id);
382 :
383 : //! Get the id of this instance
384 : /*!
385 : * \return the id of this instance
386 : */
387 : std::size_t get_id() const;
388 :
389 : //! Set if debug information should be printed
390 : /*!
391 : * \param[in] debug true if debug information should be printed
392 : */
393 : void set_debug(bool debug);
394 :
395 : //! Get if debug information should be printed
396 : /*!
397 : * \return true if debug information should be printed, false otherwise
398 : */
399 : bool get_debug() const;
400 :
401 : //! Get the version of the library used to implement the functionality
402 : /*!
403 : * \return the version of the library used to implement the functionality
404 : */
405 : virtual const std::string &get_version() = 0;
406 :
407 : //! Get the name of the library used to implement the functionality
408 : /*!
409 : * \return the name of the library used to implement the functionality
410 : */
411 : virtual const std::string &get_lib_name() = 0;
412 :
413 : //! handle sideband progress
414 : /*!
415 : * \param[in] text the text
416 : */
417 : int handle_sideband_progress(const std::string &text);
418 :
419 : //! handle transfer progress
420 : /*!
421 : * \param[in] progress the progress
422 : */
423 : int handle_transfer_progress(const git::Progress &progress);
424 :
425 : //! Set the progress
426 : /*!
427 : * \param[in] progress the progress
428 : */
429 : void set_progress(const git::Progress &progress);
430 :
431 : //! Get the progress
432 : /*!
433 : * \return the progress
434 : */
435 : void get_progress(git::Progress &progress);
436 :
437 : //! Set the progress callback
438 : /*!
439 : * \param[in] progress_callback the progress callback
440 : */
441 : void set_progress_callback(ProgressCallbackBase *progress_callback);
442 : ProgressCallbackBase *get_progress_callback();
443 :
444 : virtual Result add_note(git::NoteId ¬e_id, const std::string &reference, const std::string &message,
445 : bool overwrite = false) = 0;
446 :
447 : //! remove note
448 : /*!
449 : * \param[in] node_id the node id
450 : */
451 : virtual Result remove_note(const git::NoteId &node_id) = 0;
452 :
453 : //! remove note
454 : /*!
455 : * \param[in] commit_hash the commit hash
456 : * \param[in] reference the reference
457 : */
458 : virtual Result remove_note(const git::CommitHash &commit_hash, const std::string &reference) = 0;
459 :
460 : //! remove note last commit
461 : /*!
462 : * \param[in] reference the reference
463 : */
464 : virtual Result remove_note_last_commit(const std::string &reference) = 0;
465 :
466 : virtual Result push(const std::string &remote, const std::string &src_ref = "",
467 : const std::string &dst_ref = "") = 0;
468 : //! push notes
469 : /*!
470 : * \param[in] remote the remote
471 : * \param[in] arg the reference
472 : */
473 : virtual Result push_notes(const std::string &remote, const std::string &reference = "") = 0;
474 :
475 : //! Clear all note references
476 : /*!
477 : * \return the name of the library used to implement the functionality
478 : */
479 : void clear_notes_ref();
480 :
481 : //! Add a notes reference
482 : /*!
483 : * \param[in] notes_ref the notes reference to add
484 : */
485 : int add_notes_ref(const std::string ¬es_ref);
486 :
487 : //! Get all notes reference
488 : /*!
489 : * \return the notes references
490 : */
491 : std::vector<std::string> &get_notes_references();
492 :
493 : //! Get all notes reference
494 : /*!
495 : * \return the notes references
496 : */
497 : const std::vector<std::string> &get_notes_references() const;
498 :
499 : //! Decode the information sent by the remote git server
500 : /*!
501 : * \param[in] txt the text received
502 : * \param[out] fetch_step the fetch step we are at
503 : * \param[out] done true if the detected fetch step is done
504 : * \param[out] percentage the percentange done of the detected fetch step
505 : * \return 0 if the decoding was successful, < 0 if an error occurred
506 : */
507 : static int decode_sideband_progress(const std::string &txt, git::Progress &progress);
508 :
509 : //! Notify the progress callback (rate-limited).
510 : /*!
511 : * Throttle decision first (no lock). On emit: cache progress and invoke the
512 : * ProgressCallback. At most about every 50 ms, plus on started/done/fetch-step
513 : * changes — so libgit2 transfer ticks do not flood TaskBase / TUI.
514 : */
515 : void notify_progress_throttled(const git::Progress &progress);
516 :
517 : //! cmd start
518 : virtual void cmd_start();
519 :
520 : //! cmd end
521 : virtual void cmd_end();
522 :
523 : //! open time
524 : virtual void open_time();
525 :
526 : //! close time
527 : virtual void close_time();
528 :
529 : //! Get the open time
530 : /*!
531 : * \return the open time
532 : */
533 : virtual uint64_t get_open_time();
534 :
535 : //! Get the last cmd elapsed time
536 : /*!
537 : * \return the last cmd elapsed time
538 : */
539 : virtual uint64_t get_last_cmd_elapsed_time();
540 :
541 : //! Hybrid: ms spent reopening libgit2 after clone/fetch; others return 0
542 : /*!
543 : * Clears the stored value. Nested inside the clone/fetch wall time.
544 : */
545 : virtual uint64_t take_last_refresh_ms();
546 :
547 : protected:
548 : //!< \cond DOXY_IMPL
549 : virtual bool do_is_ssh_backend_supported(git::SshBackend backend) const = 0;
550 : virtual bool do_is_ssh_backend_available(git::SshBackend backend, bool force) = 0;
551 : virtual Result do_set_ssh_backend(git::SshBackend backend) = 0;
552 : virtual Result_t<git::SshBackend> do_get_ssh_backend() const = 0;
553 :
554 : //! One probe instance for the active Git implementation (GitMngr)
555 : static GitBase &ssh_probe();
556 : //!< \endcond
557 :
558 : private:
559 : //!< \cond DOXY_IMPL
560 :
561 : using Time = std::chrono::time_point<std::chrono::steady_clock>;
562 :
563 : std::shared_ptr<git::Person> m_person;
564 : std::shared_ptr<git::Person> m_committer;
565 : std::size_t m_id = 0; //!< The id of the git repository handled
566 : bool m_debug = false; //!< Tell if debug information should be printed
567 : git::Progress m_progress; //!< The progress info
568 : ProgressCallbackBase *m_progress_callback = nullptr; //!< The progress callback
569 : std::mutex m_progress_mutex; //!< The mutex for all access to the progress data
570 : Time m_last_progress_emit{}; //!< Last ProgressCallback emit (throttle)
571 : git::FetchStep m_last_emitted_progress_step = git::FetchStep::NOT_SET;
572 : bool m_progress_emit_armed = false; //!< True after first throttled emit
573 : Time m_open_time; //!< The time when the git repo was opened
574 : Time m_last_cmd_start_time; //!< The time when the last command was started
575 : Time m_last_cmd_end_time; //!< The time when the last command was started
576 : Time m_close_time; //!< The time when the git repo was closed
577 : std::vector<std::string> m_notes_references; //!< The vector of notes references
578 : std::map<std::string, bool, std::less<>> m_map_notes_references; //!< The map of already added notes references
579 : //!< \endcond
580 : };
581 :
582 : } // namespace esys::repo
|