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"
25#include "git_status_handler.h"
26#include "git_config_handler.h"
27#include "git_init_handler.h"
28#include "git_branch_handler.h"
29#include "git_pull_handler.h"
30#include "project_git_utils.h"
31#include "kicad_git_common.h"
32#include "kicad_git_memory.h"
33#include "kigit_failure.h"
34#include "trace_helpers.h"
35
36#include "kicad_git_compat.h"
37#include <wx/file.h>
38#include <wx/filename.h>
39#include <wx/log.h>
40#include <gestfich.h>
41#include <algorithm>
42#include <fstream>
43#include <iterator>
44#include <map>
45#include <memory>
46#include <optional>
47#include <set>
48#include <string>
49#include <vector>
50
51
53{
54 git_libgit2_init();
55
56 // libgit2 sets no timeout of its own, so an unreachable remote parks a fetch in connect()
57 // or recv() for as long as the OS allows. SSH remotes stay bounded only by libssh2.
58
59#if ( LIBGIT2_VER_MAJOR > 1 ) || ( LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR >= 7 )
60 constexpr int connectTimeoutMs = 10000;
61 constexpr int transferTimeoutMs = 30000;
62
63 if( git_libgit2_opts( GIT_OPT_SET_SERVER_CONNECT_TIMEOUT, connectTimeoutMs ) != GIT_OK
64 || git_libgit2_opts( GIT_OPT_SET_SERVER_TIMEOUT, transferTimeoutMs ) != GIT_OK )
65 {
66 wxLogTrace( traceGit, "LIBGIT_BACKEND::Init(): could not set server timeouts: %s",
68 }
69#endif
70}
71
72
74{
75 // Wait for any abandoned git cleanup threads to finish before tearing
76 // down libgit2. A worker still inside libgit2 (for example, blocked on
77 // recv() under git_remote_fetch) would otherwise race teardown and
78 // invoke undefined behaviour. Five seconds is long enough to cover a
79 // transport error timeout but short enough to avoid a perceptibly slow
80 // exit when the remote is truly unreachable.
81
82 constexpr auto kOrphanJoinTimeout = std::chrono::seconds( 5 );
83 size_t stuck = m_orphanRegistry.JoinAll( kOrphanJoinTimeout );
84
85 if( stuck > 0 )
86 {
87 wxLogTrace( traceGit, "LIBGIT_BACKEND::Shutdown(): %zu orphan git thread(s) did not finish within %lld ms; "
88 "skipping libgit2 shutdown",
89 stuck, static_cast<long long>( kOrphanJoinTimeout.count() ) );
90
91 // A stuck worker is still executing inside libgit2. Calling
92 // git_libgit2_shutdown() now would free state the worker is actively
93 // reading. Leave libgit2 initialised and let the OS reclaim
94 // resources when the process exits.
95
96 return;
97 }
98
99 git_libgit2_shutdown();
100}
101
102
104{
105#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
106 int major = 0, minor = 0, rev = 0;
107 return git_libgit2_version( &major, &minor, &rev ) == GIT_OK;
108#else
109 // On older platforms, assume available when building with libgit2
110 return true;
111#endif
112}
113
114
115// git_signature_default answers GIT_ENOTFOUND only when user.name or user.email is not set
117{
118 if( aRc == GIT_ENOTFOUND )
119 return { _( "Set your name and email before committing (gear menu > Set Author...)." ), wxEmptyString };
120
121 return { _( "Could not record the commit author." ), KIGIT_COMMON::GetLastGitError() };
122}
123
124
125static void reportRemoteLookupFailure( KIGIT_ERRORS* aHandler, int aRc, const wxString& aRemoteName )
126{
127 if( aRc == GIT_ENOTFOUND )
128 {
129 aHandler->AddError( wxString::Format( _( "There is no remote named '%s'. Add it on the Remotes page of the "
130 "Advanced Git window." ),
131 aRemoteName ),
132 wxEmptyString );
133 return;
134 }
135
136 aHandler->AddError( wxString::Format( _( "Could not read the remote '%s'." ), aRemoteName ),
138}
139
140
141// url.insteadOf can rewrite the clone URL, which would otherwise read as a new identity and drop the
142// credentials entered for the original
143static int clone_remote_cb( git_remote** aOut, git_repository* aRepo, const char* aName, const char* aUrl,
144 void* aPayload )
145{
146 if( int rc = git_remote_create( aOut, aRepo, aName, aUrl ); rc != GIT_OK )
147 return rc;
148
149 if( const char* url = git_remote_url( *aOut ) )
150 static_cast<KIGIT_COMMON*>( aPayload )->FollowRewrittenUrl( wxString::FromUTF8( url ) );
151
152 return GIT_OK;
153}
154
155
157{
158 KIGIT_COMMON* common = aHandler->GetCommon();
159 std::unique_lock<std::mutex> lock = aHandler->GetActionLock();
160
161 if( !lock.owns_lock() )
162 {
163 wxLogTrace( traceGit, "GIT_CLONE_HANDLER::PerformClone() could not lock" );
164 return false;
165 }
166
167 // Without this the progress dialog's Cancel would set a token nothing reads.
168 KIGIT_COMMON::OPERATION_GUARD operation( common, aHandler->CancelToken() );
169
170 if( aHandler->IsThisOperationCancelled() )
171 return false;
172
173 wxFileName clonePath( aHandler->GetClonePath() );
174
175 if( !clonePath.DirExists() )
176 {
177 if( !clonePath.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
178 {
179 aHandler->AddErrorString( wxString::Format( _( "Could not create directory '%s'" ),
180 aHandler->GetClonePath() ) );
181 return false;
182 }
183 }
184
185 git_clone_options cloneOptions;
186 git_clone_init_options( &cloneOptions, GIT_CLONE_OPTIONS_VERSION );
187 cloneOptions.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
188 cloneOptions.checkout_opts.progress_cb = clone_progress_cb;
189 cloneOptions.checkout_opts.progress_payload = aHandler;
190 cloneOptions.fetch_opts.callbacks.transfer_progress = transfer_progress_cb;
191 cloneOptions.fetch_opts.callbacks.credentials = credentials_cb;
192 cloneOptions.fetch_opts.callbacks.certificate_check = certificate_check_cb;
193 cloneOptions.fetch_opts.callbacks.payload = aHandler;
194 cloneOptions.fetch_opts.proxy_opts.type = GIT_PROXY_AUTO;
195 cloneOptions.remote_cb = clone_remote_cb;
196 cloneOptions.remote_cb_payload = common;
197
198 aHandler->ResetTransferState();
199 git_repository* newRepo = nullptr;
200 wxString remote = common->GetRemote();
201 std::string remoteUtf8 = remote.utf8_string();
202
203 if( int rc = git_clone( &newRepo, remoteUtf8.c_str(), aHandler->GetClonePath().mbc_str(), &cloneOptions ) )
204 {
206 wxEmptyString );
207 return false;
208 }
209
210 common->SetRepo( newRepo );
211
212 return true;
213}
214
215
216// Enough of a path's staged state to tell whether something else restaged it.
218{
219 bool present = false;
220 git_oid id = {};
221 uint32_t mode = 0;
222
223 bool operator==( const SELECTED_STATE& aOther ) const
224 {
225 return present == aOther.present && mode == aOther.mode && ( !present || git_oid_equal( &id, &aOther.id ) );
226 }
227
228 bool operator!=( const SELECTED_STATE& aOther ) const { return !( *this == aOther ); }
229};
230
231
232static std::map<std::string, SELECTED_STATE> readSelectedState( git_index* aIndex,
233 const std::set<std::string>& aSelected )
234{
235 std::map<std::string, SELECTED_STATE> result;
236
237 for( const std::string& path : aSelected )
238 {
239 SELECTED_STATE state;
240
241 if( const git_index_entry* entry = git_index_get_bypath( aIndex, path.c_str(), 0 ) )
242 {
243 state.present = true;
244 state.id = entry->id;
245 state.mode = entry->mode;
246 }
247
248 result[path] = state;
249 }
250
251 return result;
252}
253
254
255static std::map<std::string, SELECTED_STATE> readWholeState( git_index* aIndex )
256{
257 std::map<std::string, SELECTED_STATE> result;
258 size_t count = git_index_entrycount( aIndex );
259
260 for( size_t ii = 0; ii < count; ii++ )
261 {
262 if( const git_index_entry* entry = git_index_get_byindex( aIndex, ii ) )
263 {
264 SELECTED_STATE state;
265 state.present = true;
266 state.id = entry->id;
267 state.mode = entry->mode;
268 result[entry->path] = state;
269 }
270 }
271
272 return result;
273}
274
275
276// The index file itself, not the scratch copy the handle holds. Null when it cannot be read.
277static git_index* openIndexOnDisk( git_index* aRepoIndex )
278{
279 const char* indexPath = git_index_path( aRepoIndex );
280
281 if( !indexPath )
282 return nullptr;
283
284 git_index* fresh = nullptr;
285
286 if( git_index_open( &fresh, indexPath ) != GIT_OK )
287 return nullptr;
288
289 int rc = git_index_read( fresh, 1 );
290
291 // A repository with nothing staged yet has no index file, which reads as empty.
292 if( rc != GIT_OK && rc != GIT_ENOTFOUND )
293 {
294 git_index_free( fresh );
295 return nullptr;
296 }
297
298 return fresh;
299}
300
301
302static bool selectedStateOnDisk( git_index* aRepoIndex, const std::set<std::string>& aSelected,
303 std::map<std::string, SELECTED_STATE>& aOut )
304{
305 git_index* fresh = openIndexOnDisk( aRepoIndex );
306
307 if( !fresh )
308 return false;
309
310 KIGIT::GitIndexPtr freshPtr( fresh );
311 aOut = readSelectedState( fresh, aSelected );
312 return true;
313}
314
315
316static bool wholeStateOnDisk( git_index* aRepoIndex, std::map<std::string, SELECTED_STATE>& aOut )
317{
318 git_index* fresh = openIndexOnDisk( aRepoIndex );
319
320 if( !fresh )
321 return false;
322
323 KIGIT::GitIndexPtr freshPtr( fresh );
324 aOut = readWholeState( fresh );
325 return true;
326}
327
328
329// Owns its path, because index entries point into storage that rebuilding invalidates.
331{
332 std::string path;
333 git_index_entry entry;
334};
335
336
337// The index is scratch until the commit exists, so every way out reloads what the user staged.
339{
340 git_index* index = nullptr;
341 bool armed = true;
342
344 {
345 if( armed && index )
346 git_index_read( index, 1 );
347 }
348};
349
350
351// Keeps staging done outside KiCad, and moves the selected paths to what was just committed.
352// A listed file missing from the working tree is a deletion to record, not a path to add.
353static bool applySelectedPaths( git_repository* aRepo, git_index* aIndex, const std::vector<wxString>& aFiles )
354{
355 const char* workdir = git_repository_workdir( aRepo );
356
357 for( const wxString& file : aFiles )
358 {
359 // Anything at the path counts. A submodule is a directory and a symlink may point at
360 // one or at nothing, and reading those as deletions drops them out of the project.
361 bool onDisk = workdir
362 && wxFileName::Exists( wxString::FromUTF8( workdir ) + file,
363 wxFILE_EXISTS_ANY | wxFILE_EXISTS_NO_FOLLOW );
364
365 // libgit2 paths are UTF-8, so convert explicitly rather than through the locale.
366 std::string path = file.utf8_string();
367
368 int rc =
369 onDisk ? git_index_add_bypath( aIndex, path.c_str() ) : git_index_remove_bypath( aIndex, path.c_str() );
370
371 if( rc != 0 )
372 return false;
373 }
374
375 return true;
376}
377
378
379// A commit landing between the tree build and the parent lookup would be undone by this one.
380static bool branchMovedSince( git_repository* aRepo, const git_oid& aHeadAtStart, bool aHadHead )
381{
382 git_oid headNow;
383 bool hasHeadNow = git_reference_name_to_id( &headNow, aRepo, "HEAD" ) == GIT_OK;
384
385 return hasHeadNow != aHadHead || ( hasHeadNow && !git_oid_equal( &headNow, &aHeadAtStart ) );
386}
387
388
389// Something restaged a file being committed. Moving it on would destroy that record.
390static bool selectedPathsRestaged( git_index* aIndex, const std::set<std::string>& aSelected,
391 const std::map<std::string, SELECTED_STATE>& aAtStart )
392{
393 std::map<std::string, SELECTED_STATE> now;
394
395 return !selectedStateOnDisk( aIndex, aSelected, now ) || now != aAtStart;
396}
397
398
399// Catches a file staged outside KiCad that this commit does not list, which the check above misses.
400static bool indexChangedSince( git_index* aIndex, const std::map<std::string, SELECTED_STATE>& aAtStart )
401{
402 std::map<std::string, SELECTED_STATE> now;
403
404 return !wholeStateOnDisk( aIndex, now ) || now != aAtStart;
405}
406
407
414
415
416// Reread and compare before writing, or staging done elsewhere is overwritten.
417static REPUBLISH_RESULT republishIndex( git_index* aIndex, const std::vector<STAGED_ENTRY>& aCommitted,
418 const std::set<std::string>& aSelected )
419{
420 for( int attempt = 0; attempt < 3; attempt++ )
421 {
422 if( git_index_read( aIndex, 1 ) != 0 )
424
425 std::map<std::string, SELECTED_STATE> readState = readWholeState( aIndex );
426
427 for( const std::string& path : aSelected )
428 git_index_remove_bypath( aIndex, path.c_str() );
429
430 for( const STAGED_ENTRY& staged : aCommitted )
431 {
432 git_index_entry entry = staged.entry;
433 entry.path = staged.path.c_str();
434
435 if( git_index_add( aIndex, &entry ) != 0 )
437 }
438
439 std::map<std::string, SELECTED_STATE> diskState;
440
441 if( !wholeStateOnDisk( aIndex, diskState ) )
443
444 if( diskState != readState )
445 continue;
446
447 if( git_index_write( aIndex ) == 0 )
449
451 }
452
454}
455
456
457// Rebuilding from HEAD drops conflict stages, so markers would commit as resolved content.
458static bool refuseWhileOperationInProgress( git_repository* aRepo, git_index* aIndex, wxString& aError )
459{
460 if( git_index_has_conflicts( aIndex ) )
461 {
462 aError = _( "This project has conflicts that are not resolved yet. Finish or abort the merge "
463 "with git, then commit here." );
464 return true;
465 }
466
467 wxString operation;
468
469 switch( git_repository_state( aRepo ) )
470 {
471 case GIT_REPOSITORY_STATE_NONE: return false;
472 case GIT_REPOSITORY_STATE_MERGE: operation = _( "merge" ); break;
473 case GIT_REPOSITORY_STATE_REVERT:
474 case GIT_REPOSITORY_STATE_REVERT_SEQUENCE: operation = _( "revert" ); break;
475 case GIT_REPOSITORY_STATE_CHERRYPICK:
476 case GIT_REPOSITORY_STATE_CHERRYPICK_SEQUENCE: operation = _( "cherry-pick" ); break;
477 case GIT_REPOSITORY_STATE_BISECT: operation = _( "bisect" ); break;
478 default: operation = _( "rebase" ); break;
479 }
480
481 aError = wxString::Format( _( "This project is in the middle of a %s. Finish or abort it with git, "
482 "then commit here." ),
483 operation );
484 return true;
485}
486
487
488// What Commit and Amend do identically: take the index over as scratch space, remember what it
489// held, stage the chosen files over a base tree, and refuse if anything moved underneath while
490// the tree was being built. Only the base tree and the commit call itself differ between them.
492{
493 git_index* index = nullptr;
495 std::set<std::string> selectedPaths;
496 std::map<std::string, SELECTED_STATE> selectedAtStart;
497 std::map<std::string, SELECTED_STATE> indexAtStart;
498 bool haveIndexAtStart = false;
499 git_oid headAtStart = {};
500 bool hadHeadAtStart = false;
502 std::vector<STAGED_ENTRY> committedEntries;
503
505 bool Begin( git_repository* aRepo, const std::vector<wxString>& aFiles, KIGIT_ERROR_MESSAGE& aError )
506 {
507 if( git_repository_index( &index, aRepo ) != 0 )
508 {
509 aError = { _( "Could not read the list of staged files." ), KIGIT_COMMON::GetLastGitError() };
510 return false;
511 }
512
513 indexPtr.reset( index );
514
515 // The action lock only serializes KiCad, so read what is on disk before judging it.
516 git_index_read( index, 1 );
517
518 wxString blockedError;
519
520 if( refuseWhileOperationInProgress( aRepo, index, blockedError ) )
521 {
522 aError = { blockedError, wxEmptyString };
523 return false;
524 }
525
526 for( const wxString& file : aFiles )
527 selectedPaths.insert( file.utf8_string() );
528
531 hadHeadAtStart = git_reference_name_to_id( &headAtStart, aRepo, "HEAD" ) == GIT_OK;
532
533 // Armed last, so an early return above leaves the real index alone.
534 scratchGuard.index = index;
535 return true;
536 }
537
540 bool BuildTree( git_repository* aRepo, const std::vector<wxString>& aFiles, KIGIT::GitTreePtr& aTreeOut,
541 KIGIT_ERROR_MESSAGE& aError )
542 {
543 if( !applySelectedPaths( aRepo, index, aFiles ) )
544 {
545 aError = { _( "Could not stage the selected files." ), KIGIT_COMMON::GetLastGitError() };
546 return false;
547 }
548
549 // Writing this scratch index is what would unstage everything staged outside KiCad, so
550 // only the tree it describes is written.
551 for( const std::string& path : selectedPaths )
552 {
553 if( const git_index_entry* entry = git_index_get_bypath( index, path.c_str(), 0 ) )
554 committedEntries.push_back( { path, *entry } );
555 }
556
557 git_oid treeId;
558
559 if( git_index_write_tree( &treeId, index ) != 0 )
560 {
561 aError = { _( "Could not prepare the commit." ), KIGIT_COMMON::GetLastGitError() };
562 return false;
563 }
564
565 git_tree* tree = nullptr;
566
567 if( git_tree_lookup( &tree, aRepo, &treeId ) != 0 )
568 {
569 aError = { _( "Could not prepare the commit." ), KIGIT_COMMON::GetLastGitError() };
570 return false;
571 }
572
573 aTreeOut.reset( tree );
574
575 // Nothing is written yet, so the scratch guard puts the real index back on the way out.
577 {
578 aError = { _( "The branch moved while this was running, so nothing was "
579 "committed. Review the changes and try again." ),
580 wxEmptyString };
581 return false;
582 }
583
586 {
587 aError = { _( "The staged files changed outside KiCad while this ran. Nothing "
588 "was committed. Review the changes and try again." ),
589 wxEmptyString };
590 return false;
591 }
592
593 return true;
594 }
595};
596
597
598CommitResult LIBGIT_BACKEND::Commit( GIT_COMMIT_HANDLER* aHandler, const std::vector<wxString>& aFiles,
599 const wxString& aMessage, const wxString& aAuthorName,
600 const wxString& aAuthorEmail )
601{
602 git_repository* repo = aHandler->GetRepo();
603
604 if( !repo )
605 return CommitResult::Error;
606
607 COMMIT_STAGING staging;
608 KIGIT_ERROR_MESSAGE stagingError;
609
610 if( !staging.Begin( repo, aFiles, stagingError ) )
611 {
612 aHandler->AddError( stagingError );
613 return CommitResult::Error;
614 }
615
616 git_index* index = staging.index;
617
618 // Start from the HEAD tree, not the index, so the commit holds exactly the listed files.
619 if( git_repository_head_unborn( repo ) == 0 )
620 {
621 git_reference* headRef = nullptr;
622
623 if( git_repository_head( &headRef, repo ) != 0 )
624 {
625 aHandler->AddGitError( _( "Could not read the current commit." ) );
626 return CommitResult::Error;
627 }
628
629 KIGIT::GitReferencePtr headRefPtr( headRef );
630 git_tree* headTree = nullptr;
631
632 if( git_reference_peel( (git_object**) &headTree, headRef, GIT_OBJECT_TREE ) != 0 )
633 {
634 aHandler->AddGitError( _( "Could not read the current commit." ) );
635 return CommitResult::Error;
636 }
637
638 KIGIT::GitTreePtr headTreePtr( headTree );
639
640 if( git_index_read_tree( index, headTree ) != 0 )
641 {
642 aHandler->AddGitError( _( "Could not prepare the commit." ) );
643 return CommitResult::Error;
644 }
645 }
646 else if( git_index_clear( index ) != 0 )
647 {
648 aHandler->AddGitError( _( "Could not prepare the commit." ) );
649 return CommitResult::Error;
650 }
651
652 KIGIT::GitTreePtr treePtr;
653
654 if( !staging.BuildTree( repo, aFiles, treePtr, stagingError ) )
655 {
656 aHandler->AddError( stagingError );
657 return CommitResult::Error;
658 }
659
660 git_tree* tree = treePtr.get();
661
662 git_commit* parent = nullptr;
663
664 if( git_repository_head_unborn( repo ) == 0 )
665 {
666 git_reference* headRef = nullptr;
667
668 if( git_repository_head( &headRef, repo ) != 0 )
669 {
670 aHandler->AddGitError( _( "Could not read the current commit." ) );
671 return CommitResult::Error;
672 }
673
674 KIGIT::GitReferencePtr headRefPtr( headRef );
675
676 if( git_reference_peel( (git_object**) &parent, headRef, GIT_OBJECT_COMMIT ) != 0 )
677 {
678 aHandler->AddGitError( _( "Could not read the current commit." ) );
679 return CommitResult::Error;
680 }
681 }
682
683 KIGIT::GitCommitPtr parentPtr( parent );
684
685 git_signature* author = nullptr;
686
687 if( git_signature_now( &author, aAuthorName.utf8_string().c_str(), aAuthorEmail.utf8_string().c_str() ) != 0 )
688 {
689 aHandler->AddGitError( _( "Could not record the commit author." ) );
690 return CommitResult::Error;
691 }
692
693 KIGIT::GitSignaturePtr authorPtr( author );
694 git_oid oid;
695 size_t parentsCount = parent ? 1 : 0;
696
697#if( LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR == 8 \
698 && ( LIBGIT2_VER_REVISION < 2 || LIBGIT2_VER_REVISION == 3 ) )
699 git_commit* const parents[1] = { parent };
700 git_commit** const parentsPtr = parent ? parents : nullptr;
701#else
702 const git_commit* parents[1] = { parent };
703 const git_commit** parentsPtr = parent ? parents : nullptr;
704#endif
705
706 if( git_commit_create( &oid, repo, "HEAD", author, author, nullptr, aMessage.utf8_string().c_str(), tree,
707 parentsCount, parentsPtr )
708 != 0 )
709 {
710 aHandler->AddGitError( _( "Could not create the commit." ) );
711 return CommitResult::Error;
712 }
713
714 // The commit exists, so only the index is left. If anything restaged meanwhile, leave it.
716 || ( staging.haveIndexAtStart && indexChangedSince( index, staging.indexAtStart ) ) )
717 {
718 git_index_read( index, 1 );
719 aHandler->AddErrorString( _( "The commit was created, but the list of staged files was left "
720 "alone, because the staged files changed outside KiCad while "
721 "this ran." ) );
723 }
724
725 REPUBLISH_RESULT republished = republishIndex( index, staging.committedEntries, staging.selectedPaths );
726
727 if( republished == REPUBLISH_RESULT::CHANGED_EXTERNALLY )
728 {
729 aHandler->AddErrorString( _( "The commit was created, but the list of staged files was left "
730 "alone, because another program kept changing it." ) );
732 }
733
734 if( republished == REPUBLISH_RESULT::WRITE_FAILED )
735 {
736 aHandler->AddGitError( _( "The commit was created, but the list of staged files could not be updated." ) );
738 }
739
740 staging.scratchGuard.armed = false;
741
743}
744
745
746CommitResult LIBGIT_BACKEND::Amend( GIT_COMMIT_HANDLER* aHandler, const std::vector<wxString>& aFiles,
747 const wxString& aMessage, const wxString& aAuthorName,
748 const wxString& aAuthorEmail )
749{
750 git_repository* repo = aHandler->GetRepo();
751
752 if( !repo )
753 return CommitResult::Error;
754
755 if( git_repository_head_unborn( repo ) != 0 )
756 {
757 aHandler->AddErrorString( _( "Cannot amend: the branch has no commits yet." ) );
758 return CommitResult::Error;
759 }
760
761 git_reference* headRef = nullptr;
762
763 if( git_repository_head( &headRef, repo ) != 0 )
764 {
765 aHandler->AddGitError( _( "Could not read the current commit." ) );
766 return CommitResult::Error;
767 }
768
769 KIGIT::GitReferencePtr headRefPtr( headRef );
770 git_commit* headCommit = nullptr;
771
772 if( git_reference_peel( (git_object**) &headCommit, headRef, GIT_OBJECT_COMMIT ) != 0 )
773 {
774 aHandler->AddGitError( _( "Could not read the current commit." ) );
775 return CommitResult::Error;
776 }
777
778 KIGIT::GitCommitPtr headCommitPtr( headCommit );
779 COMMIT_STAGING staging;
780 KIGIT_ERROR_MESSAGE stagingError;
781
782 if( !staging.Begin( repo, aFiles, stagingError ) )
783 {
784 aHandler->AddError( stagingError );
785 return CommitResult::Error;
786 }
787
788 git_index* index = staging.index;
789
790 // The commit being amended, not its parent, or files it already held would disappear.
791 git_tree* headTree = nullptr;
792
793 if( git_commit_tree( &headTree, headCommit ) != 0 )
794 {
795 aHandler->AddGitError( _( "Could not read the current commit." ) );
796 return CommitResult::Error;
797 }
798
799 KIGIT::GitTreePtr headTreePtr( headTree );
800
801 if( git_index_read_tree( index, headTree ) != 0 )
802 {
803 aHandler->AddGitError( _( "Could not prepare the commit." ) );
804 return CommitResult::Error;
805 }
806
807 KIGIT::GitTreePtr treePtr;
808
809 if( !staging.BuildTree( repo, aFiles, treePtr, stagingError ) )
810 {
811 aHandler->AddError( stagingError );
812 return CommitResult::Error;
813 }
814
815 git_tree* tree = treePtr.get();
816
817 git_signature* author = nullptr;
818
819 if( git_signature_now( &author, aAuthorName.utf8_string().c_str(), aAuthorEmail.utf8_string().c_str() ) != 0 )
820 {
821 aHandler->AddGitError( _( "Could not record the commit author." ) );
822 return CommitResult::Error;
823 }
824
825 KIGIT::GitSignaturePtr authorPtr( author );
826 git_oid oid;
827
828 if( git_commit_amend( &oid, headCommit, "HEAD", author, author, nullptr, aMessage.utf8_string().c_str(), tree )
829 != 0 )
830 {
831 aHandler->AddGitError( _( "Could not amend the commit." ) );
832 return CommitResult::Error;
833 }
834
835 // The commit exists, so only the index is left. If anything restaged meanwhile, leave it.
837 || ( staging.haveIndexAtStart && indexChangedSince( index, staging.indexAtStart ) ) )
838 {
839 git_index_read( index, 1 );
840 aHandler->AddErrorString( _( "The commit was amended, but the list of staged files was left "
841 "alone, because the staged files changed outside KiCad while "
842 "this ran." ) );
844 }
845
846 REPUBLISH_RESULT republished = republishIndex( index, staging.committedEntries, staging.selectedPaths );
847
848 if( republished == REPUBLISH_RESULT::CHANGED_EXTERNALLY )
849 {
850 aHandler->AddErrorString( _( "The commit was amended, but the list of staged files was left "
851 "alone, because another program kept changing it." ) );
853 }
854
855 if( republished == REPUBLISH_RESULT::WRITE_FAILED )
856 {
857 aHandler->AddGitError( _( "The commit was amended, but the list of staged files could not be updated." ) );
859 }
860
861 staging.scratchGuard.armed = false;
862
864}
865
866
868{
869 KIGIT_COMMON* common = aHandler->GetCommon();
870 std::unique_lock<std::mutex> lock = aHandler->GetActionLock();
871
872 if( !lock.owns_lock() )
873 {
874 wxLogTrace( traceGit, "GIT_PUSH_HANDLER::PerformPush: Could not lock mutex" );
875 return PushResult::Busy;
876 }
877
878 // Publish cancellation only after taking the action lock. A cancelled or closing project
879 // must not start network work when a preceding read finally releases the repository.
880 KIGIT_COMMON::OPERATION_GUARD operation( common, aHandler->CancelToken() );
881
882 if( aHandler->IsThisOperationCancelled() || !aHandler->GetRepo() )
883 return PushResult::Error;
884
885 const wxString targetChanged = _( "The branch or its last commit changed while the push was waiting. "
886 "Review the changes and try again." );
887
888 auto matchesExpectedTarget = [aHandler]( git_reference* aHead )
889 {
890 if( !aHandler->HasExpectedTarget() )
891 return true;
892
893 if( !aHead || !git_reference_is_branch( aHead ) )
894 return false;
895
896 const git_oid* oid = git_reference_target( aHead );
897 wxString branch = wxString::FromUTF8( git_reference_shorthand( aHead ) );
898 wxString commit = oid ? wxString::FromUTF8( git_oid_tostr_s( oid ) ) : wxString();
899 return aHandler->MatchesExpectedTarget( branch, commit );
900 };
901
902 // Reject a changed confirmation before connecting to the remote. The target is checked
903 // again when selecting the ref below, because another program can update Git during a connect.
904 if( aHandler->HasExpectedTarget() )
905 {
906 git_reference* expectedHead = nullptr;
907 git_repository_head( &expectedHead, aHandler->GetRepo() );
908 KIGIT::GitReferencePtr expectedHeadPtr( expectedHead );
909
910 if( !matchesExpectedTarget( expectedHead ) )
911 {
912 aHandler->AddErrorString( targetChanged );
913 return PushResult::Error;
914 }
915 }
916
918
919 wxString remoteName = common->GetRemoteNameOrDefault();
920 std::string remoteNameUtf8 = remoteName.utf8_string();
921 git_remote* remote = nullptr;
922
923 if( int rc = git_remote_lookup( &remote, aHandler->GetRepo(), remoteNameUtf8.c_str() ); rc != GIT_OK )
924 {
925 reportRemoteLookupFailure( aHandler, rc, remoteName );
926 return PushResult::Error;
927 }
928
929 KIGIT::GitRemotePtr remotePtr(remote);
930
931 // The URL may have changed outside KiCad, and libgit2 connects to the push URL when there is one
932 const char* pushUrl = git_remote_pushurl( remote ) ? git_remote_pushurl( remote ) : git_remote_url( remote );
933
934 if( pushUrl )
935 common->SyncRemoteUrl( wxString::FromUTF8( pushUrl ) );
936
937 git_remote_callbacks remoteCallbacks;
938 git_remote_init_callbacks( &remoteCallbacks, GIT_REMOTE_CALLBACKS_VERSION );
939 remoteCallbacks.sideband_progress = progress_cb;
940 remoteCallbacks.transfer_progress = transfer_progress_cb;
941 remoteCallbacks.update_tips = update_cb;
942 remoteCallbacks.push_transfer_progress = push_transfer_progress_cb;
943 remoteCallbacks.push_update_reference = push_update_reference_cb;
944 remoteCallbacks.credentials = credentials_cb;
945 remoteCallbacks.certificate_check = certificate_check_cb;
946 remoteCallbacks.payload = aHandler;
947
948 git_proxy_options proxyOpts;
949 git_proxy_init_options( &proxyOpts, GIT_PROXY_OPTIONS_VERSION );
950 proxyOpts.type = GIT_PROXY_AUTO;
951 // Cancelled while this task was still queued, so it must not start network work.
952 if( aHandler->IsThisOperationCancelled() )
953 return PushResult::Error;
954
955 aHandler->ResetTransferState();
956
957 if( int connectResult = git_remote_connect( remote, GIT_DIRECTION_PUSH, &remoteCallbacks, &proxyOpts, nullptr ) )
958 {
960 remoteName );
961 return PushResult::Error;
962 }
963
964 git_push_options pushOptions;
965 git_push_init_options( &pushOptions, GIT_PUSH_OPTIONS_VERSION );
966 pushOptions.callbacks = remoteCallbacks;
967 pushOptions.proxy_opts.type = GIT_PROXY_AUTO;
968
969 git_reference* head = nullptr;
970
971 if( git_repository_head( &head, aHandler->GetRepo() ) != 0 )
972 {
973 git_remote_disconnect( remote );
974 aHandler->AddErrorString( _( "Could not get the repository head." ) );
975 return PushResult::Error;
976 }
977
978 KIGIT::GitReferencePtr headPtr( head );
979
980 // A detached HEAD is not a branch, so its name is the literal "HEAD" and the remote has no
981 // branch for it. Without this the push fails with nothing to show the user.
982 if( !git_reference_is_branch( head ) )
983 {
984 git_remote_disconnect( remote );
985 aHandler->AddErrorString( _( "This project is showing an old snapshot rather than a branch, so there "
986 "is nothing to push. Go back to a branch first." ) );
987 return PushResult::Error;
988 }
989
990 if( !matchesExpectedTarget( head ) )
991 {
992 git_remote_disconnect( remote );
993 aHandler->AddErrorString( targetChanged );
994 return PushResult::Error;
995 }
996
997 // Force push prepends "+" to the refspec source, matching `git push --force`.
998 wxString refspec = ( aForce ? wxS( "+" ) : wxS( "" ) ) + wxString( git_reference_name( head ) );
999 std::string refspecUtf8 = refspec.utf8_string();
1000 const char* refs[1] = { refspecUtf8.c_str() };
1001 const git_strarray refspecs = { (char**) refs, 1 };
1002
1003 if( int pushResult = git_remote_push( remote, &refspecs, &pushOptions ) )
1004 {
1006 remoteName );
1007 git_remote_disconnect( remote );
1008
1009 // The return code, not the message, which varies between libgit2 versions.
1010 return pushResult == GIT_ENONFASTFORWARD ? PushResult::NonFastForward : PushResult::Error;
1011 }
1012
1013 // First push from this branch: point it at where we just pushed (git push -u).
1014 if( git_reference_is_branch( head ) )
1015 {
1016 git_reference* upstreamRef = nullptr;
1017 int rc = git_branch_upstream( &upstreamRef, head );
1018
1019 if( rc == GIT_ENOTFOUND )
1020 {
1021 wxString upstreamName = wxString::Format( "%s/%s", remoteName, git_reference_shorthand( head ) );
1022 git_branch_set_upstream( head, upstreamName.utf8_string().c_str() );
1023 }
1024 else if( rc == GIT_OK )
1025 {
1026 KIGIT::GitReferencePtr upstreamPtr( upstreamRef );
1027 }
1028 }
1029
1030 git_remote_disconnect( remote );
1031
1032 return result;
1033}
1034
1035
1036std::map<wxString, FileStatus> LIBGIT_BACKEND::GetFileStatus( GIT_STATUS_HANDLER* aHandler, const wxString& aPathspec,
1037 bool aChangedOnly )
1038{
1039 std::map<wxString, FileStatus> fileStatusMap;
1040 git_repository* repo = aHandler->GetRepo();
1041
1042 if( !repo )
1043 return fileStatusMap;
1044
1045 git_status_options status_options;
1046 git_status_init_options( &status_options, GIT_STATUS_OPTIONS_VERSION );
1047 status_options.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
1048 status_options.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
1049
1050 // Per-file icons need an entry for every tracked file, so an icon clears when a file goes
1051 // back to unmodified. Listing only changes does not, and costs a walk of the whole repo.
1052 if( !aChangedOnly )
1053 status_options.flags |= GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
1054
1055 std::string pathspec_str;
1056 std::vector<const char*> pathspec_ptrs;
1057
1058 if( !aPathspec.IsEmpty() )
1059 {
1060 pathspec_str = aPathspec.utf8_string();
1061 pathspec_ptrs.push_back( pathspec_str.c_str() );
1062
1063 status_options.pathspec.strings = const_cast<char**>( pathspec_ptrs.data() );
1064 status_options.pathspec.count = pathspec_ptrs.size();
1065
1066 // Callers pass a folder, not a pattern. Without this a folder name holding a glob
1067 // character would match its neighbours instead of itself.
1068 status_options.flags |= GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH;
1069 }
1070
1071 git_status_list* status_list = nullptr;
1072
1073 if( git_status_list_new( &status_list, repo, &status_options ) != GIT_OK )
1074 {
1075 wxLogTrace( traceGit, "Failed to get git status list: %s", KIGIT_COMMON::GetLastGitError() );
1076 return fileStatusMap;
1077 }
1078
1079 KIGIT::GitStatusListPtr statusListPtr( status_list );
1080
1081 size_t count = git_status_list_entrycount( status_list );
1082 wxString repoWorkDir = aHandler->GetProjectDir();
1083
1084 for( size_t ii = 0; ii < count; ++ii )
1085 {
1086 const git_status_entry* entry = git_status_byindex( status_list, ii );
1087 std::string path( entry->head_to_index ? entry->head_to_index->old_file.path
1088 : entry->index_to_workdir->old_file.path );
1089
1090 wxString absPath = repoWorkDir + path;
1091 fileStatusMap[absPath] = FileStatus{ absPath,
1092 aHandler->ConvertStatus( entry->status ),
1093 static_cast<unsigned int>( entry->status ) };
1094 }
1095
1096 return fileStatusMap;
1097}
1098
1099
1101{
1102 git_repository* repo = aHandler->GetRepo();
1103
1104 if( !repo )
1105 return wxEmptyString;
1106
1107 git_reference* currentBranchReference = nullptr;
1108 int rc = git_repository_head( &currentBranchReference, repo );
1109 KIGIT::GitReferencePtr currentBranchReferencePtr( currentBranchReference );
1110
1111 if( currentBranchReference )
1112 {
1113 return git_reference_shorthand( currentBranchReference );
1114 }
1115 else if( rc == GIT_EUNBORNBRANCH )
1116 {
1117 return wxEmptyString;
1118 }
1119 else
1120 {
1121 wxLogTrace( traceGit, "Failed to lookup current branch: %s", KIGIT_COMMON::GetLastGitError() );
1122 return wxEmptyString;
1123 }
1124}
1125
1126
1128 const std::set<wxString>& aLocalChanges,
1129 const std::set<wxString>& aRemoteChanges,
1130 std::map<wxString, FileStatus>& aFileStatus )
1131{
1132 git_repository* repo = aHandler->GetRepo();
1133
1134 if( !repo )
1135 return;
1136
1137 wxString repoWorkDir = aHandler->GetProjectDir();
1138
1139 for( auto& [absPath, fileStatus] : aFileStatus )
1140 {
1141 wxString relativePath = absPath;
1142 if( relativePath.StartsWith( repoWorkDir ) )
1143 {
1144 relativePath = relativePath.Mid( repoWorkDir.length() );
1145
1146#ifdef _WIN32
1147 relativePath.Replace( wxS( "\\" ), wxS( "/" ) );
1148#endif
1149 }
1150
1151 std::string relativePathStd = relativePath.ToStdString();
1152
1153 if( fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CURRENT )
1154 {
1155 if( aLocalChanges.count( relativePathStd ) )
1157 else if( aRemoteChanges.count( relativePathStd ) )
1159 }
1160 }
1161}
1162
1163
1165{
1166 return aHandler->GetProjectDir();
1167}
1168
1169
1170bool LIBGIT_BACKEND::GetConfigString( GIT_CONFIG_HANDLER* aHandler, const wxString& aKey, wxString& aValue )
1171{
1172 git_repository* repo = aHandler->GetRepo();
1173
1174 if( !repo )
1175 return false;
1176
1177 git_config* config = nullptr;
1178
1179 if( git_repository_config( &config, repo ) != GIT_OK )
1180 {
1181 wxLogTrace( traceGit, "Failed to get repository config: %s", KIGIT_COMMON::GetLastGitError() );
1182 return false;
1183 }
1184
1185 KIGIT::GitConfigPtr configPtr( config );
1186
1187 git_config_entry* entry = nullptr;
1188 int result = git_config_get_entry( &entry, config, aKey.utf8_string().c_str() );
1189 KIGIT::GitConfigEntryPtr entryPtr( entry );
1190
1191 if( result != GIT_OK || entry == nullptr )
1192 {
1193 wxLogTrace( traceGit, "Config key '%s' not found", aKey );
1194 return false;
1195 }
1196
1197 aValue = wxString( entry->value );
1198 return true;
1199}
1200
1201
1202bool LIBGIT_BACKEND::IsRepository( GIT_INIT_HANDLER* aHandler, const wxString& aPath )
1203{
1204 git_repository* repo = nullptr;
1205 int error = git_repository_open( &repo, aPath.utf8_string().c_str() );
1206
1207 if( error == 0 )
1208 {
1209 git_repository_free( repo );
1210 return true;
1211 }
1212
1213 // A repository git refuses to open is still a repository. Answering false here sends the
1214 // initialize path into reinitializing it.
1215 return error == GIT_EOWNER;
1216}
1217
1218
1220{
1221 if( IsRepository( aHandler, aPath ) )
1222 {
1224 }
1225
1226 git_repository* repo = nullptr;
1227
1228 if( git_repository_init( &repo, aPath.utf8_string().c_str(), 0 ) != GIT_OK )
1229 {
1230 if( repo )
1231 git_repository_free( repo );
1232
1233 aHandler->AddError( wxString::Format( _( "Could not set up version control in %s." ), aPath ),
1235 return InitResult::Error;
1236 }
1237
1238 aHandler->GetCommon()->SetRepo( repo );
1239
1240 wxLogTrace( traceGit, "Successfully initialized Git repository at %s", aPath );
1241 return InitResult::Success;
1242}
1243
1244
1246{
1247 if( aConfig.url.IsEmpty() )
1248 return true;
1249
1250 git_repository* repo = aHandler->GetRepo();
1251
1252 if( !repo )
1253 {
1254 aHandler->AddErrorString( _( "No repository available to set up remote" ) );
1255 return false;
1256 }
1257
1259 git_remote* remote = nullptr;
1260 int error;
1261
1262 if( git_remote_lookup( &remote, repo, "origin" ) == GIT_OK )
1263 {
1264 KIGIT::GitRemotePtr remotePtr( remote );
1265 error = git_remote_set_url( repo, "origin", parsed.url.utf8_str() );
1266 }
1267 else
1268 {
1269 error = git_remote_create_with_fetchspec( &remote, repo, "origin", parsed.url.utf8_str(),
1270 "+refs/heads/*:refs/remotes/origin/*" );
1271 KIGIT::GitRemotePtr remotePtr( remote );
1272 }
1273
1274 if( error != GIT_OK )
1275 {
1276 KIGIT_FAILURE_CONTEXT context;
1278 context.remoteName = wxS( "origin" );
1279 context.url = parsed.url;
1280
1281 KIGIT_FAILURE_REPORT report =
1283 aHandler->AddError( report.summary, report.details );
1284 return false;
1285 }
1286
1287 // SetRemote clears cached credentials, so apply the accepted credentials afterwards.
1288 // The username embedded in the address takes precedence over a saved username.
1289 KIGIT_COMMON* common = aHandler->GetCommon();
1290 common->SetRemote( parsed.url );
1291 common->SetUsername( parsed.username.IsEmpty() ? aConfig.username : parsed.username );
1292 common->SetPassword( aConfig.password.IsEmpty() ? parsed.password : aConfig.password );
1293 common->SetSSHKey( aConfig.sshKey );
1294
1295 wxLogTrace( traceGit, "Successfully set up remote origin" );
1296 return true;
1297}
1298
1299
1300// The short-name lookup's code, since it is the one that tells a missing branch from a bad name
1301static int lookup_branch_reference( git_repository* repo, const wxString& aBranchName, git_reference** aReference )
1302{
1303 if( git_reference_lookup( aReference, repo, aBranchName.utf8_string().c_str() ) == GIT_OK )
1304 return GIT_OK;
1305
1306 return git_reference_dwim( aReference, repo, aBranchName.utf8_string().c_str() );
1307}
1308
1309
1310BranchResult LIBGIT_BACKEND::SwitchToBranch( GIT_BRANCH_HANDLER* aHandler, const wxString& aBranchName )
1311{
1312 git_repository* repo = aHandler->GetRepo();
1313
1314 if( !repo )
1315 {
1316 aHandler->AddErrorString( _( "No repository available" ) );
1317 return BranchResult::Error;
1318 }
1319
1320 git_reference* branchRef = nullptr;
1321
1322 if( int rc = lookup_branch_reference( repo, aBranchName, &branchRef ); rc != GIT_OK )
1323 {
1324 if( rc == GIT_ENOTFOUND )
1325 {
1326 aHandler->AddError( wxString::Format( _( "The branch '%s' no longer exists." ), aBranchName ),
1327 wxEmptyString );
1329 }
1330
1331 aHandler->AddError( wxString::Format( _( "Could not open the branch '%s'." ), aBranchName ),
1333 return BranchResult::Error;
1334 }
1335
1336 KIGIT::GitReferencePtr branchRefPtr( branchRef );
1337
1338 // A remote-tracking branch is a view of someone else's work rather than somewhere to
1339 // stand. Checking one out can only attach HEAD to a local branch of the same name, which
1340 // may sit at another commit, leaving HEAD and the working tree describing different things.
1341 if( git_reference_is_remote( branchRef ) )
1342 {
1343 aHandler->AddErrorString( wxString::Format( _( "'%s' is a branch on the remote, so the project "
1344 "cannot be switched to it. Make a local branch from "
1345 "it instead." ),
1346 aBranchName ) );
1347 return BranchResult::Error;
1348 }
1349
1350 const char* branchRefName = git_reference_name( branchRef );
1351 git_object* branchObj = nullptr;
1352
1353 if( git_revparse_single( &branchObj, repo, aBranchName.utf8_string().c_str() ) != GIT_OK )
1354 {
1355 aHandler->AddError( wxString::Format( _( "Could not open the branch '%s'." ), aBranchName ),
1357 return BranchResult::Error;
1358 }
1359
1360 KIGIT::GitObjectPtr branchObjPtr( branchObj );
1361
1362
1363 git_checkout_options checkoutOpts;
1364 git_checkout_init_options( &checkoutOpts, GIT_CHECKOUT_OPTIONS_VERSION );
1365 checkoutOpts.checkout_strategy = GIT_CHECKOUT_SAFE;
1366
1367 std::vector<wxString> conflictPaths;
1368
1369 checkoutOpts.notify_flags = GIT_CHECKOUT_NOTIFY_CONFLICT;
1370 checkoutOpts.notify_payload = &conflictPaths;
1371 checkoutOpts.notify_cb = []( git_checkout_notify_t aWhy, const char* aPath, const git_diff_file*,
1372 const git_diff_file*, const git_diff_file*, void* aPayload ) -> int
1373 {
1374 if( aWhy == GIT_CHECKOUT_NOTIFY_CONFLICT && aPath )
1375 static_cast<std::vector<wxString>*>( aPayload )->push_back( wxString::FromUTF8( aPath ) );
1376
1377 return 0;
1378 };
1379
1380 if( int rc = git_checkout_tree( repo, branchObj, &checkoutOpts ) )
1381 {
1382 // A stash does not cover never-committed files, so those need their own advice.
1383 if( rc == GIT_ECONFLICT )
1384 {
1385 wxString fileList;
1386 bool anyUntracked = false;
1387 size_t shown = std::min<size_t>( conflictPaths.size(), 10 );
1388
1389 for( size_t i = 0; i < shown; i++ )
1390 {
1391 bool tracked = IsPathInHead( repo, conflictPaths[i] );
1392
1393 anyUntracked |= !tracked;
1394 wxString tag = tracked ? wxString() : wxS( " " ) + _( "(never committed)" );
1395
1396 fileList << conflictPaths[i] << tag << wxS( "\n" );
1397 }
1398
1399 if( conflictPaths.size() > shown )
1400 fileList << wxString::Format( _( "and %zu more\n" ), conflictPaths.size() - shown );
1401
1402 wxString advice = anyUntracked ? _( "Commit or stash your changes first. Files that were never "
1403 "committed are not included in a stash, so commit or discard "
1404 "those from the Version Control panel." )
1405 : _( "Commit or stash your changes first." );
1406
1407 aHandler->AddErrorString( wxString::Format( _( "Switching to branch '%s' would overwrite:\n\n"
1408 "%s\n%s" ),
1409 aBranchName, fileList, advice ) );
1411 }
1412
1413 aHandler->AddError( wxString::Format( _( "Could not switch to the branch '%s'." ), aBranchName ),
1416 }
1417
1418 if( git_repository_set_head( repo, branchRefName ) != GIT_OK )
1419 {
1420 aHandler->AddError( wxString::Format( _( "Could not switch to the branch '%s'." ), aBranchName ),
1422 return BranchResult::Error;
1423 }
1424
1425 wxLogTrace( traceGit, "Successfully switched to branch '%s'", aBranchName );
1426 return BranchResult::Success;
1427}
1428
1429
1430// Use callbacks declared/implemented in kicad_git_common.h/.cpp
1431
1432bool LIBGIT_BACKEND::PerformFetch( GIT_PULL_HANDLER* aHandler, bool aSkipLock )
1433{
1434 // A pull already holds the lock when it fetches, and relocking from the owner is undefined.
1435 std::unique_lock<std::mutex> lock;
1436
1437 if( !aSkipLock )
1438 {
1439 lock = aHandler->GetActionLock();
1440
1441 if( !lock.owns_lock() )
1442 {
1443 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Could not lock mutex" );
1444 aHandler->AddErrorString( KIGIT::BusyMessage() );
1445 return false;
1446 }
1447 }
1448
1449 // A nested fetch must not republish or retire the pull's token while the pull still runs.
1450 std::optional<KIGIT_COMMON::OPERATION_GUARD> operation;
1451
1452 if( !aSkipLock )
1453 operation.emplace( aHandler->GetCommon(), aHandler->CancelToken() );
1454
1455 // A close or cancellation while waiting also applies before reading remote configuration.
1456 if( aHandler->IsThisOperationCancelled() || !aHandler->GetRepo() )
1457 return false;
1458
1459 wxString remoteName = aHandler->GetCommon()->GetRemoteNameOrDefault();
1460 std::string remoteNameUtf8 = remoteName.utf8_string();
1461 git_remote* remote = nullptr;
1462
1463 if( int rc = git_remote_lookup( &remote, aHandler->GetRepo(), remoteNameUtf8.c_str() ); rc != GIT_OK )
1464 {
1465 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Failed to lookup remote '%s'", remoteName );
1466 reportRemoteLookupFailure( aHandler, rc, remoteName );
1467 return false;
1468 }
1469
1470 KIGIT::GitRemotePtr remotePtr( remote );
1471
1472 // The URL may have changed outside KiCad, so the credentials follow what libgit2 connects to
1473 if( const char* url = git_remote_url( remote ) )
1474 aHandler->GetCommon()->SyncRemoteUrl( wxString::FromUTF8( url ) );
1475
1476 git_remote_callbacks remoteCallbacks;
1477 git_remote_init_callbacks( &remoteCallbacks, GIT_REMOTE_CALLBACKS_VERSION );
1478 remoteCallbacks.sideband_progress = progress_cb;
1479 remoteCallbacks.transfer_progress = transfer_progress_cb;
1480 remoteCallbacks.credentials = credentials_cb;
1481 remoteCallbacks.certificate_check = certificate_check_cb;
1482 remoteCallbacks.payload = aHandler;
1483
1484 git_proxy_options proxyOpts;
1485 git_proxy_init_options( &proxyOpts, GIT_PROXY_OPTIONS_VERSION );
1486 proxyOpts.type = GIT_PROXY_AUTO;
1487
1488 // Do not SetShuttingDown( false ) here. A close or a preference change can raise the flag
1489 // after this fetch starts, and clearing it would strand the caller on the transfer.
1490
1491 // Cancelled while this task was still queued, so it must not start network work.
1492 if( aHandler->IsThisOperationCancelled() )
1493 return false;
1494
1495 aHandler->ResetTransferState();
1496
1497 if( int rc = git_remote_connect( remote, GIT_DIRECTION_FETCH, &remoteCallbacks, &proxyOpts, nullptr ) )
1498 {
1500 return false;
1501 }
1502
1503 git_fetch_options fetchOptions;
1504 git_fetch_init_options( &fetchOptions, GIT_FETCH_OPTIONS_VERSION );
1505 fetchOptions.callbacks = remoteCallbacks;
1506
1507 if( int rc = git_remote_fetch( remote, nullptr, &fetchOptions, nullptr ) )
1508 {
1510 return false;
1511 }
1512
1513 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Fetch completed successfully" );
1514 return true;
1515}
1516
1517
1518static bool rollbackWorkingTree( git_repository* aRepo, git_commit* aHeadCommit, git_tree* aWrittenTree );
1519static bool restorePathsFromCommit( git_repository* aRepo, git_commit* aCommit,
1520 const std::vector<std::string>& aPaths );
1521
1522
1523// A failed checkout leaves the cached index ahead of disk, which reads as phantom staged changes.
1525{
1526 git_index* index = nullptr;
1527
1528 if( git_repository_index( &index, aRepo ) == GIT_OK )
1529 {
1530 KIGIT::GitIndexPtr indexPtr( index );
1531 git_index_read( index, 1 );
1532 }
1533}
1534
1535
1542
1543
1544// After a failed operation under the action lock, this is the set that operation disturbed.
1545static std::vector<std::string> changedPaths( git_repository* aRepo )
1546{
1547 std::vector<std::string> paths;
1548
1549 git_status_options statusOpts;
1550 git_status_init_options( &statusOpts, GIT_STATUS_OPTIONS_VERSION );
1551 statusOpts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
1552
1553 git_status_list* statusList = nullptr;
1554
1555 if( git_status_list_new( &statusList, aRepo, &statusOpts ) != GIT_OK )
1556 return paths;
1557
1558 KIGIT::GitStatusListPtr statusListPtr( statusList );
1559
1560 for( size_t ii = 0; ii < git_status_list_entrycount( statusList ); ii++ )
1561 {
1562 const git_status_entry* entry = git_status_byindex( statusList, ii );
1563
1564 if( entry->head_to_index && entry->head_to_index->new_file.path )
1565 paths.emplace_back( entry->head_to_index->new_file.path );
1566 else if( entry->index_to_workdir && entry->index_to_workdir->new_file.path )
1567 paths.emplace_back( entry->index_to_workdir->new_file.path );
1568 }
1569
1570 return paths;
1571}
1572
1573
1574// Tracked files only. Callers read CLEAN as permission to roll back over the tree, so an
1575// unreadable status gets its own answer rather than passing as clean.
1577{
1578 git_status_options statusOpts;
1579 git_status_init_options( &statusOpts, GIT_STATUS_OPTIONS_VERSION );
1580 statusOpts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
1581
1582 git_status_list* statusList = nullptr;
1583
1584 if( git_status_list_new( &statusList, aRepo, &statusOpts ) != GIT_OK )
1585 return TREE_STATE::UNKNOWN;
1586
1587 KIGIT::GitStatusListPtr statusListPtr( statusList );
1588
1589 return git_status_list_entrycount( statusList ) > 0 ? TREE_STATE::DIRTY : TREE_STATE::CLEAN;
1590}
1591
1592
1593// libgit2 refuses these with a count and no name, so the caller names them.
1594static bool untrackedFilesOverwrittenBy( git_repository* aRepo, git_tree* aIncomingTree,
1595 std::vector<wxString>& aCollisions )
1596{
1597 git_status_options statusOpts;
1598 git_status_init_options( &statusOpts, GIT_STATUS_OPTIONS_VERSION );
1599 statusOpts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
1600 statusOpts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
1601
1602 git_status_list* statusList = nullptr;
1603
1604 // An unreadable status must not read as nothing in the way, or the checkout overwrites it.
1605 if( git_status_list_new( &statusList, aRepo, &statusOpts ) != GIT_OK )
1606 return false;
1607
1608 KIGIT::GitStatusListPtr statusListPtr( statusList );
1609 size_t count = git_status_list_entrycount( statusList );
1610
1611 for( size_t ii = 0; ii < count; ++ii )
1612 {
1613 const git_status_entry* entry = git_status_byindex( statusList, ii );
1614
1615 if( !( entry->status & GIT_STATUS_WT_NEW ) || !entry->index_to_workdir )
1616 continue;
1617
1618 const char* path = entry->index_to_workdir->new_file.path;
1619 git_tree_entry* treeEntry = nullptr;
1620
1621 if( path && git_tree_entry_bypath( &treeEntry, aIncomingTree, path ) == GIT_OK )
1622 {
1623 KIGIT::GitTreeEntryPtr treeEntryPtr( treeEntry );
1624 aCollisions.push_back( wxString::FromUTF8( path ) );
1625 }
1626 }
1627
1628 return true;
1629}
1630
1631
1632static wxString describeUntrackedCollisions( const std::vector<wxString>& aPaths )
1633{
1634 wxString listed;
1635
1636 for( const wxString& path : aPaths )
1637 listed += wxS( "\n " ) + path;
1638
1639 return wxString::Format( _( "Getting changes would overwrite files that were never committed:%s\n\n"
1640 "Commit or discard them first." ),
1641 listed );
1642}
1643
1644
1645// A local repository pointed at a remote with history has nothing to merge from, so adopt it.
1646static PullResult adoptRemoteHistory( GIT_PULL_HANDLER* aHandler, const git_oid& aRemoteOid )
1647{
1648 git_repository* repo = aHandler->GetRepo();
1649 git_reference* symbolic = nullptr;
1650 std::string branchRefName;
1651
1652 if( git_reference_lookup( &symbolic, repo, "HEAD" ) == GIT_OK )
1653 {
1654 KIGIT::GitReferencePtr symbolicPtr( symbolic );
1655
1656 if( const char* target = git_reference_symbolic_target( symbolic ) )
1657 branchRefName = target;
1658 }
1659
1660 if( branchRefName.empty() )
1661 {
1662 aHandler->AddErrorString( _( "Could not get the repository head." ) );
1663 return PullResult::Error;
1664 }
1665
1666 git_commit* commit = nullptr;
1667
1668 if( git_commit_lookup( &commit, repo, &aRemoteOid ) != GIT_OK )
1669 {
1670 aHandler->AddGitError( _( "Could not read the fetched commit." ) );
1671 return PullResult::Error;
1672 }
1673
1674 KIGIT::GitCommitPtr commitPtr( commit );
1675 git_tree* tree = nullptr;
1676
1677 if( git_commit_tree( &tree, commit ) != GIT_OK )
1678 {
1679 aHandler->AddGitError( _( "Could not read the fetched commit." ) );
1680 return PullResult::Error;
1681 }
1682
1683 KIGIT::GitTreePtr treePtr( tree );
1684 std::vector<wxString> collisions;
1685
1686 if( !untrackedFilesOverwrittenBy( repo, tree, collisions ) )
1687 {
1688 aHandler->AddErrorString( _( "Could not read the status of the project files, so getting changes was "
1689 "stopped. Files that were never committed could have been overwritten." ) );
1690 return PullResult::Error;
1691 }
1692
1693 if( !collisions.empty() )
1694 {
1695 aHandler->AddErrorString( describeUntrackedCollisions( collisions ) );
1697 }
1698
1699 // The reference goes first, because deleting one just created restores the unborn state,
1700 // while a checkout against no previous commit has nothing to undo from.
1701 git_reference* branchRef = nullptr;
1702
1703 wxString shorthand = wxString::FromUTF8( branchRefName );
1704 shorthand.StartsWith( wxS( "refs/heads/" ), &shorthand );
1705
1706 if( git_reference_create( &branchRef, repo, branchRefName.c_str(), &aRemoteOid, 0, "pull: adopt remote history" )
1707 != GIT_OK )
1708 {
1709 aHandler->AddError( wxString::Format( _( "Could not create the branch '%s'." ), shorthand ),
1711 return PullResult::Error;
1712 }
1713
1714 KIGIT::GitReferencePtr branchRefPtr( branchRef );
1715
1716 git_checkout_options checkoutOpts;
1717 git_checkout_options_init( &checkoutOpts, GIT_CHECKOUT_OPTIONS_VERSION );
1718 checkoutOpts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING;
1719
1720 if( git_checkout_tree( repo, reinterpret_cast<git_object*>( tree ), &checkoutOpts ) != GIT_OK )
1721 {
1722 wxString gitError = KIGIT_COMMON::GetLastGitError();
1723 bool restored = git_reference_delete( branchRef ) == GIT_OK;
1724
1725 refreshCachedIndex( repo );
1726
1727 aHandler->AddError( restored ? _( "Could not check out the remote branch, so nothing was changed." )
1728 : _( "Could not check out the remote branch, and the project may be partly "
1729 "changed." ),
1730 gitError );
1731 return PullResult::Error;
1732 }
1733
1734 wxString upstream = wxString::Format( "%s/%s", aHandler->GetCommon()->GetRemoteNameOrDefault(), shorthand );
1735 git_branch_set_upstream( branchRef, upstream.utf8_string().c_str() );
1736
1738}
1739
1740
1742{
1744 std::unique_lock<std::mutex> lock = aHandler->GetActionLock();
1745
1746 if( !lock.owns_lock() )
1747 {
1748 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Could not lock mutex" );
1749 aHandler->AddErrorString( KIGIT::BusyMessage() );
1750 return PullResult::Error;
1751 }
1752
1753 // The pull owns cancellation for the whole operation, the fetch inside it included.
1754 KIGIT_COMMON::OPERATION_GUARD operation( aHandler->GetCommon(), aHandler->CancelToken() );
1755
1756 if( aHandler->IsThisOperationCancelled() || !aHandler->GetRepo() )
1757 return PullResult::Error;
1758
1759 if( !PerformFetch( aHandler, true ) )
1760 return PullResult::Error;
1761
1762 git_oid pull_merge_oid = {};
1763
1764 if( git_repository_fetchhead_foreach( aHandler->GetRepo(), fetchhead_foreach_cb, &pull_merge_oid ) )
1765 {
1766 aHandler->AddGitError( _( "Could not read the fetched changes." ) );
1767 return PullResult::Error;
1768 }
1769
1770 // Add Version Control doesn't write branch.<name>.merge, so FETCH_HEAD has no
1771 // merge-marked entry. Fall back to refs/remotes/<remote>/<branch> and persist
1772 // the upstream so subsequent pulls take the normal path.
1773#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
1774 if( git_oid_is_zero( &pull_merge_oid ) )
1775#else
1776 if( git_oid_iszero( &pull_merge_oid ) )
1777#endif
1778 {
1779 git_reference* head_ref = nullptr;
1780 wxString branch_shorthand;
1781
1782 if( git_repository_head( &head_ref, aHandler->GetRepo() ) == GIT_OK )
1783 {
1784 if( git_reference_is_branch( head_ref ) )
1785 branch_shorthand = wxString::FromUTF8( git_reference_shorthand( head_ref ) );
1786 }
1787 else if( git_repository_head_unborn( aHandler->GetRepo() ) == 1 )
1788 {
1789 // No commit yet, so HEAD is only a branch name and the first pull adopts its remote.
1790 git_reference* symbolic = nullptr;
1791
1792 if( git_reference_lookup( &symbolic, aHandler->GetRepo(), "HEAD" ) == GIT_OK )
1793 {
1794 KIGIT::GitReferencePtr symbolicPtr( symbolic );
1795
1796 if( const char* target = git_reference_symbolic_target( symbolic ) )
1797 wxString::FromUTF8( target ).StartsWith( wxS( "refs/heads/" ), &branch_shorthand );
1798 }
1799 }
1800
1801 KIGIT::GitReferencePtr headRefPtr( head_ref );
1802
1803 if( !branch_shorthand.IsEmpty() )
1804 {
1805 wxString remoteName = aHandler->GetCommon()->GetRemoteNameOrDefault();
1806 wxString remoteRefName = wxString::Format( "refs/remotes/%s/%s", remoteName, branch_shorthand );
1807
1808 if( git_reference_name_to_id( &pull_merge_oid, aHandler->GetRepo(), remoteRefName.utf8_string().c_str() )
1809 == GIT_OK
1810 && head_ref )
1811 {
1812 wxString upstream = wxString::Format( "%s/%s", remoteName, branch_shorthand );
1813 git_branch_set_upstream( head_ref, upstream.utf8_string().c_str() );
1814 }
1815 }
1816 }
1817
1818#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
1819 if( git_oid_is_zero( &pull_merge_oid ) )
1820#else
1821 if( git_oid_iszero( &pull_merge_oid ) )
1822#endif
1823 {
1824 aHandler->AddErrorString( _( "Nothing to pull: the remote has no branch matching the current local "
1825 "branch." ) );
1826 return PullResult::Error;
1827 }
1828
1829 // KiCad runs no filter program, so merging a version that keeps design files in Git LFS
1830 // would put pointer text where the design belongs. Judged before anything is written.
1831 if( KIGIT_COMMON* common = aHandler->GetCommon() )
1832 {
1833 wxString example;
1834
1835 if( KIGIT::PROJECT_GIT_UTILS::ProjectUsesGitLfs( *common, &example, &pull_merge_oid ) )
1836 {
1837 aHandler->AddErrorString( wxString::Format(
1838 _( "The changes being fetched keep '%s' in Git LFS, which KiCad cannot read or write. "
1839 "Use git on the command line for this repository." ),
1840 example ) );
1841 return PullResult::Error;
1842 }
1843 }
1844
1845 git_annotated_commit* fetchhead_commit;
1846
1847 if( git_annotated_commit_lookup( &fetchhead_commit, aHandler->GetRepo(), &pull_merge_oid ) )
1848 {
1849 aHandler->AddGitError( _( "Could not read the fetched commit." ) );
1850 return PullResult::Error;
1851 }
1852
1853 KIGIT::GitAnnotatedCommitPtr fetchheadCommitPtr( fetchhead_commit );
1854 const git_annotated_commit* merge_commits[] = { fetchhead_commit };
1855 git_merge_analysis_t merge_analysis;
1856 git_merge_preference_t merge_preference = GIT_MERGE_PREFERENCE_NONE;
1857
1858 if( git_merge_analysis( &merge_analysis, &merge_preference, aHandler->GetRepo(), merge_commits, 1 ) )
1859 {
1860 aHandler->AddGitError( _( "Could not compare the fetched changes with this branch." ) );
1861 return PullResult::Error;
1862 }
1863
1864 if( merge_analysis & GIT_MERGE_ANALYSIS_UNBORN )
1865 return adoptRemoteHistory( aHandler, pull_merge_oid );
1866
1867 if( merge_analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE )
1868 {
1869 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Repository is up to date" );
1870 git_repository_state_cleanup( aHandler->GetRepo() );
1871 return PullResult::UpToDate;
1872 }
1873
1874 if( merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD )
1875 {
1876 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Fast-forward merge" );
1877 return handleFastForward( aHandler );
1878 }
1879
1880 if( merge_analysis & GIT_MERGE_ANALYSIS_NORMAL )
1881 {
1882 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Normal merge" );
1883
1884 git_config* config = nullptr;
1885
1886 if( git_repository_config( &config, aHandler->GetRepo() ) != GIT_OK )
1887 {
1888 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Failed to get repository config" );
1889 aHandler->AddGitError( _( "Could not read the repository settings." ) );
1890 return PullResult::Error;
1891 }
1892
1893 KIGIT::GitConfigPtr configPtr( config );
1894
1895 int rebase_value = 0;
1896 int ret = git_config_get_bool( &rebase_value, config, "pull.rebase" );
1897
1898 if( ret == GIT_OK && rebase_value )
1899 {
1900 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Using rebase based on config" );
1901 return handleRebase( aHandler, merge_commits, 1 );
1902 }
1903
1904 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Using merge based on config" );
1905 return handleMerge( aHandler, merge_commits, 1 );
1906 }
1907
1908 wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Merge needs resolution" );
1909 return result;
1910}
1911
1912
1914{
1915 git_reference* rawRef = nullptr;
1916
1917 if( git_repository_head( &rawRef, aHandler->GetRepo() ) )
1918 {
1919 aHandler->AddErrorString( _( "Could not get the repository head." ) );
1920 return PullResult::Error;
1921 }
1922
1923 KIGIT::GitReferencePtr headRef( rawRef );
1924
1925 git_oid updatedRefOid;
1926 const char* branch_shorthand = git_reference_shorthand( rawRef );
1927 wxString remote_name = aHandler->GetRemotename();
1928 wxString remoteBranchName;
1929
1930 // The analysis followed branch.<name>.merge, so move to that branch. Without a configured
1931 // upstream, the remote branch of the same name stands in.
1932 git_reference* upstreamRef = nullptr;
1933
1934 if( git_branch_upstream( &upstreamRef, rawRef ) == GIT_OK )
1935 {
1936 KIGIT::GitReferencePtr upstreamPtr( upstreamRef );
1937 remoteBranchName = wxString::FromUTF8( git_reference_name( upstreamRef ) );
1938 }
1939 else
1940 {
1941 remoteBranchName = wxString::Format( "refs/remotes/%s/%s", remote_name, branch_shorthand );
1942 }
1943
1944 if( int rc = git_reference_name_to_id( &updatedRefOid, aHandler->GetRepo(), remoteBranchName.c_str() );
1945 rc != GIT_OK )
1946 {
1947 wxString shownName = remoteBranchName;
1948 shownName.StartsWith( wxS( "refs/remotes/" ), &shownName );
1949
1950 aHandler->AddError( wxString::Format( rc == GIT_ENOTFOUND ? _( "Could not find the remote branch '%s'." )
1951 : _( "Could not read the remote branch '%s'." ),
1952 shownName ),
1954 return PullResult::Error;
1955 }
1956
1957 git_commit* targetCommit = nullptr;
1958
1959 if( git_commit_lookup( &targetCommit, aHandler->GetRepo(), &updatedRefOid ) != GIT_OK )
1960 {
1961 aHandler->AddGitError( _( "Could not read the fetched commit." ) );
1962 return PullResult::Error;
1963 }
1964
1965 KIGIT::GitCommitPtr targetCommitPtr( targetCommit );
1966
1967 git_tree* targetTree = nullptr;
1968
1969 if( git_commit_tree( &targetTree, targetCommit ) != GIT_OK )
1970 {
1971 aHandler->AddGitError( _( "Could not read the fetched commit." ) );
1972 return PullResult::Error;
1973 }
1974
1975 KIGIT::GitTreePtr targetTreePtr( targetTree );
1976
1977 // A skipped file would leave the local edit reading as a reversal of the remote change.
1978 TREE_STATE treeState = trackedTreeState( aHandler->GetRepo() );
1979
1980 if( treeState == TREE_STATE::UNKNOWN )
1981 {
1982 aHandler->AddGitError( _( "Could not read the status of the project files, so getting changes was stopped." ) );
1983 return PullResult::Error;
1984 }
1985
1986 if( treeState == TREE_STATE::DIRTY )
1987 {
1988 aHandler->AddErrorString( _( "There are changes that are not committed yet, and getting changes would "
1989 "run over them. Commit them first." ) );
1991 }
1992
1993 std::vector<wxString> collisions;
1994
1995 if( !untrackedFilesOverwrittenBy( aHandler->GetRepo(), targetTree, collisions ) )
1996 {
1997 aHandler->AddErrorString( _( "Could not read the status of the project files, so getting changes was "
1998 "stopped. Files that were never committed could have been overwritten." ) );
1999 return PullResult::Error;
2000 }
2001
2002 if( !collisions.empty() )
2003 {
2004 aHandler->AddErrorString( describeUntrackedCollisions( collisions ) );
2006 }
2007
2008 git_checkout_options checkoutOptions;
2009 git_checkout_init_options( &checkoutOptions, GIT_CHECKOUT_OPTIONS_VERSION );
2010 auto notify_cb =
2011 []( git_checkout_notify_t why, const char* path, const git_diff_file* baseline,
2012 const git_diff_file* target, const git_diff_file* workdir, void* payload ) -> int
2013 {
2014 switch( why )
2015 {
2016 case GIT_CHECKOUT_NOTIFY_CONFLICT:
2017 wxLogTrace( traceGit, "Checkout conflict: %s", path ? path : "unknown" );
2018 break;
2019 case GIT_CHECKOUT_NOTIFY_DIRTY:
2020 wxLogTrace( traceGit, "Checkout dirty: %s", path ? path : "unknown" );
2021 break;
2022 case GIT_CHECKOUT_NOTIFY_UPDATED:
2023 wxLogTrace( traceGit, "Checkout updated: %s", path ? path : "unknown" );
2024 break;
2025 case GIT_CHECKOUT_NOTIFY_UNTRACKED:
2026 wxLogTrace( traceGit, "Checkout untracked: %s", path ? path : "unknown" );
2027 break;
2028 case GIT_CHECKOUT_NOTIFY_IGNORED:
2029 wxLogTrace( traceGit, "Checkout ignored: %s", path ? path : "unknown" );
2030 break;
2031 default:
2032 break;
2033 }
2034
2035 return 0;
2036 };
2037
2038 checkoutOptions.checkout_strategy = GIT_CHECKOUT_SAFE;
2039 checkoutOptions.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
2040 checkoutOptions.notify_cb = notify_cb;
2041
2042 // Where the branch is now, so the files can go back if it cannot be moved to match them.
2043 git_commit* previousCommit = nullptr;
2044
2045 if( git_reference_peel( (git_object**) &previousCommit, rawRef, GIT_OBJECT_COMMIT ) != GIT_OK )
2046 {
2047 aHandler->AddErrorString( _( "Could not read the current commit." ) );
2048 return PullResult::Error;
2049 }
2050
2051 KIGIT::GitCommitPtr previousCommitPtr( previousCommit );
2052
2053 if( git_checkout_tree( aHandler->GetRepo(), reinterpret_cast<git_object*>( targetTree ),
2054 &checkoutOptions ) != GIT_OK )
2055 {
2056 wxString gitError = KIGIT_COMMON::GetLastGitError();
2057 bool restored = rollbackWorkingTree( aHandler->GetRepo(), previousCommit, targetTree );
2058
2059 refreshCachedIndex( aHandler->GetRepo() );
2060
2061 aHandler->AddError( restored ? _( "Could not update the project files, so nothing was changed." )
2062 : _( "Could not update the project files, and they may be partly changed." ),
2063 gitError );
2064 return PullResult::Error;
2065 }
2066
2067 git_reference* updatedRef = nullptr;
2068
2069 if( git_reference_set_target( &updatedRef, rawRef, &updatedRefOid, nullptr ) != GIT_OK )
2070 {
2071 // Left here, git reads them as local changes undoing the incoming commit.
2072 wxString gitError = KIGIT_COMMON::GetLastGitError();
2073 bool restored = rollbackWorkingTree( aHandler->GetRepo(), previousCommit, targetTree );
2074
2075 refreshCachedIndex( aHandler->GetRepo() );
2076
2077 aHandler->AddError( restored ? _( "Could not move the branch, so nothing was changed." )
2078 : _( "Could not move the branch, and the project files may be left at the "
2079 "incoming version while the branch is not." ),
2080 gitError );
2081 return PullResult::Error;
2082 }
2083
2084 KIGIT::GitReferencePtr updatedRefPtr( updatedRef );
2085
2086 if( git_repository_state_cleanup( aHandler->GetRepo() ) != GIT_OK )
2087 {
2088 // Branch and files already match, so this is leftover merge state, not a failed pull.
2089 wxLogTrace( traceGit, "handleFastForward: state cleanup failed after a completed fast-forward: %s",
2091 }
2092
2094}
2095
2096
2098{
2099 if( !aRepo )
2100 return false;
2101
2102 git_status_options opts;
2103 git_status_init_options( &opts, GIT_STATUS_OPTIONS_VERSION );
2104
2105 // Only check workdir changes (unstaged), not index changes (staged)
2106 opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
2107 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED;
2108
2109 git_status_list* status_list = nullptr;
2110
2111 if( git_status_list_new( &status_list, aRepo, &opts ) != GIT_OK )
2112 {
2113 wxLogTrace( traceGit, "Failed to get status list: %s", KIGIT_COMMON::GetLastGitError() );
2114 return false;
2115 }
2116
2117 KIGIT::GitStatusListPtr status_list_ptr( status_list );
2118 size_t count = git_status_list_entrycount( status_list );
2119
2120 // Check if any of the entries are actual modifications (not just untracked files)
2121 for( size_t ii = 0; ii < count; ++ii )
2122 {
2123 const git_status_entry* entry = git_status_byindex( status_list, ii );
2124
2125 // Check for actual workdir modifications, not just untracked files
2126 if( entry->status & ( GIT_STATUS_WT_MODIFIED | GIT_STATUS_WT_DELETED | GIT_STATUS_WT_TYPECHANGE ) )
2127 return true;
2128 }
2129
2130 return false;
2131}
2132
2133
2134PullResult LIBGIT_BACKEND::handleMerge( GIT_PULL_HANDLER* aHandler, const git_annotated_commit** aMergeHeads,
2135 size_t aMergeHeadsCount )
2136{
2137 // Check for unstaged changes before attempting merge
2138 if( hasUnstagedChanges( aHandler->GetRepo() ) )
2139 {
2140 aHandler->AddErrorString( _( "Cannot merge: you have unstaged changes. Please commit or stash them "
2141 "before pulling." ) );
2143 }
2144
2145 git_repository* repo = aHandler->GetRepo();
2146
2147 // Probe now, so a missing identity fails with a clean tree rather than a rewritten one.
2148 {
2149 git_signature* probe = nullptr;
2150
2151 if( int rc = git_signature_default( &probe, repo ); rc != GIT_OK )
2152 {
2154 aHandler->AddError( failure );
2156 }
2157
2158 KIGIT::GitSignaturePtr probePtr( probe );
2159 }
2160
2161 {
2162 git_commit* theirs = nullptr;
2163
2164 if( git_commit_lookup( &theirs, repo, git_annotated_commit_id( aMergeHeads[0] ) ) == GIT_OK )
2165 {
2166 KIGIT::GitCommitPtr theirsPtr( theirs );
2167 git_tree* theirsTree = nullptr;
2168
2169 if( git_commit_tree( &theirsTree, theirs ) == GIT_OK )
2170 {
2171 KIGIT::GitTreePtr theirsTreePtr( theirsTree );
2172 std::vector<wxString> collisions;
2173
2174 if( !untrackedFilesOverwrittenBy( repo, theirsTree, collisions ) )
2175 {
2176 aHandler->AddErrorString( _( "Could not read the status of the project files, so getting "
2177 "changes was stopped. Files that were never committed could "
2178 "have been overwritten." ) );
2179 return PullResult::Error;
2180 }
2181
2182 if( !collisions.empty() )
2183 {
2184 aHandler->AddErrorString( describeUntrackedCollisions( collisions ) );
2186 }
2187 }
2188 }
2189 }
2190
2191 if( git_merge( repo, aMergeHeads, aMergeHeadsCount, nullptr, nullptr ) )
2192 {
2193 aHandler->AddGitError( _( "Could not merge the fetched changes." ) );
2195 }
2196
2197 git_index* index = nullptr;
2198
2199 if( git_repository_index( &index, repo ) != GIT_OK )
2200 {
2201 aHandler->AddErrorString( _( "Could not read repository index after merge." ) );
2203 }
2204
2205 KIGIT::GitIndexPtr indexPtr( index );
2206
2207 if( git_index_has_conflicts( index ) )
2208 {
2209 // Abort the merge and restore the pre-pull state
2210 git_object* head_obj = nullptr;
2211
2212 if( git_revparse_single( &head_obj, repo, "HEAD" ) == GIT_OK )
2213 {
2214 KIGIT::GitObjectPtr headObjPtr( head_obj );
2215 git_reset( repo, head_obj, GIT_RESET_HARD, nullptr );
2216 }
2217
2218 git_repository_state_cleanup( repo );
2219
2220 return PullResult::Conflict;
2221 }
2222
2223 git_oid tree_oid;
2224 git_tree* tree = nullptr;
2225 git_reference* head_ref = nullptr;
2226 git_oid head_oid;
2227 git_commit* head_commit = nullptr;
2228 git_commit* merge_commit = nullptr;
2229 git_signature* signature = nullptr;
2230
2231 // Merged on disk but not recorded, so a failure from here puts the tree back at HEAD.
2232 auto undoMerge = [&]()
2233 {
2234 git_object* headObj = nullptr;
2235
2236 if( git_revparse_single( &headObj, repo, "HEAD" ) == GIT_OK )
2237 {
2238 KIGIT::GitObjectPtr headObjPtr( headObj );
2239 git_reset( repo, headObj, GIT_RESET_HARD, nullptr );
2240 }
2241
2242 git_repository_state_cleanup( repo );
2243 };
2244
2245 if( git_index_write_tree( &tree_oid, index ) != GIT_OK || git_tree_lookup( &tree, repo, &tree_oid ) != GIT_OK )
2246 {
2247 aHandler->AddErrorString( _( "Could not write the merge result." ) );
2248 undoMerge();
2250 }
2251
2252 KIGIT::GitTreePtr treePtr( tree );
2253
2254 if( git_repository_head( &head_ref, repo ) != GIT_OK )
2255 {
2256 aHandler->AddErrorString( _( "Could not get the repository head." ) );
2257 undoMerge();
2259 }
2260
2261 KIGIT::GitReferencePtr headRefPtr( head_ref );
2262
2263 if( git_reference_name_to_id( &head_oid, repo, "HEAD" ) != GIT_OK
2264 || git_commit_lookup( &head_commit, repo, &head_oid ) != GIT_OK
2265 || git_commit_lookup( &merge_commit, repo, git_annotated_commit_id( aMergeHeads[0] ) ) != GIT_OK )
2266 {
2267 aHandler->AddErrorString( _( "Could not look up the commits to merge." ) );
2268 undoMerge();
2270 }
2271
2272 KIGIT::GitCommitPtr headCommitPtr( head_commit );
2273 KIGIT::GitCommitPtr mergeCommitPtr( merge_commit );
2274
2275 if( int rc = git_signature_default( &signature, repo ); rc != GIT_OK )
2276 {
2278 aHandler->AddError( failure );
2279 undoMerge();
2281 }
2282
2283 KIGIT::GitSignaturePtr signaturePtr( signature );
2284
2285 const git_commit* parents[] = { head_commit, merge_commit };
2286 wxString message = wxString::Format( _( "Merge remote-tracking branch into %s" ),
2287 git_reference_shorthand( head_ref ) );
2288 git_oid merge_commit_oid;
2289
2290 if( git_commit_create( &merge_commit_oid, repo, "HEAD", signature, signature, nullptr,
2291 message.utf8_string().c_str(), tree, 2, parents ) != GIT_OK )
2292 {
2293 aHandler->AddGitError( _( "Could not create the merge commit." ) );
2294 undoMerge();
2296 }
2297
2298 git_repository_state_cleanup( repo );
2299 return PullResult::Success;
2300}
2301
2302
2303static PullResult rebaseOnto( GIT_PULL_HANDLER* aHandler, git_repository* aRepo, const git_annotated_commit* aOnto )
2304{
2305 git_signature* signature = nullptr;
2306
2307 if( int rc = git_signature_default( &signature, aRepo ); rc != GIT_OK )
2308 {
2310 aHandler->AddError( failure );
2311 return PullResult::Error;
2312 }
2313
2314 KIGIT::GitSignaturePtr signaturePtr( signature );
2315 git_rebase_options rebase_opts;
2316 git_rebase_init_options( &rebase_opts, GIT_REBASE_OPTIONS_VERSION );
2317 git_rebase* rebase = nullptr;
2318
2319 if( git_rebase_init( &rebase, aRepo, nullptr, aOnto, nullptr, &rebase_opts ) != GIT_OK )
2320 {
2321 aHandler->AddGitError( _( "Could not start the rebase." ) );
2322 return PullResult::Error;
2323 }
2324
2325 KIGIT::GitRebasePtr rebasePtr( rebase );
2326 git_rebase_operation* op = nullptr;
2327 int nextResult = GIT_OK;
2328
2329 while( ( nextResult = git_rebase_next( &op, rebase ) ) == GIT_OK )
2330 {
2331 git_oid commit_oid;
2332 int commitResult = git_rebase_commit( &commit_oid, rebase, nullptr, signature, nullptr, nullptr );
2333
2334 // Upstream already has this change, so replaying it would be an empty commit
2335 if( commitResult == GIT_EAPPLIED )
2336 continue;
2337
2338 if( commitResult != GIT_OK )
2339 {
2340 wxString gitError = KIGIT_COMMON::GetLastGitError();
2341 git_rebase_abort( rebase );
2342
2343 if( commitResult == GIT_EUNMERGED )
2344 {
2345 aHandler->AddErrorString( _( "The rebase ran into conflicts. This is best resolved from a "
2346 "git command line." ) );
2347 return PullResult::Conflict;
2348 }
2349
2350 aHandler->AddError( _( "Could not rebase your commits onto the fetched changes." ), gitError );
2351 return PullResult::Error;
2352 }
2353 }
2354
2355 if( nextResult != GIT_ITEROVER )
2356 {
2357 wxString gitError = KIGIT_COMMON::GetLastGitError();
2358 git_rebase_abort( rebase );
2359 aHandler->AddError( _( "Could not rebase your commits onto the fetched changes." ), gitError );
2360 return PullResult::Error;
2361 }
2362
2363 if( git_rebase_finish( rebase, signature ) != GIT_OK )
2364 {
2365 wxString gitError = KIGIT_COMMON::GetLastGitError();
2366 git_rebase_abort( rebase );
2367 aHandler->AddError( _( "Could not finish the rebase." ), gitError );
2368 return PullResult::Error;
2369 }
2370
2371 return PullResult::Success;
2372}
2373
2374
2375PullResult LIBGIT_BACKEND::handleRebase( GIT_PULL_HANDLER* aHandler, const git_annotated_commit** aMergeHeads,
2376 size_t aMergeHeadsCount )
2377{
2378 // Check for unstaged changes before attempting rebase
2379 if( hasUnstagedChanges( aHandler->GetRepo() ) )
2380 {
2381 aHandler->AddErrorString( _( "Cannot pull with rebase: you have unstaged changes. Please commit or stash "
2382 "them before pulling." ) );
2384 }
2385
2386 return rebaseOnto( aHandler, aHandler->GetRepo(), aMergeHeads[0] );
2387}
2388
2389
2390git_repository* LIBGIT_BACKEND::GetRepositoryForFile( const char* aFilename, wxString* aError, wxString* aTrustCommand )
2391{
2392 git_repository* repo = nullptr;
2393
2394 // Searching from the project, as git_repository_discover does, rather than opening the git directory it
2395 // finds keeps a linked .git from moving the working folder to the link's target
2396 if( int rc = git_repository_open_ext( &repo, aFilename, 0, nullptr ); rc != GIT_OK )
2397 {
2398 wxLogTrace( traceGit, "Can't open repo for %s: %s", aFilename, KIGIT_COMMON::GetLastGitError() );
2399
2400 // Left unexplained, a repository git refuses to open looks like no repository at all.
2401 if( rc == GIT_EOWNER && aError )
2402 {
2403 git_buf gitDir = GIT_BUF_INIT;
2404
2405 if( git_repository_discover( &gitDir, aFilename, 0, nullptr ) == GIT_OK )
2406 {
2407 KIGIT::GitBufPtr gitDirPtr( &gitDir );
2408 KIGIT_OWNERSHIP_REFUSAL refusal = KIGIT::DescribeOwnershipRefusal( wxString::FromUTF8( aFilename ),
2409 wxString::FromUTF8( gitDir.ptr ) );
2410 *aError = refusal.summary;
2411
2412 if( aTrustCommand )
2413 *aTrustCommand = refusal.trustCommand;
2414 }
2415 }
2416
2417 return nullptr;
2418 }
2419
2420 return repo;
2421}
2422
2423
2424int LIBGIT_BACKEND::CreateBranch( git_repository* aRepo, const wxString& aBranchName, const wxString& aFrom )
2425{
2426 // The start point is a branch name as git would read it, local or remote, or HEAD.
2427 std::string start = aFrom.IsEmpty() ? std::string( "HEAD" ) : aFrom.utf8_string();
2428 git_object* startObj = nullptr;
2429
2430 if( int error = git_revparse_single( &startObj, aRepo, start.c_str() ); error != GIT_OK )
2431 {
2432 wxLogTrace( traceGit, "Failed to resolve branch start point '%s': %s", start, KIGIT_COMMON::GetLastGitError() );
2433 return error;
2434 }
2435
2436 KIGIT::GitObjectPtr startPtr( startObj );
2437 git_object* peeled = nullptr;
2438
2439 if( int error = git_object_peel( &peeled, startObj, GIT_OBJECT_COMMIT ); error != GIT_OK )
2440 {
2441 wxLogTrace( traceGit, "Failed to lookup commit: %s", KIGIT_COMMON::GetLastGitError() );
2442 return error;
2443 }
2444
2445 KIGIT::GitObjectPtr peeledPtr( peeled );
2446 git_commit* commit = reinterpret_cast<git_commit*>( peeled );
2447 git_reference* branchRef = nullptr;
2448
2449 std::string name = aBranchName.utf8_string();
2450
2451 if( int error = git_branch_create( &branchRef, aRepo, name.c_str(), commit, 0 ); error != GIT_OK )
2452 {
2453 wxLogTrace( traceGit, "Failed to create branch: %s", KIGIT_COMMON::GetLastGitError() );
2454 return error;
2455 }
2456
2457 git_reference_free( branchRef );
2458 return 0;
2459}
2460
2461
2462bool LIBGIT_BACKEND::DeleteBranch( git_repository* aRepo, const wxString& aBranchName )
2463{
2464 if( !aRepo || aBranchName.IsEmpty() )
2465 return false;
2466
2467 git_reference* branch = nullptr;
2468
2469 if( git_branch_lookup( &branch, aRepo, aBranchName.utf8_string().c_str(), GIT_BRANCH_LOCAL ) != GIT_OK )
2470 return false;
2471
2472 KIGIT::GitReferencePtr branchPtr( branch );
2473 return git_branch_delete( branch ) == GIT_OK;
2474}
2475
2476
2477bool LIBGIT_BACKEND::StashSave( git_repository* aRepo, const wxString& aMessage )
2478{
2479 if( !aRepo )
2480 return false;
2481
2482 git_signature* signature = nullptr;
2483
2484 if( git_signature_default( &signature, aRepo ) != GIT_OK )
2485 return false;
2486
2487 KIGIT::GitSignaturePtr signaturePtr( signature );
2488
2489 // libgit2 stashes from an unstamped index copy that trusts racily clean entries, so an edit made in the
2490 // index's timestamp tick is stashed as unchanged and then reverted. Writing the index smudges those entries
2491 git_index* index = nullptr;
2492
2493 if( git_repository_index( &index, aRepo ) != GIT_OK )
2494 return false;
2495
2496 KIGIT::GitIndexPtr indexPtr( index );
2497
2498 if( git_index_read( index, true ) != GIT_OK || git_index_write( index ) != GIT_OK )
2499 return false;
2500
2501 std::string message = aMessage.utf8_string();
2502 git_oid stashOid;
2503
2504 return git_stash_save( &stashOid, aRepo, signature, message.empty() ? nullptr : message.c_str(), GIT_STASH_DEFAULT )
2505 == GIT_OK;
2506}
2507
2508
2509static bool rollbackWorkingTree( git_repository* aRepo, git_commit* aHeadCommit, git_tree* aWrittenTree );
2510static bool restorePathsFromCommit( git_repository* aRepo, git_commit* aCommit,
2511 const std::vector<std::string>& aPaths );
2512
2513
2514// libgit2 reports a conflicting apply as applied, so a pop would drop the only copy of the
2515// stashed work. A clean tracked tree makes the rollback lossless.
2516static bool applyStashOntoCleanTree( git_repository* aRepo, size_t aIndex )
2517{
2518 if( !aRepo )
2519 return false;
2520
2521 if( trackedTreeState( aRepo ) != TREE_STATE::CLEAN )
2522 {
2523 git_error_set_str( GIT_ERROR_STASH, _( "Commit or discard the current changes first, so the stash has "
2524 "a clean version to apply to." )
2525 .utf8_string()
2526 .c_str() );
2527 return false;
2528 }
2529
2530 git_oid headOid;
2531
2532 if( git_reference_name_to_id( &headOid, aRepo, "HEAD" ) != GIT_OK )
2533 return false;
2534
2535 git_commit* headCommit = nullptr;
2536
2537 if( git_commit_lookup( &headCommit, aRepo, &headOid ) != GIT_OK )
2538 return false;
2539
2540 KIGIT::GitCommitPtr headCommitPtr( headCommit );
2541
2542 if( git_stash_apply( aRepo, aIndex, nullptr ) != GIT_OK )
2543 return false;
2544
2545 // An unreadable index cannot confirm the apply, and a caller reading success would drop the
2546 // stash, so treat it as a conflict.
2547 git_index* index = nullptr;
2548 bool conflicted = git_repository_index( &index, aRepo ) != GIT_OK || git_index_has_conflicts( index );
2549 KIGIT::GitIndexPtr indexPtr( index );
2550
2551 if( conflicted )
2552 {
2553 // Conflict markers match neither version, so the touched paths come from the status.
2554 restorePathsFromCommit( aRepo, headCommit, changedPaths( aRepo ) );
2555 git_error_set_str( GIT_ERROR_STASH, _( "The stash does not fit on top of the current version of the "
2556 "files, so nothing was changed. Restore the version it was made "
2557 "on from History, then apply it there." )
2558 .utf8_string()
2559 .c_str() );
2560 return false;
2561 }
2562
2563 return true;
2564}
2565
2566
2567bool LIBGIT_BACKEND::StashApply( git_repository* aRepo, size_t aIndex )
2568{
2569 return applyStashOntoCleanTree( aRepo, aIndex );
2570}
2571
2572
2573bool LIBGIT_BACKEND::StashPop( git_repository* aRepo, size_t aIndex )
2574{
2575 return applyStashOntoCleanTree( aRepo, aIndex ) && git_stash_drop( aRepo, aIndex ) == GIT_OK;
2576}
2577
2578
2579bool LIBGIT_BACKEND::StashDrop( git_repository* aRepo, size_t aIndex )
2580{
2581 return aRepo && git_stash_drop( aRepo, aIndex ) == GIT_OK;
2582}
2583
2584
2585bool LIBGIT_BACKEND::CreateTag( git_repository* aRepo, const wxString& aName, const wxString& aMessage )
2586{
2587 if( !aRepo || aName.IsEmpty() )
2588 return false;
2589
2590 git_object* target = nullptr;
2591
2592 if( git_revparse_single( &target, aRepo, "HEAD" ) != GIT_OK )
2593 return false;
2594
2595 KIGIT::GitObjectPtr targetPtr( target );
2596 git_oid oid;
2597
2598 if( aMessage.IsEmpty() )
2599 return git_tag_create_lightweight( &oid, aRepo, aName.utf8_string().c_str(), target, 0 ) == GIT_OK;
2600
2601 // An annotated tag carries the message, and who made it, from the repository's identity.
2602 git_signature* tagger = nullptr;
2603
2604 if( git_signature_default( &tagger, aRepo ) != GIT_OK )
2605 return false;
2606
2607 KIGIT::GitSignaturePtr taggerPtr( tagger );
2608 std::string name = aName.utf8_string();
2609 std::string message = aMessage.utf8_string();
2610
2611 return git_tag_create( &oid, aRepo, name.c_str(), target, tagger, message.c_str(), 0 ) == GIT_OK;
2612}
2613
2614
2615bool LIBGIT_BACKEND::DeleteTag( git_repository* aRepo, const wxString& aName )
2616{
2617 if( !aRepo || aName.IsEmpty() )
2618 return false;
2619
2620 return git_tag_delete( aRepo, aName.utf8_string().c_str() ) == GIT_OK;
2621}
2622
2623
2624bool LIBGIT_BACKEND::CheckoutTag( git_repository* aRepo, const wxString& aName )
2625{
2626 if( !aRepo || aName.IsEmpty() )
2627 return false;
2628
2629 git_object* target = nullptr;
2630 wxString spec = aName + wxT( "^{commit}" );
2631
2632 if( git_revparse_single( &target, aRepo, spec.utf8_string().c_str() ) != GIT_OK )
2633 return false;
2634
2635 KIGIT::GitObjectPtr targetPtr( target );
2636
2637 git_checkout_options opts;
2638 git_checkout_options_init( &opts, GIT_CHECKOUT_OPTIONS_VERSION );
2639 opts.checkout_strategy = GIT_CHECKOUT_SAFE;
2640
2641 if( git_checkout_tree( aRepo, target, &opts ) != GIT_OK )
2642 return false;
2643
2644 return git_repository_set_head_detached( aRepo, git_object_id( target ) ) == GIT_OK;
2645}
2646
2647
2648bool LIBGIT_BACKEND::AddRemote( git_repository* aRepo, const wxString& aName, const wxString& aUrl )
2649{
2650 if( !aRepo || aName.IsEmpty() )
2651 return false;
2652
2653 git_remote* remote = nullptr;
2654
2655 if( git_remote_create( &remote, aRepo, aName.utf8_string().c_str(), aUrl.utf8_string().c_str() ) != GIT_OK )
2656 return false;
2657
2658 KIGIT::GitRemotePtr remotePtr( remote );
2659 return true;
2660}
2661
2662
2663bool LIBGIT_BACKEND::SetRemoteUrl( git_repository* aRepo, const wxString& aName, const wxString& aUrl )
2664{
2665 if( !aRepo || aName.IsEmpty() )
2666 return false;
2667
2668 return git_remote_set_url( aRepo, aName.utf8_string().c_str(), aUrl.utf8_string().c_str() ) == GIT_OK;
2669}
2670
2671
2672bool LIBGIT_BACKEND::RemoveRemote( git_repository* aRepo, const wxString& aName )
2673{
2674 if( !aRepo || aName.IsEmpty() )
2675 return false;
2676
2677 return git_remote_delete( aRepo, aName.utf8_string().c_str() ) == GIT_OK;
2678}
2679
2680
2681bool LIBGIT_BACKEND::SetUserConfig( git_repository* aRepo, const wxString& aName, const wxString& aEmail )
2682{
2683 if( !aRepo )
2684 return false;
2685
2686 git_config* config = nullptr;
2687
2688 if( git_repository_config( &config, aRepo ) != GIT_OK )
2689 return false;
2690
2691 KIGIT::GitConfigPtr configPtr( config );
2692
2693 bool ok = git_config_set_string( config, "user.name", aName.utf8_string().c_str() ) == GIT_OK;
2694 ok = ok && git_config_set_string( config, "user.email", aEmail.utf8_string().c_str() ) == GIT_OK;
2695 return ok;
2696}
2697
2698
2699namespace
2700{
2701
2702// Owner-only, matching the other extracted-content helpers.
2703bool writeConflictSide( git_repository* aRepo, const git_index_entry* aEntry, const wxString& aDest )
2704{
2705 wxFile out;
2706
2707 if( !out.Create( aDest, true, wxS_IRUSR | wxS_IWUSR ) )
2708 return false;
2709
2710 if( !aEntry )
2711 return true;
2712
2713 git_blob* blob = nullptr;
2714
2715 if( git_blob_lookup( &blob, aRepo, &aEntry->id ) != GIT_OK )
2716 return false;
2717
2718 KIGIT::GitBlobPtr blobPtr( blob );
2719
2720 const void* raw = git_blob_rawcontent( blob );
2721 git_off_t size = git_blob_rawsize( blob );
2722
2723 return out.Write( raw, static_cast<size_t>( size ) ) == static_cast<size_t>( size );
2724}
2725
2726
2727// Into a fresh in-memory index, leaving the repository untouched.
2728bool remergeInMemory( git_repository* aRepo, const wxString& aPinnedTheirsOid, KIGIT::GitIndexPtr& aIndexOut,
2729 KIGIT::GitCommitPtr& aOursOut, KIGIT::GitCommitPtr& aTheirsOut, wxString& aError )
2730{
2731 git_oid oursOid;
2732
2733 if( git_reference_name_to_id( &oursOid, aRepo, "HEAD" ) != GIT_OK )
2734 {
2735 aError = _( "Could not get the repository head." );
2736 return false;
2737 }
2738
2739 git_oid theirsOid;
2740
2741 if( !aPinnedTheirsOid.IsEmpty() )
2742 {
2743 // The exact commit the conflicts came from, since the background fetch can move FETCH_HEAD.
2744 if( git_oid_fromstr( &theirsOid, aPinnedTheirsOid.utf8_string().c_str() ) != GIT_OK )
2745 {
2746 aError = _( "Could not find the fetched remote commit." );
2747 return false;
2748 }
2749 }
2750 else
2751 {
2752 git_object* fetchHead = nullptr;
2753
2754 if( git_revparse_single( &fetchHead, aRepo, "FETCH_HEAD" ) != GIT_OK )
2755 {
2756 aError = _( "Could not find the fetched remote commit." );
2757 return false;
2758 }
2759
2760 KIGIT::GitObjectPtr fetchHeadPtr( fetchHead );
2761 theirsOid = *git_object_id( fetchHead );
2762 }
2763
2764 git_commit* ours = nullptr;
2765
2766 if( git_commit_lookup( &ours, aRepo, &oursOid ) != GIT_OK )
2767 {
2768 aError = _( "Could not look up the commits to merge." );
2769 return false;
2770 }
2771
2772 aOursOut.reset( ours );
2773
2774 git_commit* theirs = nullptr;
2775
2776 if( git_commit_lookup( &theirs, aRepo, &theirsOid ) != GIT_OK )
2777 {
2778 aError = _( "Could not look up the commits to merge." );
2779 return false;
2780 }
2781
2782 aTheirsOut.reset( theirs );
2783
2784 git_tree* oursTree = nullptr;
2785
2786 if( git_commit_tree( &oursTree, ours ) != GIT_OK )
2787 {
2788 aError = _( "Could not read the commit trees." );
2789 return false;
2790 }
2791
2792 KIGIT::GitTreePtr oursTreePtr( oursTree );
2793 git_tree* theirsTree = nullptr;
2794
2795 if( git_commit_tree( &theirsTree, theirs ) != GIT_OK )
2796 {
2797 aError = _( "Could not read the commit trees." );
2798 return false;
2799 }
2800
2801 KIGIT::GitTreePtr theirsTreePtr( theirsTree );
2802
2803 // Unrelated histories have no merge base, which git_merge_trees accepts as a null ancestor.
2804 KIGIT::GitTreePtr baseTreePtr;
2805 git_oid baseOid;
2806
2807 if( git_merge_base( &baseOid, aRepo, &oursOid, &theirsOid ) == GIT_OK )
2808 {
2809 git_commit* baseCommit = nullptr;
2810
2811 if( git_commit_lookup( &baseCommit, aRepo, &baseOid ) == GIT_OK )
2812 {
2813 KIGIT::GitCommitPtr baseCommitPtr( baseCommit );
2814 git_tree* baseTree = nullptr;
2815
2816 if( git_commit_tree( &baseTree, baseCommit ) == GIT_OK )
2817 baseTreePtr.reset( baseTree );
2818 }
2819 }
2820
2821 git_merge_options mergeOpts;
2822 git_index* mergedIndex = nullptr;
2823
2824 git_merge_options_init( &mergeOpts, GIT_MERGE_OPTIONS_VERSION );
2825
2826 if( git_merge_trees( &mergedIndex, aRepo, baseTreePtr.get(), oursTree, theirsTree, &mergeOpts ) != GIT_OK )
2827 {
2828 aError = wxString::Format( _( "Merge failed: %s" ), KIGIT_COMMON::GetLastGitError() );
2829 return false;
2830 }
2831
2832 aIndexOut.reset( mergedIndex );
2833 return true;
2834}
2835
2836} // namespace
2837
2838
2839// Just these paths. Everything else is left alone, so a rollback cannot reach an untouched file.
2840static bool restorePathsFromCommit( git_repository* aRepo, git_commit* aCommit, const std::vector<std::string>& aPaths )
2841{
2842 if( aPaths.empty() )
2843 return true;
2844
2845 git_tree* tree = nullptr;
2846
2847 if( git_commit_tree( &tree, aCommit ) != GIT_OK )
2848 return false;
2849
2850 KIGIT::GitTreePtr treePtr( tree );
2851 std::vector<char*> pathSpec;
2852
2853 // git_strarray wants mutable pointers, though checkout only reads them.
2854 for( const std::string& path : aPaths )
2855 pathSpec.push_back( const_cast<char*>( path.c_str() ) );
2856
2857 git_checkout_options checkoutOpts;
2858 git_checkout_init_options( &checkoutOpts, GIT_CHECKOUT_OPTIONS_VERSION );
2859 checkoutOpts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED;
2860 checkoutOpts.paths.strings = pathSpec.data();
2861 checkoutOpts.paths.count = pathSpec.size();
2862
2863 return git_checkout_tree( aRepo, reinterpret_cast<git_object*>( tree ), &checkoutOpts ) == GIT_OK;
2864}
2865
2866
2867// Whether aPath in aTree holds exactly what is on disk at aOnDisk.
2868static bool treeHolds( git_tree* aTree, const char* aPath, const git_oid& aOnDisk )
2869{
2870 git_tree_entry* entry = nullptr;
2871
2872 if( git_tree_entry_bypath( &entry, aTree, aPath ) != GIT_OK )
2873 return false;
2874
2875 KIGIT::GitTreeEntryPtr entryPtr( entry );
2876
2877 return git_oid_equal( &aOnDisk, git_tree_entry_id( entry ) );
2878}
2879
2880
2881// Safe when nothing is there, or the file still holds what was written or what is being restored.
2882static bool safeToRestore( git_repository* aRepo, const char* aPath, git_tree* aPreviousTree, git_tree* aWrittenTree )
2883{
2884 const char* workdir = git_repository_workdir( aRepo );
2885
2886 if( !workdir )
2887 return false;
2888
2889 // Nothing there is the state a restore is meant to repair, so there is nothing to lose.
2890 if( !wxFileName::Exists( wxString::FromUTF8( workdir ) + wxString::FromUTF8( aPath ) ) )
2891 return true;
2892
2893 git_oid onDisk;
2894
2895 // Not readable as a file, such as a directory of untracked work, so it cannot be shown ours.
2896 if( git_repository_hashfile( &onDisk, aRepo, aPath, GIT_OBJECT_BLOB, nullptr ) != GIT_OK )
2897 return false;
2898
2899 return treeHolds( aWrittenTree, aPath, onDisk ) || treeHolds( aPreviousTree, aPath, onDisk );
2900}
2901
2902
2903// Only the paths the operation wrote, and only while they still hold what it wrote, so an edit
2904// made meanwhile is kept. False when anything had to be left behind.
2905static bool rollbackWorkingTree( git_repository* aRepo, git_commit* aHeadCommit, git_tree* aWrittenTree )
2906{
2907 git_tree* previousTree = nullptr;
2908
2909 if( git_commit_tree( &previousTree, aHeadCommit ) != GIT_OK )
2910 return false;
2911
2912 KIGIT::GitTreePtr previousTreePtr( previousTree );
2913
2914 git_diff* diff = nullptr;
2915
2916 if( git_diff_tree_to_tree( &diff, aRepo, previousTree, aWrittenTree, nullptr ) != GIT_OK )
2917 return false;
2918
2919 KIGIT::GitDiffPtr diffPtr( diff );
2920
2921 std::set<std::string> touched;
2922
2923 for( size_t ii = 0; ii < git_diff_num_deltas( diff ); ii++ )
2924 {
2925 const git_diff_delta* delta = git_diff_get_delta( diff, ii );
2926
2927 // A rename reports two paths, and both need putting back.
2928 for( const char* path : { delta->old_file.path, delta->new_file.path } )
2929 {
2930 if( path )
2931 touched.emplace( path );
2932 }
2933 }
2934
2935 std::vector<std::string> restorable;
2936 bool leftBehind = false;
2937
2938 for( const std::string& path : touched )
2939 {
2940 if( safeToRestore( aRepo, path.c_str(), previousTree, aWrittenTree ) )
2941 restorable.push_back( path );
2942 else
2943 leftBehind = true;
2944 }
2945
2946 if( restorable.empty() )
2947 return !leftBehind;
2948
2949 return restorePathsFromCommit( aRepo, aHeadCommit, restorable ) && !leftBehind;
2950}
2951
2952
2953// So each operation can word the same underlying failure its own way.
2962
2963
2964// Working tree first, branch last, so any failure leaves the branch and its history untouched.
2965static PUBLISH_RESULT publishPreparedCommit( git_repository* aRepo, git_tree* aTree, const git_oid& aCommitOid,
2966 git_reference* aHeadRef, git_commit* aRollbackCommit,
2967 const char* aReflogLabel, wxString& aGitError )
2968{
2969 git_checkout_options checkoutOpts;
2970 git_checkout_options_init( &checkoutOpts, GIT_CHECKOUT_OPTIONS_VERSION );
2971 checkoutOpts.checkout_strategy = GIT_CHECKOUT_FORCE;
2972
2973 if( git_checkout_tree( aRepo, reinterpret_cast<git_object*>( aTree ), &checkoutOpts ) != GIT_OK )
2974 {
2975 aGitError = KIGIT_COMMON::GetLastGitError();
2976
2977 bool rolledBack = rollbackWorkingTree( aRepo, aRollbackCommit, aTree );
2978
2979 refreshCachedIndex( aRepo );
2980
2982 }
2983
2984 git_reference* updatedRef = nullptr;
2985
2986 if( git_reference_set_target( &updatedRef, aHeadRef, &aCommitOid, aReflogLabel ) != GIT_OK )
2987 {
2988 aGitError = KIGIT_COMMON::GetLastGitError();
2989
2990 bool rolledBack = rollbackWorkingTree( aRepo, aRollbackCommit, aTree );
2991
2992 refreshCachedIndex( aRepo );
2993
2995 }
2996
2997 KIGIT::GitReferencePtr updatedRefPtr( updatedRef );
2998
3000}
3001
3002
3003RestoreResult LIBGIT_BACKEND::RestoreVersion( KIGIT_COMMON& aCommon, const wxString& aCommitHash,
3004 KIGIT_ERROR_MESSAGE& aError )
3005{
3006 git_repository* repo = aCommon.GetRepo();
3007
3008 if( !repo )
3009 {
3010 aError.details = _( "The project has no git repository." );
3011 return RestoreResult::Error;
3012 }
3013
3014 const std::optional<wxString> projectPrefix = aCommon.GetProjectPrefix();
3015
3016 if( !projectPrefix )
3017 {
3018 aError.details = _( "Could not work out which folder this project occupies in the repository, "
3019 "so nothing was restored." );
3020 return RestoreResult::Error;
3021 }
3022
3023 git_object* targetObj = nullptr;
3024
3025 if( git_revparse_single( &targetObj, repo, aCommitHash.utf8_string().c_str() ) != GIT_OK )
3026 {
3027 aError.details = _( "Could not find that version." );
3028 return RestoreResult::Error;
3029 }
3030
3031 KIGIT::GitObjectPtr targetObjPtr( targetObj );
3032 git_commit* targetCommit = nullptr;
3033
3034 if( git_object_type( targetObj ) != GIT_OBJECT_COMMIT
3035 || git_commit_lookup( &targetCommit, repo, git_object_id( targetObj ) ) != GIT_OK )
3036 {
3037 aError.details = _( "Could not find that version." );
3038 return RestoreResult::Error;
3039 }
3040
3041 KIGIT::GitCommitPtr targetCommitPtr( targetCommit );
3042 git_tree* targetTree = nullptr;
3043
3044 if( git_commit_tree( &targetTree, targetCommit ) != GIT_OK )
3045 {
3046 aError.details = _( "Could not read that version's contents." );
3047 return RestoreResult::Error;
3048 }
3049
3050 KIGIT::GitTreePtr targetTreePtr( targetTree );
3051
3052 git_reference* headRef = nullptr;
3053
3054 if( git_repository_head( &headRef, repo ) != GIT_OK )
3055 {
3056 aError.details = _( "Could not get the repository head." );
3057 return RestoreResult::Error;
3058 }
3059
3060 KIGIT::GitReferencePtr headRefPtr( headRef );
3061 git_commit* headCommit = nullptr;
3062
3063 if( git_reference_peel( (git_object**) &headCommit, headRef, GIT_OBJECT_COMMIT ) != GIT_OK )
3064 {
3065 aError.details = _( "Could not get the repository head." );
3066 return RestoreResult::Error;
3067 }
3068
3069 KIGIT::GitCommitPtr headCommitPtr( headCommit );
3070 git_tree* headTree = nullptr;
3071
3072 if( git_commit_tree( &headTree, headCommit ) != GIT_OK )
3073 {
3074 aError.details = _( "Could not get the repository head." );
3075 return RestoreResult::Error;
3076 }
3077
3078 KIGIT::GitTreePtr headTreePtr( headTree );
3079
3080 // A repository can hold several projects, so graft only this one's subtree onto the head.
3081 KIGIT::GitTreePtr effectiveTreePtr;
3082 git_tree* effectiveTree = targetTree;
3083
3084 if( !projectPrefix->IsEmpty() )
3085 {
3086 // libgit2 expects the folder path without a trailing slash.
3087 std::string prefixUtf8( projectPrefix->BeforeLast( '/' ).utf8_string() );
3088 git_tree_entry* subtreeEntry = nullptr;
3089
3090 if( git_tree_entry_bypath( &subtreeEntry, targetTree, prefixUtf8.c_str() ) != GIT_OK )
3091 {
3092 aError.details = _( "That version does not contain this project's folder." );
3093 return RestoreResult::Error;
3094 }
3095
3096 KIGIT::GitTreeEntryPtr subtreeEntryPtr( subtreeEntry );
3097
3098 if( git_tree_entry_type( subtreeEntry ) != GIT_OBJECT_TREE )
3099 {
3100 aError.details = _( "That version does not contain this project's folder." );
3101 return RestoreResult::Error;
3102 }
3103
3104 git_tree_update update;
3105 update.action = GIT_TREE_UPDATE_UPSERT;
3106 update.id = *git_tree_entry_id( subtreeEntry );
3107 update.filemode = GIT_FILEMODE_TREE;
3108 update.path = prefixUtf8.c_str();
3109
3110 git_oid combinedOid;
3111
3112 if( git_tree_create_updated( &combinedOid, repo, headTree, 1, &update ) != GIT_OK )
3113 {
3114 aError.details = wxString::Format( _( "Could not build the restored content: %s" ),
3116 return RestoreResult::Error;
3117 }
3118
3119 git_tree* combined = nullptr;
3120
3121 if( git_tree_lookup( &combined, repo, &combinedOid ) != GIT_OK )
3122 {
3123 aError.details = _( "Could not build the restored content." );
3124 return RestoreResult::Error;
3125 }
3126
3127 effectiveTreePtr.reset( combined );
3128 effectiveTree = combined;
3129 }
3130
3131 // The checkout is forced, so refuse when uncommitted work would be overwritten. An untracked
3132 // file only counts when the restored content carries its path.
3133 {
3134 git_status_options statusOpts;
3135 git_status_init_options( &statusOpts, GIT_STATUS_OPTIONS_VERSION );
3136 statusOpts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
3137 statusOpts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS;
3138
3139 git_status_list* statusList = nullptr;
3140
3141 if( git_status_list_new( &statusList, repo, &statusOpts ) != GIT_OK )
3142 {
3143 aError.details = _( "Could not read the repository status." );
3144 return RestoreResult::Error;
3145 }
3146
3147 KIGIT::GitStatusListPtr statusListPtr( statusList );
3148 bool dirty = false;
3149 size_t count = git_status_list_entrycount( statusList );
3150
3151 for( size_t ii = 0; ii < count && !dirty; ++ii )
3152 {
3153 const git_status_entry* entry = git_status_byindex( statusList, ii );
3154
3155 if( entry->status == GIT_STATUS_WT_NEW )
3156 {
3157 const char* path = entry->index_to_workdir ? entry->index_to_workdir->new_file.path : nullptr;
3158
3159 if( path )
3160 {
3161 git_tree_entry* treeEntry = nullptr;
3162
3163 if( git_tree_entry_bypath( &treeEntry, effectiveTree, path ) == GIT_OK )
3164 {
3165 KIGIT::GitTreeEntryPtr treeEntryPtr( treeEntry );
3166 dirty = true;
3167 }
3168 }
3169 }
3170 else
3171 {
3172 dirty = true;
3173 }
3174 }
3175
3176 if( dirty )
3177 return RestoreResult::Dirty;
3178 }
3179
3180 if( git_oid_equal( git_commit_tree_id( headCommit ), git_tree_id( effectiveTree ) ) )
3182
3183 git_signature* signature = nullptr;
3184
3185 if( int rc = git_signature_default( &signature, repo ); rc != GIT_OK )
3186 {
3187 aError = describeSignatureFailure( rc );
3188 return RestoreResult::Error;
3189 }
3190
3191 KIGIT::GitSignaturePtr signaturePtr( signature );
3192
3193 char shortHash[13] = { 0 };
3194 git_oid_tostr( shortHash, sizeof( shortHash ), git_commit_id( targetCommit ) );
3195
3196 // The commit stays dangling until the checkout succeeds, so a failure leaves the branch as
3197 // it was.
3198 const git_commit* parents[] = { headCommit };
3199 wxString message = wxString::Format( _( "Restore version %s" ), shortHash );
3200 git_oid restoreCommitOid;
3201
3202 if( git_commit_create( &restoreCommitOid, repo, nullptr, signature, signature, nullptr,
3203 message.utf8_string().c_str(), effectiveTree, 1, parents )
3204 != GIT_OK )
3205 {
3206 aError.details =
3207 wxString::Format( _( "Could not create the restore commit: %s" ), KIGIT_COMMON::GetLastGitError() );
3208 return RestoreResult::Error;
3209 }
3210
3211 wxString gitError;
3212
3213 switch( publishPreparedCommit( repo, effectiveTree, restoreCommitOid, headRef, headCommit, "restore", gitError ) )
3214 {
3216
3218 aError.details =
3219 wxString::Format( _( "Could not update the working files, so nothing was changed: %s" ), gitError );
3220 return RestoreResult::Error;
3221
3223 aError.details = wxString::Format( _( "Could not update the working files, and they may be partially "
3224 "changed: %s" ),
3225 gitError );
3226 return RestoreResult::Error;
3227
3229 aError.details =
3230 wxString::Format( _( "Could not record the restore commit, so nothing was changed: %s" ), gitError );
3231 return RestoreResult::Error;
3232
3234 aError.details = wxString::Format( _( "Could not record the restore commit, and the working files may "
3235 "have been changed: %s" ),
3236 gitError );
3237 return RestoreResult::Error;
3238 }
3239
3240 return RestoreResult::Error;
3241}
3242
3243
3244bool LIBGIT_BACKEND::DiscardChanges( git_repository* aRepo, const std::vector<wxString>& aPaths, wxString& aError )
3245{
3246 if( !aRepo )
3247 {
3248 aError = _( "The project has no git repository." );
3249 return false;
3250 }
3251
3252 if( aPaths.empty() )
3253 return true;
3254
3255 std::vector<std::string> pathStorage;
3256 std::vector<char*> pathPtrs;
3257 std::vector<wxString> neverCommitted;
3258
3259 for( const wxString& path : aPaths )
3260 {
3261 if( IsPathInHead( aRepo, path ) )
3262 pathStorage.push_back( path.utf8_string() );
3263 else
3264 neverCommitted.push_back( path );
3265 }
3266
3267 // HEAD has nothing to restore for these, so discarding means dropping the staged entry.
3268 if( !neverCommitted.empty() )
3269 {
3270 git_index* index = nullptr;
3271
3272 if( git_repository_index( &index, aRepo ) != GIT_OK )
3273 {
3275 return false;
3276 }
3277
3278 KIGIT::GitIndexPtr indexPtr( index );
3279
3280 for( const wxString& path : neverCommitted )
3281 git_index_remove_bypath( index, path.utf8_string().c_str() );
3282
3283 if( git_index_write( index ) != GIT_OK )
3284 {
3286 return false;
3287 }
3288 }
3289
3290 if( pathStorage.empty() )
3291 return true;
3292
3293 for( std::string& path : pathStorage )
3294 pathPtrs.push_back( path.data() );
3295
3296 git_checkout_options opts;
3297 git_checkout_init_options( &opts, GIT_CHECKOUT_OPTIONS_VERSION );
3298
3299 // Without DISABLE_PATHSPEC_MATCH these are wildmatch patterns, so a file name holding a
3300 // glob character would discard its neighbours too.
3301 opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH;
3302 opts.paths.strings = pathPtrs.data();
3303 opts.paths.count = pathPtrs.size();
3304
3305 if( git_checkout_head( aRepo, &opts ) != GIT_OK )
3306 {
3308 return false;
3309 }
3310
3311 return true;
3312}
3313
3314
3315bool LIBGIT_BACKEND::IsPathInHead( git_repository* aRepo, const wxString& aPath )
3316{
3317 if( !aRepo )
3318 return false;
3319
3320 git_oid headOid;
3321
3322 if( git_reference_name_to_id( &headOid, aRepo, "HEAD" ) != GIT_OK )
3323 return false;
3324
3325 git_commit* headCommit = nullptr;
3326
3327 if( git_commit_lookup( &headCommit, aRepo, &headOid ) != GIT_OK )
3328 return false;
3329
3330 KIGIT::GitCommitPtr headCommitPtr( headCommit );
3331 git_tree* headTree = nullptr;
3332
3333 if( git_commit_tree( &headTree, headCommit ) != GIT_OK )
3334 return false;
3335
3336 KIGIT::GitTreePtr headTreePtr( headTree );
3337 git_tree_entry* entry = nullptr;
3338
3339 if( git_tree_entry_bypath( &entry, headTree, aPath.utf8_string().c_str() ) != GIT_OK )
3340 return false;
3341
3342 KIGIT::GitTreeEntryPtr entryPtr( entry );
3343
3344 return true;
3345}
3346
3347
3348bool LIBGIT_BACKEND::AbandonOperation( git_repository* aRepo, wxString& aError )
3349{
3350 if( !aRepo )
3351 {
3352 aError = _( "The project has no git repository." );
3353 return false;
3354 }
3355
3356 // One wording for every rebase we cannot end. The user cannot act on the difference
3357 // between the states.
3358 const wxString rebaseIsAnotherProgram =
3359 _( "This rebase was started by another program, and only that program can finish or abort it." );
3360
3361 switch( git_repository_state( aRepo ) )
3362 {
3363 case GIT_REPOSITORY_STATE_NONE: return true;
3364
3365 case GIT_REPOSITORY_STATE_REBASE_MERGE:
3366 case GIT_REPOSITORY_STATE_REBASE_INTERACTIVE:
3367 {
3368 // Aborting restores the branch and its starting commit. A reset to the detached HEAD
3369 // would not.
3370 git_rebase* rebase = nullptr;
3371
3372 if( git_rebase_open( &rebase, aRepo, nullptr ) != GIT_OK )
3373 {
3374 aError = rebaseIsAnotherProgram;
3375 return false;
3376 }
3377
3378 KIGIT::GitRebasePtr rebasePtr( rebase );
3379
3380 if( git_rebase_abort( rebase ) != GIT_OK )
3381 {
3382 aError = rebaseIsAnotherProgram;
3383 return false;
3384 }
3385
3386 return true;
3387 }
3388
3389 case GIT_REPOSITORY_STATE_BISECT:
3390 // A bisect is a search through the history, not a half-finished change. A reset would
3391 // leave the project on whichever commit the search had reached.
3392 aError = _( "This project is in the middle of a search through its history, started by another "
3393 "program. Only that program can end the search and bring the project back." );
3394 return false;
3395
3396 case GIT_REPOSITORY_STATE_REBASE:
3397 case GIT_REPOSITORY_STATE_APPLY_MAILBOX:
3398 case GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE:
3399 aError = rebaseIsAnotherProgram;
3400 return false;
3401
3402 default: break;
3403 }
3404
3405 git_object* head = nullptr;
3406
3407 if( git_revparse_single( &head, aRepo, "HEAD" ) != GIT_OK )
3408 {
3410 return false;
3411 }
3412
3413 KIGIT::GitObjectPtr headPtr( head );
3414 git_tree* headTree = nullptr;
3415
3416 if( git_object_peel( reinterpret_cast<git_object**>( &headTree ), head, GIT_OBJECT_TREE ) != GIT_OK )
3417 {
3419 return false;
3420 }
3421
3422 KIGIT::GitTreePtr headTreePtr( headTree );
3423 git_index* index = nullptr;
3424
3425 if( git_repository_index( &index, aRepo ) != GIT_OK )
3426 {
3428 return false;
3429 }
3430
3431 KIGIT::GitIndexPtr indexPtr( index );
3432
3433 // Only what the operation touched, as git's own merge abort does. Going back over the whole
3434 // repository would throw away uncommitted work in files it never went near, and a
3435 // repository can hold more than one project.
3436 std::set<std::string> touched;
3437
3438 // A conflicted file has no stage zero, so the diff below does not report it.
3439 git_index_conflict_iterator* conflicts = nullptr;
3440
3441 if( git_index_conflict_iterator_new( &conflicts, index ) == GIT_OK )
3442 {
3443 const git_index_entry* ancestor = nullptr;
3444 const git_index_entry* ours = nullptr;
3445 const git_index_entry* theirs = nullptr;
3446
3447 while( git_index_conflict_next( &ancestor, &ours, &theirs, conflicts ) == GIT_OK )
3448 {
3449 for( const git_index_entry* entry : { ancestor, ours, theirs } )
3450 {
3451 if( entry && entry->path )
3452 touched.insert( entry->path );
3453 }
3454 }
3455
3456 git_index_conflict_iterator_free( conflicts );
3457 }
3458
3459 git_diff* diff = nullptr;
3460
3461 if( git_diff_tree_to_index( &diff, aRepo, headTree, index, nullptr ) != GIT_OK )
3462 {
3464 return false;
3465 }
3466
3467 KIGIT::GitDiffPtr diffPtr( diff );
3468
3470 [&touched]( const git_diff_delta& aDelta )
3471 {
3472 if( aDelta.old_file.path )
3473 touched.insert( aDelta.old_file.path );
3474
3475 if( aDelta.new_file.path )
3476 touched.insert( aDelta.new_file.path );
3477 } );
3478
3479 if( !touched.empty() )
3480 {
3481 std::vector<char*> paths;
3482
3483 for( const std::string& path : touched )
3484 paths.push_back( const_cast<char*>( path.c_str() ) );
3485
3486 git_checkout_options checkoutOpts;
3487 git_checkout_options_init( &checkoutOpts, GIT_CHECKOUT_OPTIONS_VERSION );
3488 checkoutOpts.checkout_strategy = GIT_CHECKOUT_FORCE;
3489 checkoutOpts.paths.strings = paths.data();
3490 checkoutOpts.paths.count = paths.size();
3491
3492 if( git_checkout_head( aRepo, &checkoutOpts ) != GIT_OK )
3493 {
3495 return false;
3496 }
3497 }
3498
3499 if( git_repository_state_cleanup( aRepo ) != GIT_OK )
3500 {
3502 return false;
3503 }
3504
3505 return true;
3506}
3507
3508
3509bool LIBGIT_BACKEND::GetMergeConflicts( git_repository* aRepo, const wxString& aStageDir, wxString& aHeadOid,
3510 wxString& aTheirsOid, std::vector<KIGIT_CONFLICT_SIDES>& aConflicts,
3511 wxString& aError )
3512{
3513 aConflicts.clear();
3514
3515 if( !aRepo )
3516 {
3517 aError = _( "The project has no git repository." );
3518 return false;
3519 }
3520
3521 KIGIT::GitIndexPtr mergedIndex;
3523 KIGIT::GitCommitPtr theirs;
3524
3525 if( !remergeInMemory( aRepo, wxEmptyString, mergedIndex, ours, theirs, aError ) )
3526 return false;
3527
3528 {
3529 char theirsStr[GIT_OID_HEXSZ + 1] = { 0 };
3530 git_oid_tostr( theirsStr, sizeof( theirsStr ), git_commit_id( theirs.get() ) );
3531 aTheirsOid = wxString::FromUTF8( theirsStr );
3532 }
3533
3534 if( !git_index_has_conflicts( mergedIndex.get() ) )
3535 {
3536 aError = _( "The merge has no conflicts." );
3537 return false;
3538 }
3539
3540 if( !wxFileName::Mkdir( aStageDir, 0700, wxPATH_MKDIR_FULL ) )
3541 {
3542 aError = _( "Could not create a temporary directory." );
3543 return false;
3544 }
3545
3546 char oidStr[GIT_OID_HEXSZ + 1] = { 0 };
3547 git_oid_tostr( oidStr, sizeof( oidStr ), git_commit_id( ours.get() ) );
3548 aHeadOid = wxString::FromUTF8( oidStr );
3549
3550 git_index_conflict_iterator* iter = nullptr;
3551
3552 if( git_index_conflict_iterator_new( &iter, mergedIndex.get() ) != GIT_OK )
3553 {
3554 aError = _( "Could not list the merge conflicts." );
3555 return false;
3556 }
3557
3558 KIGIT::GitIndexConflictIteratorPtr iterPtr( iter );
3559
3560 const git_index_entry* ancestor = nullptr;
3561 const git_index_entry* mine = nullptr;
3562 const git_index_entry* remote = nullptr;
3563 const wxString sep = wxFileName::GetPathSeparator();
3564 int n = 0;
3565
3566 while( git_index_conflict_next( &ancestor, &mine, &remote, iter ) == GIT_OK )
3567 {
3568 const git_index_entry* named = mine ? mine : ( remote ? remote : ancestor );
3569
3570 if( !named )
3571 continue;
3572
3574 sides.path = wxString::FromUTF8( named->path );
3575 sides.hasAncestor = ancestor != nullptr;
3576 sides.hasOurs = mine != nullptr;
3577 sides.hasTheirs = remote != nullptr;
3578
3579 wxString base = wxFileName( sides.path ).GetFullName();
3580 sides.ancestorFile = aStageDir + sep + wxString::Format( wxS( "c%d_base_%s" ), n, base );
3581 sides.oursFile = aStageDir + sep + wxString::Format( wxS( "c%d_mine_%s" ), n, base );
3582 sides.theirsFile = aStageDir + sep + wxString::Format( wxS( "c%d_remote_%s" ), n, base );
3583 n++;
3584
3585 if( !writeConflictSide( aRepo, ancestor, sides.ancestorFile )
3586 || !writeConflictSide( aRepo, mine, sides.oursFile )
3587 || !writeConflictSide( aRepo, remote, sides.theirsFile ) )
3588 {
3589 aError = _( "Could not extract the conflicting file versions." );
3590 return false;
3591 }
3592
3593 aConflicts.push_back( sides );
3594 }
3595
3596 return !aConflicts.empty();
3597}
3598
3599
3601 const wxString& aTheirsOid,
3602 const std::vector<std::pair<wxString, wxString>>& aResolvedFiles,
3603 KIGIT_ERROR_MESSAGE& aError )
3604{
3605 if( !aRepo )
3606 {
3607 aError.details = _( "The project has no git repository." );
3609 }
3610
3611 // The lock is released while the user resolves, so anything dirty now appeared during
3612 // resolution and the checkout below would revert it. Untracked files do not count, since
3613 // the recovery copies the panel writes beside a conflicted file are untracked.
3614 {
3615 git_status_options statusOpts;
3616 git_status_init_options( &statusOpts, GIT_STATUS_OPTIONS_VERSION );
3617 statusOpts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
3618
3619 git_status_list* statusList = nullptr;
3620
3621 if( git_status_list_new( &statusList, aRepo, &statusOpts ) != GIT_OK )
3622 {
3623 aError.details = _( "Could not read the repository status." );
3625 }
3626
3627 KIGIT::GitStatusListPtr statusListPtr( statusList );
3628
3629 if( git_status_list_entrycount( statusList ) > 0 )
3630 {
3631 aError.details = _( "Files changed while the conflicts were being resolved, and finishing the "
3632 "merge would overwrite those changes. Get changes again to redo the merge "
3633 "with the current files." );
3635 }
3636 }
3637
3638 KIGIT::GitIndexPtr mergedIndex;
3640 KIGIT::GitCommitPtr theirs;
3641
3642 if( !remergeInMemory( aRepo, aTheirsOid, mergedIndex, ours, theirs, aError.details ) )
3644
3645 char oidStr[GIT_OID_HEXSZ + 1] = { 0 };
3646 git_oid_tostr( oidStr, sizeof( oidStr ), git_commit_id( ours.get() ) );
3647
3648 // The resolutions were made against this head. If the branch moved, they no longer apply.
3649 if( aExpectedHeadOid != wxString::FromUTF8( oidStr ) )
3650 {
3651 aError.details = _( "The repository changed while resolving. Get changes again." );
3653 }
3654
3655 for( const auto& [path, resolvedFile] : aResolvedFiles )
3656 {
3657 std::string pathUtf8( path.utf8_string() );
3658 const git_index_entry* mine = nullptr;
3659 const git_index_entry* remote = nullptr;
3660 const git_index_entry* ancestor = nullptr;
3661
3662 git_index_conflict_get( &ancestor, &mine, &remote, mergedIndex.get(), pathUtf8.c_str() );
3663
3664 // An empty path means deleted. An empty file would commit a zero byte document instead.
3665 if( resolvedFile.IsEmpty() )
3666 {
3667 if( git_index_conflict_remove( mergedIndex.get(), pathUtf8.c_str() ) != GIT_OK )
3668 {
3669 aError.details = wxString::Format( _( "Could not mark '%s' as resolved." ), path );
3671 }
3672
3673 continue;
3674 }
3675
3676 std::ifstream in( resolvedFile.fn_str(), std::ios::binary );
3677
3678 if( !in )
3679 {
3680 aError.details = wxString::Format( _( "Could not read the resolved file for '%s'." ), path );
3682 }
3683
3684 std::string bytes( ( std::istreambuf_iterator<char>( in ) ), std::istreambuf_iterator<char>() );
3685
3686 git_oid blobOid;
3687
3688 if( git_blob_create_from_buffer( &blobOid, aRepo, bytes.data(), bytes.size() ) != GIT_OK )
3689 {
3690 aError.details = wxString::Format( _( "Could not store the resolved file for '%s'." ), path );
3692 }
3693
3694 git_index_entry entry;
3695 memset( &entry, 0, sizeof( entry ) );
3696 entry.path = pathUtf8.c_str();
3697 entry.id = blobOid;
3698 entry.mode = mine ? mine->mode : ( remote ? remote->mode : GIT_FILEMODE_BLOB );
3699
3700 if( git_index_conflict_remove( mergedIndex.get(), pathUtf8.c_str() ) != GIT_OK
3701 || git_index_add( mergedIndex.get(), &entry ) != GIT_OK )
3702 {
3703 aError.details = wxString::Format( _( "Could not mark '%s' as resolved." ), path );
3705 }
3706 }
3707
3708 if( git_index_has_conflicts( mergedIndex.get() ) )
3709 {
3710 aError.details = _( "Not every conflict was resolved." );
3712 }
3713
3714 git_oid treeOid;
3715
3716 if( git_index_write_tree_to( &treeOid, mergedIndex.get(), aRepo ) != GIT_OK )
3717 {
3718 aError.details = _( "Could not write the merge result." );
3720 }
3721
3722 git_tree* tree = nullptr;
3723
3724 if( git_tree_lookup( &tree, aRepo, &treeOid ) != GIT_OK )
3725 {
3726 aError.details = _( "Could not write the merge result." );
3728 }
3729
3730 KIGIT::GitTreePtr treePtr( tree );
3731
3732 git_signature* signature = nullptr;
3733
3734 if( int rc = git_signature_default( &signature, aRepo ); rc != GIT_OK )
3735 {
3736 aError = describeSignatureFailure( rc );
3738 }
3739
3740 KIGIT::GitSignaturePtr signaturePtr( signature );
3741
3742 git_reference* headRef = nullptr;
3743
3744 if( git_repository_head( &headRef, aRepo ) != GIT_OK )
3745 {
3746 aError.details = _( "Could not get the repository head." );
3748 }
3749
3750 KIGIT::GitReferencePtr headRefPtr( headRef );
3751
3752 const git_commit* parents[] = { ours.get(), theirs.get() };
3753 wxString message =
3754 wxString::Format( _( "Merge remote-tracking branch into %s" ), git_reference_shorthand( headRef ) );
3755 git_oid mergeCommitOid;
3756
3757 // Branch last, because the checkout is the step that can fail.
3758 if( git_commit_create( &mergeCommitOid, aRepo, nullptr, signature, signature, nullptr,
3759 message.utf8_string().c_str(), tree, 2, parents )
3760 != GIT_OK )
3761 {
3762 aError.details = wxString::Format( _( "Could not create merge commit: %s" ), KIGIT_COMMON::GetLastGitError() );
3764 }
3765
3766 // Bring the working tree and the real index to the merged content.
3767 wxString gitError;
3768
3769 switch( publishPreparedCommit( aRepo, tree, mergeCommitOid, headRef, ours.get(), "merge", gitError ) )
3770 {
3772
3774 aError.details =
3775 wxString::Format( _( "Could not update the working files, so nothing was changed: %s" ), gitError );
3777
3779 aError.details = wxString::Format( _( "Could not update the working files, and they may be partially "
3780 "changed: %s" ),
3781 gitError );
3783
3785 aError.details =
3786 wxString::Format( _( "Could not record the merge commit, so nothing was changed: %s" ), gitError );
3788
3790 aError.details = wxString::Format( _( "Could not record the merge commit, and the working files may "
3791 "have been changed: %s" ),
3792 gitError );
3794 }
3795
3797}
3798
3799
3800bool LIBGIT_BACKEND::RemoveVCS( git_repository*& aRepo, const wxString& aProjectPath, bool aRemoveGitDir,
3801 wxString* aErrors )
3802{
3803 if( aRepo )
3804 {
3805 git_repository_free( aRepo );
3806 aRepo = nullptr;
3807 }
3808
3809 if( aRemoveGitDir )
3810 {
3811 wxFileName gitDir( aProjectPath, wxEmptyString );
3812 gitDir.AppendDir( ".git" );
3813
3814 if( gitDir.DirExists() )
3815 {
3816 wxString errors;
3817
3818 if( !RmDirRecursive( gitDir.GetPath(), &errors ) )
3819 {
3820 if( aErrors )
3821 *aErrors = errors;
3822
3823 wxLogTrace( traceGit, "Failed to remove .git directory: %s", errors );
3824 return false;
3825 }
3826 }
3827 }
3828
3829 wxLogTrace( traceGit, "Successfully removed VCS from project" );
3830 return true;
3831}
int index
const char * name
KIGIT_ORPHAN_REGISTRY m_orphanRegistry
wxString GetClonePath() const
bool MatchesExpectedTarget(const wxString &aBranch, const wxString &aHeadOid) const
True without an expectation, or when both values match the captured target.
bool HasExpectedTarget() const
KIGIT_COMMON::GIT_STATUS ConvertStatus(unsigned int aGitStatus)
Convert git status flags to KIGIT_COMMON::GIT_STATUS.
static bool ProjectUsesGitLfs(const KIGIT_COMMON &aCommon, wxString *aExample=nullptr, const git_oid *aCommit=nullptr)
Whether any committed file in the project's folder goes through Git LFS.
Publishes a token for as long as its operation runs.
The project's git session.
void SyncRemoteUrl(const wxString &aRawUrl)
Bind the session to the URL a transfer is about to connect to.
std::optional< wxString > GetProjectPrefix() const
Project folder relative to the repository, with a trailing slash.
wxString GetRemote() const
void SetSSHKey(const wxString &aSSHKey)
void SetUsername(const wxString &aUsername)
wxString GetRemoteNameOrDefault() const
Returns GetRemotename() when non-empty, otherwise "origin".
static KIGIT_ERROR_INFO GetLastGitErrorInfo(int aCode)
The error libgit2 holds for a call that returned aCode.
git_repository * GetRepo() const
static wxString GetLastGitError()
libgit2's last message, without credentials from any URL it quotes, so it is safe to show or trace.
static REMOTE_URL ParseRemoteURL(const wxString &aURL)
void SetPassword(const wxString &aPassword)
void SetRemote(const wxString &aRemote)
void SetRepo(git_repository *aRepo)
void AddErrorString(const wxString aErrorString)
void AddError(const wxString &aSummary, const wxString &aDetails)
A summary the user can act on, with the raw text that explains it kept apart for the details.
void AddGitError(const wxString &aSummary)
Queue the summary with libgit2's last error as its details.
wxString GetProjectDir() const
Get the project directory path, preserving symlinks if set.
KIGIT_FAILURE_REPORT ReportTransferFailure(const KIGIT_ERROR_INFO &aError, KIGIT_OPERATION aOperation, const wxString &aRemoteName)
Classify a failed libgit2 transfer call and queue its summary and details.
git_repository * GetRepo() const
Get a pointer to the git repository.
const KIGIT_COMMON::CANCEL_TOKEN & CancelToken() const
bool IsThisOperationCancelled() const
std::unique_lock< std::mutex > GetActionLock() const
KIGIT_COMMON * GetCommon() const
Get the common object.
void ResetTransferState()
Start a transfer with nothing left from an earlier one on this handler or session: no keys or methods...
wxString GetRemotename() const
Get the remote name.
bool DiscardChanges(git_repository *aRepo, const std::vector< wxString > &aPaths, wxString &aError) override
bool AddRemote(git_repository *aRepo, const wxString &aName, const wxString &aUrl) override
bool SetRemoteUrl(git_repository *aRepo, const wxString &aName, const wxString &aUrl) 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)
RestoreResult RestoreVersion(KIGIT_COMMON &aCommon, const wxString &aCommitHash, KIGIT_ERROR_MESSAGE &aError) override
bool CheckoutTag(git_repository *aRepo, const wxString &aName) override
bool RemoveVCS(git_repository *&aRepo, const wxString &aProjectPath, bool aRemoveGitDir, wxString *aErrors) 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
bool StashSave(git_repository *aRepo, const wxString &aMessage) override
bool IsPathInHead(git_repository *aRepo, const wxString &aPath) override
git_repository * GetRepositoryForFile(const char *aFilename, wxString *aError, wxString *aTrustCommand) override
aError receives why git refused to open the repository, and aTrustCommand the command that trusts it ...
void Init() override
bool DeleteTag(git_repository *aRepo, const wxString &aName) override
bool StashPop(git_repository *aRepo, size_t aIndex) 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
bool DeleteBranch(git_repository *aRepo, const wxString &aBranchName) override
bool SetupRemote(GIT_INIT_HANDLER *aHandler, const RemoteConfig &aConfig) override
MergeCommitResult CreateMergedCommit(git_repository *aRepo, const wxString &aExpectedHeadOid, const wxString &aTheirsOid, const std::vector< std::pair< wxString, wxString > > &aResolvedFiles, KIGIT_ERROR_MESSAGE &aError) override
bool IsRepository(GIT_INIT_HANDLER *aHandler, const wxString &aPath) override
int CreateBranch(git_repository *aRepo, const wxString &aBranchName, const wxString &aFrom) override
bool IsLibraryAvailable() override
void Shutdown() override
std::map< wxString, FileStatus > GetFileStatus(GIT_STATUS_HANDLER *aHandler, const wxString &aPathspec, bool aChangedOnly) override
bool StashApply(git_repository *aRepo, size_t aIndex) override
PushResult Push(GIT_PUSH_HANDLER *aHandler, bool aForce=false) override
wxString GetWorkingDirectory(GIT_STATUS_HANDLER *aHandler) override
CommitResult Amend(GIT_COMMIT_HANDLER *aHandler, const std::vector< wxString > &aFiles, const wxString &aMessage, const wxString &aAuthorName, const wxString &aAuthorEmail) override
bool GetMergeConflicts(git_repository *aRepo, const wxString &aStageDir, wxString &aHeadOid, wxString &aTheirsOid, std::vector< KIGIT_CONFLICT_SIDES > &aConflicts, wxString &aError) override
InitResult InitializeRepository(GIT_INIT_HANDLER *aHandler, const wxString &aPath) override
bool SetUserConfig(git_repository *aRepo, const wxString &aName, const wxString &aEmail) override
bool CreateTag(git_repository *aRepo, const wxString &aName, const wxString &aMessage) override
bool StashDrop(git_repository *aRepo, size_t aIndex) override
PullResult PerformPull(GIT_PULL_HANDLER *aHandler) override
bool RemoveRemote(git_repository *aRepo, const wxString &aName) override
bool AbandonOperation(git_repository *aRepo, wxString &aError) override
Drop a merge, rebase, cherry-pick or revert left in progress and return to the last commit.
#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:457
CommitResult
Definition git_backend.h:54
@ SuccessIndexStale
Committed, but the staged list was left as another program had it. Never retry on this.
Definition git_backend.h:58
MergeCommitResult
Definition git_backend.h:67
InitResult
PullResult
PushResult
@ Busy
Another git operation held the action lock, so nothing was pushed.
const wxChar *const traceGit
Flag to enable Git debugging output.
std::string message
int certificate_check_cb(git_cert *aCert, int aValid, const char *aHost, void *aPayload)
Check an SSH host key against known_hosts, which decides alone.
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 push_update_reference_cb(const char *aRefname, const char *aStatus, 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)
RestoreResult
@ AlreadyCurrent
The chosen version's content matches the current head.
@ Restored
A new commit carrying the old version's content was created.
@ Error
The restore failed. See the error string.
@ Dirty
Uncommitted changes exist, and restoring would overwrite them.
#define GIT_BUF_INIT
static bool indexChangedSince(git_index *aIndex, const std::map< std::string, SELECTED_STATE > &aAtStart)
static void refreshCachedIndex(git_repository *aRepo)
static int lookup_branch_reference(git_repository *repo, const wxString &aBranchName, git_reference **aReference)
static bool rollbackWorkingTree(git_repository *aRepo, git_commit *aHeadCommit, git_tree *aWrittenTree)
static std::map< std::string, SELECTED_STATE > readSelectedState(git_index *aIndex, const std::set< std::string > &aSelected)
static bool safeToRestore(git_repository *aRepo, const char *aPath, git_tree *aPreviousTree, git_tree *aWrittenTree)
static bool untrackedFilesOverwrittenBy(git_repository *aRepo, git_tree *aIncomingTree, std::vector< wxString > &aCollisions)
static bool branchMovedSince(git_repository *aRepo, const git_oid &aHeadAtStart, bool aHadHead)
static int clone_remote_cb(git_remote **aOut, git_repository *aRepo, const char *aName, const char *aUrl, void *aPayload)
static PUBLISH_RESULT publishPreparedCommit(git_repository *aRepo, git_tree *aTree, const git_oid &aCommitOid, git_reference *aHeadRef, git_commit *aRollbackCommit, const char *aReflogLabel, wxString &aGitError)
PUBLISH_RESULT
static bool applyStashOntoCleanTree(git_repository *aRepo, size_t aIndex)
static PullResult rebaseOnto(GIT_PULL_HANDLER *aHandler, git_repository *aRepo, const git_annotated_commit *aOnto)
static bool refuseWhileOperationInProgress(git_repository *aRepo, git_index *aIndex, wxString &aError)
static PullResult adoptRemoteHistory(GIT_PULL_HANDLER *aHandler, const git_oid &aRemoteOid)
static TREE_STATE trackedTreeState(git_repository *aRepo)
REPUBLISH_RESULT
static git_index * openIndexOnDisk(git_index *aRepoIndex)
static wxString describeUntrackedCollisions(const std::vector< wxString > &aPaths)
static bool selectedPathsRestaged(git_index *aIndex, const std::set< std::string > &aSelected, const std::map< std::string, SELECTED_STATE > &aAtStart)
static bool treeHolds(git_tree *aTree, const char *aPath, const git_oid &aOnDisk)
static bool wholeStateOnDisk(git_index *aRepoIndex, std::map< std::string, SELECTED_STATE > &aOut)
static std::vector< std::string > changedPaths(git_repository *aRepo)
static bool selectedStateOnDisk(git_index *aRepoIndex, const std::set< std::string > &aSelected, std::map< std::string, SELECTED_STATE > &aOut)
static void reportRemoteLookupFailure(KIGIT_ERRORS *aHandler, int aRc, const wxString &aRemoteName)
static bool restorePathsFromCommit(git_repository *aRepo, git_commit *aCommit, const std::vector< std::string > &aPaths)
static std::map< std::string, SELECTED_STATE > readWholeState(git_index *aIndex)
static REPUBLISH_RESULT republishIndex(git_index *aIndex, const std::vector< STAGED_ENTRY > &aCommitted, const std::set< std::string > &aSelected)
static KIGIT_ERROR_MESSAGE describeSignatureFailure(int aRc)
static bool applySelectedPaths(git_repository *aRepo, git_index *aIndex, const std::vector< wxString > &aFiles)
std::unique_ptr< git_tree, decltype([](git_tree *aTree) { git_tree_free(aTree); })> GitTreePtr
A unique pointer for git_tree objects with automatic cleanup.
wxString BusyMessage()
What every caller says when it finds the session already busy.
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_index_conflict_iterator, decltype([](git_index_conflict_iterator *aIterator) { git_index_conflict_iterator_free(aIterator); })> GitIndexConflictIteratorPtr
A unique pointer for git_index_conflict_iterator objects with automatic cleanup.
void CollectDiffDeltas(git_diff *aDiff, const std::function< void(const git_diff_delta &)> &aCallback)
Walk every delta in a computed diff, invoking aCallback once per delta.
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.
APIEXPORT KIGIT_OWNERSHIP_REFUSAL DescribeOwnershipRefusal(const wxString &aStartDir, const wxString &aGitDir)
The message for a repository git refuses to open because another user owns it.
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.
APIEXPORT KIGIT_FAILURE_REPORT DescribeGitFailure(const KIGIT_ERROR_INFO &aError, const KIGIT_AUTH_ATTEMPT &aAuth, const KIGIT_FAILURE_CONTEXT &aContext)
Turn a failed transfer into a message the user can act on.
std::unique_ptr< git_tree_entry, decltype([](git_tree_entry *aEntry) { git_tree_entry_free(aEntry); })> GitTreeEntryPtr
A unique pointer for git_tree_entry objects with automatic cleanup.
std::unique_ptr< git_blob, decltype([](git_blob *aBlob) { git_blob_free(aBlob); })> GitBlobPtr
A unique pointer for git_blob 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_diff, decltype([](git_diff *aDiff) { git_diff_free(aDiff); })> GitDiffPtr
A unique pointer for git_diff 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.
SCRATCH_INDEX_GUARD scratchGuard
std::map< std::string, SELECTED_STATE > indexAtStart
std::map< std::string, SELECTED_STATE > selectedAtStart
bool BuildTree(git_repository *aRepo, const std::vector< wxString > &aFiles, KIGIT::GitTreePtr &aTreeOut, KIGIT_ERROR_MESSAGE &aError)
Stage the chosen files over whatever the index already holds and write the tree they make.
bool Begin(git_repository *aRepo, const std::vector< wxString > &aFiles, KIGIT_ERROR_MESSAGE &aError)
Read the index, record what it holds and take it over. False leaves the reason in aError.
std::set< std::string > selectedPaths
std::vector< STAGED_ENTRY > committedEntries
KIGIT::GitIndexPtr indexPtr
A transport address with any embedded credentials extracted.
wxString url
Keeps the SSH username, scheme and port, but never a password.
wxString theirsFile
Extracted remote version, an empty file when absent.
wxString ancestorFile
Extracted common-ancestor version, an empty file when absent.
wxString oursFile
Extracted local version, an empty file when absent.
bool hasOurs
False when the local side deleted the file.
wxString path
Repository-relative path of the conflicted file.
bool hasTheirs
False when the remote side deleted the file.
bool hasAncestor
False when the file did not exist in the common ancestor.
A summary the user can act on, with the raw text that explains it kept apart for the details.
What the failed operation was doing, as far as the message needs it.
KIGIT_OPERATION operation
wxString url
The address connected to. Messages show only its host and its stripped form.
wxString remoteName
Empty when the operation has no named remote, as for a clone.
wxString summary
What happened and what to do next, in the user's terms. Never raw text.
wxString details
The raw error, the server's text and the address without its password.
Why git will not open a repository another user owns, and the command that trusts it.
wxString trustCommand
Kept apart from the summary so it can be shown in full and copied.
bool operator==(const SELECTED_STATE &aOther) const
bool operator!=(const SELECTED_STATE &aOther) const
git_index_entry entry
std::string path
static std::vector< uint8_t > bytes(const std::string &aText)
std::string path
wxString result
Test unit parsing edge cases and error handling.
int delta
wxLogTrace helper definitions.