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 auto get_modified_files = [&]( const git_oid* from_oid, const git_oid* to_oid ) -> std::set<wxString>
279 {
280 std::set<wxString> modified_set;
281 git_revwalk* walker = nullptr;
282
283 if( !m_repo || !from_oid || IsCancelled() )
284 return modified_set;
285
286 if( git_revwalk_new( &walker, m_repo ) != GIT_OK )
287 {
288 wxLogTrace( traceGit, "Failed to create revwalker: %s", KIGIT_COMMON::GetLastGitError() );
289 return modified_set;
290 }
291
292 KIGIT::GitRevWalkPtr walkerPtr( walker );
293
294 if( git_revwalk_push( walker, from_oid ) != GIT_OK )
295 {
296 wxLogTrace( traceGit, "Failed to set from commit: %s", KIGIT_COMMON::GetLastGitError() );
297 return modified_set;
298 }
299
300 if( to_oid && git_revwalk_hide( walker, to_oid ) != GIT_OK )
301 {
302 wxLogTrace( traceGit, "Failed to set end commit (maybe new repo): %s", KIGIT_COMMON::GetLastGitError() );
303 }
304
305 git_oid oid;
306 git_commit* commit;
307
308 // iterate over all local commits not in remote
309 while( git_revwalk_next( &oid, walker ) == GIT_OK )
310 {
311 // Check for cancellation to allow early exit during shutdown
312 if( IsCancelled() )
313 return modified_set;
314
315 if( git_commit_lookup( &commit, m_repo, &oid ) != GIT_OK )
316 {
317 wxLogTrace( traceGit, "Failed to lookup commit: %s", KIGIT_COMMON::GetLastGitError() );
318 continue;
319 }
320
321 KIGIT::GitCommitPtr commitPtr( commit );
322 git_tree *tree, *parent_tree = nullptr;
323
324 if( git_commit_tree( &tree, commit ) != GIT_OK )
325 {
326 wxLogTrace( traceGit, "Failed to get commit tree: %s", KIGIT_COMMON::GetLastGitError() );
327 continue;
328 }
329
330 KIGIT::GitTreePtr treePtr( tree );
331
332 // get parent commit tree to diff against
333 if( !git_commit_parentcount( commit ) )
334 {
335 git_tree_walk(
336 tree, GIT_TREEWALK_PRE,
337 []( const char* root, const git_tree_entry* entry, void* payload )
338 {
339 std::set<wxString>* modified_set_internal = static_cast<std::set<wxString>*>( payload );
340 wxString filePath = wxString::Format( "%s%s", root, git_tree_entry_name( entry ) );
341 modified_set_internal->insert( std::move( filePath ) );
342 return 0; // continue walking
343 },
344 &modified_set );
345 continue;
346 }
347
348 git_commit* parent;
349
350 if( git_commit_parent( &parent, commit, 0 ) != GIT_OK )
351 {
352 wxLogTrace( traceGit, "Failed to get parent commit: %s", KIGIT_COMMON::GetLastGitError() );
353 continue;
354 }
355
356 KIGIT::GitCommitPtr parentPtr( parent );
357
358
359 if( git_commit_tree( &parent_tree, parent ) != GIT_OK )
360 {
361 wxLogTrace( traceGit, "Failed to get parent commit tree: %s", KIGIT_COMMON::GetLastGitError() );
362 continue;
363 }
364
365 KIGIT::GitTreePtr parentTreePtr( parent_tree );
366
367
368 git_diff* diff;
369 git_diff_options diff_opts;
370 git_diff_init_options( &diff_opts, GIT_DIFF_OPTIONS_VERSION );
371
372 if( !m_repo || !parent_tree || !tree )
373 continue;
374
375 if( git_diff_tree_to_tree( &diff, m_repo, parent_tree, tree, &diff_opts ) == GIT_OK )
376 {
377 size_t num_deltas = git_diff_num_deltas( diff );
378
379 for( size_t i = 0; i < num_deltas; ++i )
380 {
381 const git_diff_delta* delta = git_diff_get_delta( diff, i );
382 modified_set.insert( delta->new_file.path );
383 }
384
385 git_diff_free( diff );
386 }
387 else
388 {
389 wxLogTrace( traceGit, "Failed to diff trees: %s", KIGIT_COMMON::GetLastGitError() );
390 }
391 }
392
393 wxLogTrace( traceGit, "Finished walking commits with end: %s", KIGIT_COMMON::GetLastGitError() );
394
395 return modified_set;
396 };
397
398 std::pair<std::set<wxString>,std::set<wxString>> modified_files;
399
400 if( !m_repo || IsCancelled() )
401 return modified_files;
402
403 git_reference* head = nullptr;
404 git_reference* remote_head = nullptr;
405
406 if( git_repository_head( &head, m_repo ) != GIT_OK )
407 {
408 wxLogTrace( traceGit, "Failed to get modified HEAD" );
409 return modified_files;
410 }
411
412 KIGIT::GitReferencePtr headPtr( head );
413
414 if( git_branch_upstream( &remote_head, head ) != GIT_OK )
415 {
416 wxLogTrace( traceGit, "Failed to get modified remote HEAD" );
417 }
418
419 KIGIT::GitReferencePtr remoteHeadPtr( remote_head );
420
421 if( remote_head != nullptr && head != nullptr )
422 {
423 const git_oid* head_oid = git_reference_target( head );
424 const git_oid* remote_oid = git_reference_target( remote_head );
425
426 modified_files.first = get_modified_files( head_oid, remote_oid );
427 modified_files.second = get_modified_files( remote_oid, head_oid );
428 }
429
430 return modified_files;
431}
432
433
435{
436 if( !m_repo )
437 return false;
438
439 git_reference* head = nullptr;
440 git_reference* remote_head = nullptr;
441
442 if( git_repository_head( &head, m_repo ) != GIT_OK )
443 {
444 wxLogTrace( traceGit, "Failed to get HEAD: %s", KIGIT_COMMON::GetLastGitError() );
445 return false;
446 }
447
448 KIGIT::GitReferencePtr headPtr( head );
449
450 if( git_branch_upstream( &remote_head, head ) != GIT_OK )
451 {
452 // No remote branch, so we have local commits (new repo?)
453 wxLogTrace( traceGit, "Failed to get remote HEAD: %s", KIGIT_COMMON::GetLastGitError() );
454 return true;
455 }
456
457 KIGIT::GitReferencePtr remoteHeadPtr( remote_head );
458 const git_oid* head_oid = git_reference_target( head );
459 const git_oid* remote_oid = git_reference_target( remote_head );
460 git_revwalk* walker = nullptr;
461
462 if( git_revwalk_new( &walker, m_repo ) != GIT_OK )
463 {
464 wxLogTrace( traceGit, "Failed to create revwalker: %s", KIGIT_COMMON::GetLastGitError() );
465 return false;
466 }
467
468 KIGIT::GitRevWalkPtr walkerPtr( walker );
469
470 if( !head_oid || git_revwalk_push( walker, head_oid ) != GIT_OK )
471 {
472 wxLogTrace( traceGit, "Failed to push commits: %s", KIGIT_COMMON::GetLastGitError() );
473 return false;
474 }
475
476 if( remote_oid && git_revwalk_hide( walker, remote_oid ) != GIT_OK )
477 {
478 wxLogTrace( traceGit, "Failed to push/hide commits: %s", KIGIT_COMMON::GetLastGitError() );
479 return false;
480 }
481
482 git_oid oid;
483
484 // If we can't walk to the next commit, then we are at or behind the remote
485 if( git_revwalk_next( &oid, walker ) != GIT_OK )
486 {
487 wxLogTrace( traceGit, "Failed to walk to next commit: %s", KIGIT_COMMON::GetLastGitError() );
488 return false;
489 }
490
491 return true;
492}
493
494
496{
497 wxCHECK( m_repo, false );
498 git_remote* remote = nullptr;
499
500 if( git_remote_lookup( &remote, m_repo, "origin" ) != GIT_OK )
501 {
502 wxLogTrace( traceGit, "Failed to get remote for haspushpull" );
503 return false;
504 }
505
506 KIGIT::GitRemotePtr remotePtr( remote );
507
508 // Get the URLs associated with the remote
509 const char* fetch_url = git_remote_url( remote );
510 const char* push_url = git_remote_pushurl( remote );
511
512 // If no push URL is set, libgit2 defaults to using the fetch URL for pushing
513 if( !push_url )
514 {
515 wxLogTrace( traceGit, "No push URL set, using fetch URL" );
516 push_url = fetch_url;
517 }
518
519 return fetch_url && push_url;
520}
521
522
524{
525 wxCHECK( m_repo, wxEmptyString );
526
527 wxString retval;
528 git_reference* head = nullptr;
529 git_reference* upstream = nullptr;
530
531 if( git_repository_head( &head, m_repo ) != GIT_OK )
532 {
533 wxLogTrace( traceGit, "Failed to get remote name: %s", KIGIT_COMMON::GetLastGitError() );
534 return retval;
535 }
536
537 KIGIT::GitReferencePtr headPtr( head );
538
539 if( git_branch_upstream( &upstream, head ) != GIT_OK )
540 {
541 wxLogTrace( traceGit, "Failed to get upstream branch: %s", KIGIT_COMMON::GetLastGitError() );
542 git_strarray remotes = { nullptr, 0 };
543
544 if( git_remote_list( &remotes, m_repo ) == GIT_OK )
545 {
546 if( remotes.count == 1 )
547 retval = remotes.strings[0];
548
549 git_strarray_dispose( &remotes );
550 }
551 else
552 {
553 wxLogTrace( traceGit, "Failed to list remotes: %s", KIGIT_COMMON::GetLastGitError() );
554
555 // If we can't get the remote name from the upstream branch or the list of remotes,
556 // just return the default remote name
557
558 git_remote* remote = nullptr;
559
560 if( git_remote_lookup( &remote, m_repo, "origin" ) == GIT_OK )
561 {
562 retval = git_remote_name( remote );
563 git_remote_free( remote );
564 }
565 else
566 {
567 wxLogTrace( traceGit, "Failed to get remote name from default remote: %s",
569 }
570 }
571
572 return retval;
573 }
574
575 KIGIT::GitReferencePtr upstreamPtr( upstream );
576 git_buf remote_name = GIT_BUF_INIT_CONST( nullptr, 0 );
577
578 if( git_branch_remote_name( &remote_name, m_repo, git_reference_name( upstream ) ) == GIT_OK )
579 {
580 retval = remote_name.ptr;
581 git_buf_dispose( &remote_name );
582 }
583 else
584 {
585 wxLogTrace( traceGit,
586 "Failed to get remote name from upstream branch: %s",
588 }
589
590 return retval;
591}
592
593
594void KIGIT_COMMON::SetSSHKey( const wxString& aKey )
595{
596 auto it = std::find( m_publicKeys.begin(), m_publicKeys.end(), aKey );
597
598 if( it != m_publicKeys.end() )
599 m_publicKeys.erase( it );
600
601 m_publicKeys.insert( m_publicKeys.begin(), aKey );
602}
603
604
606{
607 if( !m_repo )
608 return wxEmptyString;
609
610 const char *path = git_repository_path( m_repo );
611 wxString retval = path;
612 return retval;
613}
614
615
617{
618 m_publicKeys.clear();
619
620 wxFileName keyFile( wxGetHomeDir(), wxEmptyString );
621 keyFile.AppendDir( ".ssh" );
622 keyFile.SetFullName( "id_rsa" );
623
624 if( keyFile.FileExists() )
625 m_publicKeys.push_back( keyFile.GetFullPath() );
626
627 keyFile.SetFullName( "id_dsa" );
628
629 if( keyFile.FileExists() )
630 m_publicKeys.push_back( keyFile.GetFullPath() );
631
632 keyFile.SetFullName( "id_ecdsa" );
633
634 if( keyFile.FileExists() )
635 m_publicKeys.push_back( keyFile.GetFullPath() );
636
637 keyFile.SetFullName( "id_ed25519" );
638
639 if( keyFile.FileExists() )
640 m_publicKeys.push_back( keyFile.GetFullPath() );
641
642 // Parse SSH config file for hostname information
643 wxFileName sshConfig( wxGetHomeDir(), wxEmptyString );
644 sshConfig.AppendDir( ".ssh" );
645 sshConfig.SetFullName( "config" );
646
647 if( sshConfig.FileExists() )
648 {
649 wxTextFile configFile( sshConfig.GetFullPath() );
650 configFile.Open();
651
652 bool match = false;
653
654 for( wxString line = configFile.GetFirstLine(); !configFile.Eof(); line = configFile.GetNextLine() )
655 {
656 line.Trim( false ).Trim( true );
657
658 if( line.StartsWith( "Host " ) )
659 match = false;
660
661 // The difference here is that we are matching either "Hostname" or "Host" to get the
662 // match. This is because in the absence of a "Hostname" line, the "Host" line is used
663 if( line.StartsWith( "Host" ) && line.Contains( m_hostname ) )
664 match = true;
665
666 if( match && line.StartsWith( "IdentityFile" ) )
667 {
668 wxString keyPath = line.AfterFirst( ' ' ).Trim( false ).Trim( true );
669
670 // Expand ~ to home directory if present
671 if( keyPath.StartsWith( "~" ) )
672 keyPath.Replace( "~", wxGetHomeDir(), false );
673
674 // Add the public key to the beginning of the list
675 if( wxFileName::FileExists( keyPath ) )
676 SetSSHKey( keyPath );
677 }
678 }
679
680 configFile.Close();
681 }
682}
683
684
686{
687 wxCHECK( m_repo, /* void */ );
688
689 // We want to get the current branch's upstream url as well as the stored password
690 // if one exists given the url and username.
691
692 wxString remote_name = GetRemotename();
693 git_remote* remote = nullptr;
694
695 m_remote.clear();
696 m_password.clear();
697 m_secretFetched = false;
698
699 if( git_remote_lookup( &remote, m_repo, remote_name.ToStdString().c_str() ) == GIT_OK )
700 {
701 const char* url = git_remote_url( remote );
702
703 if( url )
704 m_remote = url;
705
706 git_remote_free( remote );
707 }
708
711}
712
713
715{
716 wxString remote = m_remote;
717
718 if( remote.IsEmpty() )
719 remote = GetRemotename();
720
721 if( remote.StartsWith( "https://" ) || remote.StartsWith( "http://" ) )
722 {
724 }
725 else if( remote.StartsWith( "ssh://" ) || remote.StartsWith( "git@" ) || remote.StartsWith( "git+ssh://" )
726 || remote.EndsWith( ".git" ) )
727 {
729 }
730
732}
733
734
736{
737 if( m_remote.StartsWith( "https://" ) || m_remote.StartsWith( "http://" ) )
739 else if( m_remote.StartsWith( "ssh://" ) || m_remote.StartsWith( "git@" ) || m_remote.StartsWith( "git+ssh://" ) )
741 else
743
745 {
746 wxString uri = m_remote;
747 size_t atPos = uri.find( '@' );
748
749 if( atPos != wxString::npos )
750 {
751 size_t protoEnd = uri.find( "//" );
752
753 if( protoEnd != wxString::npos )
754 {
755 wxString credentials = uri.Mid( protoEnd + 2, atPos - protoEnd - 2 );
756 size_t colonPos = credentials.find( ':' );
757
758 if( colonPos != wxString::npos )
759 {
760 m_username = credentials.Left( colonPos );
761 m_password = credentials.Mid( colonPos + 1, credentials.Length() - colonPos - 1 );
762 }
763 else
764 {
765 m_username = credentials;
766 }
767 }
768 else
769 {
770 m_username = uri.Left( atPos );
771 }
772 }
773
774 if( m_remote.StartsWith( "git@" ) )
775 {
776 // SSH format: git@hostname:path
777 size_t colonPos = m_remote.find( ':' );
778
779 if( colonPos != wxString::npos )
780 m_hostname = m_remote.Mid( 4, colonPos - 4 );
781 }
782 else
783 {
784 // other URL format: proto://[user@]hostname/path
785 size_t hostStart = m_remote.find( "://" ) + 2;
786 size_t hostEnd = m_remote.find( '/', hostStart );
787 wxString host;
788
789 if( hostEnd != wxString::npos )
790 host = m_remote.Mid( hostStart, hostEnd - hostStart );
791 else
792 host = m_remote.Mid( hostStart );
793
794 atPos = host.find( '@' );
795
796 if( atPos != wxString::npos )
797 m_hostname = host.Mid( atPos + 1 );
798 else
799 m_hostname = host;
800 }
801 }
802
803 m_secretFetched = !m_password.IsEmpty();
804}
805
806
807int KIGIT_COMMON::HandleSSHKeyAuthentication( git_cred** aOut, const wxString& aUsername )
808{
810 return HandleSSHAgentAuthentication( aOut, aUsername );
811
812 // SSH key authentication with password
813 wxString sshKey = GetNextPublicKey();
814
815 if( sshKey.IsEmpty() )
816 {
817 wxLogTrace( traceGit, "Finished testing all possible ssh keys" );
818 m_testedTypes |= GIT_CREDENTIAL_SSH_KEY;
819 return GIT_PASSTHROUGH;
820 }
821
822 wxString sshPubKey = sshKey + ".pub";
823 wxString password = GetPassword();
824
825 wxLogTrace( traceGit, "Testing %s\n", sshKey );
826
827 if( git_credential_ssh_key_new( aOut, aUsername.mbc_str(), sshPubKey.mbc_str(), sshKey.mbc_str(),
828 password.mbc_str() ) != GIT_OK )
829 {
830 wxLogTrace( traceGit, "Failed to create SSH key credential for %s: %s",
831 aUsername, KIGIT_COMMON::GetLastGitError() );
832 return GIT_PASSTHROUGH;
833 }
834
835 return GIT_OK;
836}
837
838
839int KIGIT_COMMON::HandlePlaintextAuthentication( git_cred** aOut, const wxString& aUsername )
840{
841 wxString password = GetPassword();
842
843 git_credential_userpass_plaintext_new( aOut, aUsername.mbc_str(), password.mbc_str() );
844 m_testedTypes |= GIT_CREDENTIAL_USERPASS_PLAINTEXT;
845
846 return GIT_OK;
847}
848
849
850int KIGIT_COMMON::HandleSSHAgentAuthentication( git_cred** aOut, const wxString& aUsername )
851{
852 if( git_credential_ssh_key_from_agent( aOut, aUsername.mbc_str() ) != GIT_OK )
853 {
854 wxLogTrace( traceGit, "Failed to create SSH agent credential for %s: %s",
855 aUsername, KIGIT_COMMON::GetLastGitError() );
856 return GIT_PASSTHROUGH;
857 }
858
860 return GIT_OK;
861}
862
863
864extern "C" int fetchhead_foreach_cb( const char*, const char*,
865 const git_oid* aOID, unsigned int aIsMerge, void* aPayload )
866{
867 if( aIsMerge )
868 git_oid_cpy( (git_oid*) aPayload, aOID );
869
870 return 0;
871}
872
873
874extern "C" void clone_progress_cb( const char* aStr, size_t aLen, size_t aTotal, void* aPayload )
875{
876 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
877
878 wxString progressMessage( aStr );
879 parent->UpdateProgress( aLen, aTotal, progressMessage );
880}
881
882
883extern "C" int progress_cb( const char* str, int len, void* aPayload )
884{
885 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
886
887 if( parent->GetCommon()->IsCancelled() )
888 {
889 wxLogTrace( traceGit, "Progress CB cancelled" );
890 return GIT_EUSER;
891 }
892
893 wxString progressMessage( str, len );
894 parent->UpdateProgress( 0, 0, progressMessage );
895
896 return 0;
897}
898
899
900extern "C" int transfer_progress_cb( const git_transfer_progress* aStats, void* aPayload )
901{
902 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
903
904 wxString progressMessage = wxString::Format( _( "Received %u of %u objects" ),
905 aStats->received_objects,
906 aStats->total_objects );
907 if( parent->GetCommon()->IsCancelled() )
908 {
909 wxLogTrace( traceGit, "Transfer progress cancelled" );
910 return GIT_EUSER;
911 }
912
913 parent->UpdateProgress( aStats->received_objects, aStats->total_objects, progressMessage );
914
915 return 0;
916}
917
918
919extern "C" int update_cb( const char* aRefname, const git_oid* aFirst, const git_oid* aSecond,
920 void* aPayload )
921{
922 constexpr int cstring_len = 8;
923 char a_str[cstring_len + 1];
924 char b_str[cstring_len + 1];
925
926 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
927 wxString status;
928
929 git_oid_tostr( b_str, cstring_len, aSecond );
930
931#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
932 if( !git_oid_is_zero( aFirst ) )
933#else
934 if( !git_oid_iszero( aFirst ) )
935#endif
936 {
937 git_oid_tostr( a_str, cstring_len, aFirst );
938 status = wxString::Format( _( "* [updated] %s..%s %s" ), a_str, b_str, aRefname );
939 }
940 else
941 {
942 status = wxString::Format( _( "* [new] %s %s" ), b_str, aRefname );
943 }
944
945 parent->UpdateProgress( 0, 0, status );
946
947 return 0;
948}
949
950
951extern "C" int push_transfer_progress_cb( unsigned int aCurrent, unsigned int aTotal, size_t aBytes,
952 void* aPayload )
953{
954 long long progress = 100;
955 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
956
957 if( aTotal != 0 )
958 {
959 progress = ( aCurrent * 100ll ) / aTotal;
960 }
961
962 wxString progressMessage = wxString::Format( _( "Writing objects: %lld%% (%u/%u), %zu bytes" ),
963 progress, aCurrent, aTotal, aBytes );
964 parent->UpdateProgress( aCurrent, aTotal, progressMessage );
965
966 return 0;
967}
968
969
970extern "C" int push_update_reference_cb( const char* aRefname, const char* aStatus, void* aPayload )
971{
972 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
973 wxString status( aStatus );
974
975 if( !status.IsEmpty() )
976 {
977 wxString statusMessage = wxString::Format( _( "* [rejected] %s (%s)" ), aRefname, aStatus );
978 parent->UpdateProgress( 0, 0, statusMessage );
979 }
980 else
981 {
982 wxString statusMessage = wxString::Format( _( "[updated] %s" ), aRefname );
983 parent->UpdateProgress( 0, 0, statusMessage );
984 }
985
986 return 0;
987}
988
989
990extern "C" int credentials_cb( git_cred** aOut, const char* aUrl, const char* aUsername,
991 unsigned int aAllowedTypes, void* aPayload )
992{
993 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
994 KIGIT_COMMON* common = parent->GetCommon();
995
996 wxLogTrace( traceGit, "Credentials callback for %s, testing %d", aUrl, aAllowedTypes );
997
999 {
1000 wxLogTrace( traceGit, "Local repository, no credentials needed" );
1001 return GIT_PASSTHROUGH;
1002 }
1003
1004 if( aAllowedTypes & GIT_CREDENTIAL_USERNAME
1005 && !( parent->TestedTypes() & GIT_CREDENTIAL_USERNAME ) )
1006 {
1007 wxString username = parent->GetUsername().Trim().Trim( false );
1008 wxLogTrace( traceGit, "Username credential for %s at %s with allowed type %d",
1009 username, aUrl, aAllowedTypes );
1010
1011 if( git_credential_username_new( aOut, username.ToStdString().c_str() ) != GIT_OK )
1012 {
1013 wxLogTrace( traceGit, "Failed to create username credential for %s: %s",
1014 username, KIGIT_COMMON::GetLastGitError() );
1015 }
1016 else
1017 {
1018 wxLogTrace( traceGit, "Created username credential for %s", username );
1019 }
1020
1021 parent->TestedTypes() |= GIT_CREDENTIAL_USERNAME;
1022 }
1024 && ( aAllowedTypes & GIT_CREDENTIAL_USERPASS_PLAINTEXT )
1025 && !( parent->TestedTypes() & GIT_CREDENTIAL_USERPASS_PLAINTEXT ) )
1026 {
1027 // Plaintext authentication
1028 wxLogTrace( traceGit, "Plaintext authentication for %s at %s with allowed type %d",
1029 parent->GetUsername(), aUrl, aAllowedTypes );
1030 return common->HandlePlaintextAuthentication( aOut, parent->GetUsername() );
1031 }
1033 && ( aAllowedTypes & GIT_CREDENTIAL_SSH_KEY )
1034 && !( parent->TestedTypes() & GIT_CREDENTIAL_SSH_KEY ) )
1035 {
1036 // SSH key authentication
1037 return common->HandleSSHKeyAuthentication( aOut, parent->GetUsername() );
1038 }
1039 else
1040 {
1041 // If we didn't find anything to try, then we don't have a callback set that the
1042 // server likes
1043 if( !parent->TestedTypes() )
1044 return GIT_PASSTHROUGH;
1045
1046 git_error_clear();
1047 git_error_set_str( GIT_ERROR_NET, _( "Unable to authenticate" ).mbc_str() );
1048
1049 // Otherwise, we did try something but we failed, so return an authentication error
1050 return GIT_EAUTH;
1051 }
1052
1053 return GIT_OK;
1054};
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.
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_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
int delta
wxLogTrace helper definitions.