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