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 (C) 2023 KiCad Developers, see AUTHORS.TXT for contributors.
5 *
6 * This program is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 3
9 * of the License, or (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, you may find one here:
18 * http://www.gnu.org/licenses/gpl-3.0.html
19 * or you may search the http://www.gnu.org website for the version 3 license,
20 * or you may write to the Free Software Foundation, Inc.,
21 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
22 */
23
24#include "kicad_git_common.h"
25
26#include <wx/filename.h>
27#include <wx/log.h>
28#include <map>
29#include <vector>
30
31KIGIT_COMMON::KIGIT_COMMON( git_repository* aRepo ) :
32 m_repo( aRepo ), m_connType( GIT_CONN_TYPE::GIT_CONN_LOCAL ), m_testedTypes( 0 )
33{}
34
36{}
37
38git_repository* KIGIT_COMMON::GetRepo() const
39{
40 return m_repo;
41}
42
44{
45 git_reference* head = nullptr;
46
47 int retval = git_repository_head( &head, m_repo );
48
49 if( retval && retval != GIT_EUNBORNBRANCH && retval != GIT_ENOTFOUND )
50 return wxEmptyString;
51
52 git_reference *branch;
53
54 if( git_reference_resolve( &branch, head ) )
55 {
56 git_reference_free( head );
57 return wxEmptyString;
58 }
59
60 git_reference_free( head );
61 const char* branchName = "";
62
63 if( git_branch_name( &branchName, branch ) )
64 {
65 git_reference_free( branch );
66 return wxEmptyString;
67 }
68
69 git_reference_free( branch );
70
71 return branchName;
72}
73
74
75std::vector<wxString> KIGIT_COMMON::GetBranchNames() const
76{
77 std::vector<wxString> branchNames;
78 std::map<git_time_t, wxString> branchNamesMap;
79 wxString firstName;
80
81 git_branch_iterator* branchIterator = nullptr;
82
83 if( git_branch_iterator_new( &branchIterator, m_repo, GIT_BRANCH_LOCAL ) )
84 return branchNames;
85
86 git_reference* branchReference = nullptr;
87 git_branch_t branchType;
88
89 while( git_branch_next( &branchReference, &branchType, branchIterator ) != GIT_ITEROVER )
90 {
91 const char* branchName = "";
92
93 if( git_branch_name( &branchName, branchReference ) )
94 continue;
95
96 const git_oid* commitId = git_reference_target( branchReference );
97
98 git_commit* commit = nullptr;
99
100 if( git_commit_lookup( &commit, m_repo, commitId ) )
101 continue;
102
103 git_time_t commitTime = git_commit_time( commit );
104
105 if( git_branch_is_head( branchReference ) )
106 firstName = branchName;
107 else
108 branchNamesMap.emplace( commitTime, branchName );
109
110 git_commit_free( commit );
111 git_reference_free( branchReference );
112 }
113
114 git_branch_iterator_free( branchIterator );
115
116 // Add the current branch to the top of the list
117 if( !firstName.IsEmpty() )
118 branchNames.push_back( firstName );
119
120 // Add the remaining branches in order from newest to oldest
121 for( auto rit = branchNamesMap.rbegin(); rit != branchNamesMap.rend(); ++rit )
122 branchNames.push_back( rit->second );
123
124 return branchNames;
125}
126
127
128std::vector<wxString> KIGIT_COMMON::GetProjectDirs()
129{
130 std::vector<wxString> projDirs;
131
132 git_oid oid;
133 git_commit* commit;
134 git_tree *tree;
135
136 if( git_reference_name_to_id( &oid, m_repo, "HEAD" ) != GIT_OK )
137 {
138 wxLogError( "An error occurred: %s", git_error_last()->message );
139 return projDirs;
140 }
141
142 if( git_commit_lookup( &commit, m_repo, &oid ) != GIT_OK )
143 {
144 wxLogError( "An error occurred: %s", git_error_last()->message );
145 return projDirs;
146 }
147
148 if( git_commit_tree( &tree, commit ) != GIT_OK )
149 {
150 wxLogError( "An error occurred: %s", git_error_last()->message );
151 return projDirs;
152 }
153
154 // Define callback
155 git_tree_walk(
156 tree, GIT_TREEWALK_PRE,
157 []( const char* root, const git_tree_entry* entry, void* payload )
158 {
159 std::vector<wxString>* prjs = static_cast<std::vector<wxString>*>( payload );
160 wxFileName root_fn( git_tree_entry_name( entry ) );
161
162 root_fn.SetPath( root );
163
164 if( git_tree_entry_type( entry ) == GIT_OBJECT_BLOB
165 && ( ( root_fn.GetExt() == "kicad_pro" ) || ( root_fn.GetExt() == "pro" ) ) )
166 {
167 prjs->push_back( root_fn.GetFullPath() );
168 }
169
170 return 0; // continue walking
171 },
172 &projDirs );
173
174 git_tree_free( tree );
175 git_commit_free( commit );
176
177 std::sort( projDirs.begin(), projDirs.end(),
178 []( const wxString& a, const wxString& b )
179 {
180 int a_freq = a.Freq( wxFileName::GetPathSeparator() );
181 int b_freq = b.Freq( wxFileName::GetPathSeparator() );
182
183 if( a_freq == b_freq )
184 return a < b;
185 else
186 return a_freq < b_freq;
187
188 } );
189
190 return projDirs;
191}
192
193
194std::pair<std::set<wxString>,std::set<wxString>> KIGIT_COMMON::GetDifferentFiles() const
195{
196 auto get_modified_files = [&]( git_oid* from_oid, git_oid* to_oid ) -> std::set<wxString>
197 {
198 std::set<wxString> modified_set;
199 git_revwalk* walker = nullptr;
200
201 if( git_revwalk_new( &walker, m_repo ) != GIT_OK )
202 return modified_set;
203
204 if( ( git_revwalk_push( walker, from_oid ) != GIT_OK )
205 || ( git_revwalk_hide( walker, to_oid ) != GIT_OK ) )
206 {
207 git_revwalk_free( walker );
208 return modified_set;
209 }
210
211 git_oid oid;
212 git_commit* commit;
213
214 // iterate over all local commits not in remote
215 while( git_revwalk_next( &oid, walker ) == GIT_OK )
216 {
217 if( git_commit_lookup( &commit, m_repo, &oid ) != GIT_OK )
218 continue;
219
220 git_tree *tree, *parent_tree = nullptr;
221 if( git_commit_tree( &tree, commit ) != GIT_OK )
222 {
223 git_commit_free( commit );
224 continue;
225 }
226
227 // get parent commit tree to diff against
228 if( !git_commit_parentcount( commit ) )
229 {
230 git_tree_free( tree );
231 git_commit_free( commit );
232 continue;
233 }
234
235
236 git_commit* parent;
237 if( git_commit_parent( &parent, commit, 0 ) != GIT_OK )
238 {
239 git_tree_free( tree );
240 git_commit_free( commit );
241 continue;
242 }
243
244
245 if( git_commit_tree( &parent_tree, parent ) != GIT_OK )
246 {
247 git_tree_free( tree );
248 git_commit_free( commit );
249 git_commit_free( parent );
250 continue;
251 }
252
253
254 git_diff* diff;
255 git_diff_options diff_opts;
256 git_diff_init_options( &diff_opts, GIT_DIFF_OPTIONS_VERSION );
257
258 if( git_diff_tree_to_tree( &diff, m_repo, parent_tree, tree, &diff_opts ) == GIT_OK )
259 {
260 size_t num_deltas = git_diff_num_deltas( diff );
261
262 for( size_t i = 0; i < num_deltas; ++i )
263 {
264 const git_diff_delta* delta = git_diff_get_delta( diff, i );
265 modified_set.insert( delta->new_file.path );
266 }
267
268 git_diff_free( diff );
269 }
270
271 git_tree_free( parent_tree );
272 git_commit_free( parent );
273 git_tree_free( tree );
274 git_commit_free( commit );
275 }
276
277 git_revwalk_free( walker );
278
279 return modified_set;
280 };
281
282 std::pair<std::set<wxString>,std::set<wxString>> modified_files;
283
284 if( !m_repo )
285 return modified_files;
286
287 git_reference* head = nullptr;
288 git_reference* remote_head = nullptr;
289
290 if( git_repository_head( &head, m_repo ) != GIT_OK )
291 return modified_files;
292
293 if( git_branch_upstream( &remote_head, head ) != GIT_OK )
294 {
295 git_reference_free( head );
296 return modified_files;
297 }
298
299 git_oid head_oid = *git_reference_target( head );
300 git_oid remote_oid = *git_reference_target( remote_head );
301
302 git_reference_free( head );
303 git_reference_free( remote_head );
304
305 modified_files.first = get_modified_files( &head_oid, &remote_oid );
306 modified_files.second = get_modified_files( &remote_oid, &head_oid );
307
308 return modified_files;
309}
310
311
313{
314 if( !m_repo )
315 return false;
316
317 git_reference* head = nullptr;
318 git_reference* remote_head = nullptr;
319
320 if( git_repository_head( &head, m_repo ) != GIT_OK )
321 return false;
322
323 if( git_branch_upstream( &remote_head, head ) != GIT_OK )
324 {
325 git_reference_free( head );
326 return false;
327 }
328
329 git_oid head_oid = *git_reference_target( head );
330 git_oid remote_oid = *git_reference_target( remote_head );
331
332 git_reference_free( head );
333 git_reference_free( remote_head );
334
335 git_revwalk* walker = nullptr;
336
337 if( git_revwalk_new( &walker, m_repo ) != GIT_OK )
338 return false;
339
340 if( ( git_revwalk_push( walker, &head_oid ) != GIT_OK )
341 || ( git_revwalk_hide( walker, &remote_oid ) != GIT_OK ) )
342 {
343 git_revwalk_free( walker );
344 return false;
345 }
346
347 git_oid oid;
348
349 // If we can't walk to the next commit, then we are at or behind the remote
350 if( git_revwalk_next( &oid, walker ) != GIT_OK )
351 {
352 git_revwalk_free( walker );
353 return false;
354 }
355
356 git_revwalk_free( walker );
357 return true;
358}
359
360
362{
363 git_remote* remote = nullptr;
364
365 if( git_remote_lookup( &remote, m_repo, "origin" ) != GIT_OK )
366 {
367 return false;
368 }
369
370 // Get the URLs associated with the remote
371 const char* fetch_url = git_remote_url( remote );
372 const char* push_url = git_remote_pushurl( remote );
373
374 // If no push URL is set, libgit2 defaults to using the fetch URL for pushing
375 if( !push_url )
376 {
377 push_url = fetch_url;
378 }
379
380 // Clean up the remote object
381 git_remote_free( remote );
382
383 // Check if both URLs are valid (i.e., not NULL)
384 return fetch_url && push_url;
385}
386
387
388extern "C" int fetchhead_foreach_cb( const char*, const char*,
389 const git_oid* aOID, unsigned int aIsMerge, void* aPayload )
390{
391 if( aIsMerge )
392 git_oid_cpy( (git_oid*) aPayload, aOID );
393
394 return 0;
395}
396
397
398extern "C" void clone_progress_cb( const char* aStr, size_t aLen, size_t aTotal, void* data )
399{
400 KIGIT_COMMON* parent = (KIGIT_COMMON*) data;
401
402 wxString progressMessage( aStr );
403 parent->UpdateProgress( aLen, aTotal, progressMessage );
404}
405
406
407extern "C" int progress_cb( const char* str, int len, void* data )
408{
409 KIGIT_COMMON* parent = (KIGIT_COMMON*) data;
410
411 wxString progressMessage( str, len );
412 parent->UpdateProgress( 0, 0, progressMessage );
413
414 return 0;
415}
416
417extern "C" int transfer_progress_cb( const git_transfer_progress* aStats, void* aPayload )
418{
419 KIGIT_COMMON* parent = (KIGIT_COMMON*) aPayload;
420 wxString progressMessage = wxString::Format( _( "Received %u of %u objects" ),
421 aStats->received_objects, aStats->total_objects );
422
423 parent->UpdateProgress( aStats->received_objects, aStats->total_objects, progressMessage );
424
425 return 0;
426}
427
428extern "C" int update_cb( const char* aRefname, const git_oid* aFirst, const git_oid* aSecond,
429 void* aPayload )
430{
431 constexpr int cstring_len = 8;
432 char a_str[cstring_len + 1];
433 char b_str[cstring_len + 1];
434
435 KIGIT_COMMON* parent = (KIGIT_COMMON*) aPayload;
436 wxString status;
437
438 git_oid_tostr( b_str, cstring_len, aSecond );
439
440#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
441 if( !git_oid_is_zero( aFirst ) )
442#else
443 if( !git_oid_iszero( aFirst ) )
444#endif
445 {
446 git_oid_tostr( a_str, cstring_len, aFirst );
447 status = wxString::Format( _( "* [updated] %s..%s %s" ), a_str, b_str, aRefname );
448 }
449 else
450 {
451 status = wxString::Format( _( "* [new] %s %s" ), b_str, aRefname );
452 }
453
454 parent->UpdateProgress( 0, 0, status );
455
456 return 0;
457}
458
459
460extern "C" int push_transfer_progress_cb( unsigned int aCurrent, unsigned int aTotal, size_t aBytes,
461 void* aPayload )
462{
463 int64_t progress = 100;
464 KIGIT_COMMON* parent = (KIGIT_COMMON*) aPayload;
465
466 if( aTotal != 0 )
467 {
468 progress = ( aCurrent * 100 ) / aTotal;
469 }
470
471 wxString progressMessage = wxString::Format( _( "Writing objects: %d%% (%d/%d), %d bytes" ),
472 progress, aCurrent, aTotal, aBytes );
473 parent->UpdateProgress( aCurrent, aTotal, progressMessage );
474
475 return 0;
476}
477
478
479extern "C" int push_update_reference_cb( const char* aRefname, const char* aStatus, void* aPayload )
480{
481 KIGIT_COMMON* parent = (KIGIT_COMMON*) aPayload;
482 wxString status( aStatus );
483
484 if( !status.IsEmpty() )
485 {
486 wxString statusMessage = wxString::Format( _( "* [rejected] %s (%s)" ), aRefname, aStatus );
487 parent->UpdateProgress( 0, 0, statusMessage );
488 }
489 else
490 {
491 wxString statusMessage = wxString::Format( _( "[updated] %s" ), aRefname );
492 parent->UpdateProgress( 0, 0, statusMessage );
493 }
494
495 return 0;
496}
497
498
499extern "C" int credentials_cb( git_cred** aOut, const char* aUrl, const char* aUsername,
500 unsigned int aAllowedTypes, void* aPayload )
501{
502 KIGIT_COMMON* parent = static_cast<KIGIT_COMMON*>( aPayload );
503
505 return GIT_PASSTHROUGH;
506
507 if( aAllowedTypes & GIT_CREDTYPE_USERNAME
508 && !( parent->TestedTypes() & GIT_CREDTYPE_USERNAME ) )
509 {
510 wxString username = parent->GetUsername().Trim().Trim( false );
511 git_cred_username_new( aOut, username.ToStdString().c_str() );
512 parent->TestedTypes() |= GIT_CREDTYPE_USERNAME;
513 }
515 && ( aAllowedTypes & GIT_CREDTYPE_USERPASS_PLAINTEXT )
516 && !( parent->TestedTypes() & GIT_CREDTYPE_USERPASS_PLAINTEXT ) )
517 {
518 wxString username = parent->GetUsername().Trim().Trim( false );
519 wxString password = parent->GetPassword().Trim().Trim( false );
520
521 git_cred_userpass_plaintext_new( aOut, username.ToStdString().c_str(),
522 password.ToStdString().c_str() );
523 parent->TestedTypes() |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
524 }
526 && ( aAllowedTypes & GIT_CREDTYPE_SSH_KEY )
527 && !( parent->TestedTypes() & GIT_CREDTYPE_SSH_KEY ) )
528 {
529 // SSH key authentication
530 wxString sshKey = parent->GetSSHKey();
531 wxString sshPubKey = sshKey + ".pub";
532 wxString username = parent->GetUsername().Trim().Trim( false );
533 wxString password = parent->GetPassword().Trim().Trim( false );
534
535 git_cred_ssh_key_new( aOut, username.ToStdString().c_str(),
536 sshPubKey.ToStdString().c_str(),
537 sshKey.ToStdString().c_str(),
538 password.ToStdString().c_str() );
539 parent->TestedTypes() |= GIT_CREDTYPE_SSH_KEY;
540 }
541 else
542 {
543 return GIT_PASSTHROUGH;
544 }
545
546 return GIT_OK;
547};
git_repository * m_repo
std::vector< wxString > GetBranchNames() const
wxString GetCurrentBranchName() const
wxString GetSSHKey() const
std::vector< wxString > GetProjectDirs()
Return a vector of project files in the repository.
GIT_CONN_TYPE GetConnType() const
wxString GetPassword() const
git_repository * GetRepo() const
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 ...
bool HasPushAndPullRemote() const
wxString GetUsername() const
bool HasLocalCommits() const
unsigned & TestedTypes()
virtual void UpdateProgress(int aCurrent, int aTotal, const wxString &aMessage)
#define _(s)
void clone_progress_cb(const char *aStr, size_t aLen, size_t aTotal, void *data)
int fetchhead_foreach_cb(const char *, const char *, const git_oid *aOID, unsigned int aIsMerge, void *aPayload)
int push_update_reference_cb(const char *aRefname, const char *aStatus, void *aPayload)
int update_cb(const char *aRefname, const git_oid *aFirst, const git_oid *aSecond, void *aPayload)
int transfer_progress_cb(const git_transfer_progress *aStats, void *aPayload)
int credentials_cb(git_cred **aOut, const char *aUrl, const char *aUsername, unsigned int aAllowedTypes, void *aPayload)
int push_transfer_progress_cb(unsigned int aCurrent, unsigned int aTotal, size_t aBytes, void *aPayload)
int progress_cb(const char *str, int len, void *data)
constexpr int delta