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