38#include <wx/filename.h>
59#if ( LIBGIT2_VER_MAJOR > 1 ) || ( LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR >= 7 )
60 constexpr int connectTimeoutMs = 10000;
61 constexpr int transferTimeoutMs = 30000;
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 )
66 wxLogTrace(
traceGit,
"LIBGIT_BACKEND::Init(): could not set server timeouts: %s",
82 constexpr auto kOrphanJoinTimeout = std::chrono::seconds( 5 );
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() ) );
99 git_libgit2_shutdown();
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;
118 if( aRc == GIT_ENOTFOUND )
119 return {
_(
"Set your name and email before committing (gear menu > Set Author...)." ), wxEmptyString };
127 if( aRc == GIT_ENOTFOUND )
129 aHandler->
AddError( wxString::Format(
_(
"There is no remote named '%s'. Add it on the Remotes page of the "
130 "Advanced Git window." ),
136 aHandler->
AddError( wxString::Format(
_(
"Could not read the remote '%s'." ), aRemoteName ),
146 if(
int rc = git_remote_create( aOut, aRepo, aName, aUrl ); rc != GIT_OK )
149 if(
const char* url = git_remote_url( *aOut ) )
150 static_cast<KIGIT_COMMON*
>( aPayload )->FollowRewrittenUrl( wxString::FromUTF8( url ) );
159 std::unique_lock<std::mutex> lock = aHandler->
GetActionLock();
161 if( !lock.owns_lock() )
163 wxLogTrace(
traceGit,
"GIT_CLONE_HANDLER::PerformClone() could not lock" );
175 if( !clonePath.DirExists() )
177 if( !clonePath.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
179 aHandler->
AddErrorString( wxString::Format(
_(
"Could not create directory '%s'" ),
185 git_clone_options cloneOptions;
186 git_clone_init_options( &cloneOptions, GIT_CLONE_OPTIONS_VERSION );
187 cloneOptions.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
189 cloneOptions.checkout_opts.progress_payload = aHandler;
193 cloneOptions.fetch_opts.callbacks.payload = aHandler;
194 cloneOptions.fetch_opts.proxy_opts.type = GIT_PROXY_AUTO;
196 cloneOptions.remote_cb_payload = common;
201 std::string remoteUtf8 = remote.utf8_string();
203 if(
int rc = git_clone( &newRepo, remoteUtf8.c_str(), aHandler->
GetClonePath().mbc_str(), &cloneOptions ) )
233 const std::set<std::string>& aSelected )
235 std::map<std::string, SELECTED_STATE>
result;
237 for(
const std::string&
path : aSelected )
241 if(
const git_index_entry* entry = git_index_get_bypath( aIndex,
path.c_str(), 0 ) )
244 state.
id = entry->id;
245 state.
mode = entry->mode;
257 std::map<std::string, SELECTED_STATE>
result;
258 size_t count = git_index_entrycount( aIndex );
260 for(
size_t ii = 0; ii < count; ii++ )
262 if(
const git_index_entry* entry = git_index_get_byindex( aIndex, ii ) )
266 state.
id = entry->id;
267 state.
mode = entry->mode;
268 result[entry->path] = state;
279 const char* indexPath = git_index_path( aRepoIndex );
284 git_index* fresh =
nullptr;
286 if( git_index_open( &fresh, indexPath ) != GIT_OK )
289 int rc = git_index_read( fresh, 1 );
292 if( rc != GIT_OK && rc != GIT_ENOTFOUND )
294 git_index_free( fresh );
303 std::map<std::string, SELECTED_STATE>& aOut )
316static bool wholeStateOnDisk( git_index* aRepoIndex, std::map<std::string, SELECTED_STATE>& aOut )
346 git_index_read(
index, 1 );
355 const char* workdir = git_repository_workdir( aRepo );
357 for(
const wxString& file : aFiles )
361 bool onDisk = workdir
362 && wxFileName::Exists( wxString::FromUTF8( workdir ) + file,
363 wxFILE_EXISTS_ANY | wxFILE_EXISTS_NO_FOLLOW );
366 std::string
path = file.utf8_string();
369 onDisk ? git_index_add_bypath( aIndex,
path.c_str() ) : git_index_remove_bypath( aIndex,
path.c_str() );
383 bool hasHeadNow = git_reference_name_to_id( &headNow, aRepo,
"HEAD" ) == GIT_OK;
385 return hasHeadNow != aHadHead || ( hasHeadNow && !git_oid_equal( &headNow, &aHeadAtStart ) );
391 const std::map<std::string, SELECTED_STATE>& aAtStart )
393 std::map<std::string, SELECTED_STATE> now;
400static bool indexChangedSince( git_index* aIndex,
const std::map<std::string, SELECTED_STATE>& aAtStart )
402 std::map<std::string, SELECTED_STATE> now;
418 const std::set<std::string>& aSelected )
420 for(
int attempt = 0; attempt < 3; attempt++ )
422 if( git_index_read( aIndex, 1 ) != 0 )
425 std::map<std::string, SELECTED_STATE> readState =
readWholeState( aIndex );
427 for(
const std::string&
path : aSelected )
428 git_index_remove_bypath( aIndex,
path.c_str() );
432 git_index_entry entry = staged.entry;
433 entry.path = staged.path.c_str();
435 if( git_index_add( aIndex, &entry ) != 0 )
439 std::map<std::string, SELECTED_STATE> diskState;
444 if( diskState != readState )
447 if( git_index_write( aIndex ) == 0 )
460 if( git_index_has_conflicts( aIndex ) )
462 aError =
_(
"This project has conflicts that are not resolved yet. Finish or abort the merge "
463 "with git, then commit here." );
469 switch( git_repository_state( aRepo ) )
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;
481 aError = wxString::Format(
_(
"This project is in the middle of a %s. Finish or abort it with git, "
482 "then commit here." ),
507 if( git_repository_index( &
index, aRepo ) != 0 )
516 git_index_read(
index, 1 );
518 wxString blockedError;
522 aError = { blockedError, wxEmptyString };
526 for(
const wxString& file : aFiles )
553 if(
const git_index_entry* entry = git_index_get_bypath(
index,
path.c_str(), 0 ) )
559 if( git_index_write_tree( &treeId,
index ) != 0 )
565 git_tree* tree =
nullptr;
567 if( git_tree_lookup( &tree, aRepo, &treeId ) != 0 )
573 aTreeOut.reset( tree );
578 aError = {
_(
"The branch moved while this was running, so nothing was "
579 "committed. Review the changes and try again." ),
587 aError = {
_(
"The staged files changed outside KiCad while this ran. Nothing "
588 "was committed. Review the changes and try again." ),
599 const wxString& aMessage,
const wxString& aAuthorName,
600 const wxString& aAuthorEmail )
610 if( !staging.
Begin( repo, aFiles, stagingError ) )
619 if( git_repository_head_unborn( repo ) == 0 )
621 git_reference* headRef =
nullptr;
623 if( git_repository_head( &headRef, repo ) != 0 )
625 aHandler->
AddGitError(
_(
"Could not read the current commit." ) );
630 git_tree* headTree =
nullptr;
632 if( git_reference_peel( (git_object**) &headTree, headRef, GIT_OBJECT_TREE ) != 0 )
634 aHandler->
AddGitError(
_(
"Could not read the current commit." ) );
640 if( git_index_read_tree(
index, headTree ) != 0 )
642 aHandler->
AddGitError(
_(
"Could not prepare the commit." ) );
646 else if( git_index_clear(
index ) != 0 )
648 aHandler->
AddGitError(
_(
"Could not prepare the commit." ) );
654 if( !staging.
BuildTree( repo, aFiles, treePtr, stagingError ) )
660 git_tree* tree = treePtr.get();
662 git_commit* parent =
nullptr;
664 if( git_repository_head_unborn( repo ) == 0 )
666 git_reference* headRef =
nullptr;
668 if( git_repository_head( &headRef, repo ) != 0 )
670 aHandler->
AddGitError(
_(
"Could not read the current commit." ) );
676 if( git_reference_peel( (git_object**) &parent, headRef, GIT_OBJECT_COMMIT ) != 0 )
678 aHandler->
AddGitError(
_(
"Could not read the current commit." ) );
685 git_signature* author =
nullptr;
687 if( git_signature_now( &author, aAuthorName.utf8_string().c_str(), aAuthorEmail.utf8_string().c_str() ) != 0 )
689 aHandler->
AddGitError(
_(
"Could not record the commit author." ) );
695 size_t parentsCount = parent ? 1 : 0;
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;
702 const git_commit* parents[1] = { parent };
703 const git_commit** parentsPtr = parent ? parents :
nullptr;
706 if( git_commit_create( &oid, repo,
"HEAD", author, author,
nullptr, aMessage.utf8_string().c_str(), tree,
707 parentsCount, parentsPtr )
710 aHandler->
AddGitError(
_(
"Could not create the commit." ) );
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 "
729 aHandler->
AddErrorString(
_(
"The commit was created, but the list of staged files was left "
730 "alone, because another program kept changing it." ) );
736 aHandler->
AddGitError(
_(
"The commit was created, but the list of staged files could not be updated." ) );
747 const wxString& aMessage,
const wxString& aAuthorName,
748 const wxString& aAuthorEmail )
755 if( git_repository_head_unborn( repo ) != 0 )
757 aHandler->
AddErrorString(
_(
"Cannot amend: the branch has no commits yet." ) );
761 git_reference* headRef =
nullptr;
763 if( git_repository_head( &headRef, repo ) != 0 )
765 aHandler->
AddGitError(
_(
"Could not read the current commit." ) );
770 git_commit* headCommit =
nullptr;
772 if( git_reference_peel( (git_object**) &headCommit, headRef, GIT_OBJECT_COMMIT ) != 0 )
774 aHandler->
AddGitError(
_(
"Could not read the current commit." ) );
782 if( !staging.
Begin( repo, aFiles, stagingError ) )
791 git_tree* headTree =
nullptr;
793 if( git_commit_tree( &headTree, headCommit ) != 0 )
795 aHandler->
AddGitError(
_(
"Could not read the current commit." ) );
801 if( git_index_read_tree(
index, headTree ) != 0 )
803 aHandler->
AddGitError(
_(
"Could not prepare the commit." ) );
809 if( !staging.
BuildTree( repo, aFiles, treePtr, stagingError ) )
815 git_tree* tree = treePtr.get();
817 git_signature* author =
nullptr;
819 if( git_signature_now( &author, aAuthorName.utf8_string().c_str(), aAuthorEmail.utf8_string().c_str() ) != 0 )
821 aHandler->
AddGitError(
_(
"Could not record the commit author." ) );
828 if( git_commit_amend( &oid, headCommit,
"HEAD", author, author,
nullptr, aMessage.utf8_string().c_str(), tree )
831 aHandler->
AddGitError(
_(
"Could not amend the commit." ) );
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 "
850 aHandler->
AddErrorString(
_(
"The commit was amended, but the list of staged files was left "
851 "alone, because another program kept changing it." ) );
857 aHandler->
AddGitError(
_(
"The commit was amended, but the list of staged files could not be updated." ) );
870 std::unique_lock<std::mutex> lock = aHandler->
GetActionLock();
872 if( !lock.owns_lock() )
874 wxLogTrace(
traceGit,
"GIT_PUSH_HANDLER::PerformPush: Could not lock mutex" );
885 const wxString targetChanged =
_(
"The branch or its last commit changed while the push was waiting. "
886 "Review the changes and try again." );
888 auto matchesExpectedTarget = [aHandler]( git_reference* aHead )
893 if( !aHead || !git_reference_is_branch( aHead ) )
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();
906 git_reference* expectedHead =
nullptr;
907 git_repository_head( &expectedHead, aHandler->
GetRepo() );
910 if( !matchesExpectedTarget( expectedHead ) )
920 std::string remoteNameUtf8 = remoteName.utf8_string();
921 git_remote* remote =
nullptr;
923 if(
int rc = git_remote_lookup( &remote, aHandler->
GetRepo(), remoteNameUtf8.c_str() ); rc != GIT_OK )
932 const char* pushUrl = git_remote_pushurl( remote ) ? git_remote_pushurl( remote ) : git_remote_url( remote );
937 git_remote_callbacks remoteCallbacks;
938 git_remote_init_callbacks( &remoteCallbacks, GIT_REMOTE_CALLBACKS_VERSION );
946 remoteCallbacks.payload = aHandler;
948 git_proxy_options proxyOpts;
949 git_proxy_init_options( &proxyOpts, GIT_PROXY_OPTIONS_VERSION );
950 proxyOpts.type = GIT_PROXY_AUTO;
957 if(
int connectResult = git_remote_connect( remote, GIT_DIRECTION_PUSH, &remoteCallbacks, &proxyOpts,
nullptr ) )
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;
969 git_reference* head =
nullptr;
971 if( git_repository_head( &head, aHandler->
GetRepo() ) != 0 )
973 git_remote_disconnect( remote );
982 if( !git_reference_is_branch( head ) )
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." ) );
990 if( !matchesExpectedTarget( head ) )
992 git_remote_disconnect( remote );
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 };
1003 if(
int pushResult = git_remote_push( remote, &refspecs, &pushOptions ) )
1007 git_remote_disconnect( remote );
1014 if( git_reference_is_branch( head ) )
1016 git_reference* upstreamRef =
nullptr;
1017 int rc = git_branch_upstream( &upstreamRef, head );
1019 if( rc == GIT_ENOTFOUND )
1021 wxString upstreamName = wxString::Format(
"%s/%s", remoteName, git_reference_shorthand( head ) );
1022 git_branch_set_upstream( head, upstreamName.utf8_string().c_str() );
1024 else if( rc == GIT_OK )
1030 git_remote_disconnect( remote );
1039 std::map<wxString, FileStatus> fileStatusMap;
1043 return fileStatusMap;
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;
1053 status_options.flags |= GIT_STATUS_OPT_INCLUDE_UNMODIFIED;
1055 std::string pathspec_str;
1056 std::vector<const char*> pathspec_ptrs;
1058 if( !aPathspec.IsEmpty() )
1060 pathspec_str = aPathspec.utf8_string();
1061 pathspec_ptrs.push_back( pathspec_str.c_str() );
1063 status_options.pathspec.strings =
const_cast<char**
>( pathspec_ptrs.data() );
1064 status_options.pathspec.count = pathspec_ptrs.size();
1068 status_options.flags |= GIT_STATUS_OPT_DISABLE_PATHSPEC_MATCH;
1071 git_status_list* status_list =
nullptr;
1073 if( git_status_list_new( &status_list, repo, &status_options ) != GIT_OK )
1076 return fileStatusMap;
1081 size_t count = git_status_list_entrycount( status_list );
1084 for(
size_t ii = 0; ii < count; ++ii )
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 );
1090 wxString absPath = repoWorkDir +
path;
1091 fileStatusMap[absPath] =
FileStatus{ absPath,
1093 static_cast<unsigned int>( entry->status ) };
1096 return fileStatusMap;
1105 return wxEmptyString;
1107 git_reference* currentBranchReference =
nullptr;
1108 int rc = git_repository_head( ¤tBranchReference, repo );
1111 if( currentBranchReference )
1113 return git_reference_shorthand( currentBranchReference );
1115 else if( rc == GIT_EUNBORNBRANCH )
1117 return wxEmptyString;
1122 return wxEmptyString;
1128 const std::set<wxString>& aLocalChanges,
1129 const std::set<wxString>& aRemoteChanges,
1130 std::map<wxString, FileStatus>& aFileStatus )
1139 for(
auto& [absPath, fileStatus] : aFileStatus )
1141 wxString relativePath = absPath;
1142 if( relativePath.StartsWith( repoWorkDir ) )
1144 relativePath = relativePath.Mid( repoWorkDir.length() );
1147 relativePath.Replace( wxS(
"\\" ), wxS(
"/" ) );
1151 std::string relativePathStd = relativePath.ToStdString();
1155 if( aLocalChanges.count( relativePathStd ) )
1157 else if( aRemoteChanges.count( relativePathStd ) )
1177 git_config*
config =
nullptr;
1179 if( git_repository_config( &
config, repo ) != GIT_OK )
1187 git_config_entry* entry =
nullptr;
1188 int result = git_config_get_entry( &entry,
config, aKey.utf8_string().c_str() );
1191 if(
result != GIT_OK || entry ==
nullptr )
1193 wxLogTrace(
traceGit,
"Config key '%s' not found", aKey );
1197 aValue = wxString( entry->value );
1205 int error = git_repository_open( &repo, aPath.utf8_string().c_str() );
1209 git_repository_free( repo );
1215 return error == GIT_EOWNER;
1228 if( git_repository_init( &repo, aPath.utf8_string().c_str(), 0 ) != GIT_OK )
1231 git_repository_free( repo );
1233 aHandler->
AddError( wxString::Format(
_(
"Could not set up version control in %s." ), aPath ),
1240 wxLogTrace(
traceGit,
"Successfully initialized Git repository at %s", aPath );
1247 if( aConfig.
url.IsEmpty() )
1254 aHandler->
AddErrorString(
_(
"No repository available to set up remote" ) );
1259 git_remote* remote =
nullptr;
1262 if( git_remote_lookup( &remote, repo,
"origin" ) == GIT_OK )
1265 error = git_remote_set_url( repo,
"origin", parsed.
url.utf8_str() );
1269 error = git_remote_create_with_fetchspec( &remote, repo,
"origin", parsed.
url.utf8_str(),
1270 "+refs/heads/*:refs/remotes/origin/*" );
1274 if( error != GIT_OK )
1279 context.
url = parsed.
url;
1295 wxLogTrace(
traceGit,
"Successfully set up remote origin" );
1303 if( git_reference_lookup( aReference, repo, aBranchName.utf8_string().c_str() ) == GIT_OK )
1306 return git_reference_dwim( aReference, repo, aBranchName.utf8_string().c_str() );
1320 git_reference* branchRef =
nullptr;
1324 if( rc == GIT_ENOTFOUND )
1326 aHandler->
AddError( wxString::Format(
_(
"The branch '%s' no longer exists." ), aBranchName ),
1331 aHandler->
AddError( wxString::Format(
_(
"Could not open the branch '%s'." ), aBranchName ),
1341 if( git_reference_is_remote( branchRef ) )
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 "
1350 const char* branchRefName = git_reference_name( branchRef );
1351 git_object* branchObj =
nullptr;
1353 if( git_revparse_single( &branchObj, repo, aBranchName.utf8_string().c_str() ) != GIT_OK )
1355 aHandler->
AddError( wxString::Format(
_(
"Could not open the branch '%s'." ), aBranchName ),
1363 git_checkout_options checkoutOpts;
1364 git_checkout_init_options( &checkoutOpts, GIT_CHECKOUT_OPTIONS_VERSION );
1365 checkoutOpts.checkout_strategy = GIT_CHECKOUT_SAFE;
1367 std::vector<wxString> conflictPaths;
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
1374 if( aWhy == GIT_CHECKOUT_NOTIFY_CONFLICT && aPath )
1375 static_cast<std::vector<wxString>*
>( aPayload )->push_back( wxString::FromUTF8( aPath ) );
1380 if(
int rc = git_checkout_tree( repo, branchObj, &checkoutOpts ) )
1383 if( rc == GIT_ECONFLICT )
1386 bool anyUntracked =
false;
1387 size_t shown = std::min<size_t>( conflictPaths.size(), 10 );
1389 for(
size_t i = 0; i < shown; i++ )
1393 anyUntracked |= !tracked;
1394 wxString tag = tracked ? wxString() : wxS(
" " ) +
_(
"(never committed)" );
1396 fileList << conflictPaths[i] << tag << wxS(
"\n" );
1399 if( conflictPaths.size() > shown )
1400 fileList << wxString::Format(
_(
"and %zu more\n" ), conflictPaths.size() - shown );
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." );
1407 aHandler->
AddErrorString( wxString::Format(
_(
"Switching to branch '%s' would overwrite:\n\n"
1409 aBranchName, fileList, advice ) );
1413 aHandler->
AddError( wxString::Format(
_(
"Could not switch to the branch '%s'." ), aBranchName ),
1418 if( git_repository_set_head( repo, branchRefName ) != GIT_OK )
1420 aHandler->
AddError( wxString::Format(
_(
"Could not switch to the branch '%s'." ), aBranchName ),
1425 wxLogTrace(
traceGit,
"Successfully switched to branch '%s'", aBranchName );
1435 std::unique_lock<std::mutex> lock;
1441 if( !lock.owns_lock() )
1443 wxLogTrace(
traceGit,
"GIT_PULL_HANDLER::PerformFetch() - Could not lock mutex" );
1450 std::optional<KIGIT_COMMON::OPERATION_GUARD> operation;
1460 std::string remoteNameUtf8 = remoteName.utf8_string();
1461 git_remote* remote =
nullptr;
1463 if(
int rc = git_remote_lookup( &remote, aHandler->
GetRepo(), remoteNameUtf8.c_str() ); rc != GIT_OK )
1465 wxLogTrace(
traceGit,
"GIT_PULL_HANDLER::PerformFetch() - Failed to lookup remote '%s'", remoteName );
1473 if(
const char* url = git_remote_url( remote ) )
1476 git_remote_callbacks remoteCallbacks;
1477 git_remote_init_callbacks( &remoteCallbacks, GIT_REMOTE_CALLBACKS_VERSION );
1482 remoteCallbacks.payload = aHandler;
1484 git_proxy_options proxyOpts;
1485 git_proxy_init_options( &proxyOpts, GIT_PROXY_OPTIONS_VERSION );
1486 proxyOpts.type = GIT_PROXY_AUTO;
1497 if(
int rc = git_remote_connect( remote, GIT_DIRECTION_FETCH, &remoteCallbacks, &proxyOpts,
nullptr ) )
1503 git_fetch_options fetchOptions;
1504 git_fetch_init_options( &fetchOptions, GIT_FETCH_OPTIONS_VERSION );
1505 fetchOptions.callbacks = remoteCallbacks;
1507 if(
int rc = git_remote_fetch( remote,
nullptr, &fetchOptions,
nullptr ) )
1513 wxLogTrace(
traceGit,
"GIT_PULL_HANDLER::PerformFetch() - Fetch completed successfully" );
1520 const std::vector<std::string>& aPaths );
1526 git_index*
index =
nullptr;
1528 if( git_repository_index( &
index, aRepo ) == GIT_OK )
1531 git_index_read(
index, 1 );
1547 std::vector<std::string> paths;
1549 git_status_options statusOpts;
1550 git_status_init_options( &statusOpts, GIT_STATUS_OPTIONS_VERSION );
1551 statusOpts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
1553 git_status_list* statusList =
nullptr;
1555 if( git_status_list_new( &statusList, aRepo, &statusOpts ) != GIT_OK )
1560 for(
size_t ii = 0; ii < git_status_list_entrycount( statusList ); ii++ )
1562 const git_status_entry* entry = git_status_byindex( statusList, ii );
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 );
1578 git_status_options statusOpts;
1579 git_status_init_options( &statusOpts, GIT_STATUS_OPTIONS_VERSION );
1580 statusOpts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
1582 git_status_list* statusList =
nullptr;
1584 if( git_status_list_new( &statusList, aRepo, &statusOpts ) != GIT_OK )
1595 std::vector<wxString>& aCollisions )
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;
1602 git_status_list* statusList =
nullptr;
1605 if( git_status_list_new( &statusList, aRepo, &statusOpts ) != GIT_OK )
1609 size_t count = git_status_list_entrycount( statusList );
1611 for(
size_t ii = 0; ii < count; ++ii )
1613 const git_status_entry* entry = git_status_byindex( statusList, ii );
1615 if( !( entry->status & GIT_STATUS_WT_NEW ) || !entry->index_to_workdir )
1618 const char*
path = entry->index_to_workdir->new_file.path;
1619 git_tree_entry* treeEntry =
nullptr;
1621 if(
path && git_tree_entry_bypath( &treeEntry, aIncomingTree,
path ) == GIT_OK )
1624 aCollisions.push_back( wxString::FromUTF8(
path ) );
1636 for(
const wxString&
path : aPaths )
1637 listed += wxS(
"\n " ) +
path;
1639 return wxString::Format(
_(
"Getting changes would overwrite files that were never committed:%s\n\n"
1640 "Commit or discard them first." ),
1649 git_reference* symbolic =
nullptr;
1650 std::string branchRefName;
1652 if( git_reference_lookup( &symbolic, repo,
"HEAD" ) == GIT_OK )
1656 if(
const char* target = git_reference_symbolic_target( symbolic ) )
1657 branchRefName = target;
1660 if( branchRefName.empty() )
1662 aHandler->
AddErrorString(
_(
"Could not get the repository head." ) );
1666 git_commit* commit =
nullptr;
1668 if( git_commit_lookup( &commit, repo, &aRemoteOid ) != GIT_OK )
1670 aHandler->
AddGitError(
_(
"Could not read the fetched commit." ) );
1675 git_tree* tree =
nullptr;
1677 if( git_commit_tree( &tree, commit ) != GIT_OK )
1679 aHandler->
AddGitError(
_(
"Could not read the fetched commit." ) );
1684 std::vector<wxString> collisions;
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." ) );
1693 if( !collisions.empty() )
1701 git_reference* branchRef =
nullptr;
1703 wxString shorthand = wxString::FromUTF8( branchRefName );
1704 shorthand.StartsWith( wxS(
"refs/heads/" ), &shorthand );
1706 if( git_reference_create( &branchRef, repo, branchRefName.c_str(), &aRemoteOid, 0,
"pull: adopt remote history" )
1709 aHandler->
AddError( wxString::Format(
_(
"Could not create the branch '%s'." ), shorthand ),
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;
1720 if( git_checkout_tree( repo,
reinterpret_cast<git_object*
>( tree ), &checkoutOpts ) != GIT_OK )
1723 bool restored = git_reference_delete( branchRef ) == GIT_OK;
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 "
1735 git_branch_set_upstream( branchRef, upstream.utf8_string().c_str() );
1744 std::unique_lock<std::mutex> lock = aHandler->
GetActionLock();
1746 if( !lock.owns_lock() )
1748 wxLogTrace(
traceGit,
"GIT_PULL_HANDLER::PerformPull() - Could not lock mutex" );
1762 git_oid pull_merge_oid = {};
1766 aHandler->
AddGitError(
_(
"Could not read the fetched changes." ) );
1773#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
1774 if( git_oid_is_zero( &pull_merge_oid ) )
1776 if( git_oid_iszero( &pull_merge_oid ) )
1779 git_reference* head_ref =
nullptr;
1780 wxString branch_shorthand;
1782 if( git_repository_head( &head_ref, aHandler->
GetRepo() ) == GIT_OK )
1784 if( git_reference_is_branch( head_ref ) )
1785 branch_shorthand = wxString::FromUTF8( git_reference_shorthand( head_ref ) );
1787 else if( git_repository_head_unborn( aHandler->
GetRepo() ) == 1 )
1790 git_reference* symbolic =
nullptr;
1792 if( git_reference_lookup( &symbolic, aHandler->
GetRepo(),
"HEAD" ) == GIT_OK )
1796 if(
const char* target = git_reference_symbolic_target( symbolic ) )
1797 wxString::FromUTF8( target ).StartsWith( wxS(
"refs/heads/" ), &branch_shorthand );
1803 if( !branch_shorthand.IsEmpty() )
1806 wxString remoteRefName = wxString::Format(
"refs/remotes/%s/%s", remoteName, branch_shorthand );
1808 if( git_reference_name_to_id( &pull_merge_oid, aHandler->
GetRepo(), remoteRefName.utf8_string().c_str() )
1812 wxString upstream = wxString::Format(
"%s/%s", remoteName, branch_shorthand );
1813 git_branch_set_upstream( head_ref, upstream.utf8_string().c_str() );
1818#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
1819 if( git_oid_is_zero( &pull_merge_oid ) )
1821 if( git_oid_iszero( &pull_merge_oid ) )
1824 aHandler->
AddErrorString(
_(
"Nothing to pull: the remote has no branch matching the current local "
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." ),
1845 git_annotated_commit* fetchhead_commit;
1847 if( git_annotated_commit_lookup( &fetchhead_commit, aHandler->
GetRepo(), &pull_merge_oid ) )
1849 aHandler->
AddGitError(
_(
"Could not read the fetched 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;
1858 if( git_merge_analysis( &merge_analysis, &merge_preference, aHandler->
GetRepo(), merge_commits, 1 ) )
1860 aHandler->
AddGitError(
_(
"Could not compare the fetched changes with this branch." ) );
1864 if( merge_analysis & GIT_MERGE_ANALYSIS_UNBORN )
1867 if( merge_analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE )
1869 wxLogTrace(
traceGit,
"GIT_PULL_HANDLER::PerformPull() - Repository is up to date" );
1870 git_repository_state_cleanup( aHandler->
GetRepo() );
1874 if( merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD )
1876 wxLogTrace(
traceGit,
"GIT_PULL_HANDLER::PerformPull() - Fast-forward merge" );
1880 if( merge_analysis & GIT_MERGE_ANALYSIS_NORMAL )
1882 wxLogTrace(
traceGit,
"GIT_PULL_HANDLER::PerformPull() - Normal merge" );
1884 git_config*
config =
nullptr;
1886 if( git_repository_config( &
config, aHandler->
GetRepo() ) != GIT_OK )
1888 wxLogTrace(
traceGit,
"GIT_PULL_HANDLER::PerformPull() - Failed to get repository config" );
1889 aHandler->
AddGitError(
_(
"Could not read the repository settings." ) );
1895 int rebase_value = 0;
1896 int ret = git_config_get_bool( &rebase_value,
config,
"pull.rebase" );
1898 if( ret == GIT_OK && rebase_value )
1900 wxLogTrace(
traceGit,
"GIT_PULL_HANDLER::PerformPull() - Using rebase based on config" );
1904 wxLogTrace(
traceGit,
"GIT_PULL_HANDLER::PerformPull() - Using merge based on config" );
1908 wxLogTrace(
traceGit,
"GIT_PULL_HANDLER::PerformPull() - Merge needs resolution" );
1915 git_reference* rawRef =
nullptr;
1917 if( git_repository_head( &rawRef, aHandler->
GetRepo() ) )
1919 aHandler->
AddErrorString(
_(
"Could not get the repository head." ) );
1925 git_oid updatedRefOid;
1926 const char* branch_shorthand = git_reference_shorthand( rawRef );
1928 wxString remoteBranchName;
1932 git_reference* upstreamRef =
nullptr;
1934 if( git_branch_upstream( &upstreamRef, rawRef ) == GIT_OK )
1937 remoteBranchName = wxString::FromUTF8( git_reference_name( upstreamRef ) );
1941 remoteBranchName = wxString::Format(
"refs/remotes/%s/%s", remote_name, branch_shorthand );
1944 if(
int rc = git_reference_name_to_id( &updatedRefOid, aHandler->
GetRepo(), remoteBranchName.c_str() );
1947 wxString shownName = remoteBranchName;
1948 shownName.StartsWith( wxS(
"refs/remotes/" ), &shownName );
1950 aHandler->
AddError( wxString::Format( rc == GIT_ENOTFOUND ?
_(
"Could not find the remote branch '%s'." )
1951 :
_(
"Could not read the remote branch '%s'." ),
1957 git_commit* targetCommit =
nullptr;
1959 if( git_commit_lookup( &targetCommit, aHandler->
GetRepo(), &updatedRefOid ) != GIT_OK )
1961 aHandler->
AddGitError(
_(
"Could not read the fetched commit." ) );
1967 git_tree* targetTree =
nullptr;
1969 if( git_commit_tree( &targetTree, targetCommit ) != GIT_OK )
1971 aHandler->
AddGitError(
_(
"Could not read the fetched commit." ) );
1982 aHandler->
AddGitError(
_(
"Could not read the status of the project files, so getting changes was stopped." ) );
1988 aHandler->
AddErrorString(
_(
"There are changes that are not committed yet, and getting changes would "
1989 "run over them. Commit them first." ) );
1993 std::vector<wxString> collisions;
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." ) );
2002 if( !collisions.empty() )
2008 git_checkout_options checkoutOptions;
2009 git_checkout_init_options( &checkoutOptions, GIT_CHECKOUT_OPTIONS_VERSION );
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
2016 case GIT_CHECKOUT_NOTIFY_CONFLICT:
2019 case GIT_CHECKOUT_NOTIFY_DIRTY:
2022 case GIT_CHECKOUT_NOTIFY_UPDATED:
2025 case GIT_CHECKOUT_NOTIFY_UNTRACKED:
2028 case GIT_CHECKOUT_NOTIFY_IGNORED:
2038 checkoutOptions.checkout_strategy = GIT_CHECKOUT_SAFE;
2039 checkoutOptions.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
2040 checkoutOptions.notify_cb = notify_cb;
2043 git_commit* previousCommit =
nullptr;
2045 if( git_reference_peel( (git_object**) &previousCommit, rawRef, GIT_OBJECT_COMMIT ) != GIT_OK )
2047 aHandler->
AddErrorString(
_(
"Could not read the current commit." ) );
2053 if( git_checkout_tree( aHandler->
GetRepo(),
reinterpret_cast<git_object*
>( targetTree ),
2054 &checkoutOptions ) != GIT_OK )
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." ),
2067 git_reference* updatedRef =
nullptr;
2069 if( git_reference_set_target( &updatedRef, rawRef, &updatedRefOid,
nullptr ) != GIT_OK )
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." ),
2086 if( git_repository_state_cleanup( aHandler->
GetRepo() ) != GIT_OK )
2089 wxLogTrace(
traceGit,
"handleFastForward: state cleanup failed after a completed fast-forward: %s",
2102 git_status_options opts;
2103 git_status_init_options( &opts, GIT_STATUS_OPTIONS_VERSION );
2106 opts.show = GIT_STATUS_SHOW_WORKDIR_ONLY;
2107 opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED;
2109 git_status_list* status_list =
nullptr;
2111 if( git_status_list_new( &status_list, aRepo, &opts ) != GIT_OK )
2118 size_t count = git_status_list_entrycount( status_list );
2121 for(
size_t ii = 0; ii < count; ++ii )
2123 const git_status_entry* entry = git_status_byindex( status_list, ii );
2126 if( entry->status & ( GIT_STATUS_WT_MODIFIED | GIT_STATUS_WT_DELETED | GIT_STATUS_WT_TYPECHANGE ) )
2135 size_t aMergeHeadsCount )
2140 aHandler->
AddErrorString(
_(
"Cannot merge: you have unstaged changes. Please commit or stash them "
2141 "before pulling." ) );
2149 git_signature* probe =
nullptr;
2151 if(
int rc = git_signature_default( &probe, repo ); rc != GIT_OK )
2162 git_commit* theirs =
nullptr;
2164 if( git_commit_lookup( &theirs, repo, git_annotated_commit_id( aMergeHeads[0] ) ) == GIT_OK )
2167 git_tree* theirsTree =
nullptr;
2169 if( git_commit_tree( &theirsTree, theirs ) == GIT_OK )
2172 std::vector<wxString> collisions;
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." ) );
2182 if( !collisions.empty() )
2191 if( git_merge( repo, aMergeHeads, aMergeHeadsCount,
nullptr,
nullptr ) )
2193 aHandler->
AddGitError(
_(
"Could not merge the fetched changes." ) );
2197 git_index*
index =
nullptr;
2199 if( git_repository_index( &
index, repo ) != GIT_OK )
2201 aHandler->
AddErrorString(
_(
"Could not read repository index after merge." ) );
2207 if( git_index_has_conflicts(
index ) )
2210 git_object* head_obj =
nullptr;
2212 if( git_revparse_single( &head_obj, repo,
"HEAD" ) == GIT_OK )
2215 git_reset( repo, head_obj, GIT_RESET_HARD,
nullptr );
2218 git_repository_state_cleanup( repo );
2224 git_tree* tree =
nullptr;
2225 git_reference* head_ref =
nullptr;
2227 git_commit* head_commit =
nullptr;
2228 git_commit* merge_commit =
nullptr;
2229 git_signature* signature =
nullptr;
2232 auto undoMerge = [&]()
2234 git_object* headObj =
nullptr;
2236 if( git_revparse_single( &headObj, repo,
"HEAD" ) == GIT_OK )
2239 git_reset( repo, headObj, GIT_RESET_HARD,
nullptr );
2242 git_repository_state_cleanup( repo );
2245 if( git_index_write_tree( &tree_oid,
index ) != GIT_OK || git_tree_lookup( &tree, repo, &tree_oid ) != GIT_OK )
2254 if( git_repository_head( &head_ref, repo ) != GIT_OK )
2256 aHandler->
AddErrorString(
_(
"Could not get the repository head." ) );
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 )
2267 aHandler->
AddErrorString(
_(
"Could not look up the commits to merge." ) );
2275 if(
int rc = git_signature_default( &signature, repo ); rc != GIT_OK )
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;
2290 if( git_commit_create( &merge_commit_oid, repo,
"HEAD", signature, signature,
nullptr,
2291 message.utf8_string().c_str(), tree, 2, parents ) != GIT_OK )
2293 aHandler->
AddGitError(
_(
"Could not create the merge commit." ) );
2298 git_repository_state_cleanup( repo );
2305 git_signature* signature =
nullptr;
2307 if(
int rc = git_signature_default( &signature, aRepo ); rc != GIT_OK )
2315 git_rebase_options rebase_opts;
2316 git_rebase_init_options( &rebase_opts, GIT_REBASE_OPTIONS_VERSION );
2317 git_rebase* rebase =
nullptr;
2319 if( git_rebase_init( &rebase, aRepo,
nullptr, aOnto,
nullptr, &rebase_opts ) != GIT_OK )
2321 aHandler->
AddGitError(
_(
"Could not start the rebase." ) );
2326 git_rebase_operation* op =
nullptr;
2327 int nextResult = GIT_OK;
2329 while( ( nextResult = git_rebase_next( &op, rebase ) ) == GIT_OK )
2332 int commitResult = git_rebase_commit( &commit_oid, rebase,
nullptr, signature,
nullptr,
nullptr );
2335 if( commitResult == GIT_EAPPLIED )
2338 if( commitResult != GIT_OK )
2341 git_rebase_abort( rebase );
2343 if( commitResult == GIT_EUNMERGED )
2345 aHandler->
AddErrorString(
_(
"The rebase ran into conflicts. This is best resolved from a "
2346 "git command line." ) );
2350 aHandler->
AddError(
_(
"Could not rebase your commits onto the fetched changes." ), gitError );
2355 if( nextResult != GIT_ITEROVER )
2358 git_rebase_abort( rebase );
2359 aHandler->
AddError(
_(
"Could not rebase your commits onto the fetched changes." ), gitError );
2363 if( git_rebase_finish( rebase, signature ) != GIT_OK )
2366 git_rebase_abort( rebase );
2367 aHandler->
AddError(
_(
"Could not finish the rebase." ), gitError );
2376 size_t aMergeHeadsCount )
2381 aHandler->
AddErrorString(
_(
"Cannot pull with rebase: you have unstaged changes. Please commit or stash "
2382 "them before pulling." ) );
2396 if(
int rc = git_repository_open_ext( &repo, aFilename, 0,
nullptr ); rc != GIT_OK )
2401 if( rc == GIT_EOWNER && aError )
2405 if( git_repository_discover( &gitDir, aFilename, 0,
nullptr ) == GIT_OK )
2409 wxString::FromUTF8( gitDir.ptr ) );
2427 std::string start = aFrom.IsEmpty() ? std::string(
"HEAD" ) : aFrom.utf8_string();
2428 git_object* startObj =
nullptr;
2430 if(
int error = git_revparse_single( &startObj, aRepo, start.c_str() ); error != GIT_OK )
2437 git_object* peeled =
nullptr;
2439 if(
int error = git_object_peel( &peeled, startObj, GIT_OBJECT_COMMIT ); error != GIT_OK )
2446 git_commit* commit =
reinterpret_cast<git_commit*
>( peeled );
2447 git_reference* branchRef =
nullptr;
2449 std::string
name = aBranchName.utf8_string();
2451 if(
int error = git_branch_create( &branchRef, aRepo,
name.c_str(), commit, 0 ); error != GIT_OK )
2457 git_reference_free( branchRef );
2464 if( !aRepo || aBranchName.IsEmpty() )
2467 git_reference* branch =
nullptr;
2469 if( git_branch_lookup( &branch, aRepo, aBranchName.utf8_string().c_str(), GIT_BRANCH_LOCAL ) != GIT_OK )
2473 return git_branch_delete( branch ) == GIT_OK;
2482 git_signature* signature =
nullptr;
2484 if( git_signature_default( &signature, aRepo ) != GIT_OK )
2491 git_index*
index =
nullptr;
2493 if( git_repository_index( &
index, aRepo ) != GIT_OK )
2498 if( git_index_read(
index,
true ) != GIT_OK || git_index_write(
index ) != GIT_OK )
2501 std::string
message = aMessage.utf8_string();
2504 return git_stash_save( &stashOid, aRepo, signature,
message.empty() ?
nullptr :
message.c_str(), GIT_STASH_DEFAULT )
2511 const std::vector<std::string>& aPaths );
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." )
2532 if( git_reference_name_to_id( &headOid, aRepo,
"HEAD" ) != GIT_OK )
2535 git_commit* headCommit =
nullptr;
2537 if( git_commit_lookup( &headCommit, aRepo, &headOid ) != GIT_OK )
2542 if( git_stash_apply( aRepo, aIndex,
nullptr ) != GIT_OK )
2547 git_index*
index =
nullptr;
2548 bool conflicted = git_repository_index( &
index, aRepo ) != GIT_OK || git_index_has_conflicts(
index );
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." )
2581 return aRepo && git_stash_drop( aRepo, aIndex ) == GIT_OK;
2587 if( !aRepo || aName.IsEmpty() )
2590 git_object* target =
nullptr;
2592 if( git_revparse_single( &target, aRepo,
"HEAD" ) != GIT_OK )
2598 if( aMessage.IsEmpty() )
2599 return git_tag_create_lightweight( &oid, aRepo, aName.utf8_string().c_str(), target, 0 ) == GIT_OK;
2602 git_signature* tagger =
nullptr;
2604 if( git_signature_default( &tagger, aRepo ) != GIT_OK )
2608 std::string
name = aName.utf8_string();
2609 std::string
message = aMessage.utf8_string();
2611 return git_tag_create( &oid, aRepo,
name.c_str(), target, tagger,
message.c_str(), 0 ) == GIT_OK;
2617 if( !aRepo || aName.IsEmpty() )
2620 return git_tag_delete( aRepo, aName.utf8_string().c_str() ) == GIT_OK;
2626 if( !aRepo || aName.IsEmpty() )
2629 git_object* target =
nullptr;
2630 wxString spec = aName + wxT(
"^{commit}" );
2632 if( git_revparse_single( &target, aRepo, spec.utf8_string().c_str() ) != GIT_OK )
2637 git_checkout_options opts;
2638 git_checkout_options_init( &opts, GIT_CHECKOUT_OPTIONS_VERSION );
2639 opts.checkout_strategy = GIT_CHECKOUT_SAFE;
2641 if( git_checkout_tree( aRepo, target, &opts ) != GIT_OK )
2644 return git_repository_set_head_detached( aRepo, git_object_id( target ) ) == GIT_OK;
2650 if( !aRepo || aName.IsEmpty() )
2653 git_remote* remote =
nullptr;
2655 if( git_remote_create( &remote, aRepo, aName.utf8_string().c_str(), aUrl.utf8_string().c_str() ) != GIT_OK )
2665 if( !aRepo || aName.IsEmpty() )
2668 return git_remote_set_url( aRepo, aName.utf8_string().c_str(), aUrl.utf8_string().c_str() ) == GIT_OK;
2674 if( !aRepo || aName.IsEmpty() )
2677 return git_remote_delete( aRepo, aName.utf8_string().c_str() ) == GIT_OK;
2686 git_config*
config =
nullptr;
2688 if( git_repository_config( &
config, aRepo ) != GIT_OK )
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;
2703bool writeConflictSide(
git_repository* aRepo,
const git_index_entry* aEntry,
const wxString& aDest )
2707 if( !out.Create( aDest,
true, wxS_IRUSR | wxS_IWUSR ) )
2713 git_blob* blob =
nullptr;
2715 if( git_blob_lookup( &blob, aRepo, &aEntry->id ) != GIT_OK )
2720 const void* raw = git_blob_rawcontent( blob );
2721 git_off_t size = git_blob_rawsize( blob );
2723 return out.Write( raw,
static_cast<size_t>( size ) ) ==
static_cast<size_t>( size );
2733 if( git_reference_name_to_id( &oursOid, aRepo,
"HEAD" ) != GIT_OK )
2735 aError =
_(
"Could not get the repository head." );
2741 if( !aPinnedTheirsOid.IsEmpty() )
2744 if( git_oid_fromstr( &theirsOid, aPinnedTheirsOid.utf8_string().c_str() ) != GIT_OK )
2746 aError =
_(
"Could not find the fetched remote commit." );
2752 git_object* fetchHead =
nullptr;
2754 if( git_revparse_single( &fetchHead, aRepo,
"FETCH_HEAD" ) != GIT_OK )
2756 aError =
_(
"Could not find the fetched remote commit." );
2761 theirsOid = *git_object_id( fetchHead );
2764 git_commit* ours =
nullptr;
2766 if( git_commit_lookup( &ours, aRepo, &oursOid ) != GIT_OK )
2768 aError =
_(
"Could not look up the commits to merge." );
2772 aOursOut.reset( ours );
2774 git_commit* theirs =
nullptr;
2776 if( git_commit_lookup( &theirs, aRepo, &theirsOid ) != GIT_OK )
2778 aError =
_(
"Could not look up the commits to merge." );
2782 aTheirsOut.reset( theirs );
2784 git_tree* oursTree =
nullptr;
2786 if( git_commit_tree( &oursTree, ours ) != GIT_OK )
2788 aError =
_(
"Could not read the commit trees." );
2793 git_tree* theirsTree =
nullptr;
2795 if( git_commit_tree( &theirsTree, theirs ) != GIT_OK )
2797 aError =
_(
"Could not read the commit trees." );
2807 if( git_merge_base( &baseOid, aRepo, &oursOid, &theirsOid ) == GIT_OK )
2809 git_commit* baseCommit =
nullptr;
2811 if( git_commit_lookup( &baseCommit, aRepo, &baseOid ) == GIT_OK )
2814 git_tree* baseTree =
nullptr;
2816 if( git_commit_tree( &baseTree, baseCommit ) == GIT_OK )
2817 baseTreePtr.reset( baseTree );
2821 git_merge_options mergeOpts;
2822 git_index* mergedIndex =
nullptr;
2824 git_merge_options_init( &mergeOpts, GIT_MERGE_OPTIONS_VERSION );
2826 if( git_merge_trees( &mergedIndex, aRepo, baseTreePtr.get(), oursTree, theirsTree, &mergeOpts ) != GIT_OK )
2832 aIndexOut.reset( mergedIndex );
2842 if( aPaths.empty() )
2845 git_tree* tree =
nullptr;
2847 if( git_commit_tree( &tree, aCommit ) != GIT_OK )
2851 std::vector<char*> pathSpec;
2854 for(
const std::string&
path : aPaths )
2855 pathSpec.push_back(
const_cast<char*
>(
path.c_str() ) );
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();
2863 return git_checkout_tree( aRepo,
reinterpret_cast<git_object*
>( tree ), &checkoutOpts ) == GIT_OK;
2868static bool treeHolds( git_tree* aTree,
const char* aPath,
const git_oid& aOnDisk )
2870 git_tree_entry* entry =
nullptr;
2872 if( git_tree_entry_bypath( &entry, aTree, aPath ) != GIT_OK )
2877 return git_oid_equal( &aOnDisk, git_tree_entry_id( entry ) );
2884 const char* workdir = git_repository_workdir( aRepo );
2890 if( !wxFileName::Exists( wxString::FromUTF8( workdir ) + wxString::FromUTF8( aPath ) ) )
2896 if( git_repository_hashfile( &onDisk, aRepo, aPath, GIT_OBJECT_BLOB,
nullptr ) != GIT_OK )
2899 return treeHolds( aWrittenTree, aPath, onDisk ) ||
treeHolds( aPreviousTree, aPath, onDisk );
2907 git_tree* previousTree =
nullptr;
2909 if( git_commit_tree( &previousTree, aHeadCommit ) != GIT_OK )
2914 git_diff* diff =
nullptr;
2916 if( git_diff_tree_to_tree( &diff, aRepo, previousTree, aWrittenTree,
nullptr ) != GIT_OK )
2921 std::set<std::string> touched;
2923 for(
size_t ii = 0; ii < git_diff_num_deltas( diff ); ii++ )
2925 const git_diff_delta*
delta = git_diff_get_delta( diff, ii );
2928 for(
const char*
path : {
delta->old_file.path,
delta->new_file.path } )
2931 touched.emplace(
path );
2935 std::vector<std::string> restorable;
2936 bool leftBehind =
false;
2938 for(
const std::string&
path : touched )
2941 restorable.push_back(
path );
2946 if( restorable.empty() )
2966 git_reference* aHeadRef, git_commit* aRollbackCommit,
2967 const char* aReflogLabel, wxString& aGitError )
2969 git_checkout_options checkoutOpts;
2970 git_checkout_options_init( &checkoutOpts, GIT_CHECKOUT_OPTIONS_VERSION );
2971 checkoutOpts.checkout_strategy = GIT_CHECKOUT_FORCE;
2973 if( git_checkout_tree( aRepo,
reinterpret_cast<git_object*
>( aTree ), &checkoutOpts ) != GIT_OK )
2984 git_reference* updatedRef =
nullptr;
2986 if( git_reference_set_target( &updatedRef, aHeadRef, &aCommitOid, aReflogLabel ) != GIT_OK )
3010 aError.
details =
_(
"The project has no git repository." );
3016 if( !projectPrefix )
3018 aError.
details =
_(
"Could not work out which folder this project occupies in the repository, "
3019 "so nothing was restored." );
3023 git_object* targetObj =
nullptr;
3025 if( git_revparse_single( &targetObj, repo, aCommitHash.utf8_string().c_str() ) != GIT_OK )
3027 aError.
details =
_(
"Could not find that version." );
3032 git_commit* targetCommit =
nullptr;
3034 if( git_object_type( targetObj ) != GIT_OBJECT_COMMIT
3035 || git_commit_lookup( &targetCommit, repo, git_object_id( targetObj ) ) != GIT_OK )
3037 aError.
details =
_(
"Could not find that version." );
3042 git_tree* targetTree =
nullptr;
3044 if( git_commit_tree( &targetTree, targetCommit ) != GIT_OK )
3046 aError.
details =
_(
"Could not read that version's contents." );
3052 git_reference* headRef =
nullptr;
3054 if( git_repository_head( &headRef, repo ) != GIT_OK )
3056 aError.
details =
_(
"Could not get the repository head." );
3061 git_commit* headCommit =
nullptr;
3063 if( git_reference_peel( (git_object**) &headCommit, headRef, GIT_OBJECT_COMMIT ) != GIT_OK )
3065 aError.
details =
_(
"Could not get the repository head." );
3070 git_tree* headTree =
nullptr;
3072 if( git_commit_tree( &headTree, headCommit ) != GIT_OK )
3074 aError.
details =
_(
"Could not get the repository head." );
3082 git_tree* effectiveTree = targetTree;
3084 if( !projectPrefix->IsEmpty() )
3087 std::string prefixUtf8( projectPrefix->BeforeLast(
'/' ).utf8_string() );
3088 git_tree_entry* subtreeEntry =
nullptr;
3090 if( git_tree_entry_bypath( &subtreeEntry, targetTree, prefixUtf8.c_str() ) != GIT_OK )
3092 aError.
details =
_(
"That version does not contain this project's folder." );
3098 if( git_tree_entry_type( subtreeEntry ) != GIT_OBJECT_TREE )
3100 aError.
details =
_(
"That version does not contain this project's folder." );
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();
3110 git_oid combinedOid;
3112 if( git_tree_create_updated( &combinedOid, repo, headTree, 1, &update ) != GIT_OK )
3114 aError.
details = wxString::Format(
_(
"Could not build the restored content: %s" ),
3121 if( git_tree_lookup( &
combined, repo, &combinedOid ) != GIT_OK )
3123 aError.
details =
_(
"Could not build the restored content." );
3127 effectiveTreePtr.reset(
combined );
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;
3139 git_status_list* statusList =
nullptr;
3141 if( git_status_list_new( &statusList, repo, &statusOpts ) != GIT_OK )
3143 aError.
details =
_(
"Could not read the repository status." );
3149 size_t count = git_status_list_entrycount( statusList );
3151 for(
size_t ii = 0; ii < count && !dirty; ++ii )
3153 const git_status_entry* entry = git_status_byindex( statusList, ii );
3155 if( entry->status == GIT_STATUS_WT_NEW )
3157 const char*
path = entry->index_to_workdir ? entry->index_to_workdir->new_file.path :
nullptr;
3161 git_tree_entry* treeEntry =
nullptr;
3163 if( git_tree_entry_bypath( &treeEntry, effectiveTree,
path ) == GIT_OK )
3180 if( git_oid_equal( git_commit_tree_id( headCommit ), git_tree_id( effectiveTree ) ) )
3183 git_signature* signature =
nullptr;
3185 if(
int rc = git_signature_default( &signature, repo ); rc != GIT_OK )
3193 char shortHash[13] = { 0 };
3194 git_oid_tostr( shortHash,
sizeof( shortHash ), git_commit_id( targetCommit ) );
3198 const git_commit* parents[] = { headCommit };
3199 wxString
message = wxString::Format(
_(
"Restore version %s" ), shortHash );
3200 git_oid restoreCommitOid;
3202 if( git_commit_create( &restoreCommitOid, repo,
nullptr, signature, signature,
nullptr,
3203 message.utf8_string().c_str(), effectiveTree, 1, parents )
3213 switch(
publishPreparedCommit( repo, effectiveTree, restoreCommitOid, headRef, headCommit,
"restore", gitError ) )
3219 wxString::Format(
_(
"Could not update the working files, so nothing was changed: %s" ), gitError );
3223 aError.
details = wxString::Format(
_(
"Could not update the working files, and they may be partially "
3230 wxString::Format(
_(
"Could not record the restore commit, so nothing was changed: %s" ), gitError );
3234 aError.
details = wxString::Format(
_(
"Could not record the restore commit, and the working files may "
3235 "have been changed: %s" ),
3248 aError =
_(
"The project has no git repository." );
3252 if( aPaths.empty() )
3255 std::vector<std::string> pathStorage;
3256 std::vector<char*> pathPtrs;
3257 std::vector<wxString> neverCommitted;
3259 for(
const wxString&
path : aPaths )
3262 pathStorage.push_back(
path.utf8_string() );
3264 neverCommitted.push_back(
path );
3268 if( !neverCommitted.empty() )
3270 git_index*
index =
nullptr;
3272 if( git_repository_index( &
index, aRepo ) != GIT_OK )
3280 for(
const wxString&
path : neverCommitted )
3281 git_index_remove_bypath(
index,
path.utf8_string().c_str() );
3283 if( git_index_write(
index ) != GIT_OK )
3290 if( pathStorage.empty() )
3293 for( std::string&
path : pathStorage )
3294 pathPtrs.push_back(
path.data() );
3296 git_checkout_options opts;
3297 git_checkout_init_options( &opts, GIT_CHECKOUT_OPTIONS_VERSION );
3301 opts.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH;
3302 opts.paths.strings = pathPtrs.data();
3303 opts.paths.count = pathPtrs.size();
3305 if( git_checkout_head( aRepo, &opts ) != GIT_OK )
3322 if( git_reference_name_to_id( &headOid, aRepo,
"HEAD" ) != GIT_OK )
3325 git_commit* headCommit =
nullptr;
3327 if( git_commit_lookup( &headCommit, aRepo, &headOid ) != GIT_OK )
3331 git_tree* headTree =
nullptr;
3333 if( git_commit_tree( &headTree, headCommit ) != GIT_OK )
3337 git_tree_entry* entry =
nullptr;
3339 if( git_tree_entry_bypath( &entry, headTree, aPath.utf8_string().c_str() ) != GIT_OK )
3352 aError =
_(
"The project has no git repository." );
3358 const wxString rebaseIsAnotherProgram =
3359 _(
"This rebase was started by another program, and only that program can finish or abort it." );
3361 switch( git_repository_state( aRepo ) )
3363 case GIT_REPOSITORY_STATE_NONE:
return true;
3365 case GIT_REPOSITORY_STATE_REBASE_MERGE:
3366 case GIT_REPOSITORY_STATE_REBASE_INTERACTIVE:
3370 git_rebase* rebase =
nullptr;
3372 if( git_rebase_open( &rebase, aRepo,
nullptr ) != GIT_OK )
3374 aError = rebaseIsAnotherProgram;
3380 if( git_rebase_abort( rebase ) != GIT_OK )
3382 aError = rebaseIsAnotherProgram;
3389 case GIT_REPOSITORY_STATE_BISECT:
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." );
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;
3405 git_object* head =
nullptr;
3407 if( git_revparse_single( &head, aRepo,
"HEAD" ) != GIT_OK )
3414 git_tree* headTree =
nullptr;
3416 if( git_object_peel(
reinterpret_cast<git_object**
>( &headTree ), head, GIT_OBJECT_TREE ) != GIT_OK )
3423 git_index*
index =
nullptr;
3425 if( git_repository_index( &
index, aRepo ) != GIT_OK )
3436 std::set<std::string> touched;
3439 git_index_conflict_iterator* conflicts =
nullptr;
3441 if( git_index_conflict_iterator_new( &conflicts,
index ) == GIT_OK )
3443 const git_index_entry* ancestor =
nullptr;
3444 const git_index_entry* ours =
nullptr;
3445 const git_index_entry* theirs =
nullptr;
3447 while( git_index_conflict_next( &ancestor, &ours, &theirs, conflicts ) == GIT_OK )
3449 for(
const git_index_entry* entry : { ancestor, ours, theirs } )
3451 if( entry && entry->path )
3452 touched.insert( entry->path );
3456 git_index_conflict_iterator_free( conflicts );
3459 git_diff* diff =
nullptr;
3461 if( git_diff_tree_to_index( &diff, aRepo, headTree,
index,
nullptr ) != GIT_OK )
3470 [&touched](
const git_diff_delta& aDelta )
3472 if( aDelta.old_file.path )
3473 touched.insert( aDelta.old_file.path );
3475 if( aDelta.new_file.path )
3476 touched.insert( aDelta.new_file.path );
3479 if( !touched.empty() )
3481 std::vector<char*> paths;
3483 for(
const std::string&
path : touched )
3484 paths.push_back(
const_cast<char*
>(
path.c_str() ) );
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();
3492 if( git_checkout_head( aRepo, &checkoutOpts ) != GIT_OK )
3499 if( git_repository_state_cleanup( aRepo ) != GIT_OK )
3510 wxString& aTheirsOid, std::vector<KIGIT_CONFLICT_SIDES>& aConflicts,
3517 aError =
_(
"The project has no git repository." );
3525 if( !remergeInMemory( aRepo, wxEmptyString, mergedIndex, ours, theirs, aError ) )
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 );
3534 if( !git_index_has_conflicts( mergedIndex.get() ) )
3536 aError =
_(
"The merge has no conflicts." );
3540 if( !wxFileName::Mkdir( aStageDir, 0700, wxPATH_MKDIR_FULL ) )
3542 aError =
_(
"Could not create a temporary directory." );
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 );
3550 git_index_conflict_iterator* iter =
nullptr;
3552 if( git_index_conflict_iterator_new( &iter, mergedIndex.get() ) != GIT_OK )
3554 aError =
_(
"Could not list the merge conflicts." );
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();
3566 while( git_index_conflict_next( &ancestor, &mine, &remote, iter ) == GIT_OK )
3568 const git_index_entry* named = mine ? mine : ( remote ? remote : ancestor );
3574 sides.
path = wxString::FromUTF8( named->path );
3576 sides.
hasOurs = mine !=
nullptr;
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 );
3585 if( !writeConflictSide( aRepo, ancestor, sides.
ancestorFile )
3586 || !writeConflictSide( aRepo, mine, sides.
oursFile )
3587 || !writeConflictSide( aRepo, remote, sides.
theirsFile ) )
3589 aError =
_(
"Could not extract the conflicting file versions." );
3593 aConflicts.push_back( sides );
3596 return !aConflicts.empty();
3601 const wxString& aTheirsOid,
3602 const std::vector<std::pair<wxString, wxString>>& aResolvedFiles,
3607 aError.
details =
_(
"The project has no git repository." );
3615 git_status_options statusOpts;
3616 git_status_init_options( &statusOpts, GIT_STATUS_OPTIONS_VERSION );
3617 statusOpts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
3619 git_status_list* statusList =
nullptr;
3621 if( git_status_list_new( &statusList, aRepo, &statusOpts ) != GIT_OK )
3623 aError.
details =
_(
"Could not read the repository status." );
3629 if( git_status_list_entrycount( statusList ) > 0 )
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." );
3642 if( !remergeInMemory( aRepo, aTheirsOid, mergedIndex, ours, theirs, aError.
details ) )
3645 char oidStr[GIT_OID_HEXSZ + 1] = { 0 };
3646 git_oid_tostr( oidStr,
sizeof( oidStr ), git_commit_id( ours.get() ) );
3649 if( aExpectedHeadOid != wxString::FromUTF8( oidStr ) )
3651 aError.
details =
_(
"The repository changed while resolving. Get changes again." );
3655 for(
const auto& [
path, resolvedFile] : aResolvedFiles )
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;
3662 git_index_conflict_get( &ancestor, &mine, &remote, mergedIndex.get(), pathUtf8.c_str() );
3665 if( resolvedFile.IsEmpty() )
3667 if( git_index_conflict_remove( mergedIndex.get(), pathUtf8.c_str() ) != GIT_OK )
3669 aError.
details = wxString::Format(
_(
"Could not mark '%s' as resolved." ),
path );
3676 std::ifstream in( resolvedFile.fn_str(), std::ios::binary );
3680 aError.
details = wxString::Format(
_(
"Could not read the resolved file for '%s'." ),
path );
3684 std::string
bytes( ( std::istreambuf_iterator<char>( in ) ), std::istreambuf_iterator<char>() );
3688 if( git_blob_create_from_buffer( &blobOid, aRepo,
bytes.data(),
bytes.size() ) != GIT_OK )
3690 aError.
details = wxString::Format(
_(
"Could not store the resolved file for '%s'." ),
path );
3694 git_index_entry entry;
3695 memset( &entry, 0,
sizeof( entry ) );
3696 entry.path = pathUtf8.c_str();
3698 entry.mode = mine ? mine->mode : ( remote ? remote->mode : GIT_FILEMODE_BLOB );
3700 if( git_index_conflict_remove( mergedIndex.get(), pathUtf8.c_str() ) != GIT_OK
3701 || git_index_add( mergedIndex.get(), &entry ) != GIT_OK )
3703 aError.
details = wxString::Format(
_(
"Could not mark '%s' as resolved." ),
path );
3708 if( git_index_has_conflicts( mergedIndex.get() ) )
3710 aError.
details =
_(
"Not every conflict was resolved." );
3716 if( git_index_write_tree_to( &treeOid, mergedIndex.get(), aRepo ) != GIT_OK )
3718 aError.
details =
_(
"Could not write the merge result." );
3722 git_tree* tree =
nullptr;
3724 if( git_tree_lookup( &tree, aRepo, &treeOid ) != GIT_OK )
3726 aError.
details =
_(
"Could not write the merge result." );
3732 git_signature* signature =
nullptr;
3734 if(
int rc = git_signature_default( &signature, aRepo ); rc != GIT_OK )
3742 git_reference* headRef =
nullptr;
3744 if( git_repository_head( &headRef, aRepo ) != GIT_OK )
3746 aError.
details =
_(
"Could not get the repository head." );
3752 const git_commit* parents[] = { ours.get(), theirs.get() };
3754 wxString::Format(
_(
"Merge remote-tracking branch into %s" ), git_reference_shorthand( headRef ) );
3755 git_oid mergeCommitOid;
3758 if( git_commit_create( &mergeCommitOid, aRepo,
nullptr, signature, signature,
nullptr,
3759 message.utf8_string().c_str(), tree, 2, parents )
3769 switch(
publishPreparedCommit( aRepo, tree, mergeCommitOid, headRef, ours.get(),
"merge", gitError ) )
3775 wxString::Format(
_(
"Could not update the working files, so nothing was changed: %s" ), gitError );
3779 aError.
details = wxString::Format(
_(
"Could not update the working files, and they may be partially "
3786 wxString::Format(
_(
"Could not record the merge commit, so nothing was changed: %s" ), gitError );
3790 aError.
details = wxString::Format(
_(
"Could not record the merge commit, and the working files may "
3791 "have been changed: %s" ),
3805 git_repository_free( aRepo );
3811 wxFileName gitDir( aProjectPath, wxEmptyString );
3812 gitDir.AppendDir(
".git" );
3814 if( gitDir.DirExists() )
3823 wxLogTrace(
traceGit,
"Failed to remove .git directory: %s", errors );
3829 wxLogTrace(
traceGit,
"Successfully removed VCS from project" );
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 ...
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
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.
bool RmDirRecursive(const wxString &aFileName, wxString *aErrors)
Remove the directory aDirName and all its contents including subdirectories and their files.
@ SuccessIndexStale
Committed, but the staged list was left as another program had it. Never retry on this.
@ Busy
Another git operation held the action lock, so nothing was pushed.
const wxChar *const traceGit
Flag to enable Git debugging output.
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)
@ 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.
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)
@ CHECKOUT_FAILED_PARTIAL
@ RECORD_FAILED_ROLLED_BACK
@ CHECKOUT_FAILED_ROLLED_BACK
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)
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
static std::vector< uint8_t > bytes(const std::string &aText)
wxString result
Test unit parsing edge cases and error handling.
wxLogTrace helper definitions.