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