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