KiCad PCB EDA Suite
Loading...
Searching...
No Matches
kicad_git_common.cpp
Go to the documentation of this file.
1/*
2 * This program source code file is part of KiCad, a free EDA CAD application.
3 *
4 * Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/gpl-3.0.html
19 * or you may search the http://www.gnu.org website for the version 3 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include "kicad_git_common.h"
25
26#include <kiplatform/secrets.h>
27
28#include <wx/filename.h>
29#include <wx/log.h>
30#include <wx/textfile.h>
31#include <wx/utils.h>
32#include <map>
33#include <vector>
34
35KIGIT_COMMON::KIGIT_COMMON( git_repository* aRepo ) :
36 m_repo( aRepo ), m_connType( GIT_CONN_TYPE::GIT_CONN_LOCAL ), m_testedTypes( 0 )
37{}
38
39
41{}
42
43
44git_repository* KIGIT_COMMON::GetRepo() const
45{
46 return m_repo;
47}
48
50{
51 git_reference* head = nullptr;
52
53 int retval = git_repository_head( &head, m_repo );
54
55 if( retval && retval != GIT_EUNBORNBRANCH && retval != GIT_ENOTFOUND )
56 return wxEmptyString;
57
58 git_reference *branch;
59
60 if( git_reference_resolve( &branch, head ) )
61 {
62 git_reference_free( head );
63 return wxEmptyString;
64 }
65
66 git_reference_free( head );
67 const char* branchName = "";
68
69 if( git_branch_name( &branchName, branch ) )
70 {
71 git_reference_free( branch );
72 return wxEmptyString;
73 }
74
75 git_reference_free( branch );
76
77 return wxString( branchName );
78}
79
80
81std::vector<wxString> KIGIT_COMMON::GetBranchNames() const
82{
83 std::vector<wxString> branchNames;
84 std::map<git_time_t, wxString> branchNamesMap;
85 wxString firstName;
86
87 git_branch_iterator* branchIterator = nullptr;
88
89 if( git_branch_iterator_new( &branchIterator, m_repo, GIT_BRANCH_LOCAL ) )
90 return branchNames;
91
92 git_reference* branchReference = nullptr;
93 git_branch_t branchType;
94
95 while( git_branch_next( &branchReference, &branchType, branchIterator ) != GIT_ITEROVER )
96 {
97 const char* branchName = "";
98
99 if( git_branch_name( &branchName, branchReference ) )
100 continue;
101
102 const git_oid* commitId = git_reference_target( branchReference );
103
104 git_commit* commit = nullptr;
105
106 if( git_commit_lookup( &commit, m_repo, commitId ) )
107 continue;
108
109 git_time_t commitTime = git_commit_time( commit );
110
111 if( git_branch_is_head( branchReference ) )
112 firstName = branchName;
113 else
114 branchNamesMap.emplace( commitTime, branchName );
115
116 git_commit_free( commit );
117 git_reference_free( branchReference );
118 }
119
120 git_branch_iterator_free( branchIterator );
121
122 // Add the current branch to the top of the list
123 if( !firstName.IsEmpty() )
124 branchNames.push_back( firstName );
125
126 // Add the remaining branches in order from newest to oldest
127 for( auto rit = branchNamesMap.rbegin(); rit != branchNamesMap.rend(); ++rit )
128 branchNames.push_back( rit->second );
129
130 return branchNames;
131}
132
133
134std::vector<wxString> KIGIT_COMMON::GetProjectDirs()
135{
136 std::vector<wxString> projDirs;
137
138 git_oid oid;
139 git_commit* commit;
140 git_tree *tree;
141
142 if( git_reference_name_to_id( &oid, m_repo, "HEAD" ) != GIT_OK )
143 {
144 wxLogError( "An error occurred: %s", git_error_last()->message );
145 return projDirs;
146 }
147
148 if( git_commit_lookup( &commit, m_repo, &oid ) != GIT_OK )
149 {
150 wxLogError( "An error occurred: %s", git_error_last()->message );
151 return projDirs;
152 }
153
154 if( git_commit_tree( &tree, commit ) != GIT_OK )
155 {
156 wxLogError( "An error occurred: %s", git_error_last()->message );
157 return projDirs;
158 }
159
160 // Define callback
161 git_tree_walk(
162 tree, GIT_TREEWALK_PRE,
163 []( const char* root, const git_tree_entry* entry, void* payload )
164 {
165 std::vector<wxString>* prjs = static_cast<std::vector<wxString>*>( payload );
166 wxFileName root_fn( git_tree_entry_name( entry ) );
167
168 root_fn.SetPath( root );
169
170 if( git_tree_entry_type( entry ) == GIT_OBJECT_BLOB
171 && ( ( root_fn.GetExt() == "kicad_pro" ) || ( root_fn.GetExt() == "pro" ) ) )
172 {
173 prjs->push_back( root_fn.GetFullPath() );
174 }
175
176 return 0; // continue walking
177 },
178 &projDirs );
179
180 git_tree_free( tree );
181 git_commit_free( commit );
182
183 std::sort( projDirs.begin(), projDirs.end(),
184 []( const wxString& a, const wxString& b )
185 {
186 int a_freq = a.Freq( wxFileName::GetPathSeparator() );
187 int b_freq = b.Freq( wxFileName::GetPathSeparator() );
188
189 if( a_freq == b_freq )
190 return a < b;
191 else
192 return a_freq < b_freq;
193
194 } );
195
196 return projDirs;
197}
198
199
200std::pair<std::set<wxString>,std::set<wxString>> KIGIT_COMMON::GetDifferentFiles() const
201{
202 auto get_modified_files = [&]( git_oid* from_oid, git_oid* to_oid ) -> std::set<wxString>
203 {
204 std::set<wxString> modified_set;
205 git_revwalk* walker = nullptr;
206
207 if( git_revwalk_new( &walker, m_repo ) != GIT_OK )
208 return modified_set;
209
210 if( ( git_revwalk_push( walker, from_oid ) != GIT_OK )
211 || ( git_revwalk_hide( walker, to_oid ) != GIT_OK ) )
212 {
213 git_revwalk_free( walker );
214 return modified_set;
215 }
216
217 git_oid oid;
218 git_commit* commit;
219
220 // iterate over all local commits not in remote
221 while( git_revwalk_next( &oid, walker ) == GIT_OK )
222 {
223 if( git_commit_lookup( &commit, m_repo, &oid ) != GIT_OK )
224 continue;
225
226 git_tree *tree, *parent_tree = nullptr;
227 if( git_commit_tree( &tree, commit ) != GIT_OK )
228 {
229 git_commit_free( commit );
230 continue;
231 }
232
233 // get parent commit tree to diff against
234 if( !git_commit_parentcount( commit ) )
235 {
236 git_tree_free( tree );
237 git_commit_free( commit );
238 continue;
239 }
240
241
242 git_commit* parent;
243 if( git_commit_parent( &parent, commit, 0 ) != GIT_OK )
244 {
245 git_tree_free( tree );
246 git_commit_free( commit );
247 continue;
248 }
249
250
251 if( git_commit_tree( &parent_tree, parent ) != GIT_OK )
252 {
253 git_tree_free( tree );
254 git_commit_free( commit );
255 git_commit_free( parent );
256 continue;
257 }
258
259
260 git_diff* diff;
261 git_diff_options diff_opts;
262 git_diff_init_options( &diff_opts, GIT_DIFF_OPTIONS_VERSION );
263
264 if( git_diff_tree_to_tree( &diff, m_repo, parent_tree, tree, &diff_opts ) == GIT_OK )
265 {
266 size_t num_deltas = git_diff_num_deltas( diff );
267
268 for( size_t i = 0; i < num_deltas; ++i )
269 {
270 const git_diff_delta* delta = git_diff_get_delta( diff, i );
271 modified_set.insert( delta->new_file.path );
272 }
273
274 git_diff_free( diff );
275 }
276
277 git_tree_free( parent_tree );
278 git_commit_free( parent );
279 git_tree_free( tree );
280 git_commit_free( commit );
281 }
282
283 git_revwalk_free( walker );
284
285 return modified_set;
286 };
287
288 std::pair<std::set<wxString>,std::set<wxString>> modified_files;
289
290 if( !m_repo )
291 return modified_files;
292
293 git_reference* head = nullptr;
294 git_reference* remote_head = nullptr;
295
296 if( git_repository_head( &head, m_repo ) != GIT_OK )
297 return modified_files;
298
299 if( git_branch_upstream( &remote_head, head ) != GIT_OK )
300 {
301 git_reference_free( head );
302 return modified_files;
303 }
304
305 git_oid head_oid = *git_reference_target( head );
306 git_oid remote_oid = *git_reference_target( remote_head );
307
308 git_reference_free( head );
309 git_reference_free( remote_head );
310
311 modified_files.first = get_modified_files( &head_oid, &remote_oid );
312 modified_files.second = get_modified_files( &remote_oid, &head_oid );
313
314 return modified_files;
315}
316
317
319{
320 if( !m_repo )
321 return false;
322
323 git_reference* head = nullptr;
324 git_reference* remote_head = nullptr;
325
326 if( git_repository_head( &head, m_repo ) != GIT_OK )
327 return false;
328
329 if( git_branch_upstream( &remote_head, head ) != GIT_OK )
330 {
331 git_reference_free( head );
332 return false;
333 }
334
335 git_oid head_oid = *git_reference_target( head );
336 git_oid remote_oid = *git_reference_target( remote_head );
337
338 git_reference_free( head );
339 git_reference_free( remote_head );
340
341 git_revwalk* walker = nullptr;
342
343 if( git_revwalk_new( &walker, m_repo ) != GIT_OK )
344 return false;
345
346 if( ( git_revwalk_push( walker, &head_oid ) != GIT_OK )
347 || ( git_revwalk_hide( walker, &remote_oid ) != GIT_OK ) )
348 {
349 git_revwalk_free( walker );
350 return false;
351 }
352
353 git_oid oid;
354
355 // If we can't walk to the next commit, then we are at or behind the remote
356 if( git_revwalk_next( &oid, walker ) != GIT_OK )
357 {
358 git_revwalk_free( walker );
359 return false;
360 }
361
362 git_revwalk_free( walker );
363 return true;
364}
365
366
368{
369 git_remote* remote = nullptr;
370
371 if( git_remote_lookup( &remote, m_repo, "origin" ) != GIT_OK )
372 {
373 return false;
374 }
375
376 // Get the URLs associated with the remote
377 const char* fetch_url = git_remote_url( remote );
378 const char* push_url = git_remote_pushurl( remote );
379
380 // If no push URL is set, libgit2 defaults to using the fetch URL for pushing
381 if( !push_url )
382 {
383 push_url = fetch_url;
384 }
385
386 // Clean up the remote object
387 git_remote_free( remote );
388
389 // Check if both URLs are valid (i.e., not NULL)
390 return fetch_url && push_url;
391}
392
393
395{
396 wxString retval;
397 git_reference* head = nullptr;
398 git_reference* upstream = nullptr;
399
400 if( git_repository_head( &head, m_repo ) != GIT_OK )
401 return retval;
402
403 if( git_branch_upstream( &upstream, head ) == GIT_OK )
404 {
405 git_buf remote_name = GIT_BUF_INIT_CONST( nullptr, 0 );
406
407 if( git_branch_remote_name( &remote_name, m_repo, git_reference_name( upstream ) ) == GIT_OK )
408 {
409 retval = remote_name.ptr;
410 git_buf_dispose( &remote_name );
411 }
412
413 git_reference_free( upstream );
414 }
415
416 git_reference_free( head );
417
418 return retval;
419}
420
421void KIGIT_COMMON::SetSSHKey( const wxString& aKey )
422{
423 auto it = std::find( m_publicKeys.begin(), m_publicKeys.end(), aKey );
424
425 if( it != m_publicKeys.end() )
426 m_publicKeys.erase( it );
427
428 m_publicKeys.insert( m_publicKeys.begin(), aKey );
429}
430
431
433{
434 m_publicKeys.clear();
435
436 wxFileName keyFile( wxGetHomeDir(), wxEmptyString );
437 keyFile.AppendDir( ".ssh" );
438 keyFile.SetFullName( "id_rsa" );
439
440 if( keyFile.FileExists() )
441 m_publicKeys.push_back( keyFile.GetFullPath() );
442
443 keyFile.SetFullName( "id_dsa" );
444
445 if( keyFile.FileExists() )
446 m_publicKeys.push_back( keyFile.GetFullPath() );
447
448 keyFile.SetFullName( "id_ecdsa" );
449
450 if( keyFile.FileExists() )
451 m_publicKeys.push_back( keyFile.GetFullPath() );
452
453 keyFile.SetFullName( "id_ed25519" );
454
455 if( keyFile.FileExists() )
456 m_publicKeys.push_back( keyFile.GetFullPath() );
457
458 // Parse SSH config file for hostname information
459 wxFileName sshConfig( wxGetHomeDir(), wxEmptyString );
460 sshConfig.AppendDir( ".ssh" );
461 sshConfig.SetFullName( "config" );
462
463 if( sshConfig.FileExists() )
464 {
465 wxTextFile configFile( sshConfig.GetFullPath() );
466 configFile.Open();
467
468 bool match = false;
469
470 for( wxString line = configFile.GetFirstLine(); !configFile.Eof(); line = configFile.GetNextLine() )
471 {
472 line.Trim( false ).Trim( true );
473
474 if( line.StartsWith( "Host " ) )
475 match = false;
476
477 // The difference here is that we are matching either "Hostname" or "Host" to get the
478 // match. This is because in the absence of a "Hostname" line, the "Host" line is used
479 if( line.StartsWith( "Host" ) && line.Contains( m_hostname ) )
480 match = true;
481
482 if( match && line.StartsWith( "IdentityFile" ) )
483 {
484 wxString keyPath = line.AfterFirst( ' ' ).Trim( false ).Trim( true );
485
486 // Expand ~ to home directory if present
487 if( keyPath.StartsWith( "~" ) )
488 keyPath.Replace( "~", wxGetHomeDir(), false );
489
490 // Add the public key to the beginning of the list
491 if( wxFileName::FileExists( keyPath ) )
492 SetSSHKey( keyPath );
493 }
494 }
495
496 configFile.Close();
497 }
498}
499
500
502{
503 // We want to get the current branch's upstream url as well as the stored password
504 // if one exists given the url and username.
505
506 wxString remote_name = GetRemotename();
507 git_remote* remote = nullptr;
508
509 if( git_remote_lookup( &remote, m_repo, remote_name.ToStdString().c_str() ) == GIT_OK )
510 {
511 const char* url = git_remote_url( remote );
512
513 if( url )
514 m_remote = url;
515
516 git_remote_free( remote );
517 }
518
519 // Find the stored password if it exists
521
524}
525
527{
528 if( m_remote.StartsWith( "https://" ) || m_remote.StartsWith( "http://" ) )
530 else if( m_remote.StartsWith( "ssh://" ) || m_remote.StartsWith( "git@" ) || m_remote.StartsWith( "git+ssh://" ) )
532 else
534
536 {
537 wxString uri = m_remote;
538 size_t atPos = uri.find( '@' );
539
540 if( atPos != wxString::npos )
541 {
542 size_t protoEnd = uri.find( "//" );
543
544 if( protoEnd != wxString::npos )
545 {
546 wxString credentials = uri.Mid( protoEnd + 2, atPos - protoEnd - 2 );
547 size_t colonPos = credentials.find( ':' );
548
549 if( colonPos != wxString::npos )
550 {
551 m_username = credentials.Left( colonPos );
552 m_password = credentials.Mid( colonPos + 1, credentials.Length() - colonPos - 1 );
553 }
554 else
555 {
556 m_username = credentials;
557 }
558 }
559 else
560 {
561 m_username = uri.Left( atPos );
562 }
563 }
564
565 if( m_remote.StartsWith( "git@" ) )
566 {
567 // SSH format: git@hostname:path
568 size_t colonPos = m_remote.find( ':' );
569 if( colonPos != wxString::npos )
570 m_hostname = m_remote.Mid( 4, colonPos - 4 );
571 }
572 else
573 {
574 // other URL format: proto://[user@]hostname/path
575 size_t hostStart = m_remote.find( "://" ) + 2;
576 size_t hostEnd = m_remote.find( '/', hostStart );
577 wxString host;
578
579 if( hostEnd != wxString::npos )
580 host = m_remote.Mid( hostStart, hostEnd - hostStart );
581 else
582 host = m_remote.Mid( hostStart );
583
584 atPos = host.find( '@' );
585
586 if( atPos != wxString::npos )
587 m_hostname = host.Mid( atPos + 1 );
588 else
589 m_hostname = host;
590 }
591 }
592}
593
594
595extern "C" int fetchhead_foreach_cb( const char*, const char*,
596 const git_oid* aOID, unsigned int aIsMerge, void* aPayload )
597{
598 if( aIsMerge )
599 git_oid_cpy( (git_oid*) aPayload, aOID );
600
601 return 0;
602}
603
604
605extern "C" void clone_progress_cb( const char* aStr, size_t aLen, size_t aTotal, void* data )
606{
607 KIGIT_COMMON* parent = (KIGIT_COMMON*) data;
608
609 wxString progressMessage( aStr );
610 parent->UpdateProgress( aLen, aTotal, progressMessage );
611}
612
613
614extern "C" int progress_cb( const char* str, int len, void* data )
615{
616 KIGIT_COMMON* parent = (KIGIT_COMMON*) data;
617
618 wxString progressMessage( str, len );
619 parent->UpdateProgress( 0, 0, progressMessage );
620
621 return 0;
622}
623
624
625extern "C" int transfer_progress_cb( const git_transfer_progress* aStats, void* aPayload )
626{
627 KIGIT_COMMON* parent = (KIGIT_COMMON*) aPayload;
628 wxString progressMessage = wxString::Format( _( "Received %u of %u objects" ),
629 aStats->received_objects,
630 aStats->total_objects );
631
632 parent->UpdateProgress( aStats->received_objects, aStats->total_objects, progressMessage );
633
634 return 0;
635}
636
637
638extern "C" int update_cb( const char* aRefname, const git_oid* aFirst, const git_oid* aSecond,
639 void* aPayload )
640{
641 constexpr int cstring_len = 8;
642 char a_str[cstring_len + 1];
643 char b_str[cstring_len + 1];
644
645 KIGIT_COMMON* parent = (KIGIT_COMMON*) aPayload;
646 wxString status;
647
648 git_oid_tostr( b_str, cstring_len, aSecond );
649
650#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
651 if( !git_oid_is_zero( aFirst ) )
652#else
653 if( !git_oid_iszero( aFirst ) )
654#endif
655 {
656 git_oid_tostr( a_str, cstring_len, aFirst );
657 status = wxString::Format( _( "* [updated] %s..%s %s" ), a_str, b_str, aRefname );
658 }
659 else
660 {
661 status = wxString::Format( _( "* [new] %s %s" ), b_str, aRefname );
662 }
663
664 parent->UpdateProgress( 0, 0, status );
665
666 return 0;
667}
668
669
670extern "C" int push_transfer_progress_cb( unsigned int aCurrent, unsigned int aTotal, size_t aBytes,
671 void* aPayload )
672{
673 int64_t progress = 100;
674 KIGIT_COMMON* parent = (KIGIT_COMMON*) aPayload;
675
676 if( aTotal != 0 )
677 {
678 progress = ( aCurrent * 100 ) / aTotal;
679 }
680
681 wxString progressMessage = wxString::Format( _( "Writing objects: %d%% (%d/%d), %d bytes" ),
682 progress, aCurrent, aTotal, aBytes );
683 parent->UpdateProgress( aCurrent, aTotal, progressMessage );
684
685 return 0;
686}
687
688
689extern "C" int push_update_reference_cb( const char* aRefname, const char* aStatus, void* aPayload )
690{
691 KIGIT_COMMON* parent = (KIGIT_COMMON*) aPayload;
692 wxString status( aStatus );
693
694 if( !status.IsEmpty() )
695 {
696 wxString statusMessage = wxString::Format( _( "* [rejected] %s (%s)" ), aRefname, aStatus );
697 parent->UpdateProgress( 0, 0, statusMessage );
698 }
699 else
700 {
701 wxString statusMessage = wxString::Format( _( "[updated] %s" ), aRefname );
702 parent->UpdateProgress( 0, 0, statusMessage );
703 }
704
705 return 0;
706}
707
708
709extern "C" int credentials_cb( git_cred** aOut, const char* aUrl, const char* aUsername,
710 unsigned int aAllowedTypes, void* aPayload )
711{
712 KIGIT_COMMON* parent = static_cast<KIGIT_COMMON*>( aPayload );
713
715 return GIT_PASSTHROUGH;
716
717 if( aAllowedTypes & GIT_CREDTYPE_USERNAME
718 && !( parent->TestedTypes() & GIT_CREDTYPE_USERNAME ) )
719 {
720 wxString username = parent->GetUsername().Trim().Trim( false );
721 git_cred_username_new( aOut, username.ToStdString().c_str() );
722 parent->TestedTypes() |= GIT_CREDTYPE_USERNAME;
723 }
725 && ( aAllowedTypes & GIT_CREDTYPE_USERPASS_PLAINTEXT )
726 && !( parent->TestedTypes() & GIT_CREDTYPE_USERPASS_PLAINTEXT ) )
727 {
728 wxString username = parent->GetUsername().Trim().Trim( false );
729 wxString password = parent->GetPassword().Trim().Trim( false );
730
731 git_cred_userpass_plaintext_new( aOut, username.ToStdString().c_str(),
732 password.ToStdString().c_str() );
733 parent->TestedTypes() |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
734 }
736 && ( aAllowedTypes & GIT_CREDTYPE_SSH_KEY )
737 && !( parent->TestedTypes() & GIT_CREDTYPE_SSH_KEY ) )
738 {
739 // SSH key authentication
740 wxString sshKey = parent->GetNextPublicKey();
741
742 if( sshKey.IsEmpty() )
743 {
744 parent->TestedTypes() |= GIT_CREDTYPE_SSH_KEY;
745 return GIT_PASSTHROUGH;
746 }
747
748 wxString sshPubKey = sshKey + ".pub";
749 wxString username = parent->GetUsername().Trim().Trim( false );
750 wxString password = parent->GetPassword().Trim().Trim( false );
751
752 git_cred_ssh_key_new( aOut, username.ToStdString().c_str(),
753 sshPubKey.ToStdString().c_str(),
754 sshKey.ToStdString().c_str(),
755 password.ToStdString().c_str() );
756 }
757 else
758 {
759 return GIT_PASSTHROUGH;
760 }
761
762 return GIT_OK;
763};
wxString m_username
git_repository * m_repo
std::vector< wxString > GetBranchNames() const
void updateConnectionType()
wxString GetCurrentBranchName() const
void SetSSHKey(const wxString &aSSHKey)
std::vector< wxString > GetProjectDirs()
Return a vector of project files in the repository.
GIT_CONN_TYPE GetConnType() const
wxString GetPassword() const
git_repository * GetRepo() const
wxString m_hostname
wxString m_password
wxString GetNextPublicKey()
KIGIT_COMMON(git_repository *aRepo)
std::pair< std::set< wxString >, std::set< wxString > > GetDifferentFiles() const
Return a pair of sets of files that differ locally from the remote repository The first set is files ...
std::vector< wxString > m_publicKeys
bool HasPushAndPullRemote() const
wxString GetUsername() const
GIT_CONN_TYPE m_connType
void UpdateCurrentBranchInfo()
bool HasLocalCommits() const
unsigned & TestedTypes()
virtual void UpdateProgress(int aCurrent, int aTotal, const wxString &aMessage)
wxString GetRemotename() const
#define _(s)
void clone_progress_cb(const char *aStr, size_t aLen, size_t aTotal, void *data)
int fetchhead_foreach_cb(const char *, const char *, const git_oid *aOID, unsigned int aIsMerge, void *aPayload)
int push_update_reference_cb(const char *aRefname, const char *aStatus, void *aPayload)
int update_cb(const char *aRefname, const git_oid *aFirst, const git_oid *aSecond, void *aPayload)
int transfer_progress_cb(const git_transfer_progress *aStats, void *aPayload)
int credentials_cb(git_cred **aOut, const char *aUrl, const char *aUsername, unsigned int aAllowedTypes, void *aPayload)
int push_transfer_progress_cb(unsigned int aCurrent, unsigned int aTotal, size_t aBytes, void *aPayload)
int progress_cb(const char *str, int len, void *data)
bool GetSecret(const wxString &aService, const wxString &aKey, wxString &aSecret)
constexpr int delta