35#include <wx/filename.h>
53 static wxWindow* window =
nullptr;
112 const char* workdir = git_repository_workdir(
m_repo );
115 return wxString( workdir );
118 return wxEmptyString;
134 return wxEmptyString;
136 git_reference* head =
nullptr;
138 int retval = git_repository_head( &head,
m_repo );
140 if( retval && retval != GIT_EUNBORNBRANCH && retval != GIT_ENOTFOUND )
141 return wxEmptyString;
145 return wxEmptyString;
148 git_reference* branch;
150 if( git_reference_resolve( &branch, head ) )
152 wxLogTrace(
traceGit,
"Failed to resolve branch" );
153 return wxEmptyString;
157 const char* branchName =
"";
159 if( git_branch_name( &branchName, branch ) )
161 wxLogTrace(
traceGit,
"Failed to get branch name" );
162 return wxEmptyString;
165 return wxString( branchName );
173 if( !
m_repo || git_reference_name_to_id( &oid,
m_repo,
"HEAD" ) != GIT_OK )
174 return wxEmptyString;
176 return wxString::FromUTF8( git_oid_tostr_s( &oid ) );
224 return wxEmptyString;
228 if( git_reference_name_to_id( &headOid,
m_repo,
"HEAD" ) != GIT_OK )
229 return wxEmptyString;
231 git_commit* commit =
nullptr;
233 if( git_commit_lookup( &commit,
m_repo, &headOid ) != GIT_OK )
234 return wxEmptyString;
237 const char*
message = git_commit_message( commit );
251 git_status_options statusOpts;
252 git_status_init_options( &statusOpts, GIT_STATUS_OPTIONS_VERSION );
253 statusOpts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR;
255 git_status_list* statusList =
nullptr;
257 if( git_status_list_new( &statusList,
m_repo, &statusOpts ) != GIT_OK )
265 return git_status_list_entrycount( statusList ) > 0;
273std::map<std::string, std::vector<wxString>> collectRefNames(
git_repository* aRepo )
275 std::map<std::string, std::vector<wxString>> names;
279 if( git_reference_name_to_id( &headOid, aRepo,
"HEAD" ) == GIT_OK )
280 names[git_oid_tostr_s( &headOid )].push_back( wxS(
"HEAD" ) );
282 git_reference_iterator* iter =
nullptr;
284 if( git_reference_iterator_new( &iter, aRepo ) != GIT_OK )
288 git_reference* ref =
nullptr;
291 while( git_reference_next( &ref, iter ) == GIT_OK )
295 if( !git_reference_is_branch( ref ) && !git_reference_is_remote( ref ) && !git_reference_is_tag( ref ) )
298 git_object* target =
nullptr;
300 if( git_reference_peel( &target, ref, GIT_OBJECT_COMMIT ) != GIT_OK )
305 std::string hash = git_oid_tostr_s( git_object_id( target ) );
313wxString shortHashOf(
const git_oid* aOid )
317 char shortHash[13] = { 0 };
318 git_oid_tostr( shortHash,
sizeof( shortHash ), aOid );
319 return wxString::FromUTF8( shortHash );
344 const char* gitDir = git_repository_path(
m_repo );
350 wxFileName fetchHead( wxString::FromUTF8( gitDir ), wxS(
"FETCH_HEAD" ) );
352 if( !fetchHead.FileExists() )
355 return fetchHead.GetModificationTime();
368 if( git_oid_fromstr( &oid, aId.utf8_string().c_str() ) != GIT_OK )
371 git_commit* commit =
nullptr;
373 if( git_commit_lookup( &commit,
m_repo, &oid ) != GIT_OK )
379 detail.when = wxDateTime(
static_cast<time_t
>( git_commit_time( commit ) ) );
385 if(
const char*
message = git_commit_message( commit ) )
393 if( raw.StartsWith( wxS(
"WIP on " ), &rest ) || raw.StartsWith( wxS(
"On " ), &rest ) )
395 int colon = rest.Find( wxS(
": " ) );
397 if( colon != wxNOT_FOUND )
399 detail.branch = rest.Left( colon );
400 detail.message = rest.Mid( colon + 2 );
406 git_commit* base =
nullptr;
408 if( git_commit_parent( &base, commit, 0 ) != GIT_OK )
412 detail.baseHash = shortHashOf( git_commit_id( base ) );
414 git_tree* baseTree =
nullptr;
415 git_tree* stashTree =
nullptr;
417 if( git_commit_tree( &baseTree, base ) != GIT_OK || git_commit_tree( &stashTree, commit ) != GIT_OK )
422 git_diff* diff =
nullptr;
424 if( git_diff_tree_to_tree( &diff,
m_repo, baseTree, stashTree,
nullptr ) == GIT_OK )
429 [&
detail](
const git_diff_delta& aDelta )
432 aDelta.new_file.path ? aDelta.new_file.path : aDelta.old_file.path;
433 detail.files.push_back( wxString::FromUTF8(
path ) );
437 if( git_commit_parentcount( commit ) > 2 )
439 git_commit* untracked =
nullptr;
441 if( git_commit_parent( &untracked, commit, 2 ) == GIT_OK )
444 git_tree* untrackedTree =
nullptr;
446 if( git_commit_tree( &untrackedTree, untracked ) == GIT_OK )
451 untrackedTree, GIT_TREEWALK_PRE,
452 [](
const char* aRoot,
const git_tree_entry* aEntry,
void* aPayload ) ->
int
454 if( git_tree_entry_type( aEntry ) == GIT_OBJECT_BLOB )
456 auto* files =
static_cast<std::vector<wxString>*
>( aPayload );
457 files->push_back( wxString::FromUTF8( aRoot )
458 + wxString::FromUTF8( git_tree_entry_name( aEntry ) ) );
479 git_object* headObj =
nullptr;
481 if( git_revparse_single( &headObj,
m_repo,
"HEAD^{tree}" ) == GIT_OK )
482 headTree.reset(
reinterpret_cast<git_tree*
>( headObj ) );
484 git_diff_options opts;
485 git_diff_options_init( &opts, GIT_DIFF_OPTIONS_VERSION );
486 opts.flags = GIT_DIFF_INCLUDE_UNTRACKED | GIT_DIFF_RECURSE_UNTRACKED_DIRS | GIT_DIFF_SHOW_UNTRACKED_CONTENT;
488 git_diff* diff =
nullptr;
490 if( git_diff_tree_to_workdir_with_index( &diff,
m_repo, headTree.get(), &opts ) != GIT_OK )
494 std::pair<int, int> total{ 0, 0 };
496 for(
size_t ii = 0; ii < git_diff_num_deltas( diff ); ++ii )
498 git_patch* patch =
nullptr;
500 if( git_patch_from_diff( &patch, diff, ii ) != GIT_OK )
504 const git_diff_delta*
delta = git_patch_get_delta( patch );
507 if( !
path || aPaths.count( wxString::FromUTF8(
path ) ) == 0 )
513 if( git_patch_line_stats(
nullptr, &added, &removed, patch ) == GIT_OK )
515 total.first +=
static_cast<int>( added );
516 total.second +=
static_cast<int>( removed );
529 std::vector<KIGIT_BRANCH_INFO> branches;
531 git_branch_iterator* branchIterator =
nullptr;
533 if( git_branch_iterator_new( &branchIterator,
m_repo, GIT_BRANCH_ALL ) )
535 wxLogTrace(
traceGit,
"Failed to get branch iterator" );
540 git_reference* branchReference =
nullptr;
541 git_branch_t branchType;
546 branchReference =
nullptr;
548 int rc = git_branch_next( &branchReference, &branchType, branchIterator );
550 if( rc == GIT_ITEROVER )
559 const char* branchName =
"";
563 if( git_reference_type( branchReference ) != GIT_REFERENCE_DIRECT )
566 if( git_branch_name( &branchName, branchReference ) )
568 wxLogTrace(
traceGit,
"Failed to get branch name in iter loop" );
572 const git_oid* commitId = git_reference_target( branchReference );
573 git_commit* commit =
nullptr;
575 if( !commitId || git_commit_lookup( &commit,
m_repo, commitId ) != GIT_OK )
577 wxLogTrace(
traceGit,
"Failed to get commit in iter loop" );
582 git_time_t commitTime = git_commit_time( commit );
586 info.isRemote = branchType == GIT_BRANCH_REMOTE;
587 info.isCurrent = !
info.isRemote && git_branch_is_head( branchReference ) == 1;
588 info.tipHash = shortHashOf( commitId );
590 info.when = wxDateTime(
static_cast<time_t
>( commitTime ) );
592 branches.push_back( std::move(
info ) );
607 git_reference* ref =
nullptr;
609 if( git_reference_dwim( &ref,
m_repo, aBranch.utf8_string().c_str() ) != GIT_OK )
613 git_object* target =
nullptr;
615 if( git_reference_peel( &target, ref, GIT_OBJECT_COMMIT ) != GIT_OK )
619 git_commit* commit =
reinterpret_cast<git_commit*
>( target );
623 detail.tipHash = shortHashOf( git_commit_id( commit ) );
624 detail.when = wxDateTime(
static_cast<time_t
>( git_commit_time( commit ) ) );
626 if(
const git_signature* author = git_commit_author( commit ) )
632 if(
const char*
message = git_commit_message( commit ) )
638 git_reference* upstream =
nullptr;
640 if( !git_reference_is_branch( ref ) || git_branch_upstream( &upstream, ref ) != GIT_OK )
644 const char* upstreamName =
"";
646 if( git_branch_name( &upstreamName, upstream ) == GIT_OK )
660 std::vector<wxString> projDirs;
666 if( git_reference_name_to_id( &oid,
m_repo,
"HEAD" ) != GIT_OK )
672 if( git_commit_lookup( &commit,
m_repo, &oid ) != GIT_OK )
680 if( git_commit_tree( &tree, commit ) != GIT_OK )
690 tree, GIT_TREEWALK_PRE,
691 [](
const char* root,
const git_tree_entry* entry,
void* payload )
693 std::vector<wxString>* prjs =
static_cast<std::vector<wxString>*
>( payload );
694 wxFileName root_fn( git_tree_entry_name( entry ) );
696 root_fn.SetPath( root );
698 if( git_tree_entry_type( entry ) == GIT_OBJECT_BLOB
699 && ( ( root_fn.GetExt() ==
"kicad_pro" ) || ( root_fn.GetExt() ==
"pro" ) ) )
701 prjs->push_back( root_fn.GetFullPath() );
708 std::sort( projDirs.begin(), projDirs.end(),
709 [](
const wxString& a,
const wxString& b )
711 int a_freq = a.Freq( wxFileName::GetPathSeparator() );
712 int b_freq = b.Freq( wxFileName::GetPathSeparator() );
714 if( a_freq == b_freq )
717 return a_freq < b_freq;
726 std::pair<std::set<wxString>, std::set<wxString>> modified_files;
729 return modified_files;
731 git_reference* head =
nullptr;
732 git_reference* remote_head =
nullptr;
734 if( git_repository_head( &head,
m_repo ) != GIT_OK )
736 wxLogTrace(
traceGit,
"Failed to get modified HEAD" );
737 return modified_files;
742 if( git_branch_upstream( &remote_head, head ) != GIT_OK )
747 wxLogTrace(
traceGit,
"Failed to get modified remote HEAD" );
748 return modified_files;
753 const git_oid* head_oid = git_reference_target( head );
754 const git_oid* remote_oid = git_reference_target( remote_head );
756 if( !head_oid || !remote_oid )
757 return modified_files;
759 auto load_tree = [
this](
const git_oid* aOid ) -> git_tree*
761 git_commit* commit =
nullptr;
763 if( git_commit_lookup( &commit,
m_repo, aOid ) != GIT_OK )
770 git_tree* tree =
nullptr;
772 if( git_commit_tree( &tree, commit ) != GIT_OK )
781 git_tree* head_tree = load_tree( head_oid );
784 git_tree* remote_tree = load_tree( remote_oid );
787 if( !head_tree || !remote_tree )
788 return modified_files;
797 if( git_merge_base( &base_oid,
m_repo, head_oid, remote_oid ) != GIT_OK )
800 return modified_files;
803 git_tree* base_tree = load_tree( &base_oid );
807 return modified_files;
809 auto collect_paths = [
this]( git_tree* aOldTree, git_tree* aNewTree, std::set<wxString>& aOut )
814 git_diff_options localOpts;
815 git_diff_init_options( &localOpts, GIT_DIFF_OPTIONS_VERSION );
817 git_diff* diff =
nullptr;
819 if( git_diff_tree_to_tree( &diff,
m_repo, aOldTree, aNewTree, &localOpts ) != GIT_OK )
826 [&aOut](
const git_diff_delta& aDelta )
828 if( aDelta.new_file.path )
829 aOut.insert( wxString::FromUTF8( aDelta.new_file.path ) );
831 if( aDelta.old_file.path )
832 aOut.insert( wxString::FromUTF8( aDelta.old_file.path ) );
835 git_diff_free( diff );
838 collect_paths( base_tree, head_tree, modified_files.first );
839 collect_paths( base_tree, remote_tree, modified_files.second );
845 std::set<wxString> actuallyDifferent;
846 collect_paths( head_tree, remote_tree, actuallyDifferent );
848 auto filterToDifferent = [&]( std::set<wxString>& aSet )
850 for(
auto it = aSet.begin(); it != aSet.end(); )
851 it = actuallyDifferent.count( *it ) ? std::next( it ) : aSet.erase( it );
854 filterToDifferent( modified_files.first );
855 filterToDifferent( modified_files.second );
857 return modified_files;
862 const git_reference* aBase )
const
864 if( !
m_repo || !aBranch || !aBase )
867 git_object* branchCommit =
nullptr;
869 if( git_reference_peel( &branchCommit, aBranch, GIT_OBJECT_COMMIT ) != GIT_OK )
873 git_object* baseCommit =
nullptr;
875 if( git_reference_peel( &baseCommit, aBase, GIT_OBJECT_COMMIT ) != GIT_OK )
882 git_object_id( baseCommit ) );
896 git_reference* head =
nullptr;
898 if( git_repository_head( &head,
m_repo ) != GIT_OK )
902 git_reference* upstream =
nullptr;
903 int result = git_branch_upstream( &upstream, head );
908 return result == GIT_ENOTFOUND;
912 std::optional<KIGIT_AHEAD_BEHIND> counts =
compareRefs( head, upstream );
914 return counts && counts->ahead > 0;
920 return m_repo && git_repository_head_detached(
m_repo ) == 1;
929 git_config*
config =
nullptr;
931 if( git_repository_config( &
config,
m_repo ) != GIT_OK )
937 return git_config_get_bool( &rebase,
config,
"pull.rebase" ) == GIT_OK && rebase;
946 git_reference* head =
nullptr;
948 if( git_repository_head( &head,
m_repo ) != GIT_OK )
952 git_reference* upstream =
nullptr;
954 if( git_branch_upstream( &upstream, head ) != GIT_OK )
958 std::optional<KIGIT_AHEAD_BEHIND> counts =
compareRefs( head, upstream );
960 return counts && counts->behind > 0;
969 git_reference* branch =
nullptr;
971 if( git_branch_lookup( &branch,
m_repo, aBranch.utf8_string().c_str(), GIT_BRANCH_LOCAL ) != GIT_OK )
975 git_reference* head =
nullptr;
977 if( git_repository_head( &head,
m_repo ) != GIT_OK )
981 std::optional<KIGIT_AHEAD_BEHIND> counts =
compareRefs( branch, head );
986 return counts->ahead;
992 std::vector<wxString> messages;
997 git_revwalk* walker =
nullptr;
999 if( git_revwalk_new( &walker,
m_repo ) != GIT_OK )
1003 git_revwalk_sorting( walker, GIT_SORT_TIME | GIT_SORT_TOPOLOGICAL );
1005 if( git_revwalk_push_head( walker ) != GIT_OK )
1008 std::set<wxString> seen;
1011 while( messages.size() < aMax && git_revwalk_next( &oid, walker ) == GIT_OK )
1013 git_commit* commit =
nullptr;
1015 if( git_commit_lookup( &commit,
m_repo, &oid ) != GIT_OK )
1021 if(
const char*
text = git_commit_message( commit ) )
1027 messages.push_back(
message );
1038std::string treeEntryIdAt( git_commit* aCommit,
const char* aPath )
1040 git_tree* tree =
nullptr;
1042 if( git_commit_tree( &tree, aCommit ) != GIT_OK )
1046 git_tree_entry* entry =
nullptr;
1048 if( git_tree_entry_bypath( &entry, tree, aPath ) != GIT_OK )
1053 return git_oid_tostr_s( git_tree_entry_id( entry ) );
1059bool commitTouchesPath( git_commit* aCommit,
const std::string& aPath )
1061 std::string after = treeEntryIdAt( aCommit, aPath.c_str() );
1064 if( git_commit_parentcount( aCommit ) > 0 )
1066 git_commit* parent =
nullptr;
1068 if( git_commit_parent( &parent, aCommit, 0 ) == GIT_OK )
1071 before = treeEntryIdAt( parent, aPath.c_str() );
1075 return after != before;
1083 std::vector<KIGIT_COMMIT_INFO> commits;
1089 git_revwalk* walker =
nullptr;
1091 if( git_revwalk_new( &walker,
m_repo ) != GIT_OK )
1098 git_revwalk_sorting( walker, GIT_SORT_TIME | GIT_SORT_TOPOLOGICAL );
1100 if( aFilter.
branch.IsEmpty() )
1102 git_revwalk_push_head( walker );
1103 git_revwalk_push_glob( walker,
"refs/heads/*" );
1104 git_revwalk_push_glob( walker,
"refs/remotes/*" );
1109 git_reference* ref =
nullptr;
1111 if( git_reference_dwim( &ref,
m_repo, aFilter.
branch.utf8_string().c_str() ) != GIT_OK )
1115 git_object* target =
nullptr;
1117 if( git_reference_peel( &target, ref, GIT_OBJECT_COMMIT ) != GIT_OK )
1121 git_revwalk_push( walker, git_object_id( target ) );
1124 std::map<std::string, std::vector<wxString>> refNames = collectRefNames(
m_repo );
1125 wxString search = aFilter.
search.Lower();
1126 std::string
path = aFilter.
path.utf8_string();
1129 while( commits.size() < aMax && git_revwalk_next( &oid, walker ) == GIT_OK )
1136 git_commit* commit =
nullptr;
1138 if( git_commit_lookup( &commit,
m_repo, &oid ) != GIT_OK )
1143 if( !
path.empty() && !commitTouchesPath( commit,
path ) )
1147 info.hash = shortHashOf( &oid );
1149 if(
const char* summary = git_commit_summary( commit ) )
1152 if(
const git_signature* author = git_commit_author( commit ) )
1158 info.when = wxDateTime(
static_cast<time_t
>( git_commit_time( commit ) ) );
1160 std::string fullHash = git_oid_tostr_s( &oid );
1162 auto matches = [&](
const wxString& aText )
1164 return aText.Lower().Contains( search );
1167 if( !search.IsEmpty() && !matches(
info.summary ) && !matches(
info.author ) && !matches(
info.email )
1168 && !matches( fullHash ) )
1173 auto named = refNames.find( fullHash );
1175 if( named != refNames.end() )
1176 info.refs = named->second;
1178 commits.push_back(
info );
1192 git_object* obj =
nullptr;
1194 if( git_revparse_single( &obj,
m_repo, aHash.utf8_string().c_str() ) != GIT_OK )
1199 if( git_object_type( obj ) != GIT_OBJECT_COMMIT )
1202 git_commit* commit =
reinterpret_cast<git_commit*
>( obj );
1203 const git_oid* oid = git_commit_id( commit );
1206 detail.hash = wxString::FromUTF8( git_oid_tostr_s( oid ) );
1207 detail.shortHash = shortHashOf( oid );
1209 if(
const char*
message = git_commit_message( commit ) )
1214 if(
const git_signature* author = git_commit_author( commit ) )
1220 detail.when = wxDateTime(
static_cast<time_t
>( git_commit_time( commit ) ) );
1222 for(
unsigned ii = 0; ii < git_commit_parentcount( commit ); ++ii )
1223 detail.parents.push_back( shortHashOf( git_commit_parent_id( commit, ii ) ) );
1225 std::map<std::string, std::vector<wxString>> refNames = collectRefNames(
m_repo );
1226 auto named = refNames.find( git_oid_tostr_s( oid ) );
1228 if( named != refNames.end() )
1229 detail.refs = named->second;
1231 git_tree* tree =
nullptr;
1233 if( git_commit_tree( &tree, commit ) != GIT_OK )
1241 if( git_commit_parentcount( commit ) > 0 )
1243 git_commit* parent =
nullptr;
1245 if( git_commit_parent( &parent, commit, 0 ) == GIT_OK )
1248 git_tree* raw =
nullptr;
1250 if( git_commit_tree( &raw, parent ) == GIT_OK )
1251 parentTree.reset( raw );
1255 git_diff* diff =
nullptr;
1257 if( git_diff_tree_to_tree( &diff,
m_repo, parentTree.get(), tree,
nullptr ) != GIT_OK )
1262 git_diff_find_options findOpts;
1263 git_diff_find_options_init( &findOpts, GIT_DIFF_FIND_OPTIONS_VERSION );
1264 findOpts.flags = GIT_DIFF_FIND_RENAMES | GIT_DIFF_FIND_COPIES;
1265 git_diff_find_similar( diff, &findOpts );
1267 for(
size_t ii = 0; ii < git_diff_num_deltas( diff ); ++ii )
1269 git_patch* patch =
nullptr;
1271 if( git_patch_from_diff( &patch, diff, ii ) != GIT_OK )
1275 const git_diff_delta*
delta = git_patch_get_delta( patch );
1278 file.
path = wxString::FromUTF8(
delta->new_file.path ?
delta->new_file.path :
delta->old_file.path );
1280 file.
binary = (
delta->flags & GIT_DIFF_FLAG_BINARY ) != 0;
1282 if(
delta->status == GIT_DELTA_RENAMED ||
delta->status == GIT_DELTA_COPIED )
1283 file.
oldPath = wxString::FromUTF8(
delta->old_file.path );
1288 if( git_patch_line_stats(
nullptr, &added, &removed, patch ) == GIT_OK )
1290 file.
added =
static_cast<int>( added );
1291 file.
removed =
static_cast<int>( removed );
1296 detail.files.push_back( file );
1308 std::vector<KIGIT_STASH_INFO>* out;
1312int collectStash(
size_t aIndex,
const char* aMessage,
const git_oid* aStashId,
void* aPayload )
1314 STASH_COLLECT* ctx =
static_cast<STASH_COLLECT*
>( aPayload );
1317 info.index = aIndex;
1320 char hash[GIT_OID_HEXSZ + 1] = { 0 };
1321 git_oid_tostr( hash,
sizeof( hash ), aStashId );
1322 info.id = wxString::FromUTF8( hash );
1324 git_commit* commit =
nullptr;
1326 if( git_commit_lookup( &commit, ctx->repo, aStashId ) == GIT_OK )
1329 info.when = wxDateTime(
static_cast<time_t
>( git_commit_time( commit ) ) );
1332 ctx->out->push_back(
info );
1340 std::vector<KIGIT_STASH_INFO> stashes;
1345 STASH_COLLECT ctx{
m_repo, &stashes };
1346 git_stash_foreach(
m_repo, collectStash, &ctx );
1354 std::vector<KIGIT_TAG_INFO> tags;
1359 git_strarray tagNames = {
nullptr, 0 };
1361 if( git_tag_list( &tagNames,
m_repo ) != GIT_OK )
1366 for(
size_t i = 0; i < tagNames.count; ++i )
1371 info.name = tagName;
1377 info.summary =
detail.message.BeforeFirst(
'\n' );
1381 tags.push_back(
info );
1385 std::stable_sort( tags.begin(), tags.end(),
1388 if( !a.when.IsValid() || !b.when.IsValid() )
1389 return a.when.IsValid();
1391 return a.when > b.when;
1400 std::vector<std::pair<wxString, wxString>> remotes;
1405 git_strarray names = {
nullptr, 0 };
1407 if( git_remote_list( &names,
m_repo ) != GIT_OK )
1412 for(
size_t i = 0; i < names.count; ++i )
1414 wxString
name = wxString::FromUTF8( names.strings[i] );
1415 git_remote* remote =
nullptr;
1417 if( git_remote_lookup( &remote,
m_repo, names.strings[i] ) != GIT_OK )
1422 const char* url = git_remote_url( remote );
1432 return m_repo && git_repository_head_unborn(
m_repo ) == 1;
1442 wxString ref = wxS(
"refs/heads/" ) + aName;
1444 return git_repository_set_head(
m_repo, ref.utf8_string().c_str() ) == GIT_OK;
1500 git_object* obj =
nullptr;
1501 wxString spec = wxS(
"refs/tags/" ) + aName;
1503 if( git_revparse_single( &obj,
m_repo, spec.utf8_string().c_str() ) != GIT_OK )
1510 git_object* peeled =
nullptr;
1512 if( git_object_peel( &peeled, obj, GIT_OBJECT_COMMIT ) != GIT_OK )
1516 git_commit* commit =
reinterpret_cast<git_commit*
>( peeled );
1520 detail.hash = shortHashOf( git_commit_id( commit ) );
1522 const char*
message =
nullptr;
1523 const git_signature* who =
nullptr;
1525 if( git_object_type( obj ) == GIT_OBJECT_TAG )
1527 git_tag* tag =
reinterpret_cast<git_tag*
>( obj );
1528 message = git_tag_message( tag );
1529 who = git_tag_tagger( tag );
1533 message = git_commit_message( commit );
1535 bool annotated = who !=
nullptr;
1538 who = git_commit_author( commit );
1549 detail.when = wxDateTime(
static_cast<time_t
>( annotated ? who->when.time : git_commit_time( commit ) ) );
1612 if( !
m_repo || aPath.IsEmpty() )
1615 git_index*
index =
nullptr;
1617 if( git_repository_index( &
index,
m_repo ) != GIT_OK )
1622 const git_index_entry* ancestor =
nullptr;
1623 const git_index_entry* ours =
nullptr;
1624 const git_index_entry* theirs =
nullptr;
1626 return git_index_conflict_get( &ancestor, &ours, &theirs,
index, aPath.utf8_string().c_str() ) == GIT_OK;
1632 return m_repo && git_repository_state(
m_repo ) != GIT_REPOSITORY_STATE_NONE;
1641 switch( git_repository_state(
m_repo ) )
1645 case GIT_REPOSITORY_STATE_REBASE:
1646 case GIT_REPOSITORY_STATE_REBASE_INTERACTIVE:
1647 case GIT_REPOSITORY_STATE_REBASE_MERGE:
1649 case GIT_REPOSITORY_STATE_CHERRYPICK:
1651 case GIT_REPOSITORY_STATE_REVERT:
1665 std::vector<KIGIT_CONFLICT_SIDES>& aConflicts, wxString& aError )
1672 const std::vector<std::pair<wxString, wxString>>& aResolvedFiles,
1688 [aOwner](
const auto& aEntry )
1690 return aEntry.first == aOwner;
1700 for(
const auto& [owner, callback] : listeners )
1704 [target = owner](
const auto& aEntry )
1706 return aEntry.first == target;
1725 [
this](
const char* aName ) ->
bool
1727 git_remote* remote =
nullptr;
1729 if( git_remote_lookup( &remote,
m_repo, aName ) != GIT_OK )
1734 const char* fetch_url = git_remote_url( remote );
1735 const char* push_url = git_remote_pushurl( remote );
1739 push_url = fetch_url;
1741 return fetch_url && push_url;
1746 if( checkRemote( preferred.c_str() ) )
1749 if( preferred !=
"origin" && checkRemote(
"origin" ) )
1752 git_strarray remotes = {
nullptr, 0 };
1754 if( git_remote_list( &remotes,
m_repo ) != GIT_OK )
1756 wxLogTrace(
traceGit,
"Failed to enumerate remotes for haspushpull" );
1762 for(
size_t ii = 0; ii < remotes.count; ++ii )
1764 if( checkRemote( remotes.strings[ii] ) )
1776 if( remoteName.IsEmpty() )
1777 remoteName = wxS(
"origin" );
1786 git_strarray remotes = {
nullptr, 0 };
1788 if( git_remote_list( &remotes,
m_repo ) == GIT_OK )
1793 if( remotes.count == 1 )
1795 retval = remotes.strings[0];
1799 for(
size_t ii = 0; ii < remotes.count; ++ii )
1801 if( strcmp( remotes.strings[ii],
"origin" ) == 0 )
1803 retval = remotes.strings[ii];
1809 git_strarray_dispose( &remotes );
1818 git_remote* remote =
nullptr;
1820 if( git_remote_lookup( &remote,
m_repo,
"origin" ) == GIT_OK )
1822 retval = git_remote_name( remote );
1823 git_remote_free( remote );
1827 wxLogTrace(
traceGit,
"Failed to get remote name from default remote: %s",
1839 return wxEmptyString;
1843 git_reference* head =
nullptr;
1844 git_reference* upstream =
nullptr;
1847 if( git_repository_head( &head,
m_repo ) != GIT_OK )
1855 if( git_branch_upstream( &upstream, head ) != GIT_OK )
1862 git_buf remote_name = GIT_BUF_INIT_CONST(
nullptr, 0 );
1864 if( git_branch_remote_name( &remote_name,
m_repo, git_reference_name( upstream ) ) == GIT_OK )
1866 retval = remote_name.ptr;
1867 git_buf_dispose( &remote_name );
1872 "Failed to get remote name from upstream branch: %s",
1884 if( aKey.IsEmpty() )
1917 return wxEmptyString;
1944 if( !aEntered.
sshKey.IsEmpty() )
1969 const auto deadline = std::chrono::steady_clock::now() + aWait;
1973 while( !lock.try_lock() && std::chrono::steady_clock::now() < deadline )
1974 std::this_thread::sleep_for( std::chrono::milliseconds( 20 ) );
2011 return wxEmptyString;
2018 return wxEmptyString;
2022 if(
const char* workdir = git_repository_workdir(
m_repo ) )
2023 return wxString::FromUTF8( workdir );
2025 if(
const char*
path = git_repository_path(
m_repo ) )
2026 return wxString::FromUTF8(
path );
2028 return wxEmptyString;
2055 git_remote* remote =
nullptr;
2057 if( git_remote_lookup( &remote,
m_repo, remote_name.ToStdString().c_str() ) == GIT_OK )
2060 if(
const char* remoteUrl = git_remote_url( remote ) )
2061 url = wxString::FromUTF8( remoteUrl );
2063 git_remote_free( remote );
2073 bool keysStale =
false;
2141 size_t protoSep = aUrl.find(
"://" );
2143 if( protoSep == wxString::npos )
2144 return wxString::npos;
2146 size_t hostStart = protoSep + 3;
2147 size_t pathStart = aUrl.find_first_of(
"/?#", hostStart );
2148 size_t searchEnd = ( pathStart == wxString::npos ) ? aUrl.length() : pathStart;
2150 if( searchEnd <= hostStart )
2151 return wxString::npos;
2153 size_t at = aUrl.rfind(
'@', searchEnd - 1 );
2155 return ( at != wxString::npos && at >= hostStart ) ? at : wxString::npos;
2161 const git_error* error = git_error_last();
2163 if( error ==
nullptr )
2164 return wxString(
"No error" );
2177 if(
const git_error* error = git_error_last() )
2179 info.klass = error->klass;
2180 info.message = wxString::FromUTF8( error->message ? error->message :
"" );
2190 const std::string
text = aText.utf8_string();
2193 for(
size_t ii = 0; ii <
text.size(); ++ii )
2195 if(
text[ii] ==
'%' && ii + 2 <
text.size() && isxdigit( (
unsigned char)
text[ii + 1] )
2196 && isxdigit( (
unsigned char)
text[ii + 2] ) )
2198 decoded +=
static_cast<char>( std::stoi(
text.substr( ii + 1, 2 ),
nullptr, 16 ) );
2219 if( aPort.IsEmpty() )
2222 if( !aPort.ToLong( &port ) || port <= 0 || port > 65535 )
2225 return static_cast<int>( port );
2229static bool isScpIpv6(
const wxString& aText,
size_t aBracket )
2233 for(
size_t ii = aBracket + 1; ii < aText.length(); ++ii )
2235 wxUniChar c = aText[ii];
2243 if( c !=
':' && !wxIsxdigit( c ) )
2253 for(
size_t ii = aStart; ii < aText.length(); ++ii )
2255 if( aText[ii] ==
'@' )
2258 if( aText[ii] ==
':' )
2267static bool parseScpAddress(
const wxString& aURL, wxString& aUser, wxString& aHost, wxString& aPort )
2284 size_t user = 0, userLen = 0, host = 0, hostLen = 0, port = 0, portLen = 0;
2286 bool havePath =
false;
2288 for(
size_t ii = 0; ii < aURL.length() && !havePath; ++ii )
2290 wxUniChar c = aURL[ii];
2295 if( c ==
'@' || c ==
':' )
2305 else if( bracket++ > 1 )
2326 userLen = ii - user;
2333 state = c ==
'[' ? IPV6 : HOST;
2340 hostLen = ii - host;
2341 state = bracket ? PORT_START : PATH_START;
2345 if( bracket-- == 0 )
2348 hostLen = ii - host;
2371 hostLen = ii - host;
2372 state = bracket ? PORT_START : PATH_START;
2383 if( bracket-- == 0 )
2386 portLen = ii - port;
2399 case PATH_START: havePath =
true;
break;
2403 if( !havePath || hostLen == 0 )
2406 aUser = aURL.Mid( user, userLen );
2407 aHost = aURL.Mid( host, hostLen );
2408 aPort = aURL.Mid( port, portLen );
2411 if( aHost.StartsWith(
"[" ) && aHost.EndsWith(
"]" ) )
2412 aHost = aHost.Mid( 1, aHost.length() - 2 );
2423 size_t schemeEnd = aURL.find(
"://" );
2424 size_t hostStart = 0;
2427 if( schemeEnd != wxString::npos )
2429 wxString scheme = aURL.Left( schemeEnd ).Lower();
2431 if( scheme ==
"https" || scheme ==
"http" )
2433 else if( scheme ==
"ssh" || scheme ==
"ssh+git" || scheme ==
"git+ssh" )
2438 hostStart = schemeEnd + 3;
2439 hostEnd = aURL.find_first_of(
"/?#", hostStart );
2442 if( at != wxString::npos )
2444 wxString credentials = aURL.Mid( hostStart, at - hostStart );
2445 size_t colon = credentials.find(
':' );
2446 wxString user = credentials.Left( colon );
2449 if( colon != wxString::npos )
2452 wxString userPrefix;
2455 userPrefix = user +
"@";
2457 result.url = aURL.Left( hostStart ) + userPrefix + aURL.Mid( at + 1 );
2464 if( aURL.length() >= 2 && wxIsalpha( aURL[0] ) && aURL[1] ==
':' )
2467 hostEnd = aURL.find(
':' );
2468 size_t bracket = aURL.find(
'[' );
2470 if( bracket != wxString::npos && bracket < hostEnd )
2472 size_t endBracket = aURL.find(
']', bracket );
2474 if( endBracket != wxString::npos )
2475 hostEnd = aURL.find(
':', endBracket + 1 );
2478 size_t slash = aURL.find_first_of(
"/\\" );
2480 if( hostEnd == wxString::npos || hostEnd == 0 || ( slash != wxString::npos && slash < hostEnd ) )
2495 wxString host = aURL.Mid( hostStart, hostEnd == wxString::npos ? wxString::npos : hostEnd - hostStart );
2498 if( host.StartsWith(
"[" ) )
2500 result.hostname = host.AfterFirst(
'[' ).BeforeFirst(
']' );
2501 port = host.AfterFirst(
']' ).AfterFirst(
':' );
2505 result.hostname = host.BeforeFirst(
':' );
2506 port = host.AfterFirst(
':' );
2559 if( sshKey.IsEmpty() )
2561 wxLogTrace(
traceGit,
"Finished testing all possible ssh keys" );
2563 return GIT_PASSTHROUGH;
2566 wxString sshPubKey = sshKey +
".pub";
2569 wxLogTrace(
traceGit,
"Testing %s\n", sshKey );
2571 if( git_credential_ssh_key_new( aOut, aUsername.mbc_str(), sshPubKey.mbc_str(), sshKey.mbc_str(),
2572 password.mbc_str() ) != GIT_OK )
2574 wxLogTrace(
traceGit,
"Failed to create SSH key credential for %s: %s", sshKey,
2576 return GIT_PASSTHROUGH;
2589 int rc = git_credential_userpass_plaintext_new( aOut, aUsername.mbc_str(), password.mbc_str() );
2600 if( git_credential_ssh_key_from_agent( aOut, aUsername.mbc_str() ) != GIT_OK )
2603 return GIT_PASSTHROUGH;
2613 const git_oid* aOID,
unsigned int aIsMerge,
void* aPayload )
2616 git_oid_cpy( (git_oid*) aPayload, aOID );
2626 wxString progressMessage( aStr );
2637 wxLogTrace(
traceGit,
"Progress CB cancelled" );
2650 wxString progressMessage = wxString::Format(
_(
"Received %u of %u objects" ),
2651 aStats->received_objects,
2652 aStats->total_objects );
2655 wxLogTrace(
traceGit,
"Transfer progress cancelled" );
2659 parent->
UpdateProgress( aStats->received_objects, aStats->total_objects, progressMessage );
2665extern "C" int update_cb(
const char* aRefname,
const git_oid* aFirst,
const git_oid* aSecond,
2668 constexpr int cstring_len = 8;
2669 char a_str[cstring_len + 1];
2670 char b_str[cstring_len + 1];
2675 git_oid_tostr( b_str, cstring_len, aSecond );
2677#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
2678 if( !git_oid_is_zero( aFirst ) )
2680 if( !git_oid_iszero( aFirst ) )
2683 git_oid_tostr( a_str, cstring_len, aFirst );
2684 status = wxString::Format(
_(
"* [updated] %s..%s %s" ), a_str, b_str, aRefname );
2688 status = wxString::Format(
_(
"* [new] %s %s" ), b_str, aRefname );
2700 long long progress = 100;
2705 wxLogTrace(
traceGit,
"Push progress cancelled" );
2711 progress = ( aCurrent * 100ll ) / aTotal;
2714 wxString progressMessage = wxString::Format(
_(
"Writing objects: %lld%% (%u/%u), %zu bytes" ),
2715 progress, aCurrent, aTotal, aBytes );
2726 if( aStatus && *aStatus )
2730 parent->
SetPushRefusal( wxString::Format( wxS(
"%s: %s" ), aRefname, status ) );
2732 wxString statusMessage = wxString::Format(
_(
"* [rejected] %s (%s)" ), aRefname,
2737 git_error_set_str( GIT_ERROR_NET, aStatus );
2742 wxString statusMessage = wxString::Format(
_(
"[updated] %s" ), aRefname );
2750extern "C" int credentials_cb( git_cred** aOut,
const char* aUrl,
const char* aUsername,
2751 unsigned int aAllowedTypes,
void* aPayload )
2762 wxLogTrace(
traceGit,
"Credentials callback for %s, testing %d", safeUrl, aAllowedTypes );
2768 wxLogTrace(
traceGit,
"Local repository, no credentials needed" );
2769 return GIT_PASSTHROUGH;
2772 if( aAllowedTypes & GIT_CREDENTIAL_USERNAME
2773 && !( parent->
TestedTypes() & GIT_CREDENTIAL_USERNAME ) )
2775 wxString username = parent->
GetUsername().Trim().Trim(
false );
2777 wxLogTrace(
traceGit,
"Username credential at %s with allowed type %d", safeUrl, aAllowedTypes );
2781 if( username.IsEmpty() )
2784 git_error_set_str( GIT_ERROR_NET,
_(
"Unable to authenticate" ).mbc_str() );
2789 if(
int rc = git_credential_username_new( aOut, username.ToStdString().c_str() ); rc != GIT_OK )
2796 wxLogTrace(
traceGit,
"Created username credential" );
2802 && ( aAllowedTypes & GIT_CREDENTIAL_USERPASS_PLAINTEXT )
2803 && !( parent->
TestedTypes() & GIT_CREDENTIAL_USERPASS_PLAINTEXT )
2807 wxLogTrace(
traceGit,
"Plaintext authentication at %s with allowed type %d", safeUrl, aAllowedTypes );
2811 && ( aAllowedTypes & GIT_CREDENTIAL_SSH_KEY )
2812 && !( parent->
TestedTypes() & GIT_CREDENTIAL_SSH_KEY ) )
2815 wxString sshUser = ( aUsername && *aUsername ) ? wxString::FromUTF8( aUsername ) : parent->
GetUsername();
2822 if(
result == GIT_PASSTHROUGH )
2825 git_error_set_str( GIT_ERROR_NET,
_(
"Unable to authenticate" ).mbc_str() );
2837 git_error_set_str( GIT_ERROR_NET,
_(
"Unable to authenticate" ).mbc_str() );
2848 if( !aCert || !aHost || !aPayload || aCert->cert_type != GIT_CERT_HOSTKEY_LIBSSH2 )
2849 return GIT_PASSTHROUGH;
2851 const git_cert_hostkey* cert =
reinterpret_cast<const git_cert_hostkey*
>( aCert );
2855 auto refuse = [&](
const wxString& aReason )
2859 return GIT_ECERTIFICATE;
2863 auto refuseUnreadable = [&]()
2865 wxLogTrace(
traceGit,
"Host key from %s cannot be read, refusing it", aHost );
2866 return refuse( wxString::Format(
_(
"KiCad could not read the key %s presented, so the connection was not "
2867 "made. Ask your administrator which host key the server uses." ),
2868 wxString::FromUTF8( aHost ) ) );
2871 if( !( cert->type & GIT_CERT_SSH_RAW ) || !cert->hostkey || cert->hostkey_len == 0 )
2872 return refuseUnreadable();
2875 key.
rawKey.assign( cert->hostkey, cert->hostkey_len );
2876 key.
host = wxString::FromUTF8( aHost );
2879 if( key.
host.StartsWith( wxS(
"[" ) ) && key.
host.EndsWith( wxS(
"]" ) ) )
2887 wxLogTrace(
traceGit,
"Host %s does not match the synced remote, refusing its key", key.
host );
2888 return refuse( wxString::Format(
_(
"The address of %s could not be matched to the remote, so its key was "
2889 "not checked. Check the remote URL." ),
2897 return refuseUnreadable();
2901 wxLogTrace(
traceGit,
"Host key %s SHA256:%s for %s port %d, known_hosts state %d, libgit2 valid %d", key.
keyType,
2909 return GIT_ECERTIFICATE;
2918 wxArrayString lines = wxSplit( aKeyText,
'\n',
'\0' );
2920 if( lines.IsEmpty() || !lines[0].Contains( wxS(
"PRIVATE KEY" ) ) )
2923 if( lines[0].Contains( wxS(
"ENCRYPTED" ) ) )
2927 for(
size_t ii = 1; ii < lines.GetCount(); ++ii )
2929 if( lines[ii].Trim().IsEmpty() )
2932 if( lines[ii].StartsWith( wxS(
"Proc-Type:" ) ) && lines[ii].Contains( wxS(
"ENCRYPTED" ) ) )
2936 if( !lines[0].Contains( wxS(
"OPENSSH PRIVATE KEY" ) ) )
2942 for(
size_t ii = 1; ii < lines.GetCount(); ++ii )
2944 if( lines[ii].StartsWith( wxS(
"-----" ) ) )
2950 wxMemoryBuffer raw = wxBase64Decode( body, wxBase64DecodeMode_SkipWS );
2952 const char magic[] =
"openssh-key-v1";
2953 const size_t magicLen =
sizeof( magic );
2954 const unsigned char* data =
static_cast<const unsigned char*
>( raw.GetData() );
2956 if( raw.GetDataLen() < magicLen + 4 || std::memcmp( data, magic, magicLen ) != 0 )
2960 (
static_cast<uint32_t
>( data[magicLen] ) << 24 ) | (
static_cast<uint32_t
>( data[magicLen + 1] ) << 16 )
2961 | (
static_cast<uint32_t
>( data[magicLen + 2] ) << 8 ) |
static_cast<uint32_t
>( data[magicLen + 3] );
2963 if( nameLen == 0 || raw.GetDataLen() < magicLen + 4 + nameLen )
2966 std::string cipher(
reinterpret_cast<const char*
>( data + magicLen + 4 ), nameLen );
2968 return cipher !=
"none";
2977 git_object* obj =
nullptr;
2978 std::string refStr( aRef.ToUTF8() );
2980 if( git_revparse_single( &obj, aRepo, refStr.c_str() ) != 0 )
2982 wxLogTrace(
traceGit,
"git_revparse_single failed for ref '%s': %s",
2988 git_tree* tree =
nullptr;
2990 if( git_object_peel(
reinterpret_cast<git_object**
>( &tree ), obj, GIT_OBJECT_TREE ) != 0 )
2992 wxLogTrace(
traceGit,
"git_object_peel to tree failed for '%s': %s",
3003 std::vector<wxString> refs;
3008 auto resolves = [&](
const wxString& aRef )
3013 auto add = [&](
const wxString& aRef )
3015 if( resolves( aRef ) && std::find( refs.begin(), refs.end(), aRef ) == refs.end() )
3016 refs.push_back( aRef );
3021 git_reference* head =
nullptr;
3023 if( git_repository_head( &head, aRepo ) == 0 )
3027 if( git_reference_is_branch( head ) )
3029 current = wxString::FromUTF8( git_reference_shorthand( head ) );
3031 git_reference* tracking =
nullptr;
3033 if( git_branch_upstream( &tracking, head ) == 0 )
3036 upstream = wxString::FromUTF8( git_reference_shorthand( tracking ) );
3041 const wxString
main = wxS(
"main" );
3042 const wxString master = wxS(
"master" );
3044 for(
const wxString& base : {
main, master, upstream } )
3046 if( base != current && resolves( base ) )
3048 refs.push_back( base );
3053 add( current.IsEmpty() ? wxString( wxS(
"HEAD" ) ) : current );
3057 add( wxS(
"HEAD" ) );
3064 const std::function<
void(
const git_diff_delta& )>& aCallback )
3069 std::size_t numDeltas = git_diff_num_deltas( aDiff );
3071 for( std::size_t ii = 0; ii < numDeltas; ++ii )
3073 const git_diff_delta*
delta = git_diff_get_delta( aDiff, ii );
3078 aCallback( *
delta );
3085 if( aName.IsEmpty() )
3086 return _(
"Enter a name for the branch." );
3090 if( aName.Contains( wxS(
" " ) ) )
3091 return _(
"A branch name cannot contain spaces." );
3093 if( aName.StartsWith( wxS(
"-" ) ) )
3094 return _(
"A branch name cannot start with a dash." );
3096 if( aName.Contains( wxS(
".." ) ) )
3097 return _(
"A branch name cannot contain two dots in a row." );
3099 if( aName.EndsWith( wxS(
"/" ) ) )
3100 return _(
"A branch name cannot end with a slash." );
3104 if( git_branch_name_is_valid( &valid, aName.utf8_string().c_str() ) != GIT_OK || !valid )
3105 return wxString::Format(
_(
"'%s' is not a valid branch name." ), aName );
3107 return wxEmptyString;
3113 if( !aText || !*aText )
3116 wxString
decoded = wxString::FromUTF8( aText );
3121 return wxString( aText, wxConvISO8859_1 );
3127 return _(
"Another git operation is in progress. Try again in a moment." );
3134 message.Replace( wxS(
"\r\n" ), wxS(
"\n" ) );
3135 message.Trim(
true ).Trim(
false );
3137 wxString description = aDescription;
3138 description.Replace( wxS(
"\r\n" ), wxS(
"\n" ) );
3139 description.Trim(
true ).Trim(
false );
3141 if( !description.IsEmpty() )
3142 message << wxS(
"\n\n" ) << description;
3144 if( !aSignOff.IsEmpty() )
3146 wxString trailer = wxS(
"Signed-off-by: " ) + aSignOff;
3147 bool signedOff =
false;
3150 for( wxString line : wxSplit(
message,
'\n' ) )
3152 if( line.Trim() == trailer )
3157 message << wxS(
"\n\n" ) << trailer;
3167 message.Replace( wxS(
"\r\n" ), wxS(
"\n" ) );
3168 message.Trim(
true ).Trim(
false );
3170 aSummary =
message.BeforeFirst(
'\n' );
3171 aDescription =
message.AfterFirst(
'\n' );
3172 aDescription.Trim(
false );
virtual RestoreResult RestoreVersion(KIGIT_COMMON &aCommon, const wxString &aCommitHash, KIGIT_ERROR_MESSAGE &aError)=0
virtual bool CreateTag(git_repository *aRepo, const wxString &aName, const wxString &aMessage)=0
virtual bool CheckoutTag(git_repository *aRepo, const wxString &aName)=0
virtual int CreateBranch(git_repository *aRepo, const wxString &aBranchName, const wxString &aFrom)=0
virtual bool DeleteTag(git_repository *aRepo, const wxString &aName)=0
virtual bool SetRemoteUrl(git_repository *aRepo, const wxString &aName, const wxString &aUrl)=0
virtual bool AbandonOperation(git_repository *aRepo, wxString &aError)=0
Drop a merge, rebase, cherry-pick or revert left in progress and return to the last commit.
virtual bool GetMergeConflicts(git_repository *aRepo, const wxString &aStageDir, wxString &aHeadOid, wxString &aTheirsOid, std::vector< KIGIT_CONFLICT_SIDES > &aConflicts, wxString &aError)=0
virtual bool DeleteBranch(git_repository *aRepo, const wxString &aBranchName)=0
virtual bool AddRemote(git_repository *aRepo, const wxString &aName, const wxString &aUrl)=0
virtual bool StashPop(git_repository *aRepo, size_t aIndex)=0
virtual MergeCommitResult CreateMergedCommit(git_repository *aRepo, const wxString &aExpectedHeadOid, const wxString &aTheirsOid, const std::vector< std::pair< wxString, wxString > > &aResolvedFiles, KIGIT_ERROR_MESSAGE &aError)=0
virtual bool StashApply(git_repository *aRepo, size_t aIndex)=0
virtual bool SetUserConfig(git_repository *aRepo, const wxString &aName, const wxString &aEmail)=0
virtual bool IsPathInHead(git_repository *aRepo, const wxString &aPath)=0
virtual bool DiscardChanges(git_repository *aRepo, const std::vector< wxString > &aPaths, wxString &aError)=0
virtual bool StashSave(git_repository *aRepo, const wxString &aMessage)=0
virtual bool StashDrop(git_repository *aRepo, size_t aIndex)=0
virtual bool RemoveRemote(git_repository *aRepo, const wxString &aName)=0
static std::optional< wxString > ProjectPrefix(const wxString &aWorkDir, const wxString &aProjectPath)
The project's folder inside the repository, ending in a slash.
The project's git session.
std::mutex m_gitActionMutex
void FollowRewrittenUrl(const wxString &aRawUrl)
Bind the session to a URL that url.insteadOf rewrote, keeping the username, password and key entered ...
void updateConnectionType()
std::pair< int, int > GetWorkingTreeLineStats(const std::set< wxString > &aPaths) const
std::shared_ptr< std::atomic< bool > > CANCEL_TOKEN
One token per operation.
GIT_CONN_TYPE GetConnType() const
std::vector< KIGIT_COMMIT_INFO > GetRecentCommits(size_t aMax=50, const KIGIT_HISTORY_FILTER &aFilter={}) const
bool HasUncommittedTrackedChanges(bool *aReadOk=nullptr) const
void SyncRemoteUrl(const wxString &aRawUrl)
Bind the session to the URL a transfer is about to connect to.
CANCEL_TOKEN ActiveCancelToken() const
bool StashPop(size_t aIndex)
std::optional< wxString > GetProjectPrefix() const
Project folder relative to the repository, with a trailing slash.
void SetConnType(GIT_CONN_TYPE aConnType)
For a remote whose address names no transport, the user picks one in the prompt.
wxString GetLastCommitMessage() const
std::mutex m_credentialsMutex
Serializes the credential fields between the UI thread and the authentication callback's worker threa...
wxString GetCurrentBranchName() const
wxString GetGitRootDirectory() const
bool IsOperationInProgress() const
True while a merge, rebase, cherry-pick or revert is in progress, which refuses commits.
KIGIT_TAG_DETAIL GetTagDetail(const wxString &aName) const
wxString GetSSHKey() const
The key the project was set up with, even when its file is gone, otherwise the first key offered.
void SetSSHKey(const wxString &aSSHKey)
bool IsPullRebaseConfigured() const
True when the repository is set to rebase rather than merge when getting changes.
KIGIT_COMMIT_DETAIL GetCommitDetail(const wxString &aHash) const
static const unsigned KIGIT_CREDENTIAL_SSH_AGENT
int HandlePlaintextAuthentication(git_cred **aOut, const wxString &aUsername)
bool HasUpstreamCommits() const
True when the upstream branch has commits the current branch does not.
bool CheckoutTag(const wxString &aName)
wxString remoteNameWithoutUpstream() const
Remote to use when HEAD names no upstream, empty when several remotes leave it ambiguous.
wxString m_publicKeysHost
bool DeleteTag(const wxString &aName)
std::vector< wxString > GetProjectDirs()
Return a vector of project files in the repository.
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
KIGIT_COMMON(git_repository *aRepo, const wxString &aProjectPath=wxEmptyString)
aProjectPath is the absolute project directory.
CREDENTIAL_IDENTITY GetCredentialIdentity() const
void NotifyRefreshListeners()
std::vector< wxString > GetRecentCommitMessages(size_t aMax) const
wxString GetProjectDir() const
Return the path set by SetProjectDir(), or the repository's working directory.
bool RemoveRemote(const wxString &aName)
std::optional< size_t > GetUnmergedCommitCount(const wxString &aBranch) const
Commits in aBranch that HEAD does not contain. No value when either cannot be read.
bool CreateBranch(const wxString &aName, const wxString &aFrom=wxEmptyString)
wxString GetNextPublicKey()
By value, so the caller never holds a pointer into the vector.
bool AddRemote(const wxString &aName, const wxString &aUrl)
int SSHPortFor(const wxString &aHost) const
The SSH port the session's remote names for aHost, 22 when it names none, or -1 when the remote is on...
bool GetMergeConflicts(const wxString &aStageDir, wxString &aHeadOid, wxString &aTheirsOid, std::vector< KIGIT_CONFLICT_SIDES > &aConflicts, wxString &aError)
Recompute the merge in memory and extract each conflicted file's three versions into aStageDir,...
void AddRefreshListener(void *aOwner, std::function< void()> aCallback)
std::vector< KIGIT_STASH_INFO > GetStashes() const
std::unique_lock< std::mutex > TryLockActionFor(std::chrono::milliseconds aWait)
TryLockAction that keeps trying for aWait before giving up.
bool StartUnbornBranch(const wxString &aName)
Point HEAD at a branch that does not exist yet, so the first commit creates it.
std::atomic< unsigned > m_testedTypes
std::pair< std::set< wxString >, std::set< wxString > > GetDifferentFiles() const
Return a pair of sets of files that differ locally from the remote repository The first set is files ...
bool IsHeadUnborn() const
True while the repository has no commits, so HEAD names a branch that does not exist yet.
bool ApplyPromptedCredentials(const CREDENTIAL_IDENTITY &aFor, const PROMPTED_CREDENTIALS &aEntered)
Apply what the prompt collected, unless the session has moved to another remote since aFor was taken.
std::vector< std::pair< void *, std::function< void()> > > m_refreshListeners
std::vector< wxString > m_publicKeys
bool IsShuttingDown() const
Background timers ask this before starting work of their own.
KIGIT_BRANCH_DETAIL GetBranchDetail(const wxString &aBranch) const
wxString GetHeadCommitId() const
Full commit ID at HEAD, or empty for an unborn or missing HEAD.
bool HasPushAndPullRemote() const
bool StashApply(size_t aIndex)
bool DiscardChanges(const std::vector< wxString > &aPaths, wxString &aError)
Put the given working directory relative paths back to their committed state.
void rebuildPublicKeysUnlocked()
Rebuild m_publicKeys as the project key, when offerable, then the discovered keys,...
bool SetUserConfig(const wxString &aName, const wxString &aEmail)
void updateConnectionTypeUnlocked()
Body of updateConnectionType, for callers that already hold m_credentialsMutex.
bool IsPathInHead(const wxString &aPath)
True when the working directory relative path exists in the HEAD commit's tree.
KIGIT_STASH_DETAIL GetStashDetail(const wxString &aId) const
std::vector< std::pair< wxString, wxString > > GetRemotes() const
Returns every remote as a (name, URL) pair.
static wxString GetLastGitError()
libgit2's last message, without credentials from any URL it quotes, so it is safe to show or trace.
bool CreateTag(const wxString &aName, const wxString &aMessage=wxEmptyString)
MergeCommitResult CreateMergedCommit(const wxString &aExpectedHeadOid, const wxString &aTheirsOid, const std::vector< std::pair< wxString, wxString > > &aResolvedFiles, KIGIT_ERROR_MESSAGE &aError)
Redo the merge with each conflicted path replaced by its resolved file, then commit and check out.
bool AbandonOperation(wxString &aError)
Drop that operation and return to the last commit. The caller holds the action lock.
void UpdateCurrentBranchInfo()
wxString GetFirstOfferedSSHKey() const
The key the next attempt will try first, for the credential prompt to show.
void RemoveRefreshListener(void *aOwner)
bool StashDrop(size_t aIndex)
bool HasLocalCommits() const
True when HEAD has commits its upstream does not, or has no upstream yet.
std::vector< wxString > m_discoveredKeys
bool StashSave(const wxString &aMessage)
Set the uncommitted tracked changes aside as a new stash, as git stash push would.
wxDateTime GetLastFetchTime() const
static REMOTE_URL ParseRemoteURL(const wxString &aURL)
GitOperation OperationInProgress() const
Which operation that is, so the panel can name it.
int HandleSSHAgentAuthentication(git_cred **aOut, const wxString &aUsername)
std::optional< KIGIT_AHEAD_BEHIND > compareRefs(const git_reference *aBranch, const git_reference *aBase) const
Count commits unique to aBranch and aBase. No value if the history cannot be read.
RestoreResult RestoreVersion(const wxString &aCommitHash, KIGIT_ERROR_MESSAGE &aError)
Restore the project as it was at aCommitHash, recorded as a new commit on top of the current head.
std::vector< KIGIT_TAG_INFO > GetTags() const
std::vector< KIGIT_BRANCH_INFO > GetBranches() const
Local and remote branches with their tip commits.
bool DeleteBranch(const wxString &aName)
bool IsPathConflicted(const wxString &aPath) const
True while aPath is one of the files a merge or rebase stopped on.
wxString GetRemotename() const
bool IsHeadDetached() const
True when HEAD points at a commit rather than a branch, as after checking out a tag.
int HandleSSHKeyAuthentication(git_cred **aOut, const wxString &aUsername, KIGIT_AUTH_ATTEMPT &aAttempt)
Offer the agent's keys once, then the next key file, recording in aAttempt which file was offered.
bool SetRemoteUrl(const wxString &aName, const wxString &aUrl)
void SetPushRefusal(const wxString &aStatus)
void SetHostKeyRefusal(const wxString &aReason)
wxString GetUsername() const
Get the username.
KIGIT_AUTH_ATTEMPT & AuthAttempt()
std::atomic< unsigned > & TestedTypes()
Return the connection types that have been tested for authentication.
bool IsThisOperationCancelled() const
void SetHostKeyFailure(const KIGIT::HOST_KEY_FAILURE &aFailure)
KIGIT_COMMON * GetCommon() const
Get the common object.
KIGIT_COMMON::GIT_CONN_TYPE GetConnType() const
Get the connection type.
void ReceiveSideband(const char *aData, size_t aLength)
Keep sideband text the server sent for the user, such as why it refused a pushed branch,...
void UpdateProgress(int aCurrent, int aTotal, const wxString &aMessage) override
GIT_BACKEND * GetGitBackend()
const wxChar *const traceGit
Flag to enable Git debugging output.
@ USER
The main config directory (e.g. ~/.config/kicad/)
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)
static size_t findUserinfoAt(const wxString &aUrl)
static bool scpHasUser(const wxString &aText, size_t aStart)
int transfer_progress_cb(const git_transfer_progress *aStats, void *aPayload)
static bool isScpIpv6(const wxString &aText, size_t aBracket)
static int parsePort(const wxString &aPort)
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)
static bool parseScpAddress(const wxString &aURL, wxString &aUser, wxString &aHost, wxString &aPort)
static wxString decodePercent(const wxString &aText)
std::unique_ptr< git_tree, decltype([](git_tree *aTree) { git_tree_free(aTree); })> GitTreePtr
A unique pointer for git_tree objects with automatic cleanup.
APIEXPORT wxString StripEmbeddedUserinfo(const wxString &aText)
Remove the user and password from every URL quoted in a message, since the remote's can carry a passw...
bool IsPrivateKeyEncrypted(const wxString &aKeyText)
Report whether an SSH private key is protected by a passphrase.
void SplitCommitMessage(const wxString &aMessage, wxString &aSummary, wxString &aDescription)
Split a commit message into its first line and the description after the first blank line.
APIEXPORT bool IsOfferableSSHKey(const wxString &aKey)
Whether aKey can be offered to a server.
APIEXPORT wxString PrintableServerText(const wxString &aText)
Text a server sent, made safe to show: no terminal escapes, control or bidi characters,...
wxString BranchNameError(const wxString &aName)
Empty when aName is a branch name git would accept, otherwise why it would not, in the words to show ...
git_tree * ResolveRefToTree(git_repository *aRepo, const wxString &aRef)
Resolve a string ref (branch name, short OID, full OID, tag) to its tree.
@ MATCH
An entry names the host with this key.
std::unique_ptr< git_revwalk, decltype([](git_revwalk *aWalker) { git_revwalk_free(aWalker); })> GitRevWalkPtr
A unique pointer for git_revwalk 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.
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_status_list, decltype([](git_status_list *aList) { git_status_list_free(aList); })> GitStatusListPtr
A unique pointer for git_status_list objects with automatic cleanup.
APIEXPORT KNOWN_HOST_STATE LookupKnownHost(const wxString &aKnownHostsText, const wxString &aHost, int aPort, const wxString &aKeyType, const std::string &aRawKey)
Classify a server's key against the text of a known_hosts file, as OpenSSH reads it.
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_patch, decltype([](git_patch *aPatch) { git_patch_free(aPatch); })> GitPatchPtr
A unique pointer for git_patch 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.
wxString ComposeCommitMessage(const wxString &aSummary, const wxString &aDescription, const wxString &aSignOff)
Join a summary line, an optional description and an optional sign-off into one commit message.
APIEXPORT wxString DecodeServerText(const char *aData, size_t aLength)
Bytes a server sent, as UTF-8 or, when they are not valid UTF-8, one character for each byte.
std::unique_ptr< git_strarray, decltype([](git_strarray *aArray) { git_strarray_free(aArray); })> GitStrArrayPtr
A unique pointer for git_strarray objects with automatic cleanup.
APIEXPORT wxString HomeDir()
The user's home folder, where ssh looks for its config and default keys.
APIEXPORT wxString ReadKnownHosts()
The text of KnownHostsPath(). Empty when it is missing or cannot be read.
std::unique_ptr< git_reference_iterator, decltype([](git_reference_iterator *aIter) { git_reference_iterator_free(aIter); })> GitReferenceIteratorPtr
A unique pointer for git_reference_iterator objects with automatic cleanup.
APIEXPORT wxWindow *& ActiveAdvancedGitWindow()
The advanced git window, or null when none is open.
APIEXPORT wxString HostKeyType(const std::string &aRawKey)
The type a raw SSH key names for itself, such as ssh-ed25519. Empty when the key is malformed.
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.
APIEXPORT wxString HostKeyFingerprint(const std::string &aRawKey)
SHA256 of a raw key in unpadded base64, as ssh shows it after "SHA256:".
wxString DecodeRepoText(const char *aText)
Decode text that came out of a repository.
std::vector< wxString > CompareBranchRefs(git_repository *aRepo)
Refs to offer when comparing branches, base first and head second.
APIEXPORT bool IsSameSSHKey(const wxString &aKey, const wxString &aOther)
Whether two key paths name the same file.
std::unique_ptr< git_branch_iterator, decltype([](git_branch_iterator *aIter) { git_branch_iterator_free(aIter); })> GitBranchIteratorPtr
A unique pointer for git_branch_iterator 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.
APIEXPORT std::vector< wxString > DefaultSSHKeyCandidates(const wxString &aHomeDir, const wxString &aHost)
Private keys to offer aHost, best first.
A server key the connection refused, for the UI to offer to trust or to report.
wxString keyType
As known_hosts names it, such as ssh-ed25519.
wxString fingerprint
SHA256 in unpadded base64, as ssh shows it after "SHA256:".
std::string rawKey
The key as the server sent it.
size_t behind
Commits only in the second reference.
size_t ahead
Commits only in the first reference.
What the credential callback handed out during one operation, kept to explain a rejection.
KIGIT_COMMON::GIT_CONN_TYPE connType
std::vector< wxString > keysTried
Key files offered, in order.
bool agentTried
The SSH agent was offered before any key file.
wxString oldPath
For a rename or copy, where the file came from.
KIGIT::FILE_CHANGE_STATUS status
The remote the credential prompt was opened for.
What the credential prompt collected.
std::optional< wxString > username
A transport address with any embedded credentials extracted.
int port
0 when the address names none.
wxString url
Keeps the SSH username, scheme and port, but never a password.
What libgit2 reported for a failed call, read before anything else can replace it.
A summary the user can act on, with the raw text that explains it kept apart for the details.
wxString search
Keep only commits whose summary, author or hash contains this.
wxString path
Keep only commits that changed this file or folder, relative to the root.
wxString branch
One branch, local or remote, or empty for every branch and HEAD.
static std::string decoded(const std::string &aBase64)
wxString result
Test unit parsing edge cases and error handling.
wxLogTrace helper definitions.