KiCad PCB EDA Suite
Loading...
Searching...
No Matches
libgit_backend.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 "libgit_backend.h"
25
26#include "git_clone_handler.h"
27#include "git_commit_handler.h"
28#include "git_push_handler.h"
31#include "git_status_handler.h"
32#include "git_config_handler.h"
33#include "git_init_handler.h"
34#include "git_branch_handler.h"
35#include "git_pull_handler.h"
36#include "git_revert_handler.h"
37#include "project_git_utils.h"
38#include "kicad_git_common.h"
39#include "kicad_git_memory.h"
40#include "trace_helpers.h"
41
42#include "kicad_git_compat.h"
43#include <wx/filename.h>
44#include <wx/log.h>
45#include <gestfich.h>
46#include <algorithm>
47#include <iterator>
48#include <memory>
49#include <time.h>
50
51static std::string getFirstLineFromCommitMessage( const std::string& aMessage )
52{
53 if( aMessage.empty() )
54 return aMessage;
55
56 size_t firstLineEnd = aMessage.find_first_of( '\n' );
57
58 if( firstLineEnd != std::string::npos )
59 return aMessage.substr( 0, firstLineEnd );
60
61 return aMessage;
62}
63
64
65static std::string getFormattedCommitDate( const git_time& aTime )
66{
67 char dateBuffer[64];
68 time_t time = static_cast<time_t>( aTime.time );
69 struct tm timeInfo;
70
71#ifdef _WIN32
72 localtime_s( &timeInfo, &time );
73#else
74 gmtime_r( &time, &timeInfo );
75#endif
76
77 strftime( dateBuffer, sizeof( dateBuffer ), "%Y-%b-%d %H:%M:%S", &timeInfo );
78 return dateBuffer;
79}
80
81
83{
84 git_libgit2_init();
85}
86
87
89{
90 // Wait for any abandoned git cleanup threads to finish before tearing
91 // down libgit2. A worker still inside libgit2 (for example, blocked on
92 // recv() under git_remote_fetch) would otherwise race teardown and
93 // invoke undefined behaviour. Five seconds is long enough to cover a
94 // transport error timeout but short enough to avoid a perceptibly slow
95 // exit when the remote is truly unreachable.
96
97 constexpr auto kOrphanJoinTimeout = std::chrono::seconds( 5 );
98 size_t stuck = m_orphanRegistry.JoinAll( kOrphanJoinTimeout );
99
100 if( stuck > 0 )
101 {
102 wxLogTrace( traceGit,
103 "LIBGIT_BACKEND::Shutdown(): %zu orphan git thread(s) "
104 "did not finish within %lld ms; skipping libgit2 shutdown",
105 stuck,
106 static_cast<long long>( kOrphanJoinTimeout.count() ) );
107
108 // A stuck worker is still executing inside libgit2. Calling
109 // git_libgit2_shutdown() now would free state the worker is actively
110 // reading. Leave libgit2 initialised and let the OS reclaim
111 // resources when the process exits.
112
113 return;
114 }
115
116 git_libgit2_shutdown();
117}
118
119
121{
122#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
123 int major = 0, minor = 0, rev = 0;
124 return git_libgit2_version( &major, &minor, &rev ) == GIT_OK;
125#else
126 // On older platforms, assume available when building with libgit2
127 return true;
128#endif
129}
130
131
133{
134 KIGIT_COMMON* common = aHandler->GetCommon();
135 std::unique_lock<std::mutex> lock( common->m_gitActionMutex, std::try_to_lock );
136
137 if( !lock.owns_lock() )
138 {
139 wxLogTrace( traceGit, "GIT_CLONE_HANDLER::PerformClone() could not lock" );
140 return false;
141 }
142
143 wxFileName clonePath( aHandler->GetClonePath() );
144
145 if( !clonePath.DirExists() )
146 {
147 if( !clonePath.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
148 {
149 aHandler->AddErrorString( wxString::Format( _( "Could not create directory '%s'" ),
150 aHandler->GetClonePath() ) );
151 return false;
152 }
153 }
154
155 git_clone_options cloneOptions;
156 git_clone_init_options( &cloneOptions, GIT_CLONE_OPTIONS_VERSION );
157 cloneOptions.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
158 cloneOptions.checkout_opts.progress_cb = clone_progress_cb;
159 cloneOptions.checkout_opts.progress_payload = aHandler;
160 cloneOptions.fetch_opts.callbacks.transfer_progress = transfer_progress_cb;
161 cloneOptions.fetch_opts.callbacks.credentials = credentials_cb;
162 cloneOptions.fetch_opts.callbacks.payload = aHandler;
163
164 aHandler->TestedTypes() = 0;
165 aHandler->ResetNextKey();
166 git_repository* newRepo = nullptr;
167 wxString remote = common->m_remote;
168
169 if( git_clone( &newRepo, remote.mbc_str(), aHandler->GetClonePath().mbc_str(),
170 &cloneOptions ) != 0 )
171 {
172 aHandler->AddErrorString( wxString::Format( _( "Could not clone repository '%s' : %s" ), remote, KIGIT_COMMON::GetLastGitError() ) );
173 return false;
174 }
175
176 common->SetRepo( newRepo );
177
178 return true;
179}
180
181
183 const std::vector<wxString>& aFiles,
184 const wxString& aMessage,
185 const wxString& aAuthorName,
186 const wxString& aAuthorEmail )
187{
188 git_repository* repo = aHandler->GetRepo();
189
190 if( !repo )
191 return CommitResult::Error;
192
193 git_index* index = nullptr;
194
195 if( git_repository_index( &index, repo ) != 0 )
196 {
197 aHandler->AddErrorString( wxString::Format( _( "Failed to get repository index: %s" ),
199 return CommitResult::Error;
200 }
201
202 KIGIT::GitIndexPtr indexPtr( index );
203
204 for( const wxString& file : aFiles )
205 {
206 if( git_index_add_bypath( index, file.mb_str() ) != 0 )
207 {
208 aHandler->AddErrorString( wxString::Format( _( "Failed to add file to index: %s" ),
210 return CommitResult::Error;
211 }
212 }
213
214 if( git_index_write( index ) != 0 )
215 {
216 aHandler->AddErrorString( wxString::Format( _( "Failed to write index: %s" ),
218 return CommitResult::Error;
219 }
220
221 git_oid tree_id;
222
223 if( git_index_write_tree( &tree_id, index ) != 0 )
224 {
225 aHandler->AddErrorString( wxString::Format( _( "Failed to write tree: %s" ),
227 return CommitResult::Error;
228 }
229
230 git_tree* tree = nullptr;
231
232 if( git_tree_lookup( &tree, repo, &tree_id ) != 0 )
233 {
234 aHandler->AddErrorString( wxString::Format( _( "Failed to lookup tree: %s" ),
236 return CommitResult::Error;
237 }
238
239 KIGIT::GitTreePtr treePtr( tree );
240 git_commit* parent = nullptr;
241
242 if( git_repository_head_unborn( repo ) == 0 )
243 {
244 git_reference* headRef = nullptr;
245
246 if( git_repository_head( &headRef, repo ) != 0 )
247 {
248 aHandler->AddErrorString( wxString::Format( _( "Failed to get HEAD reference: %s" ),
250 return CommitResult::Error;
251 }
252
253 KIGIT::GitReferencePtr headRefPtr( headRef );
254
255 if( git_reference_peel( (git_object**) &parent, headRef, GIT_OBJECT_COMMIT ) != 0 )
256 {
257 aHandler->AddErrorString( wxString::Format( _( "Failed to get commit: %s" ),
259 return CommitResult::Error;
260 }
261 }
262
263 KIGIT::GitCommitPtr parentPtr( parent );
264
265 git_signature* author = nullptr;
266
267 if( git_signature_now( &author, aAuthorName.mb_str(), aAuthorEmail.mb_str() ) != 0 )
268 {
269 aHandler->AddErrorString( wxString::Format( _( "Failed to create author signature: %s" ),
271 return CommitResult::Error;
272 }
273
274 KIGIT::GitSignaturePtr authorPtr( author );
275 git_oid oid;
276 size_t parentsCount = parent ? 1 : 0;
277
278#if( LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR == 8 \
279 && ( LIBGIT2_VER_REVISION < 2 || LIBGIT2_VER_REVISION == 3 ) )
280 git_commit* const parents[1] = { parent };
281 git_commit** const parentsPtr = parent ? parents : nullptr;
282#else
283 const git_commit* parents[1] = { parent };
284 const git_commit** parentsPtr = parent ? parents : nullptr;
285#endif
286
287 if( git_commit_create( &oid, repo, "HEAD", author, author, nullptr,
288 aMessage.mb_str(), tree, parentsCount, parentsPtr ) != 0 )
289 {
290 aHandler->AddErrorString( wxString::Format( _( "Failed to create commit: %s" ),
292 return CommitResult::Error;
293 }
294
296}
297
298
300{
301 KIGIT_COMMON* common = aHandler->GetCommon();
302 std::unique_lock<std::mutex> lock( common->m_gitActionMutex, std::try_to_lock );
303
304 if( !lock.owns_lock() )
305 {
306 wxLogTrace( traceGit, "GIT_PUSH_HANDLER::PerformPush: Could not lock mutex" );
307 return PushResult::Error;
308 }
309
311
312 wxString remoteName = common->GetRemoteNameOrDefault();
313 std::string remoteNameUtf8 = remoteName.utf8_string();
314 git_remote* remote = nullptr;
315
316 if( git_remote_lookup( &remote, aHandler->GetRepo(), remoteNameUtf8.c_str() ) != 0 )
317 {
318 aHandler->AddErrorString( wxString::Format( _( "Could not lookup remote '%s'" ),
319 remoteName ) );
320 return PushResult::Error;
321 }
322
323 KIGIT::GitRemotePtr remotePtr(remote);
324
325 git_remote_callbacks remoteCallbacks;
326 git_remote_init_callbacks( &remoteCallbacks, GIT_REMOTE_CALLBACKS_VERSION );
327 remoteCallbacks.sideband_progress = progress_cb;
328 remoteCallbacks.transfer_progress = transfer_progress_cb;
329 remoteCallbacks.update_tips = update_cb;
330 remoteCallbacks.push_transfer_progress = push_transfer_progress_cb;
331 remoteCallbacks.credentials = credentials_cb;
332 remoteCallbacks.payload = aHandler;
333 common->SetCancelled( false );
334
335 aHandler->TestedTypes() = 0;
336 aHandler->ResetNextKey();
337
338 if( git_remote_connect( remote, GIT_DIRECTION_PUSH, &remoteCallbacks, nullptr, nullptr ) )
339 {
340 aHandler->AddErrorString( wxString::Format( _( "Could not connect to remote: %s" ),
342 return PushResult::Error;
343 }
344
345 git_push_options pushOptions;
346 git_push_init_options( &pushOptions, GIT_PUSH_OPTIONS_VERSION );
347 pushOptions.callbacks = remoteCallbacks;
348
349 git_reference* head = nullptr;
350
351 if( git_repository_head( &head, aHandler->GetRepo() ) != 0 )
352 {
353 git_remote_disconnect( remote );
354 aHandler->AddErrorString( _( "Could not get repository head" ) );
355 return PushResult::Error;
356 }
357
358 KIGIT::GitReferencePtr headPtr( head );
359
360 const char* refs[1];
361 refs[0] = git_reference_name( head );
362 const git_strarray refspecs = { (char**) refs, 1 };
363
364 if( git_remote_push( remote, &refspecs, &pushOptions ) )
365 {
366 aHandler->AddErrorString( wxString::Format( _( "Could not push to remote: %s" ),
368 git_remote_disconnect( remote );
369 return PushResult::Error;
370 }
371
372 git_remote_disconnect( remote );
373
374 return result;
375}
376
377
379{
380 git_repository* repo = aHandler->GetRepo();
381
382 if( !repo )
383 return false;
384
385 git_status_options opts;
386 git_status_init_options( &opts, GIT_STATUS_OPTIONS_VERSION );
387
388 opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
389 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX
390 | GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
391
392 git_status_list* status_list = nullptr;
393
394 if( git_status_list_new( &status_list, repo, &opts ) != GIT_OK )
395 {
396 wxLogTrace( traceGit, "Failed to get status list: %s", KIGIT_COMMON::GetLastGitError() );
397 return false;
398 }
399
400 KIGIT::GitStatusListPtr status_list_ptr( status_list );
401 bool hasChanges = ( git_status_list_entrycount( status_list ) > 0 );
402
403 return hasChanges;
404}
405
406
407std::map<wxString, FileStatus> LIBGIT_BACKEND::GetFileStatus( GIT_STATUS_HANDLER* aHandler,
408 const wxString& aPathspec )
409{
410 std::map<wxString, FileStatus> fileStatusMap;
411 git_repository* repo = aHandler->GetRepo();
412
413 if( !repo )
414 return fileStatusMap;
415
416 git_status_options status_options;
417 git_status_init_options( &status_options, GIT_STATUS_OPTIONS_VERSION );
418 status_options.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
419 status_options.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
420
421 std::string pathspec_str;
422 std::vector<const char*> pathspec_ptrs;
423
424 if( !aPathspec.IsEmpty() )
425 {
426 pathspec_str = aPathspec.ToStdString();
427 pathspec_ptrs.push_back( pathspec_str.c_str() );
428
429 status_options.pathspec.strings = const_cast<char**>( pathspec_ptrs.data() );
430 status_options.pathspec.count = pathspec_ptrs.size();
431 }
432
433 git_status_list* status_list = nullptr;
434
435 if( git_status_list_new( &status_list, repo, &status_options ) != GIT_OK )
436 {
437 wxLogTrace( traceGit, "Failed to get git status list: %s", KIGIT_COMMON::GetLastGitError() );
438 return fileStatusMap;
439 }
440
441 KIGIT::GitStatusListPtr statusListPtr( status_list );
442
443 size_t count = git_status_list_entrycount( status_list );
444 wxString repoWorkDir = aHandler->GetProjectDir();
445
446 for( size_t ii = 0; ii < count; ++ii )
447 {
448 const git_status_entry* entry = git_status_byindex( status_list, ii );
449 std::string path( entry->head_to_index ? entry->head_to_index->old_file.path
450 : entry->index_to_workdir->old_file.path );
451
452 wxString absPath = repoWorkDir + path;
453 fileStatusMap[absPath] = FileStatus{ absPath,
454 aHandler->ConvertStatus( entry->status ),
455 static_cast<unsigned int>( entry->status ) };
456 }
457
458 return fileStatusMap;
459}
460
461
463{
464 git_repository* repo = aHandler->GetRepo();
465
466 if( !repo )
467 return wxEmptyString;
468
469 git_reference* currentBranchReference = nullptr;
470 int rc = git_repository_head( &currentBranchReference, repo );
471 KIGIT::GitReferencePtr currentBranchReferencePtr( currentBranchReference );
472
473 if( currentBranchReference )
474 {
475 return git_reference_shorthand( currentBranchReference );
476 }
477 else if( rc == GIT_EUNBORNBRANCH )
478 {
479 return wxEmptyString;
480 }
481 else
482 {
483 wxLogTrace( traceGit, "Failed to lookup current branch: %s", KIGIT_COMMON::GetLastGitError() );
484 return wxEmptyString;
485 }
486}
487
488
490 const std::set<wxString>& aLocalChanges,
491 const std::set<wxString>& aRemoteChanges,
492 std::map<wxString, FileStatus>& aFileStatus )
493{
494 git_repository* repo = aHandler->GetRepo();
495
496 if( !repo )
497 return;
498
499 wxString repoWorkDir = aHandler->GetProjectDir();
500
501 for( auto& [absPath, fileStatus] : aFileStatus )
502 {
503 wxString relativePath = absPath;
504 if( relativePath.StartsWith( repoWorkDir ) )
505 {
506 relativePath = relativePath.Mid( repoWorkDir.length() );
507
508#ifdef _WIN32
509 relativePath.Replace( wxS( "\\" ), wxS( "/" ) );
510#endif
511 }
512
513 std::string relativePathStd = relativePath.ToStdString();
514
515 if( fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CURRENT )
516 {
517 if( aLocalChanges.count( relativePathStd ) )
518 {
520 }
521 else if( aRemoteChanges.count( relativePathStd ) )
522 {
524 }
525 }
526 }
527}
528
529
531{
532 return aHandler->GetProjectDir();
533}
534
535
537{
538 return aHandler->GetProjectDir();
539}
540
541
542bool LIBGIT_BACKEND::GetConfigString( GIT_CONFIG_HANDLER* aHandler, const wxString& aKey, wxString& aValue )
543{
544 git_repository* repo = aHandler->GetRepo();
545
546 if( !repo )
547 return false;
548
549 git_config* config = nullptr;
550
551 if( git_repository_config( &config, repo ) != GIT_OK )
552 {
553 wxLogTrace( traceGit, "Failed to get repository config: %s", KIGIT_COMMON::GetLastGitError() );
554 return false;
555 }
556
557 KIGIT::GitConfigPtr configPtr( config );
558
559 git_config_entry* entry = nullptr;
560 int result = git_config_get_entry( &entry, config, aKey.mb_str() );
561 KIGIT::GitConfigEntryPtr entryPtr( entry );
562
563 if( result != GIT_OK || entry == nullptr )
564 {
565 wxLogTrace( traceGit, "Config key '%s' not found", aKey );
566 return false;
567 }
568
569 aValue = wxString( entry->value );
570 return true;
571}
572
573
574bool LIBGIT_BACKEND::IsRepository( GIT_INIT_HANDLER* aHandler, const wxString& aPath )
575{
576 git_repository* repo = nullptr;
577 int error = git_repository_open( &repo, aPath.mb_str() );
578
579 if( error == 0 )
580 {
581 git_repository_free( repo );
582 return true;
583 }
584
585 return false;
586}
587
588
590{
591 if( IsRepository( aHandler, aPath ) )
592 {
594 }
595
596 git_repository* repo = nullptr;
597
598 if( git_repository_init( &repo, aPath.mb_str(), 0 ) != GIT_OK )
599 {
600 if( repo )
601 git_repository_free( repo );
602
603 aHandler->AddErrorString( wxString::Format( _( "Failed to initialize Git repository: %s" ),
605 return InitResult::Error;
606 }
607
608 aHandler->GetCommon()->SetRepo( repo );
609
610 wxLogTrace( traceGit, "Successfully initialized Git repository at %s", aPath );
611 return InitResult::Success;
612}
613
614
616{
617 if( aConfig.url.IsEmpty() )
618 return true;
619
620 git_repository* repo = aHandler->GetRepo();
621
622 if( !repo )
623 {
624 aHandler->AddErrorString( _( "No repository available to set up remote" ) );
625 return false;
626 }
627
628 aHandler->GetCommon()->SetUsername( aConfig.username );
629 aHandler->GetCommon()->SetPassword( aConfig.password );
630 aHandler->GetCommon()->SetSSHKey( aConfig.sshKey );
631
632 git_remote* remote = nullptr;
633 wxString fullURL;
634
636 {
637 fullURL = aConfig.username + "@" + aConfig.url;
638 }
640 {
641 fullURL = aConfig.url.StartsWith( "https" ) ? "https://" : "http://";
642
643 if( !aConfig.username.empty() )
644 {
645 fullURL.append( aConfig.username );
646
647 if( !aConfig.password.empty() )
648 {
649 fullURL.append( wxS( ":" ) );
650 fullURL.append( aConfig.password );
651 }
652
653 fullURL.append( wxS( "@" ) );
654 }
655
656 wxString bareURL = aConfig.url;
657
658 if( bareURL.StartsWith( "https://" ) )
659 bareURL = bareURL.Mid( 8 );
660 else if( bareURL.StartsWith( "http://" ) )
661 bareURL = bareURL.Mid( 7 );
662
663 fullURL.append( bareURL );
664 }
665 else
666 {
667 fullURL = aConfig.url;
668 }
669
670 int error;
671
672 if( git_remote_lookup( &remote, repo, "origin" ) == GIT_OK )
673 {
674 KIGIT::GitRemotePtr remotePtr( remote );
675 error = git_remote_set_url( repo, "origin", fullURL.ToStdString().c_str() );
676 }
677 else
678 {
679 error = git_remote_create_with_fetchspec( &remote, repo, "origin", fullURL.ToStdString().c_str(),
680 "+refs/heads/*:refs/remotes/origin/*" );
681 KIGIT::GitRemotePtr remotePtr( remote );
682 }
683
684 if( error != GIT_OK )
685 {
686 aHandler->AddErrorString(
687 wxString::Format( _( "Failed to set up remote: %s" ), KIGIT_COMMON::GetLastGitError() ) );
688 return false;
689 }
690
691 wxLogTrace( traceGit, "Successfully set up remote origin" );
692 return true;
693}
694
695
696static bool lookup_branch_reference( git_repository* repo, const wxString& aBranchName,
697 git_reference** aReference )
698{
699 if( git_reference_lookup( aReference, repo, aBranchName.mb_str() ) == GIT_OK )
700 return true;
701
702 if( git_reference_dwim( aReference, repo, aBranchName.mb_str() ) == GIT_OK )
703 return true;
704
705 return false;
706}
707
708
709BranchResult LIBGIT_BACKEND::SwitchToBranch( GIT_BRANCH_HANDLER* aHandler, const wxString& aBranchName )
710{
711 git_repository* repo = aHandler->GetRepo();
712
713 if( !repo )
714 {
715 aHandler->AddErrorString( _( "No repository available" ) );
716 return BranchResult::Error;
717 }
718
719 git_reference* branchRef = nullptr;
720
721 if( !lookup_branch_reference( repo, aBranchName, &branchRef ) )
722 {
723 aHandler->AddErrorString( wxString::Format( _( "Failed to lookup branch '%s': %s" ),
724 aBranchName, KIGIT_COMMON::GetLastGitError() ) );
726 }
727
728 KIGIT::GitReferencePtr branchRefPtr( branchRef );
729 const char* branchRefName = git_reference_name( branchRef );
730 git_object* branchObj = nullptr;
731
732 if( git_revparse_single( &branchObj, repo, aBranchName.mb_str() ) != GIT_OK )
733 {
734 aHandler->AddErrorString( wxString::Format( _( "Failed to find branch head for '%s': %s" ),
735 aBranchName, KIGIT_COMMON::GetLastGitError() ) );
736 return BranchResult::Error;
737 }
738
739 KIGIT::GitObjectPtr branchObjPtr( branchObj );
740
741 if( git_checkout_tree( repo, branchObj, nullptr ) != GIT_OK )
742 {
743 aHandler->AddErrorString( wxString::Format( _( "Failed to switch to branch '%s': %s" ),
744 aBranchName, KIGIT_COMMON::GetLastGitError() ) );
746 }
747
748 if( git_repository_set_head( repo, branchRefName ) != GIT_OK )
749 {
750 aHandler->AddErrorString( wxString::Format( _( "Failed to update HEAD reference for branch '%s': %s" ),
751 aBranchName, KIGIT_COMMON::GetLastGitError() ) );
752 return BranchResult::Error;
753 }
754
755 wxLogTrace( traceGit, "Successfully switched to branch '%s'", aBranchName );
757}
758
759
760bool LIBGIT_BACKEND::BranchExists( GIT_BRANCH_HANDLER* aHandler, const wxString& aBranchName )
761{
762 git_repository* repo = aHandler->GetRepo();
763
764 if( !repo )
765 return false;
766
767 git_reference* branchRef = nullptr;
768 bool exists = lookup_branch_reference( repo, aBranchName, &branchRef );
769
770 if( branchRef )
771 git_reference_free( branchRef );
772
773 return exists;
774}
775
776
777// Use callbacks declared/implemented in kicad_git_common.h/.cpp
778
779bool LIBGIT_BACKEND::PerformFetch( GIT_PULL_HANDLER* aHandler, bool aSkipLock )
780{
781 if( !aHandler->GetRepo() )
782 {
783 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - No repository found" );
784 return false;
785 }
786
787 std::unique_lock<std::mutex> lock( aHandler->GetCommon()->m_gitActionMutex, std::try_to_lock );
788
789 if( !aSkipLock && !lock.owns_lock() )
790 {
791 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Could not lock mutex" );
792 return false;
793 }
794
795 wxString remoteName = aHandler->GetCommon()->GetRemoteNameOrDefault();
796 std::string remoteNameUtf8 = remoteName.utf8_string();
797 git_remote* remote = nullptr;
798
799 if( git_remote_lookup( &remote, aHandler->GetRepo(), remoteNameUtf8.c_str() ) != 0 )
800 {
801 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Failed to lookup remote '%s'",
802 remoteName );
803 aHandler->AddErrorString( wxString::Format( _( "Could not lookup remote '%s'" ),
804 remoteName ) );
805 return false;
806 }
807
808 KIGIT::GitRemotePtr remotePtr( remote );
809
810 git_remote_callbacks remoteCallbacks;
811 git_remote_init_callbacks( &remoteCallbacks, GIT_REMOTE_CALLBACKS_VERSION );
812 remoteCallbacks.sideband_progress = progress_cb;
813 remoteCallbacks.transfer_progress = transfer_progress_cb;
814 remoteCallbacks.credentials = credentials_cb;
815 remoteCallbacks.payload = aHandler;
816 aHandler->GetCommon()->SetCancelled( false );
817
818 aHandler->TestedTypes() = 0;
819 aHandler->ResetNextKey();
820
821 if( git_remote_connect( remote, GIT_DIRECTION_FETCH, &remoteCallbacks, nullptr, nullptr ) )
822 {
823 wxString errorMsg = KIGIT_COMMON::GetLastGitError();
824 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Failed to connect to remote: %s", errorMsg );
825 aHandler->AddErrorString( wxString::Format( _( "Could not connect to remote '%s': %s" ),
826 remoteName, errorMsg ) );
827 return false;
828 }
829
830 git_fetch_options fetchOptions;
831 git_fetch_init_options( &fetchOptions, GIT_FETCH_OPTIONS_VERSION );
832 fetchOptions.callbacks = remoteCallbacks;
833
834 if( git_remote_fetch( remote, nullptr, &fetchOptions, nullptr ) )
835 {
836 wxString errorMsg = KIGIT_COMMON::GetLastGitError();
837 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Failed to fetch from remote: %s", errorMsg );
838 aHandler->AddErrorString( wxString::Format( _( "Could not fetch data from remote '%s': %s" ),
839 remoteName, errorMsg ) );
840 return false;
841 }
842
843 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Fetch completed successfully" );
844 return true;
845}
846
847
849{
851 std::unique_lock<std::mutex> lock( aHandler->GetCommon()->m_gitActionMutex, std::try_to_lock );
852
853 if( !lock.owns_lock() )
854 {
855 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Could not lock mutex" );
856 return PullResult::Error;
857 }
858
859 if( !PerformFetch( aHandler, true ) )
860 return PullResult::Error;
861
862 git_oid pull_merge_oid = {};
863
864 if( git_repository_fetchhead_foreach( aHandler->GetRepo(), fetchhead_foreach_cb, &pull_merge_oid ) )
865 {
866 aHandler->AddErrorString( _( "Could not read 'FETCH_HEAD'" ) );
867 return PullResult::Error;
868 }
869
870 git_annotated_commit* fetchhead_commit;
871
872 if( git_annotated_commit_lookup( &fetchhead_commit, aHandler->GetRepo(), &pull_merge_oid ) )
873 {
874 aHandler->AddErrorString( _( "Could not lookup commit" ) );
875 return PullResult::Error;
876 }
877
878 KIGIT::GitAnnotatedCommitPtr fetchheadCommitPtr( fetchhead_commit );
879 const git_annotated_commit* merge_commits[] = { fetchhead_commit };
880 git_merge_analysis_t merge_analysis;
881 git_merge_preference_t merge_preference = GIT_MERGE_PREFERENCE_NONE;
882
883 if( git_merge_analysis( &merge_analysis, &merge_preference, aHandler->GetRepo(), merge_commits, 1 ) )
884 {
885 aHandler->AddErrorString( _( "Could not analyze merge" ) );
886 return PullResult::Error;
887 }
888
889 if( merge_analysis & GIT_MERGE_ANALYSIS_UNBORN )
890 {
891 aHandler->AddErrorString( _( "Invalid HEAD. Cannot merge." ) );
893 }
894
895 if( merge_analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE )
896 {
897 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Repository is up to date" );
898 git_repository_state_cleanup( aHandler->GetRepo() );
900 }
901
902 if( merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD )
903 {
904 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Fast-forward merge" );
905 return handleFastForward( aHandler );
906 }
907
908 if( merge_analysis & GIT_MERGE_ANALYSIS_NORMAL )
909 {
910 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Normal merge" );
911
912 git_config* config = nullptr;
913
914 if( git_repository_config( &config, aHandler->GetRepo() ) != GIT_OK )
915 {
916 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Failed to get repository config" );
917 aHandler->AddErrorString( _( "Could not access repository configuration" ) );
918 return PullResult::Error;
919 }
920
921 KIGIT::GitConfigPtr configPtr( config );
922
923 int rebase_value = 0;
924 int ret = git_config_get_bool( &rebase_value, config, "pull.rebase" );
925
926 if( ret == GIT_OK && rebase_value )
927 {
928 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Using rebase based on config" );
929 return handleRebase( aHandler, merge_commits, 1 );
930 }
931
932 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Using merge based on config" );
933 return handleMerge( aHandler, merge_commits, 1 );
934 }
935
936 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Merge needs resolution" );
937 return result;
938}
939
940
942{
943 git_reference* rawRef = nullptr;
944
945 if( git_repository_head( &rawRef, aHandler->GetRepo() ) )
946 {
947 aHandler->AddErrorString( _( "Could not get repository head" ) );
948 return PullResult::Error;
949 }
950
951 KIGIT::GitReferencePtr headRef( rawRef );
952
953 git_oid updatedRefOid;
954 const char* currentBranchName = git_reference_name( rawRef );
955 const char* branch_shorthand = git_reference_shorthand( rawRef );
956 wxString remote_name = aHandler->GetRemotename();
957 wxString remoteBranchName = wxString::Format( "refs/remotes/%s/%s", remote_name, branch_shorthand );
958
959 if( git_reference_name_to_id( &updatedRefOid, aHandler->GetRepo(), remoteBranchName.c_str() ) != GIT_OK )
960 {
961 aHandler->AddErrorString( wxString::Format( _( "Could not get reference OID for reference '%s'" ),
962 remoteBranchName ) );
963 return PullResult::Error;
964 }
965
966 git_commit* targetCommit = nullptr;
967
968 if( git_commit_lookup( &targetCommit, aHandler->GetRepo(), &updatedRefOid ) != GIT_OK )
969 {
970 aHandler->AddErrorString( _( "Could not look up target commit" ) );
971 return PullResult::Error;
972 }
973
974 KIGIT::GitCommitPtr targetCommitPtr( targetCommit );
975
976 git_tree* targetTree = nullptr;
977
978 if( git_commit_tree( &targetTree, targetCommit ) != GIT_OK )
979 {
980 git_commit_free( targetCommit );
981 aHandler->AddErrorString( _( "Could not get tree from target commit" ) );
982 return PullResult::Error;
983 }
984
985 KIGIT::GitTreePtr targetTreePtr( targetTree );
986
987 git_checkout_options checkoutOptions;
988 git_checkout_init_options( &checkoutOptions, GIT_CHECKOUT_OPTIONS_VERSION );
989 auto notify_cb = []( git_checkout_notify_t why, const char* path, const git_diff_file* baseline,
990 const git_diff_file* target, const git_diff_file* workdir, void* payload ) -> int
991 {
992 switch( why )
993 {
994 case GIT_CHECKOUT_NOTIFY_CONFLICT:
995 wxLogTrace( traceGit, "Checkout conflict: %s", path ? path : "unknown" );
996 break;
997 case GIT_CHECKOUT_NOTIFY_DIRTY:
998 wxLogTrace( traceGit, "Checkout dirty: %s", path ? path : "unknown" );
999 break;
1000 case GIT_CHECKOUT_NOTIFY_UPDATED:
1001 wxLogTrace( traceGit, "Checkout updated: %s", path ? path : "unknown" );
1002 break;
1003 case GIT_CHECKOUT_NOTIFY_UNTRACKED:
1004 wxLogTrace( traceGit, "Checkout untracked: %s", path ? path : "unknown" );
1005 break;
1006 case GIT_CHECKOUT_NOTIFY_IGNORED:
1007 wxLogTrace( traceGit, "Checkout ignored: %s", path ? path : "unknown" );
1008 break;
1009 default:
1010 break;
1011 }
1012
1013 return 0;
1014 };
1015
1016 checkoutOptions.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS;
1017 checkoutOptions.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
1018 checkoutOptions.notify_cb = notify_cb;
1019
1020 if( git_checkout_tree( aHandler->GetRepo(), reinterpret_cast<git_object*>( targetTree ),
1021 &checkoutOptions ) != GIT_OK )
1022 {
1023 aHandler->AddErrorString( _( "Failed to perform checkout operation." ) );
1024 return PullResult::Error;
1025 }
1026
1027 git_reference* updatedRef = nullptr;
1028
1029 if( git_reference_set_target( &updatedRef, rawRef, &updatedRefOid, nullptr ) != GIT_OK )
1030 {
1031 aHandler->AddErrorString( wxString::Format( _( "Failed to update reference '%s' to point to '%s'" ),
1032 currentBranchName, git_oid_tostr_s( &updatedRefOid ) ) );
1033 return PullResult::Error;
1034 }
1035
1036 KIGIT::GitReferencePtr updatedRefPtr( updatedRef );
1037
1038 if( git_repository_state_cleanup( aHandler->GetRepo() ) != GIT_OK )
1039 {
1040 aHandler->AddErrorString( _( "Failed to clean up repository state after fast-forward." ) );
1041 return PullResult::Error;
1042 }
1043
1044 git_revwalk* revWalker = nullptr;
1045
1046 if( git_revwalk_new( &revWalker, aHandler->GetRepo() ) != GIT_OK )
1047 {
1048 aHandler->AddErrorString( _( "Failed to initialize revision walker." ) );
1049 return PullResult::Error;
1050 }
1051
1052 KIGIT::GitRevWalkPtr revWalkerPtr( revWalker );
1053 git_revwalk_sorting( revWalker, GIT_SORT_TIME );
1054
1055 if( git_revwalk_push_glob( revWalker, currentBranchName ) != GIT_OK )
1056 {
1057 aHandler->AddErrorString( _( "Failed to push reference to revision walker." ) );
1058 return PullResult::Error;
1059 }
1060
1061 std::pair<std::string, std::vector<CommitDetails>>& branchCommits = aHandler->m_fetchResults.emplace_back();
1062 branchCommits.first = currentBranchName;
1063
1064 git_oid commitOid;
1065
1066 while( git_revwalk_next( &commitOid, revWalker ) == GIT_OK )
1067 {
1068 git_commit* commit = nullptr;
1069
1070 if( git_commit_lookup( &commit, aHandler->GetRepo(), &commitOid ) )
1071 {
1072 aHandler->AddErrorString( wxString::Format( _( "Could not lookup commit '%s'" ),
1073 git_oid_tostr_s( &commitOid ) ) );
1074 return PullResult::Error;
1075 }
1076
1077 KIGIT::GitCommitPtr commitPtr( commit );
1078
1079 CommitDetails details;
1080 details.m_sha = git_oid_tostr_s( &commitOid );
1081 details.m_firstLine = getFirstLineFromCommitMessage( git_commit_message( commit ) );
1082 details.m_author = git_commit_author( commit )->name;
1083 details.m_date = getFormattedCommitDate( git_commit_author( commit )->when );
1084
1085 branchCommits.second.push_back( details );
1086 }
1087
1089}
1090
1091
1092bool LIBGIT_BACKEND::hasUnstagedChanges( git_repository* aRepo )
1093{
1094 if( !aRepo )
1095 return false;
1096
1097 git_status_options opts;
1098 git_status_init_options( &opts, GIT_STATUS_OPTIONS_VERSION );
1099
1100 // Only check workdir changes (unstaged), not index changes (staged)
1101 opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
1102 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED;
1103
1104 git_status_list* status_list = nullptr;
1105
1106 if( git_status_list_new( &status_list, aRepo, &opts ) != GIT_OK )
1107 {
1108 wxLogTrace( traceGit, "Failed to get status list: %s", KIGIT_COMMON::GetLastGitError() );
1109 return false;
1110 }
1111
1112 KIGIT::GitStatusListPtr status_list_ptr( status_list );
1113 size_t count = git_status_list_entrycount( status_list );
1114
1115 // Check if any of the entries are actual modifications (not just untracked files)
1116 for( size_t ii = 0; ii < count; ++ii )
1117 {
1118 const git_status_entry* entry = git_status_byindex( status_list, ii );
1119
1120 // Check for actual workdir modifications, not just untracked files
1121 if( entry->status & ( GIT_STATUS_WT_MODIFIED | GIT_STATUS_WT_DELETED | GIT_STATUS_WT_TYPECHANGE ) )
1122 {
1123 return true;
1124 }
1125 }
1126
1127 return false;
1128}
1129
1130
1132 const git_annotated_commit** aMergeHeads,
1133 size_t aMergeHeadsCount )
1134{
1135 // Check for unstaged changes before attempting merge
1136 if( hasUnstagedChanges( aHandler->GetRepo() ) )
1137 {
1138 aHandler->AddErrorString(
1139 _( "Cannot merge: you have unstaged changes. "
1140 "Please commit or stash them before pulling." ) );
1142 }
1143
1144 if( git_merge( aHandler->GetRepo(), aMergeHeads, aMergeHeadsCount, nullptr, nullptr ) )
1145 {
1146 wxString errorMsg = KIGIT_COMMON::GetLastGitError();
1147 aHandler->AddErrorString( wxString::Format( _( "Merge failed: %s" ), errorMsg ) );
1149 }
1150
1151 return PullResult::Success;
1152}
1153
1154
1156 const git_annotated_commit** aMergeHeads,
1157 size_t aMergeHeadsCount )
1158{
1159 // Check for unstaged changes before attempting rebase
1160 if( hasUnstagedChanges( aHandler->GetRepo() ) )
1161 {
1162 aHandler->AddErrorString(
1163 _( "Cannot pull with rebase: you have unstaged changes. "
1164 "Please commit or stash them before pulling." ) );
1166 }
1167
1168 git_rebase_options rebase_opts;
1169 git_rebase_init_options( &rebase_opts, GIT_REBASE_OPTIONS_VERSION );
1170
1171 git_rebase* rebase = nullptr;
1172
1173 if( git_rebase_init( &rebase, aHandler->GetRepo(), nullptr, aMergeHeads[0], nullptr, &rebase_opts ) )
1174 {
1175 wxString errorMsg = KIGIT_COMMON::GetLastGitError();
1176 aHandler->AddErrorString( wxString::Format( _( "Rebase failed to start: %s" ), errorMsg ) );
1178 }
1179
1180 KIGIT::GitRebasePtr rebasePtr( rebase );
1181
1182 while( true )
1183 {
1184 git_rebase_operation* op = nullptr;
1185
1186 if( git_rebase_next( &op, rebase ) != 0 )
1187 break;
1188
1189 if( git_rebase_commit( nullptr, rebase, nullptr, nullptr, nullptr, nullptr ) )
1190 {
1191 wxString errorMsg = KIGIT_COMMON::GetLastGitError();
1192 aHandler->AddErrorString( wxString::Format( _( "Rebase commit failed: %s" ), errorMsg ) );
1194 }
1195 }
1196
1197 if( git_rebase_finish( rebase, nullptr ) )
1198 {
1199 wxString errorMsg = KIGIT_COMMON::GetLastGitError();
1200 aHandler->AddErrorString( wxString::Format( _( "Rebase finish failed: %s" ), errorMsg ) );
1202 }
1203
1204 return PullResult::Success;
1205}
1206
1207
1209{
1210 git_object* head_commit = NULL;
1211 git_checkout_options opts;
1212 git_checkout_init_options( &opts, GIT_CHECKOUT_OPTIONS_VERSION );
1213
1214 if( git_revparse_single( &head_commit, aHandler->m_repository, "HEAD" ) != 0 )
1215 {
1216 return;
1217 }
1218
1219 opts.checkout_strategy = GIT_CHECKOUT_FORCE;
1220 char** paths = new char*[aHandler->m_filesToRevert.size()];
1221
1222 for( size_t ii = 0; ii < aHandler->m_filesToRevert.size(); ii++ )
1223 {
1224 paths[ii] = wxStrdup( aHandler->m_filesToRevert[ii].ToUTF8() );
1225 }
1226
1227 git_strarray arr = { paths, aHandler->m_filesToRevert.size() };
1228
1229 opts.paths = arr;
1230 opts.progress_cb = nullptr;
1231 opts.notify_cb = nullptr;
1232 opts.notify_payload = static_cast<void*>( aHandler );
1233
1234 if( git_checkout_tree( aHandler->m_repository, head_commit, &opts ) != 0 )
1235 {
1236 const git_error* e = git_error_last();
1237
1238 if( e )
1239 {
1240 wxLogTrace( traceGit, wxS( "Checkout failed: %d: %s" ), e->klass, e->message );
1241 }
1242 }
1243
1244 for( size_t ii = 0; ii < aHandler->m_filesToRevert.size(); ii++ )
1245 delete( paths[ii] );
1246
1247 delete[] paths;
1248
1249 git_object_free( head_commit );
1250}
1251
1252
1253git_repository* LIBGIT_BACKEND::GetRepositoryForFile( const char* aFilename )
1254{
1255 git_repository* repo = nullptr;
1256 git_buf repo_path = GIT_BUF_INIT;
1257
1258 if( git_repository_discover( &repo_path, aFilename, 0, nullptr ) != GIT_OK )
1259 {
1260 wxLogTrace( traceGit, "Can't repo discover %s: %s", aFilename,
1262 return nullptr;
1263 }
1264
1265 KIGIT::GitBufPtr repo_path_ptr( &repo_path );
1266
1267 if( git_repository_open( &repo, repo_path.ptr ) != GIT_OK )
1268 {
1269 wxLogTrace( traceGit, "Can't open repo for %s: %s", repo_path.ptr,
1271 return nullptr;
1272 }
1273
1274 return repo;
1275}
1276
1277
1278int LIBGIT_BACKEND::CreateBranch( git_repository* aRepo, const wxString& aBranchName )
1279{
1280 git_oid head_oid;
1281
1282 if( int error = git_reference_name_to_id( &head_oid, aRepo, "HEAD" ); error != GIT_OK )
1283 {
1284 wxLogTrace( traceGit, "Failed to lookup HEAD reference: %s",
1286 return error;
1287 }
1288
1289 git_commit* commit = nullptr;
1290
1291 if( int error = git_commit_lookup( &commit, aRepo, &head_oid ); error != GIT_OK )
1292 {
1293 wxLogTrace( traceGit, "Failed to lookup commit: %s",
1295 return error;
1296 }
1297
1298 KIGIT::GitCommitPtr commitPtr( commit );
1299 git_reference* branchRef = nullptr;
1300
1301 if( int error = git_branch_create( &branchRef, aRepo, aBranchName.mb_str(), commit, 0 ); error != GIT_OK )
1302 {
1303 wxLogTrace( traceGit, "Failed to create branch: %s",
1305 return error;
1306 }
1307
1308 git_reference_free( branchRef );
1309 return 0;
1310}
1311
1312
1313bool LIBGIT_BACKEND::RemoveVCS( git_repository*& aRepo, const wxString& aProjectPath,
1314 bool aRemoveGitDir, wxString* aErrors )
1315{
1316 if( aRepo )
1317 {
1318 git_repository_free( aRepo );
1319 aRepo = nullptr;
1320 }
1321
1322 if( aRemoveGitDir )
1323 {
1324 wxFileName gitDir( aProjectPath, wxEmptyString );
1325 gitDir.AppendDir( ".git" );
1326
1327 if( gitDir.DirExists() )
1328 {
1329 wxString errors;
1330
1331 if( !RmDirRecursive( gitDir.GetPath(), &errors ) )
1332 {
1333 if( aErrors )
1334 *aErrors = errors;
1335
1336 wxLogTrace( traceGit, "Failed to remove .git directory: %s", errors );
1337 return false;
1338 }
1339 }
1340 }
1341
1342 wxLogTrace( traceGit, "Successfully removed VCS from project" );
1343 return true;
1344}
1345
1346
1347bool LIBGIT_BACKEND::AddToIndex( GIT_ADD_TO_INDEX_HANDLER* aHandler, const wxString& aFilePath )
1348{
1349 git_repository* repo = aHandler->GetRepo();
1350
1351 git_index* index = nullptr;
1352 size_t at_pos = 0;
1353
1354 if( git_repository_index( &index, repo ) != 0 )
1355 {
1356 wxLogError( "Failed to get repository index" );
1357 return false;
1358 }
1359
1360 KIGIT::GitIndexPtr indexPtr( index );
1361
1362 if( git_index_find( &at_pos, index, aFilePath.ToUTF8().data() ) == GIT_OK )
1363 {
1364 wxLogError( "%s already in index", aFilePath );
1365 return false;
1366 }
1367
1368 aHandler->m_filesToAdd.push_back( aFilePath );
1369 return true;
1370}
1371
1372
1374{
1375 git_repository* repo = aHandler->GetRepo();
1376 git_index* index = nullptr;
1377
1378 aHandler->m_filesFailedToAdd.clear();
1379
1380 if( git_repository_index( &index, repo ) != 0 )
1381 {
1382 wxLogError( "Failed to get repository index" );
1383 std::copy( aHandler->m_filesToAdd.begin(), aHandler->m_filesToAdd.end(),
1384 std::back_inserter( aHandler->m_filesFailedToAdd ) );
1385 return false;
1386 }
1387
1388 KIGIT::GitIndexPtr indexPtr( index );
1389
1390 for( auto& file : aHandler->m_filesToAdd )
1391 {
1392 if( git_index_add_bypath( index, file.ToUTF8().data() ) != 0 )
1393 {
1394 wxLogError( "Failed to add %s to index", file );
1395 aHandler->m_filesFailedToAdd.push_back( file );
1396 continue;
1397 }
1398 }
1399
1400 if( git_index_write( index ) != 0 )
1401 {
1402 wxLogError( "Failed to write index" );
1403 aHandler->m_filesFailedToAdd.clear();
1404 std::copy( aHandler->m_filesToAdd.begin(), aHandler->m_filesToAdd.end(),
1405 std::back_inserter( aHandler->m_filesFailedToAdd ) );
1406 return false;
1407 }
1408
1409 return true;
1410}
1411
1412
1413bool LIBGIT_BACKEND::RemoveFromIndex( GIT_REMOVE_FROM_INDEX_HANDLER* aHandler, const wxString& aFilePath )
1414{
1415 git_repository* repo = aHandler->GetRepo();
1416 git_index* index = nullptr;
1417 size_t at_pos = 0;
1418
1419 if( git_repository_index( &index, repo ) != 0 )
1420 {
1421 wxLogError( "Failed to get repository index" );
1422 return false;
1423 }
1424
1425 KIGIT::GitIndexPtr indexPtr( index );
1426
1427 if( git_index_find( &at_pos, index, aFilePath.ToUTF8().data() ) != 0 )
1428 {
1429 wxLogError( "Failed to find index entry for %s", aFilePath );
1430 return false;
1431 }
1432
1433 aHandler->m_filesToRemove.push_back( aFilePath );
1434 return true;
1435}
1436
1437
1439{
1440 git_repository* repo = aHandler->GetRepo();
1441
1442 for( auto& file : aHandler->m_filesToRemove )
1443 {
1444 git_index* index = nullptr;
1445 git_oid oid;
1446
1447 if( git_repository_index( &index, repo ) != 0 )
1448 {
1449 wxLogError( "Failed to get repository index" );
1450 return;
1451 }
1452
1453 KIGIT::GitIndexPtr indexPtr( index );
1454
1455 if( git_index_remove_bypath( index, file.ToUTF8().data() ) != 0 )
1456 {
1457 wxLogError( "Failed to remove index entry for %s", file );
1458 return;
1459 }
1460
1461 if( git_index_write( index ) != 0 )
1462 {
1463 wxLogError( "Failed to write index" );
1464 return;
1465 }
1466
1467 if( git_index_write_tree( &oid, index ) != 0 )
1468 {
1469 wxLogError( "Failed to write index tree" );
1470 return;
1471 }
1472 }
1473}
int index
std::vector< wxString > m_filesToAdd
std::vector< wxString > m_filesFailedToAdd
KIGIT_ORPHAN_REGISTRY m_orphanRegistry
wxString GetClonePath() const
void AddErrorString(const wxString &aErrorString)
std::vector< std::pair< std::string, std::vector< CommitDetails > > > m_fetchResults
std::vector< wxString > m_filesToRevert
git_repository * m_repository
KIGIT_COMMON::GIT_STATUS ConvertStatus(unsigned int aGitStatus)
Convert git status flags to KIGIT_COMMON::GIT_STATUS.
std::mutex m_gitActionMutex
static wxString GetLastGitError()
void SetSSHKey(const wxString &aSSHKey)
void SetUsername(const wxString &aUsername)
wxString GetRemoteNameOrDefault() const
Returns GetRemotename() when non-empty, otherwise "origin".
git_repository * GetRepo() const
void SetCancelled(bool aCancel)
void SetPassword(const wxString &aPassword)
void SetRepo(git_repository *aRepo)
void AddErrorString(const wxString aErrorString)
wxString GetProjectDir() const
Get the project directory path, preserving symlinks if set.
git_repository * GetRepo() const
Get a pointer to the git repository.
unsigned & TestedTypes()
Return the connection types that have been tested for authentication.
KIGIT_COMMON * GetCommon() const
Get the common object.
wxString GetRemotename() const
Get the remote name.
void ResetNextKey()
Reset the next public key to test.
bool PerformAddToIndex(GIT_ADD_TO_INDEX_HANDLER *aHandler) override
void PerformRevert(GIT_REVERT_HANDLER *aHandler) override
PullResult handleMerge(GIT_PULL_HANDLER *aHandler, const git_annotated_commit **aMergeHeads, size_t aMergeHeadsCount)
bool PerformFetch(GIT_PULL_HANDLER *aHandler, bool aSkipLock) override
static bool hasUnstagedChanges(git_repository *aRepo)
bool Clone(GIT_CLONE_HANDLER *aHandler) override
PullResult handleRebase(GIT_PULL_HANDLER *aHandler, const git_annotated_commit **aMergeHeads, size_t aMergeHeadsCount)
void PerformRemoveFromIndex(GIT_REMOVE_FROM_INDEX_HANDLER *aHandler) override
bool RemoveVCS(git_repository *&aRepo, const wxString &aProjectPath, bool aRemoveGitDir, wxString *aErrors) override
bool RemoveFromIndex(GIT_REMOVE_FROM_INDEX_HANDLER *aHandler, const wxString &aFilePath) override
BranchResult SwitchToBranch(GIT_BRANCH_HANDLER *aHandler, const wxString &aBranchName) override
PullResult handleFastForward(GIT_PULL_HANDLER *aHandler)
CommitResult Commit(GIT_COMMIT_HANDLER *aHandler, const std::vector< wxString > &aFiles, const wxString &aMessage, const wxString &aAuthorName, const wxString &aAuthorEmail) override
std::map< wxString, FileStatus > GetFileStatus(GIT_STATUS_HANDLER *aHandler, const wxString &aPathspec) override
bool HasChangedFiles(GIT_STATUS_HANDLER *aHandler) override
void Init() override
void UpdateRemoteStatus(GIT_STATUS_HANDLER *aHandler, const std::set< wxString > &aLocalChanges, const std::set< wxString > &aRemoteChanges, std::map< wxString, FileStatus > &aFileStatus) override
wxString GetCurrentBranchName(GIT_STATUS_HANDLER *aHandler) override
PushResult Push(GIT_PUSH_HANDLER *aHandler) override
bool GetConfigString(GIT_CONFIG_HANDLER *aHandler, const wxString &aKey, wxString &aValue) override
git_repository * GetRepositoryForFile(const char *aFilename) override
bool AddToIndex(GIT_ADD_TO_INDEX_HANDLER *aHandler, const wxString &aFilePath) override
bool SetupRemote(GIT_INIT_HANDLER *aHandler, const RemoteConfig &aConfig) override
bool IsRepository(GIT_INIT_HANDLER *aHandler, const wxString &aPath) override
bool IsLibraryAvailable() override
void Shutdown() override
wxString GetWorkingDirectory(GIT_STATUS_HANDLER *aHandler) override
bool BranchExists(GIT_BRANCH_HANDLER *aHandler, const wxString &aBranchName) override
InitResult InitializeRepository(GIT_INIT_HANDLER *aHandler, const wxString &aPath) override
PullResult PerformPull(GIT_PULL_HANDLER *aHandler) override
int CreateBranch(git_repository *aRepo, const wxString &aBranchName) override
#define _(s)
bool RmDirRecursive(const wxString &aFileName, wxString *aErrors)
Remove the directory aDirName and all its contents including subdirectories and their files.
Definition gestfich.cpp:404
CommitResult
Definition git_backend.h:56
InitResult
PullResult
PushResult
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 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)
#define GIT_BUF_INIT
static bool lookup_branch_reference(git_repository *repo, const wxString &aBranchName, git_reference **aReference)
static std::string getFormattedCommitDate(const git_time &aTime)
static std::string getFirstLineFromCommitMessage(const std::string &aMessage)
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_buf, decltype([](git_buf *aBuf) { git_buf_free(aBuf); })> GitBufPtr
A unique pointer for git_buf objects with automatic cleanup.
std::unique_ptr< git_annotated_commit, decltype([](git_annotated_commit *aCommit) { git_annotated_commit_free(aCommit); })> GitAnnotatedCommitPtr
A unique pointer for git_annotated_commit objects with automatic cleanup.
std::unique_ptr< git_status_list, decltype([](git_status_list *aList) { git_status_list_free(aList); })> GitStatusListPtr
A unique pointer for git_status_list objects with automatic cleanup.
std::unique_ptr< git_config, decltype([](git_config *aConfig) { git_config_free(aConfig); })> GitConfigPtr
A unique pointer for git_config 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_config_entry, decltype([](git_config_entry *aEntry) { git_config_entry_free(aEntry); })> GitConfigEntryPtr
A unique pointer for git_config_entry objects with automatic cleanup.
std::unique_ptr< git_signature, decltype([](git_signature *aSignature) { git_signature_free(aSignature); })> GitSignaturePtr
A unique pointer for git_signature objects with automatic cleanup.
std::unique_ptr< git_rebase, decltype([](git_rebase *aRebase) { git_rebase_free(aRebase); })> GitRebasePtr
A unique pointer for git_rebase objects with automatic cleanup.
std::unique_ptr< git_index, decltype([](git_index *aIndex) { git_index_free(aIndex); })> GitIndexPtr
A unique pointer for git_index objects with automatic cleanup.
std::unique_ptr< git_object, decltype([](git_object *aObject) { git_object_free(aObject); })> GitObjectPtr
A unique pointer for git_object 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.
std::string m_sha
std::string m_date
std::string m_firstLine
std::string m_author
KIGIT_COMMON::GIT_CONN_TYPE connType
std::string path
wxString result
Test unit parsing edge cases and error handling.
wxLogTrace helper definitions.