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