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