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, see <https://www.gnu.org/licenses/>.
18 */
19
20#include "libgit_backend.h"
21
22#include "git_clone_handler.h"
23#include "git_commit_handler.h"
24#include "git_push_handler.h"
27#include "git_status_handler.h"
28#include "git_config_handler.h"
29#include "git_init_handler.h"
30#include "git_branch_handler.h"
31#include "git_pull_handler.h"
32#include "git_revert_handler.h"
33#include "project_git_utils.h"
34#include "kicad_git_common.h"
35#include "kicad_git_memory.h"
36#include "trace_helpers.h"
37
38#include "kicad_git_compat.h"
39#include <wx/filename.h>
40#include <wx/log.h>
41#include <gestfich.h>
42#include <algorithm>
43#include <iterator>
44#include <memory>
45#include <time.h>
46
47static std::string getFirstLineFromCommitMessage( const std::string& aMessage )
48{
49 if( aMessage.empty() )
50 return aMessage;
51
52 size_t firstLineEnd = aMessage.find_first_of( '\n' );
53
54 if( firstLineEnd != std::string::npos )
55 return aMessage.substr( 0, firstLineEnd );
56
57 return aMessage;
58}
59
60
61static std::string getFormattedCommitDate( const git_time& aTime )
62{
63 char dateBuffer[64];
64 time_t time = static_cast<time_t>( aTime.time );
65 struct tm timeInfo;
66
67#ifdef _WIN32
68 localtime_s( &timeInfo, &time );
69#else
70 gmtime_r( &time, &timeInfo );
71#endif
72
73 strftime( dateBuffer, sizeof( dateBuffer ), "%Y-%b-%d %H:%M:%S", &timeInfo );
74 return dateBuffer;
75}
76
77
79{
80 git_libgit2_init();
81}
82
83
85{
86 // Wait for any abandoned git cleanup threads to finish before tearing
87 // down libgit2. A worker still inside libgit2 (for example, blocked on
88 // recv() under git_remote_fetch) would otherwise race teardown and
89 // invoke undefined behaviour. Five seconds is long enough to cover a
90 // transport error timeout but short enough to avoid a perceptibly slow
91 // exit when the remote is truly unreachable.
92
93 constexpr auto kOrphanJoinTimeout = std::chrono::seconds( 5 );
94 size_t stuck = m_orphanRegistry.JoinAll( kOrphanJoinTimeout );
95
96 if( stuck > 0 )
97 {
98 wxLogTrace( traceGit,
99 "LIBGIT_BACKEND::Shutdown(): %zu orphan git thread(s) "
100 "did not finish within %lld ms; skipping libgit2 shutdown",
101 stuck,
102 static_cast<long long>( kOrphanJoinTimeout.count() ) );
103
104 // A stuck worker is still executing inside libgit2. Calling
105 // git_libgit2_shutdown() now would free state the worker is actively
106 // reading. Leave libgit2 initialised and let the OS reclaim
107 // resources when the process exits.
108
109 return;
110 }
111
112 git_libgit2_shutdown();
113}
114
115
117{
118#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
119 int major = 0, minor = 0, rev = 0;
120 return git_libgit2_version( &major, &minor, &rev ) == GIT_OK;
121#else
122 // On older platforms, assume available when building with libgit2
123 return true;
124#endif
125}
126
127
129{
130 KIGIT_COMMON* common = aHandler->GetCommon();
131 std::unique_lock<std::mutex> lock( common->m_gitActionMutex, std::try_to_lock );
132
133 if( !lock.owns_lock() )
134 {
135 wxLogTrace( traceGit, "GIT_CLONE_HANDLER::PerformClone() could not lock" );
136 return false;
137 }
138
139 wxFileName clonePath( aHandler->GetClonePath() );
140
141 if( !clonePath.DirExists() )
142 {
143 if( !clonePath.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
144 {
145 aHandler->AddErrorString( wxString::Format( _( "Could not create directory '%s'" ),
146 aHandler->GetClonePath() ) );
147 return false;
148 }
149 }
150
151 git_clone_options cloneOptions;
152 git_clone_init_options( &cloneOptions, GIT_CLONE_OPTIONS_VERSION );
153 cloneOptions.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
154 cloneOptions.checkout_opts.progress_cb = clone_progress_cb;
155 cloneOptions.checkout_opts.progress_payload = aHandler;
156 cloneOptions.fetch_opts.callbacks.transfer_progress = transfer_progress_cb;
157 cloneOptions.fetch_opts.callbacks.credentials = credentials_cb;
158 cloneOptions.fetch_opts.callbacks.payload = aHandler;
159 cloneOptions.fetch_opts.proxy_opts.type = GIT_PROXY_AUTO;
160
161 aHandler->TestedTypes() = 0;
162 aHandler->ResetNextKey();
163 git_repository* newRepo = nullptr;
164 wxString remote = common->m_remote;
165
166 if( git_clone( &newRepo, remote.mbc_str(), aHandler->GetClonePath().mbc_str(),
167 &cloneOptions ) != 0 )
168 {
169 aHandler->AddErrorString( wxString::Format( _( "Could not clone repository '%s' : %s" ), remote, KIGIT_COMMON::GetLastGitError() ) );
170 return false;
171 }
172
173 common->SetRepo( newRepo );
174
175 return true;
176}
177
178
180 const std::vector<wxString>& aFiles,
181 const wxString& aMessage,
182 const wxString& aAuthorName,
183 const wxString& aAuthorEmail )
184{
185 git_repository* repo = aHandler->GetRepo();
186
187 if( !repo )
188 return CommitResult::Error;
189
190 git_index* index = nullptr;
191
192 if( git_repository_index( &index, repo ) != 0 )
193 {
194 aHandler->AddErrorString( wxString::Format( _( "Failed to get repository index: %s" ),
196 return CommitResult::Error;
197 }
198
199 KIGIT::GitIndexPtr indexPtr( index );
200
201 for( const wxString& file : aFiles )
202 {
203 if( git_index_add_bypath( index, file.mb_str() ) != 0 )
204 {
205 aHandler->AddErrorString( wxString::Format( _( "Failed to add file to index: %s" ),
207 return CommitResult::Error;
208 }
209 }
210
211 if( git_index_write( index ) != 0 )
212 {
213 aHandler->AddErrorString( wxString::Format( _( "Failed to write index: %s" ),
215 return CommitResult::Error;
216 }
217
218 git_oid tree_id;
219
220 if( git_index_write_tree( &tree_id, index ) != 0 )
221 {
222 aHandler->AddErrorString( wxString::Format( _( "Failed to write tree: %s" ),
224 return CommitResult::Error;
225 }
226
227 git_tree* tree = nullptr;
228
229 if( git_tree_lookup( &tree, repo, &tree_id ) != 0 )
230 {
231 aHandler->AddErrorString( wxString::Format( _( "Failed to lookup tree: %s" ),
233 return CommitResult::Error;
234 }
235
236 KIGIT::GitTreePtr treePtr( tree );
237 git_commit* parent = nullptr;
238
239 if( git_repository_head_unborn( repo ) == 0 )
240 {
241 git_reference* headRef = nullptr;
242
243 if( git_repository_head( &headRef, repo ) != 0 )
244 {
245 aHandler->AddErrorString( wxString::Format( _( "Failed to get HEAD reference: %s" ),
247 return CommitResult::Error;
248 }
249
250 KIGIT::GitReferencePtr headRefPtr( headRef );
251
252 if( git_reference_peel( (git_object**) &parent, headRef, GIT_OBJECT_COMMIT ) != 0 )
253 {
254 aHandler->AddErrorString( wxString::Format( _( "Failed to get commit: %s" ),
256 return CommitResult::Error;
257 }
258 }
259
260 KIGIT::GitCommitPtr parentPtr( parent );
261
262 git_signature* author = nullptr;
263
264 if( git_signature_now( &author, aAuthorName.mb_str(), aAuthorEmail.mb_str() ) != 0 )
265 {
266 aHandler->AddErrorString( wxString::Format( _( "Failed to create author signature: %s" ),
268 return CommitResult::Error;
269 }
270
271 KIGIT::GitSignaturePtr authorPtr( author );
272 git_oid oid;
273 size_t parentsCount = parent ? 1 : 0;
274
275#if( LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR == 8 \
276 && ( LIBGIT2_VER_REVISION < 2 || LIBGIT2_VER_REVISION == 3 ) )
277 git_commit* const parents[1] = { parent };
278 git_commit** const parentsPtr = parent ? parents : nullptr;
279#else
280 const git_commit* parents[1] = { parent };
281 const git_commit** parentsPtr = parent ? parents : nullptr;
282#endif
283
284 if( git_commit_create( &oid, repo, "HEAD", author, author, nullptr,
285 aMessage.mb_str(), tree, parentsCount, parentsPtr ) != 0 )
286 {
287 aHandler->AddErrorString( wxString::Format( _( "Failed to create commit: %s" ),
289 return CommitResult::Error;
290 }
291
293}
294
295
296CommitResult LIBGIT_BACKEND::Amend( GIT_COMMIT_HANDLER* aHandler, const std::vector<wxString>& aFiles,
297 const wxString& aMessage, const wxString& aAuthorName,
298 const wxString& aAuthorEmail )
299{
300 git_repository* repo = aHandler->GetRepo();
301
302 if( !repo )
303 return CommitResult::Error;
304
305 if( git_repository_head_unborn( repo ) != 0 )
306 {
307 aHandler->AddErrorString( _( "Cannot amend: the branch has no commits yet." ) );
308 return CommitResult::Error;
309 }
310
311 git_reference* headRef = nullptr;
312
313 if( git_repository_head( &headRef, repo ) != 0 )
314 {
315 aHandler->AddErrorString(
316 wxString::Format( _( "Failed to get HEAD reference: %s" ), KIGIT_COMMON::GetLastGitError() ) );
317 return CommitResult::Error;
318 }
319
320 KIGIT::GitReferencePtr headRefPtr( headRef );
321 git_commit* headCommit = nullptr;
322
323 if( git_reference_peel( (git_object**) &headCommit, headRef, GIT_OBJECT_COMMIT ) != 0 )
324 {
325 aHandler->AddErrorString(
326 wxString::Format( _( "Failed to get HEAD commit: %s" ), KIGIT_COMMON::GetLastGitError() ) );
327 return CommitResult::Error;
328 }
329
330 KIGIT::GitCommitPtr headCommitPtr( headCommit );
331 git_index* index = nullptr;
332
333 if( git_repository_index( &index, repo ) != 0 )
334 {
335 aHandler->AddErrorString(
336 wxString::Format( _( "Failed to get repository index: %s" ), KIGIT_COMMON::GetLastGitError() ) );
337 return CommitResult::Error;
338 }
339
340 KIGIT::GitIndexPtr indexPtr( index );
341
342 git_commit* parentCommit = nullptr;
343
344 if( git_commit_parentcount( headCommit ) > 0 && git_commit_parent( &parentCommit, headCommit, 0 ) != 0 )
345 {
346 aHandler->AddErrorString(
347 wxString::Format( _( "Failed to get parent commit: %s" ), KIGIT_COMMON::GetLastGitError() ) );
348 return CommitResult::Error;
349 }
350
351 KIGIT::GitCommitPtr parentCommitPtr( parentCommit );
352
353 if( aFiles.empty() )
354 {
355 git_tree* headTree = nullptr;
356
357 if( git_commit_tree( &headTree, headCommit ) != 0 )
358 {
359 aHandler->AddErrorString(
360 wxString::Format( _( "Failed to get HEAD tree: %s" ), KIGIT_COMMON::GetLastGitError() ) );
361 return CommitResult::Error;
362 }
363
364 KIGIT::GitTreePtr headTreePtr( headTree );
365
366 if( git_index_read_tree( index, headTree ) != 0 )
367 {
368 aHandler->AddErrorString(
369 wxString::Format( _( "Failed to reset index: %s" ), KIGIT_COMMON::GetLastGitError() ) );
370 return CommitResult::Error;
371 }
372 }
373 else
374 {
375 // Rebuild from the parent's tree so files can be dropped, then re-apply the selected files.
376 if( parentCommit )
377 {
378 git_tree* parentTree = nullptr;
379
380 if( git_commit_tree( &parentTree, parentCommit ) != 0 )
381 {
382 aHandler->AddErrorString(
383 wxString::Format( _( "Failed to get parent tree: %s" ), KIGIT_COMMON::GetLastGitError() ) );
384 return CommitResult::Error;
385 }
386
387 KIGIT::GitTreePtr parentTreePtr( parentTree );
388
389 if( git_index_read_tree( index, parentTree ) != 0 )
390 {
391 aHandler->AddErrorString(
392 wxString::Format( _( "Failed to reset index: %s" ), KIGIT_COMMON::GetLastGitError() ) );
393 return CommitResult::Error;
394 }
395 }
396 else
397 {
398 // Amending the very first commit: start from an empty tree.
399 git_index_clear( index );
400 }
401
402 const char* workdir = git_repository_workdir( repo );
403
404 for( const wxString& file : aFiles )
405 {
406 bool onDisk = workdir && wxFileName::FileExists( wxString::FromUTF8( workdir ) + file );
407
408 int rc = onDisk ? git_index_add_bypath( index, file.mb_str() )
409 : git_index_remove_bypath( index, file.mb_str() );
410
411 if( rc != 0 )
412 {
413 aHandler->AddErrorString(
414 wxString::Format( _( "Failed to add file to index: %s" ), KIGIT_COMMON::GetLastGitError() ) );
415 return CommitResult::Error;
416 }
417 }
418 }
419
420 if( git_index_write( index ) != 0 )
421 {
422 aHandler->AddErrorString(
423 wxString::Format( _( "Failed to write index: %s" ), KIGIT_COMMON::GetLastGitError() ) );
424 return CommitResult::Error;
425 }
426
427 git_oid tree_id;
428
429 if( git_index_write_tree( &tree_id, index ) != 0 )
430 {
431 aHandler->AddErrorString(
432 wxString::Format( _( "Failed to write tree: %s" ), KIGIT_COMMON::GetLastGitError() ) );
433 return CommitResult::Error;
434 }
435
436 git_tree* tree = nullptr;
437
438 if( git_tree_lookup( &tree, repo, &tree_id ) != 0 )
439 {
440 aHandler->AddErrorString(
441 wxString::Format( _( "Failed to lookup tree: %s" ), KIGIT_COMMON::GetLastGitError() ) );
442 return CommitResult::Error;
443 }
444
445 KIGIT::GitTreePtr treePtr( tree );
446 git_signature* author = nullptr;
447
448 if( git_signature_now( &author, aAuthorName.mb_str(), aAuthorEmail.mb_str() ) != 0 )
449 {
450 aHandler->AddErrorString(
451 wxString::Format( _( "Failed to create author signature: %s" ), KIGIT_COMMON::GetLastGitError() ) );
452 return CommitResult::Error;
453 }
454
455 KIGIT::GitSignaturePtr authorPtr( author );
456 git_oid oid;
457
458 if( git_commit_amend( &oid, headCommit, "HEAD", author, author, nullptr, aMessage.mb_str(), tree ) != 0 )
459 {
460 aHandler->AddErrorString(
461 wxString::Format( _( "Failed to amend commit: %s" ), KIGIT_COMMON::GetLastGitError() ) );
462 return CommitResult::Error;
463 }
464
466}
467
468
470{
471 KIGIT_COMMON* common = aHandler->GetCommon();
472 std::unique_lock<std::mutex> lock( common->m_gitActionMutex, std::try_to_lock );
473
474 if( !lock.owns_lock() )
475 {
476 wxLogTrace( traceGit, "GIT_PUSH_HANDLER::PerformPush: Could not lock mutex" );
477 return PushResult::Error;
478 }
479
481
482 wxString remoteName = common->GetRemoteNameOrDefault();
483 std::string remoteNameUtf8 = remoteName.utf8_string();
484 git_remote* remote = nullptr;
485
486 if( git_remote_lookup( &remote, aHandler->GetRepo(), remoteNameUtf8.c_str() ) != 0 )
487 {
488 aHandler->AddErrorString( wxString::Format( _( "Could not lookup remote '%s'" ),
489 remoteName ) );
490 return PushResult::Error;
491 }
492
493 KIGIT::GitRemotePtr remotePtr(remote);
494
495 git_remote_callbacks remoteCallbacks;
496 git_remote_init_callbacks( &remoteCallbacks, GIT_REMOTE_CALLBACKS_VERSION );
497 remoteCallbacks.sideband_progress = progress_cb;
498 remoteCallbacks.transfer_progress = transfer_progress_cb;
499 remoteCallbacks.update_tips = update_cb;
500 remoteCallbacks.push_transfer_progress = push_transfer_progress_cb;
501 remoteCallbacks.credentials = credentials_cb;
502 remoteCallbacks.payload = aHandler;
503
504 git_proxy_options proxyOpts;
505 git_proxy_init_options( &proxyOpts, GIT_PROXY_OPTIONS_VERSION );
506 proxyOpts.type = GIT_PROXY_AUTO;
507 common->SetCancelled( false );
508
509 aHandler->TestedTypes() = 0;
510 aHandler->ResetNextKey();
511
512 if( git_remote_connect( remote, GIT_DIRECTION_PUSH, &remoteCallbacks, &proxyOpts, nullptr ) )
513 {
514 aHandler->AddErrorString( wxString::Format( _( "Could not connect to remote: %s" ),
516 return PushResult::Error;
517 }
518
519 git_push_options pushOptions;
520 git_push_init_options( &pushOptions, GIT_PUSH_OPTIONS_VERSION );
521 pushOptions.callbacks = remoteCallbacks;
522 pushOptions.proxy_opts.type = GIT_PROXY_AUTO;
523
524 git_reference* head = nullptr;
525
526 if( git_repository_head( &head, aHandler->GetRepo() ) != 0 )
527 {
528 git_remote_disconnect( remote );
529 aHandler->AddErrorString( _( "Could not get repository head" ) );
530 return PushResult::Error;
531 }
532
533 KIGIT::GitReferencePtr headPtr( head );
534
535 // Force push prepends "+" to the refspec source, matching `git push --force`.
536 wxString refspec = ( aForce ? wxS( "+" ) : wxS( "" ) ) + wxString( git_reference_name( head ) );
537 std::string refspecUtf8 = refspec.utf8_string();
538 const char* refs[1] = { refspecUtf8.c_str() };
539 const git_strarray refspecs = { (char**) refs, 1 };
540
541 if( git_remote_push( remote, &refspecs, &pushOptions ) )
542 {
543 wxString errorMsg = KIGIT_COMMON::GetLastGitError();
544 aHandler->AddErrorString( wxString::Format( _( "Could not push to remote: %s" ), errorMsg ) );
545 git_remote_disconnect( remote );
546
547 wxString lower = errorMsg.Lower();
548
549 if( lower.Contains( wxS( "non-fast-forward" ) ) || lower.Contains( wxS( "non-fastforwardable" ) )
550 || lower.Contains( wxS( "would not be" ) ) )
551 {
553 }
554
555 return PushResult::Error;
556 }
557
558 // First push from this branch: point it at where we just pushed (git push -u).
559 if( git_reference_is_branch( head ) )
560 {
561 git_reference* upstreamRef = nullptr;
562 int rc = git_branch_upstream( &upstreamRef, head );
563
564 if( rc == GIT_ENOTFOUND )
565 {
566 wxString upstreamName = wxString::Format( "%s/%s", remoteName, git_reference_shorthand( head ) );
567 git_branch_set_upstream( head, upstreamName.utf8_string().c_str() );
568 }
569 else if( rc == GIT_OK )
570 {
571 KIGIT::GitReferencePtr upstreamPtr( upstreamRef );
572 }
573 }
574
575 git_remote_disconnect( remote );
576
577 return result;
578}
579
580
582{
583 git_repository* repo = aHandler->GetRepo();
584
585 if( !repo )
586 return false;
587
588 git_status_options opts;
589 git_status_init_options( &opts, GIT_STATUS_OPTIONS_VERSION );
590
591 opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
592 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX
593 | GIT_STATUS_OPT_SORT_CASE_SENSITIVELY;
594
595 git_status_list* status_list = nullptr;
596
597 if( git_status_list_new( &status_list, repo, &opts ) != GIT_OK )
598 {
599 wxLogTrace( traceGit, "Failed to get status list: %s", KIGIT_COMMON::GetLastGitError() );
600 return false;
601 }
602
603 KIGIT::GitStatusListPtr status_list_ptr( status_list );
604 bool hasChanges = ( git_status_list_entrycount( status_list ) > 0 );
605
606 return hasChanges;
607}
608
609
610std::map<wxString, FileStatus> LIBGIT_BACKEND::GetFileStatus( GIT_STATUS_HANDLER* aHandler,
611 const wxString& aPathspec )
612{
613 std::map<wxString, FileStatus> fileStatusMap;
614 git_repository* repo = aHandler->GetRepo();
615
616 if( !repo )
617 return fileStatusMap;
618
619 git_status_options status_options;
620 git_status_init_options( &status_options, GIT_STATUS_OPTIONS_VERSION );
621 status_options.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
622 status_options.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_INCLUDE_UNMODIFIED
623 | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
624
625 std::string pathspec_str;
626 std::vector<const char*> pathspec_ptrs;
627
628 if( !aPathspec.IsEmpty() )
629 {
630 pathspec_str = aPathspec.ToStdString();
631 pathspec_ptrs.push_back( pathspec_str.c_str() );
632
633 status_options.pathspec.strings = const_cast<char**>( pathspec_ptrs.data() );
634 status_options.pathspec.count = pathspec_ptrs.size();
635 }
636
637 git_status_list* status_list = nullptr;
638
639 if( git_status_list_new( &status_list, repo, &status_options ) != GIT_OK )
640 {
641 wxLogTrace( traceGit, "Failed to get git status list: %s", KIGIT_COMMON::GetLastGitError() );
642 return fileStatusMap;
643 }
644
645 KIGIT::GitStatusListPtr statusListPtr( status_list );
646
647 size_t count = git_status_list_entrycount( status_list );
648 wxString repoWorkDir = aHandler->GetProjectDir();
649
650 for( size_t ii = 0; ii < count; ++ii )
651 {
652 const git_status_entry* entry = git_status_byindex( status_list, ii );
653 std::string path( entry->head_to_index ? entry->head_to_index->old_file.path
654 : entry->index_to_workdir->old_file.path );
655
656 wxString absPath = repoWorkDir + path;
657 fileStatusMap[absPath] = FileStatus{ absPath,
658 aHandler->ConvertStatus( entry->status ),
659 static_cast<unsigned int>( entry->status ) };
660 }
661
662 return fileStatusMap;
663}
664
665
667{
668 git_repository* repo = aHandler->GetRepo();
669
670 if( !repo )
671 return wxEmptyString;
672
673 git_reference* currentBranchReference = nullptr;
674 int rc = git_repository_head( &currentBranchReference, repo );
675 KIGIT::GitReferencePtr currentBranchReferencePtr( currentBranchReference );
676
677 if( currentBranchReference )
678 {
679 return git_reference_shorthand( currentBranchReference );
680 }
681 else if( rc == GIT_EUNBORNBRANCH )
682 {
683 return wxEmptyString;
684 }
685 else
686 {
687 wxLogTrace( traceGit, "Failed to lookup current branch: %s", KIGIT_COMMON::GetLastGitError() );
688 return wxEmptyString;
689 }
690}
691
692
694 const std::set<wxString>& aLocalChanges,
695 const std::set<wxString>& aRemoteChanges,
696 std::map<wxString, FileStatus>& aFileStatus )
697{
698 git_repository* repo = aHandler->GetRepo();
699
700 if( !repo )
701 return;
702
703 wxString repoWorkDir = aHandler->GetProjectDir();
704
705 for( auto& [absPath, fileStatus] : aFileStatus )
706 {
707 wxString relativePath = absPath;
708 if( relativePath.StartsWith( repoWorkDir ) )
709 {
710 relativePath = relativePath.Mid( repoWorkDir.length() );
711
712#ifdef _WIN32
713 relativePath.Replace( wxS( "\\" ), wxS( "/" ) );
714#endif
715 }
716
717 std::string relativePathStd = relativePath.ToStdString();
718
719 if( fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CURRENT )
720 {
721 if( aLocalChanges.count( relativePathStd ) )
722 {
724 }
725 else if( aRemoteChanges.count( relativePathStd ) )
726 {
728 }
729 }
730 }
731}
732
733
735{
736 return aHandler->GetProjectDir();
737}
738
739
741{
742 return aHandler->GetProjectDir();
743}
744
745
746bool LIBGIT_BACKEND::GetConfigString( GIT_CONFIG_HANDLER* aHandler, const wxString& aKey, wxString& aValue )
747{
748 git_repository* repo = aHandler->GetRepo();
749
750 if( !repo )
751 return false;
752
753 git_config* config = nullptr;
754
755 if( git_repository_config( &config, repo ) != GIT_OK )
756 {
757 wxLogTrace( traceGit, "Failed to get repository config: %s", KIGIT_COMMON::GetLastGitError() );
758 return false;
759 }
760
761 KIGIT::GitConfigPtr configPtr( config );
762
763 git_config_entry* entry = nullptr;
764 int result = git_config_get_entry( &entry, config, aKey.mb_str() );
765 KIGIT::GitConfigEntryPtr entryPtr( entry );
766
767 if( result != GIT_OK || entry == nullptr )
768 {
769 wxLogTrace( traceGit, "Config key '%s' not found", aKey );
770 return false;
771 }
772
773 aValue = wxString( entry->value );
774 return true;
775}
776
777
778bool LIBGIT_BACKEND::IsRepository( GIT_INIT_HANDLER* aHandler, const wxString& aPath )
779{
780 git_repository* repo = nullptr;
781 int error = git_repository_open( &repo, aPath.mb_str() );
782
783 if( error == 0 )
784 {
785 git_repository_free( repo );
786 return true;
787 }
788
789 return false;
790}
791
792
794{
795 if( IsRepository( aHandler, aPath ) )
796 {
798 }
799
800 git_repository* repo = nullptr;
801
802 if( git_repository_init( &repo, aPath.mb_str(), 0 ) != GIT_OK )
803 {
804 if( repo )
805 git_repository_free( repo );
806
807 aHandler->AddErrorString( wxString::Format( _( "Failed to initialize Git repository: %s" ),
809 return InitResult::Error;
810 }
811
812 aHandler->GetCommon()->SetRepo( repo );
813
814 wxLogTrace( traceGit, "Successfully initialized Git repository at %s", aPath );
815 return InitResult::Success;
816}
817
818
820{
821 if( aConfig.url.IsEmpty() )
822 return true;
823
824 git_repository* repo = aHandler->GetRepo();
825
826 if( !repo )
827 {
828 aHandler->AddErrorString( _( "No repository available to set up remote" ) );
829 return false;
830 }
831
832 aHandler->GetCommon()->SetUsername( aConfig.username );
833 aHandler->GetCommon()->SetPassword( aConfig.password );
834 aHandler->GetCommon()->SetSSHKey( aConfig.sshKey );
835
836 git_remote* remote = nullptr;
837 wxString fullURL;
838
840 {
841 wxString userPrefix;
842
843 if( !aConfig.url.Contains( "@" ) && !aConfig.username.IsEmpty() )
844 userPrefix = aConfig.username + "@";
845
846 if( aConfig.url.StartsWith( "ssh://" ) )
847 fullURL = "ssh://" + userPrefix + aConfig.url.Mid( 6 );
848 else if( aConfig.url.Contains( ":" ) )
849 fullURL = userPrefix + aConfig.url;
850 else
851 fullURL = "ssh://" + userPrefix + aConfig.url;
852 }
854 {
855 fullURL = aConfig.url.StartsWith( "https" ) ? "https://" : "http://";
856
857 if( !aConfig.username.empty() )
858 {
859 fullURL.append( aConfig.username );
860
861 if( !aConfig.password.empty() )
862 {
863 fullURL.append( wxS( ":" ) );
864 fullURL.append( aConfig.password );
865 }
866
867 fullURL.append( wxS( "@" ) );
868 }
869
870 wxString bareURL = aConfig.url;
871
872 if( bareURL.StartsWith( "https://" ) )
873 bareURL = bareURL.Mid( 8 );
874 else if( bareURL.StartsWith( "http://" ) )
875 bareURL = bareURL.Mid( 7 );
876
877 fullURL.append( bareURL );
878 }
879 else
880 {
881 fullURL = aConfig.url;
882 }
883
884 int error;
885
886 if( git_remote_lookup( &remote, repo, "origin" ) == GIT_OK )
887 {
888 KIGIT::GitRemotePtr remotePtr( remote );
889 error = git_remote_set_url( repo, "origin", fullURL.ToStdString().c_str() );
890 }
891 else
892 {
893 error = git_remote_create_with_fetchspec( &remote, repo, "origin", fullURL.ToStdString().c_str(),
894 "+refs/heads/*:refs/remotes/origin/*" );
895 KIGIT::GitRemotePtr remotePtr( remote );
896 }
897
898 if( error != GIT_OK )
899 {
900 aHandler->AddErrorString(
901 wxString::Format( _( "Failed to set up remote: %s" ), KIGIT_COMMON::GetLastGitError() ) );
902 return false;
903 }
904
905 // Sync the remote URL onto common so subsequent fetch/push see the right
906 // connection type; otherwise credentials_cb short-circuits as local.
907 aHandler->GetCommon()->SetRemote( fullURL );
908
909 wxLogTrace( traceGit, "Successfully set up remote origin" );
910 return true;
911}
912
913
914static bool lookup_branch_reference( git_repository* repo, const wxString& aBranchName,
915 git_reference** aReference )
916{
917 if( git_reference_lookup( aReference, repo, aBranchName.mb_str() ) == GIT_OK )
918 return true;
919
920 if( git_reference_dwim( aReference, repo, aBranchName.mb_str() ) == GIT_OK )
921 return true;
922
923 return false;
924}
925
926
927BranchResult LIBGIT_BACKEND::SwitchToBranch( GIT_BRANCH_HANDLER* aHandler, const wxString& aBranchName )
928{
929 git_repository* repo = aHandler->GetRepo();
930
931 if( !repo )
932 {
933 aHandler->AddErrorString( _( "No repository available" ) );
934 return BranchResult::Error;
935 }
936
937 git_reference* branchRef = nullptr;
938
939 if( !lookup_branch_reference( repo, aBranchName, &branchRef ) )
940 {
941 aHandler->AddErrorString( wxString::Format( _( "Failed to lookup branch '%s': %s" ),
942 aBranchName, KIGIT_COMMON::GetLastGitError() ) );
944 }
945
946 KIGIT::GitReferencePtr branchRefPtr( branchRef );
947 const char* branchRefName = git_reference_name( branchRef );
948 git_object* branchObj = nullptr;
949
950 if( git_revparse_single( &branchObj, repo, aBranchName.mb_str() ) != GIT_OK )
951 {
952 aHandler->AddErrorString( wxString::Format( _( "Failed to find branch head for '%s': %s" ),
953 aBranchName, KIGIT_COMMON::GetLastGitError() ) );
954 return BranchResult::Error;
955 }
956
957 KIGIT::GitObjectPtr branchObjPtr( branchObj );
958
959
960 git_checkout_options checkoutOpts;
961 git_checkout_init_options( &checkoutOpts, GIT_CHECKOUT_OPTIONS_VERSION );
962 checkoutOpts.checkout_strategy = GIT_CHECKOUT_SAFE;
963
964 if( git_checkout_tree( repo, branchObj, &checkoutOpts ) != GIT_OK )
965 {
966 aHandler->AddErrorString( wxString::Format( _( "Failed to switch to branch '%s': %s" ),
967 aBranchName, KIGIT_COMMON::GetLastGitError() ) );
969 }
970
971 KIGIT::GitReferencePtr localBranchPtr;
972 const char* headTarget = branchRefName;
973
974 if( git_reference_is_remote( branchRef ) )
975 {
976 wxString localName = wxString::FromUTF8( git_reference_shorthand( branchRef ) );
977 size_t slash = localName.find( '/' );
978
979 if( slash != wxString::npos )
980 localName = localName.Mid( slash + 1 );
981
982 std::string localNameUtf8 = localName.utf8_string();
983 git_reference* localBranch = nullptr;
984
985 if( git_branch_lookup( &localBranch, repo, localNameUtf8.c_str(), GIT_BRANCH_LOCAL ) != GIT_OK )
986 {
987 git_commit* target = nullptr;
988
989 if( git_commit_lookup( &target, repo, git_object_id( branchObj ) ) != GIT_OK )
990 {
991 aHandler->AddErrorString( wxString::Format( _( "Failed to switch to branch '%s': %s" ), aBranchName,
993 return BranchResult::Error;
994 }
995
996 KIGIT::GitCommitPtr targetPtr( target );
997
998 if( git_branch_create( &localBranch, repo, localNameUtf8.c_str(), target, 0 ) != GIT_OK )
999 {
1000 aHandler->AddErrorString( wxString::Format( _( "Failed to create local branch '%s': %s" ), localName,
1002 return BranchResult::Error;
1003 }
1004
1005 git_branch_set_upstream( localBranch, git_reference_shorthand( branchRef ) );
1006 }
1007
1008 localBranchPtr.reset( localBranch );
1009 headTarget = git_reference_name( localBranch );
1010 }
1011
1012 if( git_repository_set_head( repo, headTarget ) != GIT_OK )
1013 {
1014 aHandler->AddErrorString( wxString::Format( _( "Failed to update HEAD reference for branch '%s': %s" ),
1015 aBranchName, KIGIT_COMMON::GetLastGitError() ) );
1016 return BranchResult::Error;
1017 }
1018
1019 wxLogTrace( traceGit, "Successfully switched to branch '%s'", aBranchName );
1020 return BranchResult::Success;
1021}
1022
1023
1024bool LIBGIT_BACKEND::BranchExists( GIT_BRANCH_HANDLER* aHandler, const wxString& aBranchName )
1025{
1026 git_repository* repo = aHandler->GetRepo();
1027
1028 if( !repo )
1029 return false;
1030
1031 git_reference* branchRef = nullptr;
1032 bool exists = lookup_branch_reference( repo, aBranchName, &branchRef );
1033
1034 if( branchRef )
1035 git_reference_free( branchRef );
1036
1037 return exists;
1038}
1039
1040
1041// Use callbacks declared/implemented in kicad_git_common.h/.cpp
1042
1043bool LIBGIT_BACKEND::PerformFetch( GIT_PULL_HANDLER* aHandler, bool aSkipLock )
1044{
1045 if( !aHandler->GetRepo() )
1046 {
1047 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - No repository found" );
1048 return false;
1049 }
1050
1051 std::unique_lock<std::mutex> lock( aHandler->GetCommon()->m_gitActionMutex, std::try_to_lock );
1052
1053 if( !aSkipLock && !lock.owns_lock() )
1054 {
1055 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Could not lock mutex" );
1056 return false;
1057 }
1058
1059 wxString remoteName = aHandler->GetCommon()->GetRemoteNameOrDefault();
1060 std::string remoteNameUtf8 = remoteName.utf8_string();
1061 git_remote* remote = nullptr;
1062
1063 if( git_remote_lookup( &remote, aHandler->GetRepo(), remoteNameUtf8.c_str() ) != 0 )
1064 {
1065 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Failed to lookup remote '%s'",
1066 remoteName );
1067 aHandler->AddErrorString( wxString::Format( _( "Could not lookup remote '%s'" ),
1068 remoteName ) );
1069 return false;
1070 }
1071
1072 KIGIT::GitRemotePtr remotePtr( remote );
1073
1074 git_remote_callbacks remoteCallbacks;
1075 git_remote_init_callbacks( &remoteCallbacks, GIT_REMOTE_CALLBACKS_VERSION );
1076 remoteCallbacks.sideband_progress = progress_cb;
1077 remoteCallbacks.transfer_progress = transfer_progress_cb;
1078 remoteCallbacks.credentials = credentials_cb;
1079 remoteCallbacks.payload = aHandler;
1080
1081 git_proxy_options proxyOpts;
1082 git_proxy_init_options( &proxyOpts, GIT_PROXY_OPTIONS_VERSION );
1083 proxyOpts.type = GIT_PROXY_AUTO;
1084 aHandler->GetCommon()->SetCancelled( false );
1085
1086 aHandler->TestedTypes() = 0;
1087 aHandler->ResetNextKey();
1088
1089 if( git_remote_connect( remote, GIT_DIRECTION_FETCH, &remoteCallbacks, &proxyOpts, nullptr ) )
1090 {
1091 wxString errorMsg = KIGIT_COMMON::GetLastGitError();
1092 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Failed to connect to remote: %s", errorMsg );
1093 aHandler->AddErrorString( wxString::Format( _( "Could not connect to remote '%s': %s" ),
1094 remoteName, errorMsg ) );
1095 return false;
1096 }
1097
1098 git_fetch_options fetchOptions;
1099 git_fetch_init_options( &fetchOptions, GIT_FETCH_OPTIONS_VERSION );
1100 fetchOptions.callbacks = remoteCallbacks;
1101
1102 if( git_remote_fetch( remote, nullptr, &fetchOptions, nullptr ) )
1103 {
1104 wxString errorMsg = KIGIT_COMMON::GetLastGitError();
1105 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Failed to fetch from remote: %s", errorMsg );
1106 aHandler->AddErrorString( wxString::Format( _( "Could not fetch data from remote '%s': %s" ),
1107 remoteName, errorMsg ) );
1108 return false;
1109 }
1110
1111 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Fetch completed successfully" );
1112 return true;
1113}
1114
1115
1117{
1119 std::unique_lock<std::mutex> lock( aHandler->GetCommon()->m_gitActionMutex, std::try_to_lock );
1120
1121 if( !lock.owns_lock() )
1122 {
1123 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Could not lock mutex" );
1124 return PullResult::Error;
1125 }
1126
1127 if( !PerformFetch( aHandler, true ) )
1128 return PullResult::Error;
1129
1130 git_oid pull_merge_oid = {};
1131
1132 if( git_repository_fetchhead_foreach( aHandler->GetRepo(), fetchhead_foreach_cb, &pull_merge_oid ) )
1133 {
1134 aHandler->AddErrorString( _( "Could not read 'FETCH_HEAD'" ) );
1135 return PullResult::Error;
1136 }
1137
1138 // Add Version Control doesn't write branch.<name>.merge, so FETCH_HEAD has no
1139 // merge-marked entry. Fall back to refs/remotes/<remote>/<branch> and persist
1140 // the upstream so subsequent pulls take the normal path.
1141#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
1142 if( git_oid_is_zero( &pull_merge_oid ) )
1143#else
1144 if( git_oid_iszero( &pull_merge_oid ) )
1145#endif
1146 {
1147 git_reference* head_ref = nullptr;
1148
1149 if( git_repository_head( &head_ref, aHandler->GetRepo() ) == GIT_OK )
1150 {
1151 KIGIT::GitReferencePtr headRefPtr( head_ref );
1152
1153 if( git_reference_is_branch( head_ref ) )
1154 {
1155 const char* branch_shorthand = git_reference_shorthand( head_ref );
1156 wxString remoteName = aHandler->GetCommon()->GetRemoteNameOrDefault();
1157 wxString remoteRefName = wxString::Format( "refs/remotes/%s/%s", remoteName, branch_shorthand );
1158
1159 if( git_reference_name_to_id( &pull_merge_oid, aHandler->GetRepo(),
1160 remoteRefName.utf8_string().c_str() )
1161 == GIT_OK )
1162 {
1163 wxString upstream = wxString::Format( "%s/%s", remoteName, branch_shorthand );
1164 git_branch_set_upstream( head_ref, upstream.utf8_string().c_str() );
1165 }
1166 }
1167 }
1168 }
1169
1170#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
1171 if( git_oid_is_zero( &pull_merge_oid ) )
1172#else
1173 if( git_oid_iszero( &pull_merge_oid ) )
1174#endif
1175 {
1176 aHandler->AddErrorString( _( "Nothing to pull: the remote has no branch matching "
1177 "the current local branch." ) );
1178 return PullResult::Error;
1179 }
1180
1181 git_annotated_commit* fetchhead_commit;
1182
1183 if( git_annotated_commit_lookup( &fetchhead_commit, aHandler->GetRepo(), &pull_merge_oid ) )
1184 {
1185 aHandler->AddErrorString( _( "Could not lookup commit" ) );
1186 return PullResult::Error;
1187 }
1188
1189 KIGIT::GitAnnotatedCommitPtr fetchheadCommitPtr( fetchhead_commit );
1190 const git_annotated_commit* merge_commits[] = { fetchhead_commit };
1191 git_merge_analysis_t merge_analysis;
1192 git_merge_preference_t merge_preference = GIT_MERGE_PREFERENCE_NONE;
1193
1194 if( git_merge_analysis( &merge_analysis, &merge_preference, aHandler->GetRepo(), merge_commits, 1 ) )
1195 {
1196 aHandler->AddErrorString( _( "Could not analyze merge" ) );
1197 return PullResult::Error;
1198 }
1199
1200 if( merge_analysis & GIT_MERGE_ANALYSIS_UNBORN )
1201 {
1202 aHandler->AddErrorString( _( "Invalid HEAD. Cannot merge." ) );
1204 }
1205
1206 if( merge_analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE )
1207 {
1208 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Repository is up to date" );
1209 git_repository_state_cleanup( aHandler->GetRepo() );
1210 return PullResult::UpToDate;
1211 }
1212
1213 if( merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD )
1214 {
1215 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Fast-forward merge" );
1216 return handleFastForward( aHandler );
1217 }
1218
1219 if( merge_analysis & GIT_MERGE_ANALYSIS_NORMAL )
1220 {
1221 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Normal merge" );
1222
1223 git_config* config = nullptr;
1224
1225 if( git_repository_config( &config, aHandler->GetRepo() ) != GIT_OK )
1226 {
1227 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Failed to get repository config" );
1228 aHandler->AddErrorString( _( "Could not access repository configuration" ) );
1229 return PullResult::Error;
1230 }
1231
1232 KIGIT::GitConfigPtr configPtr( config );
1233
1234 int rebase_value = 0;
1235 int ret = git_config_get_bool( &rebase_value, config, "pull.rebase" );
1236
1237 if( ret == GIT_OK && rebase_value )
1238 {
1239 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Using rebase based on config" );
1240 return handleRebase( aHandler, merge_commits, 1 );
1241 }
1242
1243 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Using merge based on config" );
1244 return handleMerge( aHandler, merge_commits, 1 );
1245 }
1246
1247 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Merge needs resolution" );
1248 return result;
1249}
1250
1251
1253{
1254 git_reference* rawRef = nullptr;
1255
1256 if( git_repository_head( &rawRef, aHandler->GetRepo() ) )
1257 {
1258 aHandler->AddErrorString( _( "Could not get repository head" ) );
1259 return PullResult::Error;
1260 }
1261
1262 KIGIT::GitReferencePtr headRef( rawRef );
1263
1264 git_oid updatedRefOid;
1265 const char* currentBranchName = git_reference_name( rawRef );
1266 const char* branch_shorthand = git_reference_shorthand( rawRef );
1267 wxString remote_name = aHandler->GetRemotename();
1268 wxString remoteBranchName = wxString::Format( "refs/remotes/%s/%s", remote_name, branch_shorthand );
1269
1270 if( git_reference_name_to_id( &updatedRefOid, aHandler->GetRepo(), remoteBranchName.c_str() ) != GIT_OK )
1271 {
1272 aHandler->AddErrorString( wxString::Format( _( "Could not get reference OID for reference '%s'" ),
1273 remoteBranchName ) );
1274 return PullResult::Error;
1275 }
1276
1277 git_commit* targetCommit = nullptr;
1278
1279 if( git_commit_lookup( &targetCommit, aHandler->GetRepo(), &updatedRefOid ) != GIT_OK )
1280 {
1281 aHandler->AddErrorString( _( "Could not look up target commit" ) );
1282 return PullResult::Error;
1283 }
1284
1285 KIGIT::GitCommitPtr targetCommitPtr( targetCommit );
1286
1287 git_tree* targetTree = nullptr;
1288
1289 if( git_commit_tree( &targetTree, targetCommit ) != GIT_OK )
1290 {
1291 git_commit_free( targetCommit );
1292 aHandler->AddErrorString( _( "Could not get tree from target commit" ) );
1293 return PullResult::Error;
1294 }
1295
1296 KIGIT::GitTreePtr targetTreePtr( targetTree );
1297
1298 git_checkout_options checkoutOptions;
1299 git_checkout_init_options( &checkoutOptions, GIT_CHECKOUT_OPTIONS_VERSION );
1300 auto notify_cb = []( git_checkout_notify_t why, const char* path, const git_diff_file* baseline,
1301 const git_diff_file* target, const git_diff_file* workdir, void* payload ) -> int
1302 {
1303 switch( why )
1304 {
1305 case GIT_CHECKOUT_NOTIFY_CONFLICT:
1306 wxLogTrace( traceGit, "Checkout conflict: %s", path ? path : "unknown" );
1307 break;
1308 case GIT_CHECKOUT_NOTIFY_DIRTY:
1309 wxLogTrace( traceGit, "Checkout dirty: %s", path ? path : "unknown" );
1310 break;
1311 case GIT_CHECKOUT_NOTIFY_UPDATED:
1312 wxLogTrace( traceGit, "Checkout updated: %s", path ? path : "unknown" );
1313 break;
1314 case GIT_CHECKOUT_NOTIFY_UNTRACKED:
1315 wxLogTrace( traceGit, "Checkout untracked: %s", path ? path : "unknown" );
1316 break;
1317 case GIT_CHECKOUT_NOTIFY_IGNORED:
1318 wxLogTrace( traceGit, "Checkout ignored: %s", path ? path : "unknown" );
1319 break;
1320 default:
1321 break;
1322 }
1323
1324 return 0;
1325 };
1326
1327 checkoutOptions.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS;
1328 checkoutOptions.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
1329 checkoutOptions.notify_cb = notify_cb;
1330
1331 if( git_checkout_tree( aHandler->GetRepo(), reinterpret_cast<git_object*>( targetTree ),
1332 &checkoutOptions ) != GIT_OK )
1333 {
1334 aHandler->AddErrorString( _( "Failed to perform checkout operation." ) );
1335 return PullResult::Error;
1336 }
1337
1338 git_reference* updatedRef = nullptr;
1339
1340 if( git_reference_set_target( &updatedRef, rawRef, &updatedRefOid, nullptr ) != GIT_OK )
1341 {
1342 aHandler->AddErrorString( wxString::Format( _( "Failed to update reference '%s' to point to '%s'" ),
1343 currentBranchName, git_oid_tostr_s( &updatedRefOid ) ) );
1344 return PullResult::Error;
1345 }
1346
1347 KIGIT::GitReferencePtr updatedRefPtr( updatedRef );
1348
1349 if( git_repository_state_cleanup( aHandler->GetRepo() ) != GIT_OK )
1350 {
1351 aHandler->AddErrorString( _( "Failed to clean up repository state after fast-forward." ) );
1352 return PullResult::Error;
1353 }
1354
1355 git_revwalk* revWalker = nullptr;
1356
1357 if( git_revwalk_new( &revWalker, aHandler->GetRepo() ) != GIT_OK )
1358 {
1359 aHandler->AddErrorString( _( "Failed to initialize revision walker." ) );
1360 return PullResult::Error;
1361 }
1362
1363 KIGIT::GitRevWalkPtr revWalkerPtr( revWalker );
1364 git_revwalk_sorting( revWalker, GIT_SORT_TIME );
1365
1366 if( git_revwalk_push_glob( revWalker, currentBranchName ) != GIT_OK )
1367 {
1368 aHandler->AddErrorString( _( "Failed to push reference to revision walker." ) );
1369 return PullResult::Error;
1370 }
1371
1372 std::pair<std::string, std::vector<CommitDetails>>& branchCommits = aHandler->m_fetchResults.emplace_back();
1373 branchCommits.first = currentBranchName;
1374
1375 git_oid commitOid;
1376
1377 while( git_revwalk_next( &commitOid, revWalker ) == GIT_OK )
1378 {
1379 git_commit* commit = nullptr;
1380
1381 if( git_commit_lookup( &commit, aHandler->GetRepo(), &commitOid ) )
1382 {
1383 aHandler->AddErrorString( wxString::Format( _( "Could not lookup commit '%s'" ),
1384 git_oid_tostr_s( &commitOid ) ) );
1385 return PullResult::Error;
1386 }
1387
1388 KIGIT::GitCommitPtr commitPtr( commit );
1389
1390 CommitDetails details;
1391 details.m_sha = git_oid_tostr_s( &commitOid );
1392 details.m_firstLine = getFirstLineFromCommitMessage( git_commit_message( commit ) );
1393 details.m_author = git_commit_author( commit )->name;
1394 details.m_date = getFormattedCommitDate( git_commit_author( commit )->when );
1395
1396 branchCommits.second.push_back( details );
1397 }
1398
1400}
1401
1402
1403bool LIBGIT_BACKEND::hasUnstagedChanges( git_repository* aRepo )
1404{
1405 if( !aRepo )
1406 return false;
1407
1408 git_status_options opts;
1409 git_status_init_options( &opts, GIT_STATUS_OPTIONS_VERSION );
1410
1411 // Only check workdir changes (unstaged), not index changes (staged)
1412 opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
1413 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED;
1414
1415 git_status_list* status_list = nullptr;
1416
1417 if( git_status_list_new( &status_list, aRepo, &opts ) != GIT_OK )
1418 {
1419 wxLogTrace( traceGit, "Failed to get status list: %s", KIGIT_COMMON::GetLastGitError() );
1420 return false;
1421 }
1422
1423 KIGIT::GitStatusListPtr status_list_ptr( status_list );
1424 size_t count = git_status_list_entrycount( status_list );
1425
1426 // Check if any of the entries are actual modifications (not just untracked files)
1427 for( size_t ii = 0; ii < count; ++ii )
1428 {
1429 const git_status_entry* entry = git_status_byindex( status_list, ii );
1430
1431 // Check for actual workdir modifications, not just untracked files
1432 if( entry->status & ( GIT_STATUS_WT_MODIFIED | GIT_STATUS_WT_DELETED | GIT_STATUS_WT_TYPECHANGE ) )
1433 {
1434 return true;
1435 }
1436 }
1437
1438 return false;
1439}
1440
1441
1443 const git_annotated_commit** aMergeHeads,
1444 size_t aMergeHeadsCount )
1445{
1446 // Check for unstaged changes before attempting merge
1447 if( hasUnstagedChanges( aHandler->GetRepo() ) )
1448 {
1449 aHandler->AddErrorString(
1450 _( "Cannot merge: you have unstaged changes. "
1451 "Please commit or stash them before pulling." ) );
1453 }
1454
1455 git_repository* repo = aHandler->GetRepo();
1456
1457 if( git_merge( repo, aMergeHeads, aMergeHeadsCount, nullptr, nullptr ) )
1458 {
1459 wxString errorMsg = KIGIT_COMMON::GetLastGitError();
1460 aHandler->AddErrorString( wxString::Format( _( "Merge failed: %s" ), errorMsg ) );
1462 }
1463
1464 git_index* index = nullptr;
1465
1466 if( git_repository_index( &index, repo ) != GIT_OK )
1467 {
1468 aHandler->AddErrorString( _( "Could not read repository index after merge." ) );
1470 }
1471
1472 KIGIT::GitIndexPtr indexPtr( index );
1473
1474 if( git_index_has_conflicts( index ) )
1475 {
1476 // Abort the merge and restore the pre-pull state
1477 git_object* head_obj = nullptr;
1478
1479 if( git_revparse_single( &head_obj, repo, "HEAD" ) == GIT_OK )
1480 {
1481 KIGIT::GitObjectPtr headObjPtr( head_obj );
1482 git_reset( repo, head_obj, GIT_RESET_HARD, nullptr );
1483 }
1484
1485 git_repository_state_cleanup( repo );
1486
1487 return PullResult::Conflict;
1488 }
1489
1490 git_oid tree_oid;
1491 git_tree* tree = nullptr;
1492 git_reference* head_ref = nullptr;
1493 git_oid head_oid;
1494 git_commit* head_commit = nullptr;
1495 git_commit* merge_commit = nullptr;
1496 git_signature* signature = nullptr;
1497
1498 if( git_index_write_tree( &tree_oid, index ) != GIT_OK || git_tree_lookup( &tree, repo, &tree_oid ) != GIT_OK )
1499 {
1500 aHandler->AddErrorString( _( "Could not write the merge result." ) );
1502 }
1503
1504 KIGIT::GitTreePtr treePtr( tree );
1505
1506 if( git_repository_head( &head_ref, repo ) != GIT_OK )
1507 {
1508 aHandler->AddErrorString( _( "Could not get the repository head." ) );
1510 }
1511
1512 KIGIT::GitReferencePtr headRefPtr( head_ref );
1513
1514 if( git_reference_name_to_id( &head_oid, repo, "HEAD" ) != GIT_OK
1515 || git_commit_lookup( &head_commit, repo, &head_oid ) != GIT_OK
1516 || git_commit_lookup( &merge_commit, repo, git_annotated_commit_id( aMergeHeads[0] ) ) != GIT_OK )
1517 {
1518 aHandler->AddErrorString( _( "Could not look up the commits to merge." ) );
1520 }
1521
1522 KIGIT::GitCommitPtr headCommitPtr( head_commit );
1523 KIGIT::GitCommitPtr mergeCommitPtr( merge_commit );
1524
1525 if( git_signature_default( &signature, repo ) != GIT_OK )
1526 {
1527 aHandler->AddErrorString( _( "Could not create a commit signature. Set user.name "
1528 "and user.email in your git configuration." ) );
1530 }
1531
1532 KIGIT::GitSignaturePtr signaturePtr( signature );
1533
1534 const git_commit* parents[] = { head_commit, merge_commit };
1535 wxString message =
1536 wxString::Format( _( "Merge remote-tracking branch into %s" ), git_reference_shorthand( head_ref ) );
1537 git_oid merge_commit_oid;
1538
1539 if( git_commit_create( &merge_commit_oid, repo, "HEAD", signature, signature, nullptr,
1540 message.utf8_string().c_str(), tree, 2, parents )
1541 != GIT_OK )
1542 {
1543 aHandler->AddErrorString(
1544 wxString::Format( _( "Could not create merge commit: %s" ), KIGIT_COMMON::GetLastGitError() ) );
1546 }
1547
1548 git_repository_state_cleanup( repo );
1549 return PullResult::Success;
1550}
1551
1552
1554 const git_annotated_commit** aMergeHeads,
1555 size_t aMergeHeadsCount )
1556{
1557 // Check for unstaged changes before attempting rebase
1558 if( hasUnstagedChanges( aHandler->GetRepo() ) )
1559 {
1560 aHandler->AddErrorString(
1561 _( "Cannot pull with rebase: you have unstaged changes. "
1562 "Please commit or stash them before pulling." ) );
1564 }
1565
1566 git_rebase_options rebase_opts;
1567 git_rebase_init_options( &rebase_opts, GIT_REBASE_OPTIONS_VERSION );
1568
1569 git_rebase* rebase = nullptr;
1570
1571 if( git_rebase_init( &rebase, aHandler->GetRepo(), nullptr, aMergeHeads[0], nullptr, &rebase_opts ) )
1572 {
1573 wxString errorMsg = KIGIT_COMMON::GetLastGitError();
1574 aHandler->AddErrorString( wxString::Format( _( "Rebase failed to start: %s" ), errorMsg ) );
1576 }
1577
1578 KIGIT::GitRebasePtr rebasePtr( rebase );
1579
1580 while( true )
1581 {
1582 git_rebase_operation* op = nullptr;
1583
1584 if( git_rebase_next( &op, rebase ) != 0 )
1585 break;
1586
1587 if( git_rebase_commit( nullptr, rebase, nullptr, nullptr, nullptr, nullptr ) )
1588 {
1589 wxString errorMsg = KIGIT_COMMON::GetLastGitError();
1590 aHandler->AddErrorString( wxString::Format( _( "Rebase commit failed: %s" ), errorMsg ) );
1592 }
1593 }
1594
1595 if( git_rebase_finish( rebase, nullptr ) )
1596 {
1597 wxString errorMsg = KIGIT_COMMON::GetLastGitError();
1598 aHandler->AddErrorString( wxString::Format( _( "Rebase finish failed: %s" ), errorMsg ) );
1600 }
1601
1602 return PullResult::Success;
1603}
1604
1605
1606static bool lookup_upstream_ref( GIT_PULL_HANDLER* aHandler, git_reference** aUpstreamRef )
1607{
1608 git_repository* repo = aHandler->GetRepo();
1609 git_reference* head_ref = nullptr;
1610
1611 if( git_repository_head( &head_ref, repo ) != GIT_OK )
1612 return false;
1613
1614 KIGIT::GitReferencePtr headRefPtr( head_ref );
1615 wxString remoteName = aHandler->GetCommon()->GetRemoteNameOrDefault();
1616 wxString remoteRef = wxString::Format( "refs/remotes/%s/%s", remoteName, git_reference_shorthand( head_ref ) );
1617
1618 return git_reference_lookup( aUpstreamRef, repo, remoteRef.utf8_string().c_str() ) == GIT_OK;
1619}
1620
1621
1623{
1624 git_repository* repo = aHandler->GetRepo();
1625
1626 if( !repo )
1627 return false;
1628
1629 std::unique_lock<std::mutex> lock( aHandler->GetCommon()->m_gitActionMutex, std::try_to_lock );
1630
1631 if( !lock.owns_lock() )
1632 {
1633 aHandler->AddErrorString( _( "Another git operation is in progress." ) );
1634 return false;
1635 }
1636
1637 git_reference* upstream_ref = nullptr;
1638
1639 if( !lookup_upstream_ref( aHandler, &upstream_ref ) )
1640 {
1641 aHandler->AddErrorString( _( "Could not find the matching remote branch." ) );
1642 return false;
1643 }
1644
1645 KIGIT::GitReferencePtr upstreamRefPtr( upstream_ref );
1646 git_object* target = nullptr;
1647
1648 if( git_reference_peel( &target, upstream_ref, GIT_OBJECT_COMMIT ) != GIT_OK )
1649 {
1650 aHandler->AddErrorString( _( "Could not read the remote branch." ) );
1651 return false;
1652 }
1653
1654 KIGIT::GitObjectPtr targetPtr( target );
1655
1656 if( git_reset( repo, target, GIT_RESET_HARD, nullptr ) != GIT_OK )
1657 {
1658 aHandler->AddErrorString(
1659 wxString::Format( _( "Could not reset to the remote branch: %s" ), KIGIT_COMMON::GetLastGitError() ) );
1660 return false;
1661 }
1662
1663 git_repository_state_cleanup( repo );
1664 return true;
1665}
1666
1667
1669{
1670 git_repository* repo = aHandler->GetRepo();
1671
1672 if( !repo )
1673 return PullResult::Error;
1674
1675 std::unique_lock<std::mutex> lock( aHandler->GetCommon()->m_gitActionMutex, std::try_to_lock );
1676
1677 if( !lock.owns_lock() )
1678 {
1679 aHandler->AddErrorString( _( "Another git operation is in progress." ) );
1680 return PullResult::Error;
1681 }
1682
1683 if( hasUnstagedChanges( repo ) )
1684 {
1685 aHandler->AddErrorString( _( "Cannot rebase: you have unstaged changes. Please commit "
1686 "or stash them first." ) );
1688 }
1689
1690 git_reference* upstream_ref = nullptr;
1691
1692 if( !lookup_upstream_ref( aHandler, &upstream_ref ) )
1693 {
1694 aHandler->AddErrorString( _( "Could not find the matching remote branch." ) );
1695 return PullResult::Error;
1696 }
1697
1698 KIGIT::GitReferencePtr upstreamRefPtr( upstream_ref );
1699 git_annotated_commit* onto = nullptr;
1700
1701 if( git_annotated_commit_from_ref( &onto, repo, upstream_ref ) != GIT_OK )
1702 {
1703 aHandler->AddErrorString( _( "Could not read the remote branch." ) );
1704 return PullResult::Error;
1705 }
1706
1707 KIGIT::GitAnnotatedCommitPtr ontoPtr( onto );
1708 git_signature* signature = nullptr;
1709
1710 if( git_signature_default( &signature, repo ) != GIT_OK )
1711 {
1712 aHandler->AddErrorString( _( "Could not create a commit signature. Set user.name and "
1713 "user.email in your git configuration." ) );
1714 return PullResult::Error;
1715 }
1716
1717 KIGIT::GitSignaturePtr signaturePtr( signature );
1718 git_rebase_options rebase_opts;
1719 git_rebase_init_options( &rebase_opts, GIT_REBASE_OPTIONS_VERSION );
1720 git_rebase* rebase = nullptr;
1721
1722 if( git_rebase_init( &rebase, repo, nullptr, onto, nullptr, &rebase_opts ) != GIT_OK )
1723 {
1724 aHandler->AddErrorString(
1725 wxString::Format( _( "Rebase failed to start: %s" ), KIGIT_COMMON::GetLastGitError() ) );
1726 return PullResult::Error;
1727 }
1728
1729 KIGIT::GitRebasePtr rebasePtr( rebase );
1730 git_rebase_operation* op = nullptr;
1731
1732 while( git_rebase_next( &op, rebase ) == GIT_OK )
1733 {
1734 git_index* index = nullptr;
1735
1736 if( git_repository_index( &index, repo ) == GIT_OK )
1737 {
1738 KIGIT::GitIndexPtr indexPtr( index );
1739
1740 if( git_index_has_conflicts( index ) )
1741 {
1742 git_rebase_abort( rebase );
1743 aHandler->AddErrorString( _( "The rebase ran into conflicts. This is best "
1744 "resolved from a git command line." ) );
1745 return PullResult::Conflict;
1746 }
1747 }
1748
1749 git_oid commit_oid;
1750
1751 if( git_rebase_commit( &commit_oid, rebase, nullptr, signature, nullptr, nullptr ) != GIT_OK )
1752 {
1753 git_rebase_abort( rebase );
1754 aHandler->AddErrorString( _( "The rebase ran into conflicts. This is best "
1755 "resolved from a git command line." ) );
1756 return PullResult::Conflict;
1757 }
1758 }
1759
1760 if( git_rebase_finish( rebase, signature ) != GIT_OK )
1761 {
1762 git_rebase_abort( rebase );
1763 aHandler->AddErrorString(
1764 wxString::Format( _( "Rebase finish failed: %s" ), KIGIT_COMMON::GetLastGitError() ) );
1765 return PullResult::Error;
1766 }
1767
1768 return PullResult::Success;
1769}
1770
1771
1773{
1774 git_object* head_commit = NULL;
1775 git_checkout_options opts;
1776 git_checkout_init_options( &opts, GIT_CHECKOUT_OPTIONS_VERSION );
1777
1778 if( git_revparse_single( &head_commit, aHandler->m_repository, "HEAD" ) != 0 )
1779 {
1780 return;
1781 }
1782
1783 opts.checkout_strategy = GIT_CHECKOUT_FORCE;
1784 char** paths = new char*[aHandler->m_filesToRevert.size()];
1785
1786 for( size_t ii = 0; ii < aHandler->m_filesToRevert.size(); ii++ )
1787 {
1788 paths[ii] = wxStrdup( aHandler->m_filesToRevert[ii].ToUTF8() );
1789 }
1790
1791 git_strarray arr = { paths, aHandler->m_filesToRevert.size() };
1792
1793 opts.paths = arr;
1794 opts.progress_cb = nullptr;
1795 opts.notify_cb = nullptr;
1796 opts.notify_payload = static_cast<void*>( aHandler );
1797
1798 if( git_checkout_tree( aHandler->m_repository, head_commit, &opts ) != 0 )
1799 {
1800 const git_error* e = git_error_last();
1801
1802 if( e )
1803 {
1804 wxLogTrace( traceGit, wxS( "Checkout failed: %d: %s" ), e->klass, e->message );
1805 }
1806 }
1807
1808 for( size_t ii = 0; ii < aHandler->m_filesToRevert.size(); ii++ )
1809 delete( paths[ii] );
1810
1811 delete[] paths;
1812
1813 git_object_free( head_commit );
1814}
1815
1816
1817git_repository* LIBGIT_BACKEND::GetRepositoryForFile( const char* aFilename )
1818{
1819 git_repository* repo = nullptr;
1820 git_buf repo_path = GIT_BUF_INIT;
1821
1822 if( git_repository_discover( &repo_path, aFilename, 0, nullptr ) != GIT_OK )
1823 {
1824 wxLogTrace( traceGit, "Can't repo discover %s: %s", aFilename,
1826 return nullptr;
1827 }
1828
1829 KIGIT::GitBufPtr repo_path_ptr( &repo_path );
1830
1831 if( git_repository_open( &repo, repo_path.ptr ) != GIT_OK )
1832 {
1833 wxLogTrace( traceGit, "Can't open repo for %s: %s", repo_path.ptr,
1835 return nullptr;
1836 }
1837
1838 return repo;
1839}
1840
1841
1842int LIBGIT_BACKEND::CreateBranch( git_repository* aRepo, const wxString& aBranchName )
1843{
1844 git_oid head_oid;
1845
1846 if( int error = git_reference_name_to_id( &head_oid, aRepo, "HEAD" ); error != GIT_OK )
1847 {
1848 wxLogTrace( traceGit, "Failed to lookup HEAD reference: %s",
1850 return error;
1851 }
1852
1853 git_commit* commit = nullptr;
1854
1855 if( int error = git_commit_lookup( &commit, aRepo, &head_oid ); error != GIT_OK )
1856 {
1857 wxLogTrace( traceGit, "Failed to lookup commit: %s",
1859 return error;
1860 }
1861
1862 KIGIT::GitCommitPtr commitPtr( commit );
1863 git_reference* branchRef = nullptr;
1864
1865 if( int error = git_branch_create( &branchRef, aRepo, aBranchName.mb_str(), commit, 0 ); error != GIT_OK )
1866 {
1867 wxLogTrace( traceGit, "Failed to create branch: %s",
1869 return error;
1870 }
1871
1872 git_reference_free( branchRef );
1873 return 0;
1874}
1875
1876
1877bool LIBGIT_BACKEND::RemoveVCS( git_repository*& aRepo, const wxString& aProjectPath,
1878 bool aRemoveGitDir, wxString* aErrors )
1879{
1880 if( aRepo )
1881 {
1882 git_repository_free( aRepo );
1883 aRepo = nullptr;
1884 }
1885
1886 if( aRemoveGitDir )
1887 {
1888 wxFileName gitDir( aProjectPath, wxEmptyString );
1889 gitDir.AppendDir( ".git" );
1890
1891 if( gitDir.DirExists() )
1892 {
1893 wxString errors;
1894
1895 if( !RmDirRecursive( gitDir.GetPath(), &errors ) )
1896 {
1897 if( aErrors )
1898 *aErrors = errors;
1899
1900 wxLogTrace( traceGit, "Failed to remove .git directory: %s", errors );
1901 return false;
1902 }
1903 }
1904 }
1905
1906 wxLogTrace( traceGit, "Successfully removed VCS from project" );
1907 return true;
1908}
1909
1910
1911bool LIBGIT_BACKEND::AddToIndex( GIT_ADD_TO_INDEX_HANDLER* aHandler, const wxString& aFilePath )
1912{
1913 git_repository* repo = aHandler->GetRepo();
1914
1915 git_index* index = nullptr;
1916 size_t at_pos = 0;
1917
1918 if( git_repository_index( &index, repo ) != 0 )
1919 {
1920 wxLogError( "Failed to get repository index" );
1921 return false;
1922 }
1923
1924 KIGIT::GitIndexPtr indexPtr( index );
1925
1926 if( git_index_find( &at_pos, index, aFilePath.ToUTF8().data() ) == GIT_OK )
1927 {
1928 wxLogError( "%s already in index", aFilePath );
1929 return false;
1930 }
1931
1932 aHandler->m_filesToAdd.push_back( aFilePath );
1933 return true;
1934}
1935
1936
1938{
1939 git_repository* repo = aHandler->GetRepo();
1940 git_index* index = nullptr;
1941
1942 aHandler->m_filesFailedToAdd.clear();
1943
1944 if( git_repository_index( &index, repo ) != 0 )
1945 {
1946 wxLogError( "Failed to get repository index" );
1947 std::copy( aHandler->m_filesToAdd.begin(), aHandler->m_filesToAdd.end(),
1948 std::back_inserter( aHandler->m_filesFailedToAdd ) );
1949 return false;
1950 }
1951
1952 KIGIT::GitIndexPtr indexPtr( index );
1953
1954 for( auto& file : aHandler->m_filesToAdd )
1955 {
1956 if( git_index_add_bypath( index, file.ToUTF8().data() ) != 0 )
1957 {
1958 wxLogError( "Failed to add %s to index", file );
1959 aHandler->m_filesFailedToAdd.push_back( file );
1960 continue;
1961 }
1962 }
1963
1964 if( git_index_write( index ) != 0 )
1965 {
1966 wxLogError( "Failed to write index" );
1967 aHandler->m_filesFailedToAdd.clear();
1968 std::copy( aHandler->m_filesToAdd.begin(), aHandler->m_filesToAdd.end(),
1969 std::back_inserter( aHandler->m_filesFailedToAdd ) );
1970 return false;
1971 }
1972
1973 return true;
1974}
1975
1976
1977bool LIBGIT_BACKEND::RemoveFromIndex( GIT_REMOVE_FROM_INDEX_HANDLER* aHandler, const wxString& aFilePath )
1978{
1979 git_repository* repo = aHandler->GetRepo();
1980 git_index* index = nullptr;
1981 size_t at_pos = 0;
1982
1983 if( git_repository_index( &index, repo ) != 0 )
1984 {
1985 wxLogError( "Failed to get repository index" );
1986 return false;
1987 }
1988
1989 KIGIT::GitIndexPtr indexPtr( index );
1990
1991 if( git_index_find( &at_pos, index, aFilePath.ToUTF8().data() ) != 0 )
1992 {
1993 wxLogError( "Failed to find index entry for %s", aFilePath );
1994 return false;
1995 }
1996
1997 aHandler->m_filesToRemove.push_back( aFilePath );
1998 return true;
1999}
2000
2001
2003{
2004 git_repository* repo = aHandler->GetRepo();
2005
2006 for( auto& file : aHandler->m_filesToRemove )
2007 {
2008 git_index* index = nullptr;
2009 git_oid oid;
2010
2011 if( git_repository_index( &index, repo ) != 0 )
2012 {
2013 wxLogError( "Failed to get repository index" );
2014 return;
2015 }
2016
2017 KIGIT::GitIndexPtr indexPtr( index );
2018
2019 if( git_index_remove_bypath( index, file.ToUTF8().data() ) != 0 )
2020 {
2021 wxLogError( "Failed to remove index entry for %s", file );
2022 return;
2023 }
2024
2025 if( git_index_write( index ) != 0 )
2026 {
2027 wxLogError( "Failed to write index" );
2028 return;
2029 }
2030
2031 if( git_index_write_tree( &oid, index ) != 0 )
2032 {
2033 wxLogError( "Failed to write index tree" );
2034 return;
2035 }
2036 }
2037}
int index
std::vector< wxString > m_filesToAdd
std::vector< wxString > m_filesFailedToAdd
KIGIT_ORPHAN_REGISTRY m_orphanRegistry
wxString GetClonePath() const
void AddErrorString(const wxString &aErrorString)
std::vector< std::pair< std::string, std::vector< CommitDetails > > > m_fetchResults
std::vector< wxString > m_filesToRevert
git_repository * m_repository
KIGIT_COMMON::GIT_STATUS ConvertStatus(unsigned int aGitStatus)
Convert git status flags to KIGIT_COMMON::GIT_STATUS.
std::mutex m_gitActionMutex
static wxString GetLastGitError()
void SetSSHKey(const wxString &aSSHKey)
void SetUsername(const wxString &aUsername)
wxString GetRemoteNameOrDefault() const
Returns GetRemotename() when non-empty, otherwise "origin".
git_repository * GetRepo() const
void SetCancelled(bool aCancel)
void SetPassword(const wxString &aPassword)
void SetRemote(const wxString &aRemote)
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
PullResult RebaseOntoUpstream(GIT_PULL_HANDLER *aHandler) 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
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
bool ResetToUpstream(GIT_PULL_HANDLER *aHandler) override
PushResult Push(GIT_PUSH_HANDLER *aHandler, bool aForce=false) override
wxString GetWorkingDirectory(GIT_STATUS_HANDLER *aHandler) override
bool BranchExists(GIT_BRANCH_HANDLER *aHandler, const wxString &aBranchName) override
CommitResult Amend(GIT_COMMIT_HANDLER *aHandler, const std::vector< wxString > &aFiles, const wxString &aMessage, const wxString &aAuthorName, const wxString &aAuthorEmail) 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:403
CommitResult
Definition git_backend.h:52
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 bool lookup_upstream_ref(GIT_PULL_HANDLER *aHandler, git_reference **aUpstreamRef)
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.