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 = GIT_DIFF_OPTIONS_INIT;
256
257 if( git_diff_tree_to_tree( &diff, m_repo, parent_tree, tree, &diff_opts ) == GIT_OK )
258 {
259 size_t num_deltas = git_diff_num_deltas( diff );
260
261 for( size_t i = 0; i < num_deltas; ++i )
262 {
263 const git_diff_delta* delta = git_diff_get_delta( diff, i );
264 modified_set.insert( delta->new_file.path );
265 }
266
267 git_diff_free( diff );
268 }
269
270 git_tree_free( parent_tree );
271 git_commit_free( parent );
272 git_tree_free( tree );
273 git_commit_free( commit );
274 }
275
276 git_revwalk_free( walker );
277
278 return modified_set;
279 };
280
281 std::pair<std::set<wxString>,std::set<wxString>> modified_files;
282
283 if( !m_repo )
284 return modified_files;
285
286 git_reference* head = nullptr;
287 git_reference* remote_head = nullptr;
288
289 if( git_repository_head( &head, m_repo ) != GIT_OK )
290 return modified_files;
291
292 if( git_branch_upstream( &remote_head, head ) != GIT_OK )
293 {
294 git_reference_free( head );
295 return modified_files;
296 }
297
298 git_oid head_oid = *git_reference_target( head );
299 git_oid remote_oid = *git_reference_target( remote_head );
300
301 git_reference_free( head );
302 git_reference_free( remote_head );
303
304 modified_files.first = get_modified_files( &head_oid, &remote_oid );
305 modified_files.second = get_modified_files( &remote_oid, &head_oid );
306
307 return modified_files;
308}
309
310
312{
313 if( !m_repo )
314 return false;
315
316 git_reference* head = nullptr;
317 git_reference* remote_head = nullptr;
318
319 if( git_repository_head( &head, m_repo ) != GIT_OK )
320 return false;
321
322 if( git_branch_upstream( &remote_head, head ) != GIT_OK )
323 {
324 git_reference_free( head );
325 return false;
326 }
327
328 git_oid head_oid = *git_reference_target( head );
329 git_oid remote_oid = *git_reference_target( remote_head );
330
331 git_reference_free( head );
332 git_reference_free( remote_head );
333
334 git_revwalk* walker = nullptr;
335
336 if( git_revwalk_new( &walker, m_repo ) != GIT_OK )
337 return false;
338
339 if( ( git_revwalk_push( walker, &head_oid ) != GIT_OK )
340 || ( git_revwalk_hide( walker, &remote_oid ) != GIT_OK ) )
341 {
342 git_revwalk_free( walker );
343 return false;
344 }
345
346 git_oid oid;
347
348 // If we can't walk to the next commit, then we are at or behind the remote
349 if( git_revwalk_next( &oid, walker ) != GIT_OK )
350 {
351 git_revwalk_free( walker );
352 return false;
353 }
354
355 git_revwalk_free( walker );
356 return true;
357}
358
359
361{
362 git_remote* remote = nullptr;
363
364 if( git_remote_lookup( &remote, m_repo, "origin" ) != GIT_OK )
365 {
366 return false;
367 }
368
369 // Get the URLs associated with the remote
370 const char* fetch_url = git_remote_url( remote );
371 const char* push_url = git_remote_pushurl( remote );
372
373 // If no push URL is set, libgit2 defaults to using the fetch URL for pushing
374 if( !push_url )
375 {
376 push_url = fetch_url;
377 }
378
379 // Clean up the remote object
380 git_remote_free( remote );
381
382 // Check if both URLs are valid (i.e., not NULL)
383 return fetch_url && push_url;
384}
385
386
387extern "C" int fetchhead_foreach_cb( const char*, const char*,
388 const git_oid* aOID, unsigned int aIsMerge, void* aPayload )
389{
390 if( aIsMerge )
391 git_oid_cpy( (git_oid*) aPayload, aOID );
392
393 return 0;
394}
395
396
397extern "C" void clone_progress_cb( const char* aStr, size_t aLen, size_t aTotal, void* data )
398{
399 KIGIT_COMMON* parent = (KIGIT_COMMON*) data;
400
401 wxString progressMessage( aStr );
402 parent->UpdateProgress( aLen, aTotal, progressMessage );
403}
404
405
406extern "C" int progress_cb( const char* str, int len, void* data )
407{
408 KIGIT_COMMON* parent = (KIGIT_COMMON*) data;
409
410 wxString progressMessage( str, len );
411 parent->UpdateProgress( 0, 0, progressMessage );
412
413 return 0;
414}
415
416extern "C" int transfer_progress_cb( const git_transfer_progress* aStats, void* aPayload )
417{
418 KIGIT_COMMON* parent = (KIGIT_COMMON*) aPayload;
419 wxString progressMessage = wxString::Format( _( "Received %u of %u objects" ),
420 aStats->received_objects, aStats->total_objects );
421
422 parent->UpdateProgress( aStats->received_objects, aStats->total_objects, progressMessage );
423
424 return 0;
425}
426
427extern "C" int update_cb( const char* aRefname, const git_oid* aFirst, const git_oid* aSecond,
428 void* aPayload )
429{
430 constexpr int cstring_len = 8;
431 char a_str[cstring_len + 1];
432 char b_str[cstring_len + 1];
433
434 KIGIT_COMMON* parent = (KIGIT_COMMON*) aPayload;
435 wxString status;
436
437 git_oid_tostr( b_str, cstring_len, aSecond );
438
439#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
440 if( !git_oid_is_zero( aFirst ) )
441#else
442 if( !git_oid_iszero( aFirst ) )
443#endif
444 {
445 git_oid_tostr( a_str, cstring_len, aFirst );
446 status = wxString::Format( _( "* [updated] %s..%s %s" ), a_str, b_str, aRefname );
447 }
448 else
449 {
450 status = wxString::Format( _( "* [new] %s %s" ), b_str, aRefname );
451 }
452
453 parent->UpdateProgress( 0, 0, status );
454
455 return 0;
456}
457
458
459extern "C" int push_transfer_progress_cb( unsigned int aCurrent, unsigned int aTotal, size_t aBytes,
460 void* aPayload )
461{
462 int64_t progress = 100;
463 KIGIT_COMMON* parent = (KIGIT_COMMON*) aPayload;
464
465 if( aTotal != 0 )
466 {
467 progress = ( aCurrent * 100 ) / aTotal;
468 }
469
470 wxString progressMessage = wxString::Format( _( "Writing objects: %d%% (%d/%d), %d bytes" ),
471 progress, aCurrent, aTotal, aBytes );
472 parent->UpdateProgress( aCurrent, aTotal, progressMessage );
473
474 return 0;
475}
476
477
478extern "C" int push_update_reference_cb( const char* aRefname, const char* aStatus, void* aPayload )
479{
480 KIGIT_COMMON* parent = (KIGIT_COMMON*) aPayload;
481 wxString status( aStatus );
482
483 if( !status.IsEmpty() )
484 {
485 wxString statusMessage = wxString::Format( _( "* [rejected] %s (%s)" ), aRefname, aStatus );
486 parent->UpdateProgress( 0, 0, statusMessage );
487 }
488 else
489 {
490 wxString statusMessage = wxString::Format( _( "[updated] %s" ), aRefname );
491 parent->UpdateProgress( 0, 0, statusMessage );
492 }
493
494 return 0;
495}
496
497
498extern "C" int credentials_cb( git_cred** aOut, const char* aUrl, const char* aUsername,
499 unsigned int aAllowedTypes, void* aPayload )
500{
501 KIGIT_COMMON* parent = static_cast<KIGIT_COMMON*>( aPayload );
502
504 return GIT_PASSTHROUGH;
505
506 if( aAllowedTypes & GIT_CREDTYPE_USERNAME
507 && !( parent->TestedTypes() & GIT_CREDTYPE_USERNAME ) )
508 {
509 wxString username = parent->GetUsername().Trim().Trim( false );
510 git_cred_username_new( aOut, username.ToStdString().c_str() );
511 parent->TestedTypes() |= GIT_CREDTYPE_USERNAME;
512 }
514 && ( aAllowedTypes & GIT_CREDTYPE_USERPASS_PLAINTEXT )
515 && !( parent->TestedTypes() & GIT_CREDTYPE_USERPASS_PLAINTEXT ) )
516 {
517 wxString username = parent->GetUsername().Trim().Trim( false );
518 wxString password = parent->GetPassword().Trim().Trim( false );
519
520 git_cred_userpass_plaintext_new( aOut, username.ToStdString().c_str(),
521 password.ToStdString().c_str() );
522 parent->TestedTypes() |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
523 }
525 && ( aAllowedTypes & GIT_CREDTYPE_SSH_KEY )
526 && !( parent->TestedTypes() & GIT_CREDTYPE_SSH_KEY ) )
527 {
528 // SSH key authentication
529 wxString sshKey = parent->GetSSHKey();
530 wxString sshPubKey = sshKey + ".pub";
531 wxString username = parent->GetUsername().Trim().Trim( false );
532 wxString password = parent->GetPassword().Trim().Trim( false );
533
534 git_cred_ssh_key_new( aOut, username.ToStdString().c_str(),
535 sshPubKey.ToStdString().c_str(),
536 sshKey.ToStdString().c_str(),
537 password.ToStdString().c_str() );
538 parent->TestedTypes() |= GIT_CREDTYPE_SSH_KEY;
539 }
540 else
541 {
542 return GIT_PASSTHROUGH;
543 }
544
545 return GIT_OK;
546};
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