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#include "kicad_git_memory.h"
26#include "git_repo_mixin.h"
27
28#include <git/git_progress.h>
30#include <kiplatform/secrets.h>
31#include <trace_helpers.h>
32
33#include <git2.h>
34#include <wx/filename.h>
35#include <wx/log.h>
36#include <wx/textfile.h>
37#include <wx/utils.h>
38#include <map>
39#include <vector>
40
41KIGIT_COMMON::KIGIT_COMMON( git_repository* aRepo ) :
43 m_nextPublicKey( 0 ), m_secretFetched( false )
44{}
45
46
48 // Initialize base class and member variables
49 m_repo( aOther.m_repo ),
50 m_projectDir( aOther.m_projectDir ),
51 m_connType( aOther.m_connType ),
52 m_remote( aOther.m_remote ),
53 m_hostname( aOther.m_hostname ),
54 m_username( aOther.m_username ),
55 m_password( aOther.m_password ),
57
58 // The mutex is default-initialized, not copied
60 m_publicKeys( aOther.m_publicKeys ),
63{
64}
65
66
69
70
71git_repository* KIGIT_COMMON::GetRepo() const
72{
73 return m_repo;
74}
75
76
78{
79 if( !m_projectDir.IsEmpty() )
80 return m_projectDir;
81
82 if( m_repo )
83 {
84 const char* workdir = git_repository_workdir( m_repo );
85
86 if( workdir )
87 return wxString( workdir );
88 }
89
90 return wxEmptyString;
91}
92
93
95{
96 wxCHECK( m_repo, wxEmptyString );
97 git_reference* head = nullptr;
98
99 int retval = git_repository_head( &head, m_repo );
100
101 if( retval && retval != GIT_EUNBORNBRANCH && retval != GIT_ENOTFOUND )
102 return wxEmptyString;
103
104 KIGIT::GitReferencePtr headPtr( head );
105 git_reference* branch;
106
107 if( git_reference_resolve( &branch, head ) )
108 {
109 wxLogTrace( traceGit, "Failed to resolve branch" );
110 return wxEmptyString;
111 }
112
113 KIGIT::GitReferencePtr branchPtr( branch );
114 const char* branchName = "";
115
116 if( git_branch_name( &branchName, branch ) )
117 {
118 wxLogTrace( traceGit, "Failed to get branch name" );
119 return wxEmptyString;
120 }
121
122 return wxString( branchName );
123}
124
125
127{
128 if( !m_secretFetched )
129 {
131 {
132 wxString secret;
133
135 m_password = secret;
136 }
137
138 m_secretFetched = true;
139 }
140
141 return m_password;
142}
143
144
145std::vector<wxString> KIGIT_COMMON::GetBranchNames() const
146{
147 if( !m_repo )
148 return {};
149
150 std::vector<wxString> branchNames;
151 std::map<git_time_t, wxString> branchNamesMap;
152 wxString firstName;
153
154 git_branch_iterator* branchIterator = nullptr;
155
156 if( git_branch_iterator_new( &branchIterator, m_repo, GIT_BRANCH_LOCAL ) )
157 {
158 wxLogTrace( traceGit, "Failed to get branch iterator" );
159 return branchNames;
160 }
161
162 KIGIT::GitBranchIteratorPtr branchIteratorPtr( branchIterator );
163 git_reference* branchReference = nullptr;
164 git_branch_t branchType;
165
166 while( git_branch_next( &branchReference, &branchType, branchIterator ) != GIT_ITEROVER )
167 {
168 const char* branchName = "";
169 KIGIT::GitReferencePtr branchReferencePtr( branchReference );
170
171 if( git_branch_name( &branchName, branchReference ) )
172 {
173 wxLogTrace( traceGit, "Failed to get branch name in iter loop" );
174 continue;
175 }
176
177 const git_oid* commitId = git_reference_target( branchReference );
178
179 git_commit* commit = nullptr;
180
181 if( git_commit_lookup( &commit, m_repo, commitId ) )
182 {
183 wxLogTrace( traceGit, "Failed to get commit in iter loop" );
184 continue;
185 }
186
187 KIGIT::GitCommitPtr commitPtr( commit );
188 git_time_t commitTime = git_commit_time( commit );
189
190 if( git_branch_is_head( branchReference ) )
191 firstName = branchName;
192 else
193 branchNamesMap.emplace( commitTime, branchName );
194 }
195
196 // Add the current branch to the top of the list
197 if( !firstName.IsEmpty() )
198 branchNames.push_back( firstName );
199
200 // Add the remaining branches in order from newest to oldest
201 for( auto rit = branchNamesMap.rbegin(); rit != branchNamesMap.rend(); ++rit )
202 branchNames.push_back( rit->second );
203
204 return branchNames;
205}
206
207
208std::vector<wxString> KIGIT_COMMON::GetProjectDirs()
209{
210 wxCHECK( m_repo, {} );
211 std::vector<wxString> projDirs;
212
213 git_oid oid;
214 git_commit* commit;
215 git_tree *tree;
216
217 if( git_reference_name_to_id( &oid, m_repo, "HEAD" ) != GIT_OK )
218 {
219 wxLogTrace( traceGit, "An error occurred: %s", KIGIT_COMMON::GetLastGitError() );
220 return projDirs;
221 }
222
223 if( git_commit_lookup( &commit, m_repo, &oid ) != GIT_OK )
224 {
225 wxLogTrace( traceGit, "An error occurred: %s", KIGIT_COMMON::GetLastGitError() );
226 return projDirs;
227 }
228
229 KIGIT::GitCommitPtr commitPtr( commit );
230
231 if( git_commit_tree( &tree, commit ) != GIT_OK )
232 {
233 wxLogTrace( traceGit, "An error occurred: %s", KIGIT_COMMON::GetLastGitError() );
234 return projDirs;
235 }
236
237 KIGIT::GitTreePtr treePtr( tree );
238
239 // Define callback
240 git_tree_walk(
241 tree, GIT_TREEWALK_PRE,
242 []( const char* root, const git_tree_entry* entry, void* payload )
243 {
244 std::vector<wxString>* prjs = static_cast<std::vector<wxString>*>( payload );
245 wxFileName root_fn( git_tree_entry_name( entry ) );
246
247 root_fn.SetPath( root );
248
249 if( git_tree_entry_type( entry ) == GIT_OBJECT_BLOB
250 && ( ( root_fn.GetExt() == "kicad_pro" ) || ( root_fn.GetExt() == "pro" ) ) )
251 {
252 prjs->push_back( root_fn.GetFullPath() );
253 }
254
255 return 0; // continue walking
256 },
257 &projDirs );
258
259 std::sort( projDirs.begin(), projDirs.end(),
260 []( const wxString& a, const wxString& b )
261 {
262 int a_freq = a.Freq( wxFileName::GetPathSeparator() );
263 int b_freq = b.Freq( wxFileName::GetPathSeparator() );
264
265 if( a_freq == b_freq )
266 return a < b;
267 else
268 return a_freq < b_freq;
269
270 } );
271
272 return projDirs;
273}
274
275
276std::pair<std::set<wxString>, std::set<wxString>> KIGIT_COMMON::GetDifferentFiles() const
277{
278 std::pair<std::set<wxString>, std::set<wxString>> modified_files;
279
280 if( !m_repo || IsCancelled() )
281 return modified_files;
282
283 git_reference* head = nullptr;
284 git_reference* remote_head = nullptr;
285
286 if( git_repository_head( &head, m_repo ) != GIT_OK )
287 {
288 wxLogTrace( traceGit, "Failed to get modified HEAD" );
289 return modified_files;
290 }
291
292 KIGIT::GitReferencePtr headPtr( head );
293
294 if( git_branch_upstream( &remote_head, head ) != GIT_OK )
295 {
296 // Branch has no upstream tracking ref configured. Without an upstream there is
297 // nothing to compare against, so leave both sets empty. Walking commits unbounded
298 // would otherwise dump the entire history's root tree into the result.
299 wxLogTrace( traceGit, "Failed to get modified remote HEAD" );
300 return modified_files;
301 }
302
303 KIGIT::GitReferencePtr remoteHeadPtr( remote_head );
304
305 const git_oid* head_oid = git_reference_target( head );
306 const git_oid* remote_oid = git_reference_target( remote_head );
307
308 if( !head_oid || !remote_oid )
309 return modified_files;
310
311 auto load_tree =
312 [this]( const git_oid* aOid ) -> git_tree*
313 {
314 git_commit* commit = nullptr;
315
316 if( git_commit_lookup( &commit, m_repo, aOid ) != GIT_OK )
317 {
318 wxLogTrace( traceGit, "Failed to lookup commit for diff: %s",
320 return nullptr;
321 }
322
323 KIGIT::GitCommitPtr commitPtr( commit );
324 git_tree* tree = nullptr;
325
326 if( git_commit_tree( &tree, commit ) != GIT_OK )
327 {
328 wxLogTrace( traceGit, "Failed to get commit tree for diff: %s",
330 return nullptr;
331 }
332
333 return tree;
334 };
335
336 git_tree* head_tree = load_tree( head_oid );
337 KIGIT::GitTreePtr headTreePtr( head_tree );
338
339 git_tree* remote_tree = load_tree( remote_oid );
340 KIGIT::GitTreePtr remoteTreePtr( remote_tree );
341
342 if( !head_tree || !remote_tree )
343 return modified_files;
344
345 // Find the merge-base so AHEAD and BEHIND can be distinguished. AHEAD = files that
346 // changed between merge-base and HEAD (only in local commits). BEHIND = files that
347 // changed between merge-base and the remote tip (only in remote commits). Without a
348 // shared history the merge-base lookup fails; in that case we treat both sets as empty
349 // because there is no meaningful "ahead vs behind" partition to compute.
350 git_oid base_oid;
351
352 if( git_merge_base( &base_oid, m_repo, head_oid, remote_oid ) != GIT_OK )
353 {
354 wxLogTrace( traceGit, "No merge base between local and remote: %s",
356 return modified_files;
357 }
358
359 git_tree* base_tree = load_tree( &base_oid );
360 KIGIT::GitTreePtr baseTreePtr( base_tree );
361
362 if( !base_tree )
363 return modified_files;
364
365 auto collect_paths =
366 [this]( git_tree* aOldTree, git_tree* aNewTree, std::set<wxString>& aOut )
367 {
368 if( IsCancelled() )
369 return;
370
371 git_diff_options localOpts;
372 git_diff_init_options( &localOpts, GIT_DIFF_OPTIONS_VERSION );
373
374 git_diff* diff = nullptr;
375
376 if( git_diff_tree_to_tree( &diff, m_repo, aOldTree, aNewTree, &localOpts )
377 != GIT_OK )
378 {
379 wxLogTrace( traceGit, "Failed to diff trees: %s",
381 return;
382 }
383
384 size_t numDeltas = git_diff_num_deltas( diff );
385
386 for( size_t ii = 0; ii < numDeltas; ++ii )
387 {
388 const git_diff_delta* delta = git_diff_get_delta( diff, ii );
389
390 if( delta->new_file.path )
391 aOut.insert( wxString::FromUTF8( delta->new_file.path ) );
392
393 if( delta->old_file.path )
394 aOut.insert( wxString::FromUTF8( delta->old_file.path ) );
395 }
396
397 git_diff_free( diff );
398 };
399
400 collect_paths( base_tree, head_tree, modified_files.first ); // AHEAD
401 collect_paths( base_tree, remote_tree, modified_files.second ); // BEHIND
402
403 return modified_files;
404}
405
406
408{
409 if( !m_repo )
410 return false;
411
412 git_reference* head = nullptr;
413 git_reference* remote_head = nullptr;
414
415 if( git_repository_head( &head, m_repo ) != GIT_OK )
416 {
417 wxLogTrace( traceGit, "Failed to get HEAD: %s", KIGIT_COMMON::GetLastGitError() );
418 return false;
419 }
420
421 KIGIT::GitReferencePtr headPtr( head );
422
423 if( git_branch_upstream( &remote_head, head ) != GIT_OK )
424 {
425 // No remote branch, so we have local commits (new repo?)
426 wxLogTrace( traceGit, "Failed to get remote HEAD: %s", KIGIT_COMMON::GetLastGitError() );
427 return true;
428 }
429
430 KIGIT::GitReferencePtr remoteHeadPtr( remote_head );
431 const git_oid* head_oid = git_reference_target( head );
432 const git_oid* remote_oid = git_reference_target( remote_head );
433 git_revwalk* walker = nullptr;
434
435 if( git_revwalk_new( &walker, m_repo ) != GIT_OK )
436 {
437 wxLogTrace( traceGit, "Failed to create revwalker: %s", KIGIT_COMMON::GetLastGitError() );
438 return false;
439 }
440
441 KIGIT::GitRevWalkPtr walkerPtr( walker );
442
443 if( !head_oid || git_revwalk_push( walker, head_oid ) != GIT_OK )
444 {
445 wxLogTrace( traceGit, "Failed to push commits: %s", KIGIT_COMMON::GetLastGitError() );
446 return false;
447 }
448
449 if( remote_oid && git_revwalk_hide( walker, remote_oid ) != GIT_OK )
450 {
451 wxLogTrace( traceGit, "Failed to push/hide commits: %s", KIGIT_COMMON::GetLastGitError() );
452 return false;
453 }
454
455 git_oid oid;
456
457 // If we can't walk to the next commit, then we are at or behind the remote
458 if( git_revwalk_next( &oid, walker ) != GIT_OK )
459 {
460 wxLogTrace( traceGit, "Failed to walk to next commit: %s", KIGIT_COMMON::GetLastGitError() );
461 return false;
462 }
463
464 return true;
465}
466
467
469{
470 wxCHECK( m_repo, false );
471
472 // Prefer the upstream remote configured for the current branch. Fall back to
473 // "origin" and then to any remote that exposes a fetch URL so that repos
474 // cloned with non-default remote names also enable push/pull operations.
475 auto checkRemote =
476 [this]( const char* aName ) -> bool
477 {
478 git_remote* remote = nullptr;
479
480 if( git_remote_lookup( &remote, m_repo, aName ) != GIT_OK )
481 return false;
482
483 KIGIT::GitRemotePtr remotePtr( remote );
484
485 const char* fetch_url = git_remote_url( remote );
486 const char* push_url = git_remote_pushurl( remote );
487
488 // libgit2 defaults to the fetch URL for pushing when no push URL is set
489 if( !push_url )
490 push_url = fetch_url;
491
492 return fetch_url && push_url;
493 };
494
495 std::string preferred = GetRemoteNameOrDefault().utf8_string();
496
497 if( checkRemote( preferred.c_str() ) )
498 return true;
499
500 if( preferred != "origin" && checkRemote( "origin" ) )
501 return true;
502
503 git_strarray remotes = { nullptr, 0 };
504
505 if( git_remote_list( &remotes, m_repo ) != GIT_OK )
506 {
507 wxLogTrace( traceGit, "Failed to enumerate remotes for haspushpull" );
508 return false;
509 }
510
511 KIGIT::GitStrArrayPtr remotesPtr( &remotes );
512
513 for( size_t ii = 0; ii < remotes.count; ++ii )
514 {
515 if( checkRemote( remotes.strings[ii] ) )
516 return true;
517 }
518
519 return false;
520}
521
522
524{
525 wxString remoteName = GetRemotename();
526
527 if( remoteName.IsEmpty() )
528 remoteName = wxS( "origin" );
529
530 return remoteName;
531}
532
533
535{
536 wxCHECK( m_repo, wxEmptyString );
537
538 wxString retval;
539 git_reference* head = nullptr;
540 git_reference* upstream = nullptr;
541
542 if( git_repository_head( &head, m_repo ) != GIT_OK )
543 {
544 wxLogTrace( traceGit, "Failed to get remote name: %s", KIGIT_COMMON::GetLastGitError() );
545 return retval;
546 }
547
548 KIGIT::GitReferencePtr headPtr( head );
549
550 if( git_branch_upstream( &upstream, head ) != GIT_OK )
551 {
552 wxLogTrace( traceGit, "Failed to get upstream branch: %s", KIGIT_COMMON::GetLastGitError() );
553 git_strarray remotes = { nullptr, 0 };
554
555 if( git_remote_list( &remotes, m_repo ) == GIT_OK )
556 {
557 // No upstream tracking branch. Prefer "origin" when present, otherwise pick the
558 // single configured remote. Returning empty for ambiguous (multiple) remotes
559 // tells callers to fall back to whatever default they want.
560 if( remotes.count == 1 )
561 {
562 retval = remotes.strings[0];
563 }
564 else
565 {
566 for( size_t ii = 0; ii < remotes.count; ++ii )
567 {
568 if( strcmp( remotes.strings[ii], "origin" ) == 0 )
569 {
570 retval = remotes.strings[ii];
571 break;
572 }
573 }
574 }
575
576 git_strarray_dispose( &remotes );
577 }
578 else
579 {
580 wxLogTrace( traceGit, "Failed to list remotes: %s", KIGIT_COMMON::GetLastGitError() );
581
582 // If we can't get the remote name from the upstream branch or the list of remotes,
583 // just return the default remote name
584
585 git_remote* remote = nullptr;
586
587 if( git_remote_lookup( &remote, m_repo, "origin" ) == GIT_OK )
588 {
589 retval = git_remote_name( remote );
590 git_remote_free( remote );
591 }
592 else
593 {
594 wxLogTrace( traceGit, "Failed to get remote name from default remote: %s",
596 }
597 }
598
599 return retval;
600 }
601
602 KIGIT::GitReferencePtr upstreamPtr( upstream );
603 git_buf remote_name = GIT_BUF_INIT_CONST( nullptr, 0 );
604
605 if( git_branch_remote_name( &remote_name, m_repo, git_reference_name( upstream ) ) == GIT_OK )
606 {
607 retval = remote_name.ptr;
608 git_buf_dispose( &remote_name );
609 }
610 else
611 {
612 wxLogTrace( traceGit,
613 "Failed to get remote name from upstream branch: %s",
615 }
616
617 return retval;
618}
619
620
621void KIGIT_COMMON::SetSSHKey( const wxString& aKey )
622{
623 auto it = std::find( m_publicKeys.begin(), m_publicKeys.end(), aKey );
624
625 if( it != m_publicKeys.end() )
626 m_publicKeys.erase( it );
627
628 m_publicKeys.insert( m_publicKeys.begin(), aKey );
629}
630
631
633{
634 if( !m_repo )
635 return wxEmptyString;
636
637 // Prefer the working directory (the user-visible project root). Fall back to
638 // git_repository_path (the .git directory) only for bare repositories.
639 if( const char* workdir = git_repository_workdir( m_repo ) )
640 return wxString::FromUTF8( workdir );
641
642 if( const char* path = git_repository_path( m_repo ) )
643 return wxString::FromUTF8( path );
644
645 return wxEmptyString;
646}
647
648
650{
651 m_publicKeys.clear();
652
653 wxFileName keyFile( wxGetHomeDir(), wxEmptyString );
654 keyFile.AppendDir( ".ssh" );
655 keyFile.SetFullName( "id_rsa" );
656
657 if( keyFile.FileExists() )
658 m_publicKeys.push_back( keyFile.GetFullPath() );
659
660 keyFile.SetFullName( "id_dsa" );
661
662 if( keyFile.FileExists() )
663 m_publicKeys.push_back( keyFile.GetFullPath() );
664
665 keyFile.SetFullName( "id_ecdsa" );
666
667 if( keyFile.FileExists() )
668 m_publicKeys.push_back( keyFile.GetFullPath() );
669
670 keyFile.SetFullName( "id_ed25519" );
671
672 if( keyFile.FileExists() )
673 m_publicKeys.push_back( keyFile.GetFullPath() );
674
675 // Parse SSH config file for hostname information
676 wxFileName sshConfig( wxGetHomeDir(), wxEmptyString );
677 sshConfig.AppendDir( ".ssh" );
678 sshConfig.SetFullName( "config" );
679
680 if( sshConfig.FileExists() )
681 {
682 wxTextFile configFile( sshConfig.GetFullPath() );
683 configFile.Open();
684
685 bool match = false;
686
687 for( wxString line = configFile.GetFirstLine(); !configFile.Eof(); line = configFile.GetNextLine() )
688 {
689 line.Trim( false ).Trim( true );
690
691 if( line.StartsWith( "Host " ) )
692 match = false;
693
694 // The difference here is that we are matching either "Hostname" or "Host" to get the
695 // match. This is because in the absence of a "Hostname" line, the "Host" line is used
696 if( line.StartsWith( "Host" ) && line.Contains( m_hostname ) )
697 match = true;
698
699 if( match && line.StartsWith( "IdentityFile" ) )
700 {
701 wxString keyPath = line.AfterFirst( ' ' ).Trim( false ).Trim( true );
702
703 // Expand ~ to home directory if present
704 if( keyPath.StartsWith( "~" ) )
705 keyPath.Replace( "~", wxGetHomeDir(), false );
706
707 // Add the public key to the beginning of the list
708 if( wxFileName::FileExists( keyPath ) )
709 SetSSHKey( keyPath );
710 }
711 }
712
713 configFile.Close();
714 }
715}
716
717
719{
720 wxCHECK( m_repo, /* void */ );
721
722 // We want to get the current branch's upstream url as well as the stored password
723 // if one exists given the url and username.
724
725 wxString remote_name = GetRemotename();
726 git_remote* remote = nullptr;
727
728 m_remote.clear();
729 m_password.clear();
730 m_secretFetched = false;
731
732 if( git_remote_lookup( &remote, m_repo, remote_name.ToStdString().c_str() ) == GIT_OK )
733 {
734 const char* url = git_remote_url( remote );
735
736 if( url )
737 m_remote = url;
738
739 git_remote_free( remote );
740 }
741
744}
745
746
748{
749 wxString remote = m_remote;
750
751 if( remote.IsEmpty() )
752 remote = GetRemotename();
753
754 if( remote.StartsWith( "https://" ) || remote.StartsWith( "http://" ) )
755 {
757 }
758 else if( remote.StartsWith( "ssh://" ) || remote.StartsWith( "git@" ) || remote.StartsWith( "git+ssh://" )
759 || remote.EndsWith( ".git" ) )
760 {
762 }
763
765}
766
767
769{
770 if( m_remote.StartsWith( "https://" ) || m_remote.StartsWith( "http://" ) )
772 else if( m_remote.StartsWith( "ssh://" ) || m_remote.StartsWith( "git@" ) || m_remote.StartsWith( "git+ssh://" ) )
774 else
776
778 {
779 wxString uri = m_remote;
780 size_t atPos = uri.find( '@' );
781
782 if( atPos != wxString::npos )
783 {
784 size_t protoEnd = uri.find( "//" );
785
786 if( protoEnd != wxString::npos )
787 {
788 wxString credentials = uri.Mid( protoEnd + 2, atPos - protoEnd - 2 );
789 size_t colonPos = credentials.find( ':' );
790
791 if( colonPos != wxString::npos )
792 {
793 m_username = credentials.Left( colonPos );
794 m_password = credentials.Mid( colonPos + 1, credentials.Length() - colonPos - 1 );
795 }
796 else
797 {
798 m_username = credentials;
799 }
800 }
801 else
802 {
803 m_username = uri.Left( atPos );
804 }
805 }
806
807 if( m_remote.StartsWith( "git@" ) )
808 {
809 // SSH format: git@hostname:path
810 size_t colonPos = m_remote.find( ':' );
811
812 if( colonPos != wxString::npos )
813 m_hostname = m_remote.Mid( 4, colonPos - 4 );
814 }
815 else
816 {
817 // other URL format: proto://[user@]hostname/path
818 size_t hostStart = m_remote.find( "://" ) + 2;
819 size_t hostEnd = m_remote.find( '/', hostStart );
820 wxString host;
821
822 if( hostEnd != wxString::npos )
823 host = m_remote.Mid( hostStart, hostEnd - hostStart );
824 else
825 host = m_remote.Mid( hostStart );
826
827 atPos = host.find( '@' );
828
829 if( atPos != wxString::npos )
830 m_hostname = host.Mid( atPos + 1 );
831 else
832 m_hostname = host;
833 }
834 }
835
836 m_secretFetched = !m_password.IsEmpty();
837}
838
839
840int KIGIT_COMMON::HandleSSHKeyAuthentication( git_cred** aOut, const wxString& aUsername )
841{
843 {
844 if( HandleSSHAgentAuthentication( aOut, aUsername ) == GIT_OK )
845 return GIT_OK;
846 // Agent unavailable or has no matching key; fall through to configured key.
847 }
848
849 // SSH key authentication with password
850 wxString sshKey = GetNextPublicKey();
851
852 if( sshKey.IsEmpty() )
853 {
854 wxLogTrace( traceGit, "Finished testing all possible ssh keys" );
855 m_testedTypes |= GIT_CREDENTIAL_SSH_KEY;
856 return GIT_PASSTHROUGH;
857 }
858
859 wxString sshPubKey = sshKey + ".pub";
860 wxString password = GetPassword();
861
862 wxLogTrace( traceGit, "Testing %s\n", sshKey );
863
864 if( git_credential_ssh_key_new( aOut, aUsername.mbc_str(), sshPubKey.mbc_str(), sshKey.mbc_str(),
865 password.mbc_str() ) != GIT_OK )
866 {
867 wxLogTrace( traceGit, "Failed to create SSH key credential for %s: %s",
868 aUsername, KIGIT_COMMON::GetLastGitError() );
869 return GIT_PASSTHROUGH;
870 }
871
872 return GIT_OK;
873}
874
875
876int KIGIT_COMMON::HandlePlaintextAuthentication( git_cred** aOut, const wxString& aUsername )
877{
878 wxString password = GetPassword();
879
880 git_credential_userpass_plaintext_new( aOut, aUsername.mbc_str(), password.mbc_str() );
881 m_testedTypes |= GIT_CREDENTIAL_USERPASS_PLAINTEXT;
882
883 return GIT_OK;
884}
885
886
887int KIGIT_COMMON::HandleSSHAgentAuthentication( git_cred** aOut, const wxString& aUsername )
888{
890
891 if( git_credential_ssh_key_from_agent( aOut, aUsername.mbc_str() ) != GIT_OK )
892 {
893 wxLogTrace( traceGit, "Failed to create SSH agent credential for %s: %s",
894 aUsername, KIGIT_COMMON::GetLastGitError() );
895 return GIT_PASSTHROUGH;
896 }
897
898 return GIT_OK;
899}
900
901
902extern "C" int fetchhead_foreach_cb( const char*, const char*,
903 const git_oid* aOID, unsigned int aIsMerge, void* aPayload )
904{
905 if( aIsMerge )
906 git_oid_cpy( (git_oid*) aPayload, aOID );
907
908 return 0;
909}
910
911
912extern "C" void clone_progress_cb( const char* aStr, size_t aLen, size_t aTotal, void* aPayload )
913{
914 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
915
916 wxString progressMessage( aStr );
917 parent->UpdateProgress( aLen, aTotal, progressMessage );
918}
919
920
921extern "C" int progress_cb( const char* str, int len, void* aPayload )
922{
923 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
924
925 if( parent->GetCommon()->IsCancelled() )
926 {
927 wxLogTrace( traceGit, "Progress CB cancelled" );
928 return GIT_EUSER;
929 }
930
931 wxString progressMessage( str, len );
932 parent->UpdateProgress( 0, 0, progressMessage );
933
934 return 0;
935}
936
937
938extern "C" int transfer_progress_cb( const git_transfer_progress* aStats, void* aPayload )
939{
940 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
941
942 wxString progressMessage = wxString::Format( _( "Received %u of %u objects" ),
943 aStats->received_objects,
944 aStats->total_objects );
945 if( parent->GetCommon()->IsCancelled() )
946 {
947 wxLogTrace( traceGit, "Transfer progress cancelled" );
948 return GIT_EUSER;
949 }
950
951 parent->UpdateProgress( aStats->received_objects, aStats->total_objects, progressMessage );
952
953 return 0;
954}
955
956
957extern "C" int update_cb( const char* aRefname, const git_oid* aFirst, const git_oid* aSecond,
958 void* aPayload )
959{
960 constexpr int cstring_len = 8;
961 char a_str[cstring_len + 1];
962 char b_str[cstring_len + 1];
963
964 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
965 wxString status;
966
967 git_oid_tostr( b_str, cstring_len, aSecond );
968
969#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
970 if( !git_oid_is_zero( aFirst ) )
971#else
972 if( !git_oid_iszero( aFirst ) )
973#endif
974 {
975 git_oid_tostr( a_str, cstring_len, aFirst );
976 status = wxString::Format( _( "* [updated] %s..%s %s" ), a_str, b_str, aRefname );
977 }
978 else
979 {
980 status = wxString::Format( _( "* [new] %s %s" ), b_str, aRefname );
981 }
982
983 parent->UpdateProgress( 0, 0, status );
984
985 return 0;
986}
987
988
989extern "C" int push_transfer_progress_cb( unsigned int aCurrent, unsigned int aTotal, size_t aBytes,
990 void* aPayload )
991{
992 long long progress = 100;
993 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
994
995 if( aTotal != 0 )
996 {
997 progress = ( aCurrent * 100ll ) / aTotal;
998 }
999
1000 wxString progressMessage = wxString::Format( _( "Writing objects: %lld%% (%u/%u), %zu bytes" ),
1001 progress, aCurrent, aTotal, aBytes );
1002 parent->UpdateProgress( aCurrent, aTotal, progressMessage );
1003
1004 return 0;
1005}
1006
1007
1008extern "C" int push_update_reference_cb( const char* aRefname, const char* aStatus, void* aPayload )
1009{
1010 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
1011 wxString status( aStatus );
1012
1013 if( !status.IsEmpty() )
1014 {
1015 wxString statusMessage = wxString::Format( _( "* [rejected] %s (%s)" ), aRefname, aStatus );
1016 parent->UpdateProgress( 0, 0, statusMessage );
1017 }
1018 else
1019 {
1020 wxString statusMessage = wxString::Format( _( "[updated] %s" ), aRefname );
1021 parent->UpdateProgress( 0, 0, statusMessage );
1022 }
1023
1024 return 0;
1025}
1026
1027
1028extern "C" int credentials_cb( git_cred** aOut, const char* aUrl, const char* aUsername,
1029 unsigned int aAllowedTypes, void* aPayload )
1030{
1031 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
1032 KIGIT_COMMON* common = parent->GetCommon();
1033
1034 wxLogTrace( traceGit, "Credentials callback for %s, testing %d", aUrl, aAllowedTypes );
1035
1037 {
1038 wxLogTrace( traceGit, "Local repository, no credentials needed" );
1039 return GIT_PASSTHROUGH;
1040 }
1041
1042 if( aAllowedTypes & GIT_CREDENTIAL_USERNAME
1043 && !( parent->TestedTypes() & GIT_CREDENTIAL_USERNAME ) )
1044 {
1045 wxString username = parent->GetUsername().Trim().Trim( false );
1046 wxLogTrace( traceGit, "Username credential for %s at %s with allowed type %d",
1047 username, aUrl, aAllowedTypes );
1048
1049 if( git_credential_username_new( aOut, username.ToStdString().c_str() ) != GIT_OK )
1050 {
1051 wxLogTrace( traceGit, "Failed to create username credential for %s: %s",
1052 username, KIGIT_COMMON::GetLastGitError() );
1053 }
1054 else
1055 {
1056 wxLogTrace( traceGit, "Created username credential for %s", username );
1057 }
1058
1059 parent->TestedTypes() |= GIT_CREDENTIAL_USERNAME;
1060 }
1062 && ( aAllowedTypes & GIT_CREDENTIAL_USERPASS_PLAINTEXT )
1063 && !( parent->TestedTypes() & GIT_CREDENTIAL_USERPASS_PLAINTEXT ) )
1064 {
1065 // Plaintext authentication
1066 wxLogTrace( traceGit, "Plaintext authentication for %s at %s with allowed type %d",
1067 parent->GetUsername(), aUrl, aAllowedTypes );
1068 return common->HandlePlaintextAuthentication( aOut, parent->GetUsername() );
1069 }
1071 && ( aAllowedTypes & GIT_CREDENTIAL_SSH_KEY )
1072 && !( parent->TestedTypes() & GIT_CREDENTIAL_SSH_KEY ) )
1073 {
1074 // SSH key authentication
1075 int result = common->HandleSSHKeyAuthentication( aOut, parent->GetUsername() );
1076
1077 // Translate exhausted-keys PASSTHROUGH into a proper auth error so the
1078 // retry loop runs and libgit2 doesn't emit "no callback set".
1079 if( result == GIT_PASSTHROUGH )
1080 {
1081 git_error_clear();
1082 git_error_set_str( GIT_ERROR_NET, _( "Unable to authenticate" ).mbc_str() );
1083 common->SetAuthFailure();
1084 return GIT_EAUTH;
1085 }
1086
1087 return result;
1088 }
1089 else
1090 {
1091 // If we didn't find anything to try, then we don't have a callback set that the
1092 // server likes
1093 if( !parent->TestedTypes() )
1094 return GIT_PASSTHROUGH;
1095
1096 git_error_clear();
1097 git_error_set_str( GIT_ERROR_NET, _( "Unable to authenticate" ).mbc_str() );
1098
1099 // Otherwise, we did try something but we failed, so return an authentication error
1100 common->SetAuthFailure();
1101 return GIT_EAUTH;
1102 }
1103
1104 return GIT_OK;
1105};
virtual void UpdateProgress(int aCurrent, int aTotal, const wxString &aMessage)
std::mutex m_gitActionMutex
git_repository * m_repo
std::vector< wxString > GetBranchNames() const
GIT_CONN_TYPE GetConnType() const
unsigned m_testedTypes
static wxString GetLastGitError()
wxString GetCurrentBranchName() const
wxString GetGitRootDirectory() const
void SetSSHKey(const wxString &aSSHKey)
bool IsCancelled() const
static const unsigned KIGIT_CREDENTIAL_SSH_AGENT
int HandlePlaintextAuthentication(git_cred **aOut, const wxString &aUsername)
std::vector< wxString > GetProjectDirs()
Return a vector of project files in the repository.
wxString GetRemoteNameOrDefault() const
Returns GetRemotename() when non-empty, otherwise "origin".
git_repository * GetRepo() const
wxString GetProjectDir() const
Get the project directory path.
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
wxString m_projectDir
bool HasPushAndPullRemote() const
GIT_CONN_TYPE m_connType
void UpdateCurrentBranchInfo()
bool HasLocalCommits() const
wxString GetPassword()
int HandleSSHAgentAuthentication(git_cred **aOut, const wxString &aUsername)
wxString GetRemotename() const
int HandleSSHKeyAuthentication(git_cred **aOut, const wxString &aUsername)
wxString GetUsername() const
Get the username.
unsigned & TestedTypes()
Return the connection types that have been tested for authentication.
KIGIT_COMMON * GetCommon() const
Get the common object.
KIGIT_COMMON::GIT_CONN_TYPE GetConnType() const
Get the connection type.
#define _(s)
const wxChar *const traceGit
Flag to enable Git debugging output.
int fetchhead_foreach_cb(const char *, const char *, const git_oid *aOID, unsigned int aIsMerge, void *aPayload)
int progress_cb(const char *str, int len, 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)
void clone_progress_cb(const char *aStr, size_t aLen, size_t aTotal, void *aPayload)
int push_transfer_progress_cb(unsigned int aCurrent, unsigned int aTotal, size_t aBytes, void *aPayload)
std::unique_ptr< git_tree, decltype([](git_tree *aTree) { git_tree_free(aTree); })> GitTreePtr
A unique pointer for git_tree objects with automatic cleanup.
std::unique_ptr< git_revwalk, decltype([](git_revwalk *aWalker) { git_revwalk_free(aWalker); })> GitRevWalkPtr
A unique pointer for git_revwalk objects with automatic cleanup.
std::unique_ptr< git_commit, decltype([](git_commit *aCommit) { git_commit_free(aCommit); })> GitCommitPtr
A unique pointer for git_commit objects with automatic cleanup.
std::unique_ptr< git_reference, decltype([](git_reference *aRef) { git_reference_free(aRef); })> GitReferencePtr
A unique pointer for git_reference objects with automatic cleanup.
std::unique_ptr< git_strarray, decltype([](git_strarray *aArray) { git_strarray_free(aArray); })> GitStrArrayPtr
A unique pointer for git_strarray objects with automatic cleanup.
std::unique_ptr< git_branch_iterator, decltype([](git_branch_iterator *aIter) { git_branch_iterator_free(aIter); })> GitBranchIteratorPtr
A unique pointer for git_branch_iterator objects with automatic cleanup.
std::unique_ptr< git_remote, decltype([](git_remote *aRemote) { git_remote_free(aRemote); })> GitRemotePtr
A unique pointer for git_remote objects with automatic cleanup.
bool GetSecret(const wxString &aService, const wxString &aKey, wxString &aSecret)
std::string path
wxString result
Test unit parsing edge cases and error handling.
int delta
wxLogTrace helper definitions.