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
47 // Initialize base class and member variables
48 m_repo( aOther.m_repo ),
49 m_projectDir( aOther.m_projectDir ),
50 m_connType( aOther.m_connType ),
51 m_remote( aOther.m_remote ),
52 m_hostname( aOther.m_hostname ),
53 m_username( aOther.m_username ),
54 m_password( aOther.m_password ),
56 // The mutex is default-initialized, not copied
58 m_publicKeys( aOther.m_publicKeys ),
61{
62}
63
66
67
68git_repository* KIGIT_COMMON::GetRepo() const
69{
70 return m_repo;
71}
72
73
75{
76 if( !m_projectDir.IsEmpty() )
77 return m_projectDir;
78
79 if( m_repo )
80 {
81 const char* workdir = git_repository_workdir( m_repo );
82
83 if( workdir )
84 return wxString( workdir );
85 }
86
87 return wxEmptyString;
88}
89
90
92{
93 wxCHECK( m_repo, wxEmptyString );
94 git_reference* head = nullptr;
95
96 int retval = git_repository_head( &head, m_repo );
97
98 if( retval && retval != GIT_EUNBORNBRANCH && retval != GIT_ENOTFOUND )
99 return wxEmptyString;
100
101 KIGIT::GitReferencePtr headPtr( head );
102 git_reference* branch;
103
104 if( git_reference_resolve( &branch, head ) )
105 {
106 wxLogTrace( traceGit, "Failed to resolve branch" );
107 return wxEmptyString;
108 }
109
110 KIGIT::GitReferencePtr branchPtr( branch );
111 const char* branchName = "";
112
113 if( git_branch_name( &branchName, branch ) )
114 {
115 wxLogTrace( traceGit, "Failed to get branch name" );
116 return wxEmptyString;
117 }
118
119 return wxString( branchName );
120}
121
122
124{
125 if( !m_secretFetched )
126 {
128 {
129 wxString secret;
130
132 m_password = secret;
133 }
134
135 m_secretFetched = true;
136 }
137
138 return m_password;
139}
140
141
142std::vector<wxString> KIGIT_COMMON::GetBranchNames() const
143{
144 if( !m_repo )
145 return {};
146
147 std::vector<wxString> branchNames;
148 std::map<git_time_t, wxString> branchNamesMap;
149 wxString firstName;
150
151 git_branch_iterator* branchIterator = nullptr;
152
153 if( git_branch_iterator_new( &branchIterator, m_repo, GIT_BRANCH_LOCAL ) )
154 {
155 wxLogTrace( traceGit, "Failed to get branch iterator" );
156 return branchNames;
157 }
158
159 KIGIT::GitBranchIteratorPtr branchIteratorPtr( branchIterator );
160 git_reference* branchReference = nullptr;
161 git_branch_t branchType;
162
163 while( git_branch_next( &branchReference, &branchType, branchIterator ) != GIT_ITEROVER )
164 {
165 const char* branchName = "";
166 KIGIT::GitReferencePtr branchReferencePtr( branchReference );
167
168 if( git_branch_name( &branchName, branchReference ) )
169 {
170 wxLogTrace( traceGit, "Failed to get branch name in iter loop" );
171 continue;
172 }
173
174 const git_oid* commitId = git_reference_target( branchReference );
175
176 git_commit* commit = nullptr;
177
178 if( git_commit_lookup( &commit, m_repo, commitId ) )
179 {
180 wxLogTrace( traceGit, "Failed to get commit in iter loop" );
181 continue;
182 }
183
184 KIGIT::GitCommitPtr commitPtr( commit );
185 git_time_t commitTime = git_commit_time( commit );
186
187 if( git_branch_is_head( branchReference ) )
188 firstName = branchName;
189 else
190 branchNamesMap.emplace( commitTime, branchName );
191 }
192
193 // Add the current branch to the top of the list
194 if( !firstName.IsEmpty() )
195 branchNames.push_back( firstName );
196
197 // Add the remaining branches in order from newest to oldest
198 for( auto rit = branchNamesMap.rbegin(); rit != branchNamesMap.rend(); ++rit )
199 branchNames.push_back( rit->second );
200
201 return branchNames;
202}
203
204
205std::vector<wxString> KIGIT_COMMON::GetProjectDirs()
206{
207 wxCHECK( m_repo, {} );
208 std::vector<wxString> projDirs;
209
210 git_oid oid;
211 git_commit* commit;
212 git_tree *tree;
213
214 if( git_reference_name_to_id( &oid, m_repo, "HEAD" ) != GIT_OK )
215 {
216 wxLogTrace( traceGit, "An error occurred: %s", KIGIT_COMMON::GetLastGitError() );
217 return projDirs;
218 }
219
220 if( git_commit_lookup( &commit, m_repo, &oid ) != GIT_OK )
221 {
222 wxLogTrace( traceGit, "An error occurred: %s", KIGIT_COMMON::GetLastGitError() );
223 return projDirs;
224 }
225
226 KIGIT::GitCommitPtr commitPtr( commit );
227
228 if( git_commit_tree( &tree, commit ) != GIT_OK )
229 {
230 wxLogTrace( traceGit, "An error occurred: %s", KIGIT_COMMON::GetLastGitError() );
231 return projDirs;
232 }
233
234 KIGIT::GitTreePtr treePtr( tree );
235
236 // Define callback
237 git_tree_walk(
238 tree, GIT_TREEWALK_PRE,
239 []( const char* root, const git_tree_entry* entry, void* payload )
240 {
241 std::vector<wxString>* prjs = static_cast<std::vector<wxString>*>( payload );
242 wxFileName root_fn( git_tree_entry_name( entry ) );
243
244 root_fn.SetPath( root );
245
246 if( git_tree_entry_type( entry ) == GIT_OBJECT_BLOB
247 && ( ( root_fn.GetExt() == "kicad_pro" ) || ( root_fn.GetExt() == "pro" ) ) )
248 {
249 prjs->push_back( root_fn.GetFullPath() );
250 }
251
252 return 0; // continue walking
253 },
254 &projDirs );
255
256 std::sort( projDirs.begin(), projDirs.end(),
257 []( const wxString& a, const wxString& b )
258 {
259 int a_freq = a.Freq( wxFileName::GetPathSeparator() );
260 int b_freq = b.Freq( wxFileName::GetPathSeparator() );
261
262 if( a_freq == b_freq )
263 return a < b;
264 else
265 return a_freq < b_freq;
266
267 } );
268
269 return projDirs;
270}
271
272
273std::pair<std::set<wxString>,std::set<wxString>> KIGIT_COMMON::GetDifferentFiles() const
274{
275 auto get_modified_files = [&]( const git_oid* from_oid, const git_oid* to_oid ) -> std::set<wxString>
276 {
277 std::set<wxString> modified_set;
278 git_revwalk* walker = nullptr;
279
280 if( !m_repo || !from_oid || IsCancelled() )
281 return modified_set;
282
283 if( git_revwalk_new( &walker, m_repo ) != GIT_OK )
284 {
285 wxLogTrace( traceGit, "Failed to create revwalker: %s", KIGIT_COMMON::GetLastGitError() );
286 return modified_set;
287 }
288
289 KIGIT::GitRevWalkPtr walkerPtr( walker );
290
291 if( git_revwalk_push( walker, from_oid ) != GIT_OK )
292 {
293 wxLogTrace( traceGit, "Failed to set from commit: %s", KIGIT_COMMON::GetLastGitError() );
294 return modified_set;
295 }
296
297 if( to_oid && git_revwalk_hide( walker, to_oid ) != GIT_OK )
298 {
299 wxLogTrace( traceGit, "Failed to set end commit (maybe new repo): %s", KIGIT_COMMON::GetLastGitError() );
300 }
301
302 git_oid oid;
303 git_commit* commit;
304
305 // iterate over all local commits not in remote
306 while( git_revwalk_next( &oid, walker ) == GIT_OK )
307 {
308 // Check for cancellation to allow early exit during shutdown
309 if( IsCancelled() )
310 return modified_set;
311
312 if( git_commit_lookup( &commit, m_repo, &oid ) != GIT_OK )
313 {
314 wxLogTrace( traceGit, "Failed to lookup commit: %s", KIGIT_COMMON::GetLastGitError() );
315 continue;
316 }
317
318 KIGIT::GitCommitPtr commitPtr( commit );
319 git_tree *tree, *parent_tree = nullptr;
320
321 if( git_commit_tree( &tree, commit ) != GIT_OK )
322 {
323 wxLogTrace( traceGit, "Failed to get commit tree: %s", KIGIT_COMMON::GetLastGitError() );
324 continue;
325 }
326
327 KIGIT::GitTreePtr treePtr( tree );
328
329 // get parent commit tree to diff against
330 if( !git_commit_parentcount( commit ) )
331 {
332 git_tree_walk(
333 tree, GIT_TREEWALK_PRE,
334 []( const char* root, const git_tree_entry* entry, void* payload )
335 {
336 std::set<wxString>* modified_set_internal = static_cast<std::set<wxString>*>( payload );
337 wxString filePath = wxString::Format( "%s%s", root, git_tree_entry_name( entry ) );
338 modified_set_internal->insert( std::move( filePath ) );
339 return 0; // continue walking
340 },
341 &modified_set );
342 continue;
343 }
344
345 git_commit* parent;
346
347 if( git_commit_parent( &parent, commit, 0 ) != GIT_OK )
348 {
349 wxLogTrace( traceGit, "Failed to get parent commit: %s", KIGIT_COMMON::GetLastGitError() );
350 continue;
351 }
352
353 KIGIT::GitCommitPtr parentPtr( parent );
354
355
356 if( git_commit_tree( &parent_tree, parent ) != GIT_OK )
357 {
358 wxLogTrace( traceGit, "Failed to get parent commit tree: %s", KIGIT_COMMON::GetLastGitError() );
359 continue;
360 }
361
362 KIGIT::GitTreePtr parentTreePtr( parent_tree );
363
364
365 git_diff* diff;
366 git_diff_options diff_opts;
367 git_diff_init_options( &diff_opts, GIT_DIFF_OPTIONS_VERSION );
368
369 if( !m_repo || !parent_tree || !tree )
370 continue;
371
372 if( git_diff_tree_to_tree( &diff, m_repo, parent_tree, tree, &diff_opts ) == GIT_OK )
373 {
374 size_t num_deltas = git_diff_num_deltas( diff );
375
376 for( size_t i = 0; i < num_deltas; ++i )
377 {
378 const git_diff_delta* delta = git_diff_get_delta( diff, i );
379 modified_set.insert( delta->new_file.path );
380 }
381
382 git_diff_free( diff );
383 }
384 else
385 {
386 wxLogTrace( traceGit, "Failed to diff trees: %s", KIGIT_COMMON::GetLastGitError() );
387 }
388 }
389
390 wxLogTrace( traceGit, "Finished walking commits with end: %s", KIGIT_COMMON::GetLastGitError() );
391
392 return modified_set;
393 };
394
395 std::pair<std::set<wxString>,std::set<wxString>> modified_files;
396
397 if( !m_repo || IsCancelled() )
398 return modified_files;
399
400 git_reference* head = nullptr;
401 git_reference* remote_head = nullptr;
402
403 if( git_repository_head( &head, m_repo ) != GIT_OK )
404 {
405 wxLogTrace( traceGit, "Failed to get modified HEAD" );
406 return modified_files;
407 }
408
409 KIGIT::GitReferencePtr headPtr( head );
410
411 if( git_branch_upstream( &remote_head, head ) != GIT_OK )
412 {
413 wxLogTrace( traceGit, "Failed to get modified remote HEAD" );
414 }
415
416 KIGIT::GitReferencePtr remoteHeadPtr( remote_head );
417
418 if( remote_head != nullptr && head != nullptr )
419 {
420 const git_oid* head_oid = git_reference_target( head );
421 const git_oid* remote_oid = git_reference_target( remote_head );
422
423 modified_files.first = get_modified_files( head_oid, remote_oid );
424 modified_files.second = get_modified_files( remote_oid, head_oid );
425 }
426
427 return modified_files;
428}
429
430
432{
433 if( !m_repo )
434 return false;
435
436 git_reference* head = nullptr;
437 git_reference* remote_head = nullptr;
438
439 if( git_repository_head( &head, m_repo ) != GIT_OK )
440 {
441 wxLogTrace( traceGit, "Failed to get HEAD: %s", KIGIT_COMMON::GetLastGitError() );
442 return false;
443 }
444
445 KIGIT::GitReferencePtr headPtr( head );
446
447 if( git_branch_upstream( &remote_head, head ) != GIT_OK )
448 {
449 // No remote branch, so we have local commits (new repo?)
450 wxLogTrace( traceGit, "Failed to get remote HEAD: %s", KIGIT_COMMON::GetLastGitError() );
451 return true;
452 }
453
454 KIGIT::GitReferencePtr remoteHeadPtr( remote_head );
455 const git_oid* head_oid = git_reference_target( head );
456 const git_oid* remote_oid = git_reference_target( remote_head );
457 git_revwalk* walker = nullptr;
458
459 if( git_revwalk_new( &walker, m_repo ) != GIT_OK )
460 {
461 wxLogTrace( traceGit, "Failed to create revwalker: %s", KIGIT_COMMON::GetLastGitError() );
462 return false;
463 }
464
465 KIGIT::GitRevWalkPtr walkerPtr( walker );
466
467 if( !head_oid || git_revwalk_push( walker, head_oid ) != GIT_OK )
468 {
469 wxLogTrace( traceGit, "Failed to push commits: %s", KIGIT_COMMON::GetLastGitError() );
470 return false;
471 }
472
473 if( remote_oid && git_revwalk_hide( walker, remote_oid ) != GIT_OK )
474 {
475 wxLogTrace( traceGit, "Failed to push/hide commits: %s", KIGIT_COMMON::GetLastGitError() );
476 return false;
477 }
478
479 git_oid oid;
480
481 // If we can't walk to the next commit, then we are at or behind the remote
482 if( git_revwalk_next( &oid, walker ) != GIT_OK )
483 {
484 wxLogTrace( traceGit, "Failed to walk to next commit: %s", KIGIT_COMMON::GetLastGitError() );
485 return false;
486 }
487
488 return true;
489}
490
491
493{
494 wxCHECK( m_repo, false );
495 git_remote* remote = nullptr;
496
497 if( git_remote_lookup( &remote, m_repo, "origin" ) != GIT_OK )
498 {
499 wxLogTrace( traceGit, "Failed to get remote for haspushpull" );
500 return false;
501 }
502
503 KIGIT::GitRemotePtr remotePtr( remote );
504
505 // Get the URLs associated with the remote
506 const char* fetch_url = git_remote_url( remote );
507 const char* push_url = git_remote_pushurl( remote );
508
509 // If no push URL is set, libgit2 defaults to using the fetch URL for pushing
510 if( !push_url )
511 {
512 wxLogTrace( traceGit, "No push URL set, using fetch URL" );
513 push_url = fetch_url;
514 }
515
516 return fetch_url && push_url;
517}
518
519
521{
522 wxCHECK( m_repo, wxEmptyString );
523
524 wxString retval;
525 git_reference* head = nullptr;
526 git_reference* upstream = nullptr;
527
528 if( git_repository_head( &head, m_repo ) != GIT_OK )
529 {
530 wxLogTrace( traceGit, "Failed to get remote name: %s", KIGIT_COMMON::GetLastGitError() );
531 return retval;
532 }
533
534 KIGIT::GitReferencePtr headPtr( head );
535
536 if( git_branch_upstream( &upstream, head ) != GIT_OK )
537 {
538 wxLogTrace( traceGit, "Failed to get upstream branch: %s", KIGIT_COMMON::GetLastGitError() );
539 git_strarray remotes = { nullptr, 0 };
540
541 if( git_remote_list( &remotes, m_repo ) == GIT_OK )
542 {
543 if( remotes.count == 1 )
544 retval = remotes.strings[0];
545
546 git_strarray_dispose( &remotes );
547 }
548 else
549 {
550 wxLogTrace( traceGit, "Failed to list remotes: %s", KIGIT_COMMON::GetLastGitError() );
551
552 // If we can't get the remote name from the upstream branch or the list of remotes,
553 // just return the default remote name
554
555 git_remote* remote = nullptr;
556
557 if( git_remote_lookup( &remote, m_repo, "origin" ) == GIT_OK )
558 {
559 retval = git_remote_name( remote );
560 git_remote_free( remote );
561 }
562 else
563 {
564 wxLogTrace( traceGit, "Failed to get remote name from default remote: %s",
566 }
567 }
568
569 return retval;
570 }
571
572 KIGIT::GitReferencePtr upstreamPtr( upstream );
573 git_buf remote_name = GIT_BUF_INIT_CONST( nullptr, 0 );
574
575 if( git_branch_remote_name( &remote_name, m_repo, git_reference_name( upstream ) ) == GIT_OK )
576 {
577 retval = remote_name.ptr;
578 git_buf_dispose( &remote_name );
579 }
580 else
581 {
582 wxLogTrace( traceGit,
583 "Failed to get remote name from upstream branch: %s",
585 }
586
587 return retval;
588}
589
590void KIGIT_COMMON::SetSSHKey( const wxString& aKey )
591{
592 auto it = std::find( m_publicKeys.begin(), m_publicKeys.end(), aKey );
593
594 if( it != m_publicKeys.end() )
595 m_publicKeys.erase( it );
596
597 m_publicKeys.insert( m_publicKeys.begin(), aKey );
598}
599
600
602{
603 if( !m_repo )
604 return wxEmptyString;
605
606 const char *path = git_repository_path( m_repo );
607 wxString retval = path;
608 return retval;
609}
610
611
613{
614 m_publicKeys.clear();
615
616 wxFileName keyFile( wxGetHomeDir(), wxEmptyString );
617 keyFile.AppendDir( ".ssh" );
618 keyFile.SetFullName( "id_rsa" );
619
620 if( keyFile.FileExists() )
621 m_publicKeys.push_back( keyFile.GetFullPath() );
622
623 keyFile.SetFullName( "id_dsa" );
624
625 if( keyFile.FileExists() )
626 m_publicKeys.push_back( keyFile.GetFullPath() );
627
628 keyFile.SetFullName( "id_ecdsa" );
629
630 if( keyFile.FileExists() )
631 m_publicKeys.push_back( keyFile.GetFullPath() );
632
633 keyFile.SetFullName( "id_ed25519" );
634
635 if( keyFile.FileExists() )
636 m_publicKeys.push_back( keyFile.GetFullPath() );
637
638 // Parse SSH config file for hostname information
639 wxFileName sshConfig( wxGetHomeDir(), wxEmptyString );
640 sshConfig.AppendDir( ".ssh" );
641 sshConfig.SetFullName( "config" );
642
643 if( sshConfig.FileExists() )
644 {
645 wxTextFile configFile( sshConfig.GetFullPath() );
646 configFile.Open();
647
648 bool match = false;
649
650 for( wxString line = configFile.GetFirstLine(); !configFile.Eof(); line = configFile.GetNextLine() )
651 {
652 line.Trim( false ).Trim( true );
653
654 if( line.StartsWith( "Host " ) )
655 match = false;
656
657 // The difference here is that we are matching either "Hostname" or "Host" to get the
658 // match. This is because in the absence of a "Hostname" line, the "Host" line is used
659 if( line.StartsWith( "Host" ) && line.Contains( m_hostname ) )
660 match = true;
661
662 if( match && line.StartsWith( "IdentityFile" ) )
663 {
664 wxString keyPath = line.AfterFirst( ' ' ).Trim( false ).Trim( true );
665
666 // Expand ~ to home directory if present
667 if( keyPath.StartsWith( "~" ) )
668 keyPath.Replace( "~", wxGetHomeDir(), false );
669
670 // Add the public key to the beginning of the list
671 if( wxFileName::FileExists( keyPath ) )
672 SetSSHKey( keyPath );
673 }
674 }
675
676 configFile.Close();
677 }
678}
679
680
682{
683 wxCHECK( m_repo, /* void */ );
684
685 // We want to get the current branch's upstream url as well as the stored password
686 // if one exists given the url and username.
687
688 wxString remote_name = GetRemotename();
689 git_remote* remote = nullptr;
690
691 m_remote.clear();
692 m_password.clear();
693 m_secretFetched = false;
694
695 if( git_remote_lookup( &remote, m_repo, remote_name.ToStdString().c_str() ) == GIT_OK )
696 {
697 const char* url = git_remote_url( remote );
698
699 if( url )
700 m_remote = url;
701
702 git_remote_free( remote );
703 }
704
707}
708
710{
711 wxString remote = m_remote;
712
713 if( remote.IsEmpty() )
714 remote = GetRemotename();
715
716 if( remote.StartsWith( "https://" ) || remote.StartsWith( "http://" ) )
717 {
719 }
720 else if( remote.StartsWith( "ssh://" ) || remote.StartsWith( "git@" ) || remote.StartsWith( "git+ssh://" )
721 || remote.EndsWith( ".git" ) )
722 {
724 }
725
727}
728
730{
731 if( m_remote.StartsWith( "https://" ) || m_remote.StartsWith( "http://" ) )
733 else if( m_remote.StartsWith( "ssh://" ) || m_remote.StartsWith( "git@" ) || m_remote.StartsWith( "git+ssh://" ) )
735 else
737
739 {
740 wxString uri = m_remote;
741 size_t atPos = uri.find( '@' );
742
743 if( atPos != wxString::npos )
744 {
745 size_t protoEnd = uri.find( "//" );
746
747 if( protoEnd != wxString::npos )
748 {
749 wxString credentials = uri.Mid( protoEnd + 2, atPos - protoEnd - 2 );
750 size_t colonPos = credentials.find( ':' );
751
752 if( colonPos != wxString::npos )
753 {
754 m_username = credentials.Left( colonPos );
755 m_password = credentials.Mid( colonPos + 1, credentials.Length() - colonPos - 1 );
756 }
757 else
758 {
759 m_username = credentials;
760 }
761 }
762 else
763 {
764 m_username = uri.Left( atPos );
765 }
766 }
767
768 if( m_remote.StartsWith( "git@" ) )
769 {
770 // SSH format: git@hostname:path
771 size_t colonPos = m_remote.find( ':' );
772 if( colonPos != wxString::npos )
773 m_hostname = m_remote.Mid( 4, colonPos - 4 );
774 }
775 else
776 {
777 // other URL format: proto://[user@]hostname/path
778 size_t hostStart = m_remote.find( "://" ) + 2;
779 size_t hostEnd = m_remote.find( '/', hostStart );
780 wxString host;
781
782 if( hostEnd != wxString::npos )
783 host = m_remote.Mid( hostStart, hostEnd - hostStart );
784 else
785 host = m_remote.Mid( hostStart );
786
787 atPos = host.find( '@' );
788
789 if( atPos != wxString::npos )
790 m_hostname = host.Mid( atPos + 1 );
791 else
792 m_hostname = host;
793 }
794 }
795
796 m_secretFetched = !m_password.IsEmpty();
797}
798
799
800int KIGIT_COMMON::HandleSSHKeyAuthentication( git_cred** aOut, const wxString& aUsername )
801{
803 return HandleSSHAgentAuthentication( aOut, aUsername );
804
805 // SSH key authentication with password
806 wxString sshKey = GetNextPublicKey();
807
808 if( sshKey.IsEmpty() )
809 {
810 wxLogTrace( traceGit, "Finished testing all possible ssh keys" );
811 m_testedTypes |= GIT_CREDENTIAL_SSH_KEY;
812 return GIT_PASSTHROUGH;
813 }
814
815 wxString sshPubKey = sshKey + ".pub";
816 wxString password = GetPassword();
817
818 wxLogTrace( traceGit, "Testing %s\n", sshKey );
819
820 if( git_credential_ssh_key_new( aOut, aUsername.mbc_str(), sshPubKey.mbc_str(), sshKey.mbc_str(),
821 password.mbc_str() ) != GIT_OK )
822 {
823 wxLogTrace( traceGit, "Failed to create SSH key credential for %s: %s",
824 aUsername, KIGIT_COMMON::GetLastGitError() );
825 return GIT_PASSTHROUGH;
826 }
827
828 return GIT_OK;
829}
830
831
832int KIGIT_COMMON::HandlePlaintextAuthentication( git_cred** aOut, const wxString& aUsername )
833{
834 wxString password = GetPassword();
835
836 git_credential_userpass_plaintext_new( aOut, aUsername.mbc_str(), password.mbc_str() );
837 m_testedTypes |= GIT_CREDENTIAL_USERPASS_PLAINTEXT;
838
839 return GIT_OK;
840}
841
842int KIGIT_COMMON::HandleSSHAgentAuthentication( git_cred** aOut, const wxString& aUsername )
843{
844 if( git_credential_ssh_key_from_agent( aOut, aUsername.mbc_str() ) != GIT_OK )
845 {
846 wxLogTrace( traceGit, "Failed to create SSH agent credential for %s: %s",
847 aUsername, KIGIT_COMMON::GetLastGitError() );
848 return GIT_PASSTHROUGH;
849 }
850
852 return GIT_OK;
853}
854
855
856extern "C" int fetchhead_foreach_cb( const char*, const char*,
857 const git_oid* aOID, unsigned int aIsMerge, void* aPayload )
858{
859 if( aIsMerge )
860 git_oid_cpy( (git_oid*) aPayload, aOID );
861
862 return 0;
863}
864
865
866extern "C" void clone_progress_cb( const char* aStr, size_t aLen, size_t aTotal, void* aPayload )
867{
868 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
869
870 wxString progressMessage( aStr );
871 parent->UpdateProgress( aLen, aTotal, progressMessage );
872}
873
874
875extern "C" int progress_cb( const char* str, int len, void* aPayload )
876{
877 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
878
879 if( parent->GetCommon()->IsCancelled() )
880 {
881 wxLogTrace( traceGit, "Progress CB cancelled" );
882 return GIT_EUSER;
883 }
884
885 wxString progressMessage( str, len );
886 parent->UpdateProgress( 0, 0, progressMessage );
887
888 return 0;
889}
890
891
892extern "C" int transfer_progress_cb( const git_transfer_progress* aStats, void* aPayload )
893{
894 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
895
896 wxString progressMessage = wxString::Format( _( "Received %u of %u objects" ),
897 aStats->received_objects,
898 aStats->total_objects );
899 if( parent->GetCommon()->IsCancelled() )
900 {
901 wxLogTrace( traceGit, "Transfer progress cancelled" );
902 return GIT_EUSER;
903 }
904
905 parent->UpdateProgress( aStats->received_objects, aStats->total_objects, progressMessage );
906
907 return 0;
908}
909
910
911extern "C" int update_cb( const char* aRefname, const git_oid* aFirst, const git_oid* aSecond,
912 void* aPayload )
913{
914 constexpr int cstring_len = 8;
915 char a_str[cstring_len + 1];
916 char b_str[cstring_len + 1];
917
918 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
919 wxString status;
920
921 git_oid_tostr( b_str, cstring_len, aSecond );
922
923#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
924 if( !git_oid_is_zero( aFirst ) )
925#else
926 if( !git_oid_iszero( aFirst ) )
927#endif
928 {
929 git_oid_tostr( a_str, cstring_len, aFirst );
930 status = wxString::Format( _( "* [updated] %s..%s %s" ), a_str, b_str, aRefname );
931 }
932 else
933 {
934 status = wxString::Format( _( "* [new] %s %s" ), b_str, aRefname );
935 }
936
937 parent->UpdateProgress( 0, 0, status );
938
939 return 0;
940}
941
942
943extern "C" int push_transfer_progress_cb( unsigned int aCurrent, unsigned int aTotal, size_t aBytes,
944 void* aPayload )
945{
946 long long progress = 100;
947 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
948
949 if( aTotal != 0 )
950 {
951 progress = ( aCurrent * 100ll ) / aTotal;
952 }
953
954 wxString progressMessage = wxString::Format( _( "Writing objects: %lld%% (%u/%u), %zu bytes" ),
955 progress, aCurrent, aTotal, aBytes );
956 parent->UpdateProgress( aCurrent, aTotal, progressMessage );
957
958 return 0;
959}
960
961
962extern "C" int push_update_reference_cb( const char* aRefname, const char* aStatus, void* aPayload )
963{
964 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
965 wxString status( aStatus );
966
967 if( !status.IsEmpty() )
968 {
969 wxString statusMessage = wxString::Format( _( "* [rejected] %s (%s)" ), aRefname, aStatus );
970 parent->UpdateProgress( 0, 0, statusMessage );
971 }
972 else
973 {
974 wxString statusMessage = wxString::Format( _( "[updated] %s" ), aRefname );
975 parent->UpdateProgress( 0, 0, statusMessage );
976 }
977
978 return 0;
979}
980
981
982extern "C" int credentials_cb( git_cred** aOut, const char* aUrl, const char* aUsername,
983 unsigned int aAllowedTypes, void* aPayload )
984{
985 KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
986 KIGIT_COMMON* common = parent->GetCommon();
987
988 wxLogTrace( traceGit, "Credentials callback for %s, testing %d", aUrl, aAllowedTypes );
989
991 {
992 wxLogTrace( traceGit, "Local repository, no credentials needed" );
993 return GIT_PASSTHROUGH;
994 }
995
996 if( aAllowedTypes & GIT_CREDENTIAL_USERNAME
997 && !( parent->TestedTypes() & GIT_CREDENTIAL_USERNAME ) )
998 {
999 wxString username = parent->GetUsername().Trim().Trim( false );
1000 wxLogTrace( traceGit, "Username credential for %s at %s with allowed type %d",
1001 username, aUrl, aAllowedTypes );
1002 if( git_credential_username_new( aOut, username.ToStdString().c_str() ) != GIT_OK )
1003 {
1004 wxLogTrace( traceGit, "Failed to create username credential for %s: %s",
1005 username, KIGIT_COMMON::GetLastGitError() );
1006 }
1007 else
1008 {
1009 wxLogTrace( traceGit, "Created username credential for %s", username );
1010 }
1011
1012 parent->TestedTypes() |= GIT_CREDENTIAL_USERNAME;
1013 }
1015 && ( aAllowedTypes & GIT_CREDENTIAL_USERPASS_PLAINTEXT )
1016 && !( parent->TestedTypes() & GIT_CREDENTIAL_USERPASS_PLAINTEXT ) )
1017 {
1018 // Plaintext authentication
1019 wxLogTrace( traceGit, "Plaintext authentication for %s at %s with allowed type %d",
1020 parent->GetUsername(), aUrl, aAllowedTypes );
1021 return common->HandlePlaintextAuthentication( aOut, parent->GetUsername() );
1022 }
1024 && ( aAllowedTypes & GIT_CREDENTIAL_SSH_KEY )
1025 && !( parent->TestedTypes() & GIT_CREDENTIAL_SSH_KEY ) )
1026 {
1027 // SSH key authentication
1028 return common->HandleSSHKeyAuthentication( aOut, parent->GetUsername() );
1029 }
1030 else
1031 {
1032 // If we didn't find anything to try, then we don't have a callback set that the
1033 // server likes
1034 if( !parent->TestedTypes() )
1035 return GIT_PASSTHROUGH;
1036
1037 git_error_clear();
1038 git_error_set_str( GIT_ERROR_NET, _( "Unable to authenticate" ).mbc_str() );
1039 // Otherwise, we did try something but we failed, so return an authentication error
1040 return GIT_EAUTH;
1041 }
1042
1043 return GIT_OK;
1044};
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.