32#include <wx/filename.h>
34#include <wx/textfile.h>
84 const char* workdir = git_repository_workdir(
m_repo );
87 return wxString( workdir );
96 wxCHECK(
m_repo, wxEmptyString );
98 git_reference* head =
nullptr;
100 if( git_repository_head( &head,
m_repo ) != GIT_OK )
101 return wxEmptyString;
105 if( !git_reference_is_branch( head ) )
106 return wxEmptyString;
108 git_reference* upstream =
nullptr;
110 if( git_branch_upstream( &upstream, head ) == GIT_OK )
113 const char* shorthand = git_reference_shorthand( upstream );
116 return wxString::FromUTF8( shorthand );
121 const char* branch_shorthand = git_reference_shorthand( head );
123 if( !branch_shorthand )
124 return wxEmptyString;
132 wxCHECK(
m_repo, wxEmptyString );
133 git_reference* head =
nullptr;
135 int retval = git_repository_head( &head,
m_repo );
137 if( retval && retval != GIT_EUNBORNBRANCH && retval != GIT_ENOTFOUND )
138 return wxEmptyString;
141 git_reference* branch;
143 if( git_reference_resolve( &branch, head ) )
145 wxLogTrace(
traceGit,
"Failed to resolve branch" );
146 return wxEmptyString;
150 const char* branchName =
"";
152 if( git_branch_name( &branchName, branch ) )
154 wxLogTrace(
traceGit,
"Failed to get branch name" );
155 return wxEmptyString;
158 return wxString( branchName );
186 std::vector<wxString> branchNames;
187 std::map<git_time_t, wxString> branchNamesMap;
190 git_branch_iterator* branchIterator =
nullptr;
192 if( git_branch_iterator_new( &branchIterator,
m_repo, GIT_BRANCH_LOCAL ) )
194 wxLogTrace(
traceGit,
"Failed to get branch iterator" );
199 git_reference* branchReference =
nullptr;
200 git_branch_t branchType;
202 while( git_branch_next( &branchReference, &branchType, branchIterator ) != GIT_ITEROVER )
204 const char* branchName =
"";
207 if( git_branch_name( &branchName, branchReference ) )
209 wxLogTrace(
traceGit,
"Failed to get branch name in iter loop" );
213 const git_oid* commitId = git_reference_target( branchReference );
215 git_commit* commit =
nullptr;
217 if( git_commit_lookup( &commit,
m_repo, commitId ) )
219 wxLogTrace(
traceGit,
"Failed to get commit in iter loop" );
224 git_time_t commitTime = git_commit_time( commit );
226 if( git_branch_is_head( branchReference ) )
227 firstName = branchName;
229 branchNamesMap.emplace( commitTime, branchName );
233 if( !firstName.IsEmpty() )
234 branchNames.push_back( firstName );
237 for(
auto rit = branchNamesMap.rbegin(); rit != branchNamesMap.rend(); ++rit )
238 branchNames.push_back( rit->second );
247 std::vector<wxString> projDirs;
253 if( git_reference_name_to_id( &oid,
m_repo,
"HEAD" ) != GIT_OK )
259 if( git_commit_lookup( &commit,
m_repo, &oid ) != GIT_OK )
267 if( git_commit_tree( &tree, commit ) != GIT_OK )
277 tree, GIT_TREEWALK_PRE,
278 [](
const char* root,
const git_tree_entry* entry,
void* payload )
280 std::vector<wxString>* prjs =
static_cast<std::vector<wxString>*
>( payload );
281 wxFileName root_fn( git_tree_entry_name( entry ) );
283 root_fn.SetPath( root );
285 if( git_tree_entry_type( entry ) == GIT_OBJECT_BLOB
286 && ( ( root_fn.GetExt() ==
"kicad_pro" ) || ( root_fn.GetExt() ==
"pro" ) ) )
288 prjs->push_back( root_fn.GetFullPath() );
295 std::sort( projDirs.begin(), projDirs.end(),
296 [](
const wxString& a,
const wxString& b )
298 int a_freq = a.Freq( wxFileName::GetPathSeparator() );
299 int b_freq = b.Freq( wxFileName::GetPathSeparator() );
301 if( a_freq == b_freq )
304 return a_freq < b_freq;
314 std::pair<std::set<wxString>, std::set<wxString>> modified_files;
317 return modified_files;
319 git_reference* head =
nullptr;
320 git_reference* remote_head =
nullptr;
322 if( git_repository_head( &head,
m_repo ) != GIT_OK )
324 wxLogTrace(
traceGit,
"Failed to get modified HEAD" );
325 return modified_files;
330 if( git_branch_upstream( &remote_head, head ) != GIT_OK )
335 wxLogTrace(
traceGit,
"Failed to get modified remote HEAD" );
336 return modified_files;
341 const git_oid* head_oid = git_reference_target( head );
342 const git_oid* remote_oid = git_reference_target( remote_head );
344 if( !head_oid || !remote_oid )
345 return modified_files;
348 [
this](
const git_oid* aOid ) -> git_tree*
350 git_commit* commit =
nullptr;
352 if( git_commit_lookup( &commit,
m_repo, aOid ) != GIT_OK )
354 wxLogTrace(
traceGit,
"Failed to lookup commit for diff: %s",
360 git_tree* tree =
nullptr;
362 if( git_commit_tree( &tree, commit ) != GIT_OK )
364 wxLogTrace(
traceGit,
"Failed to get commit tree for diff: %s",
372 git_tree* head_tree = load_tree( head_oid );
375 git_tree* remote_tree = load_tree( remote_oid );
378 if( !head_tree || !remote_tree )
379 return modified_files;
388 if( git_merge_base( &base_oid,
m_repo, head_oid, remote_oid ) != GIT_OK )
390 wxLogTrace(
traceGit,
"No merge base between local and remote: %s",
392 return modified_files;
395 git_tree* base_tree = load_tree( &base_oid );
399 return modified_files;
402 [
this]( git_tree* aOldTree, git_tree* aNewTree, std::set<wxString>& aOut )
407 git_diff_options localOpts;
408 git_diff_init_options( &localOpts, GIT_DIFF_OPTIONS_VERSION );
410 git_diff* diff =
nullptr;
412 if( git_diff_tree_to_tree( &diff,
m_repo, aOldTree, aNewTree, &localOpts )
415 wxLogTrace(
traceGit,
"Failed to diff trees: %s",
421 [&aOut](
const git_diff_delta& aDelta )
423 if( aDelta.new_file.path )
424 aOut.insert( wxString::FromUTF8( aDelta.new_file.path ) );
426 if( aDelta.old_file.path )
427 aOut.insert( wxString::FromUTF8( aDelta.old_file.path ) );
430 git_diff_free( diff );
433 collect_paths( base_tree, head_tree, modified_files.first );
434 collect_paths( base_tree, remote_tree, modified_files.second );
440 std::set<wxString> actuallyDifferent;
441 collect_paths( head_tree, remote_tree, actuallyDifferent );
443 auto filterToDifferent = [&]( std::set<wxString>& aSet )
445 for(
auto it = aSet.begin(); it != aSet.end(); )
446 it = actuallyDifferent.count( *it ) ? std::next( it ) : aSet.erase( it );
449 filterToDifferent( modified_files.first );
450 filterToDifferent( modified_files.second );
452 return modified_files;
461 git_reference* head =
nullptr;
462 git_reference* remote_head =
nullptr;
464 if( git_repository_head( &head,
m_repo ) != GIT_OK )
472 if( git_branch_upstream( &remote_head, head ) != GIT_OK )
480 const git_oid* head_oid = git_reference_target( head );
481 const git_oid* remote_oid = git_reference_target( remote_head );
482 git_revwalk* walker =
nullptr;
484 if( git_revwalk_new( &walker,
m_repo ) != GIT_OK )
492 if( !head_oid || git_revwalk_push( walker, head_oid ) != GIT_OK )
498 if( remote_oid && git_revwalk_hide( walker, remote_oid ) != GIT_OK )
507 if( git_revwalk_next( &oid, walker ) != GIT_OK )
525 [
this](
const char* aName ) ->
bool
527 git_remote* remote =
nullptr;
529 if( git_remote_lookup( &remote,
m_repo, aName ) != GIT_OK )
534 const char* fetch_url = git_remote_url( remote );
535 const char* push_url = git_remote_pushurl( remote );
539 push_url = fetch_url;
541 return fetch_url && push_url;
546 if( checkRemote( preferred.c_str() ) )
549 if( preferred !=
"origin" && checkRemote(
"origin" ) )
552 git_strarray remotes = {
nullptr, 0 };
554 if( git_remote_list( &remotes,
m_repo ) != GIT_OK )
556 wxLogTrace(
traceGit,
"Failed to enumerate remotes for haspushpull" );
562 for(
size_t ii = 0; ii < remotes.count; ++ii )
564 if( checkRemote( remotes.strings[ii] ) )
576 if( remoteName.IsEmpty() )
577 remoteName = wxS(
"origin" );
585 wxCHECK(
m_repo, wxEmptyString );
588 git_reference* head =
nullptr;
589 git_reference* upstream =
nullptr;
591 if( git_repository_head( &head,
m_repo ) != GIT_OK )
599 if( git_branch_upstream( &upstream, head ) != GIT_OK )
602 git_strarray remotes = {
nullptr, 0 };
604 if( git_remote_list( &remotes,
m_repo ) == GIT_OK )
609 if( remotes.count == 1 )
611 retval = remotes.strings[0];
615 for(
size_t ii = 0; ii < remotes.count; ++ii )
617 if( strcmp( remotes.strings[ii],
"origin" ) == 0 )
619 retval = remotes.strings[ii];
625 git_strarray_dispose( &remotes );
634 git_remote* remote =
nullptr;
636 if( git_remote_lookup( &remote,
m_repo,
"origin" ) == GIT_OK )
638 retval = git_remote_name( remote );
639 git_remote_free( remote );
643 wxLogTrace(
traceGit,
"Failed to get remote name from default remote: %s",
652 git_buf remote_name = GIT_BUF_INIT_CONST(
nullptr, 0 );
654 if( git_branch_remote_name( &remote_name,
m_repo, git_reference_name( upstream ) ) == GIT_OK )
656 retval = remote_name.ptr;
657 git_buf_dispose( &remote_name );
662 "Failed to get remote name from upstream branch: %s",
684 return wxEmptyString;
688 if(
const char* workdir = git_repository_workdir(
m_repo ) )
689 return wxString::FromUTF8( workdir );
691 if(
const char*
path = git_repository_path(
m_repo ) )
692 return wxString::FromUTF8(
path );
694 return wxEmptyString;
702 wxFileName keyFile( wxGetHomeDir(), wxEmptyString );
703 keyFile.AppendDir(
".ssh" );
704 keyFile.SetFullName(
"id_rsa" );
706 if( keyFile.FileExists() )
709 keyFile.SetFullName(
"id_dsa" );
711 if( keyFile.FileExists() )
714 keyFile.SetFullName(
"id_ecdsa" );
716 if( keyFile.FileExists() )
719 keyFile.SetFullName(
"id_ed25519" );
721 if( keyFile.FileExists() )
725 wxFileName sshConfig( wxGetHomeDir(), wxEmptyString );
726 sshConfig.AppendDir(
".ssh" );
727 sshConfig.SetFullName(
"config" );
729 if( sshConfig.FileExists() )
731 wxTextFile configFile( sshConfig.GetFullPath() );
736 for( wxString line = configFile.GetFirstLine(); !configFile.Eof(); line = configFile.GetNextLine() )
738 line.Trim(
false ).Trim(
true );
740 if( line.StartsWith(
"Host " ) )
745 if( line.StartsWith(
"Host" ) && line.Contains(
m_hostname ) )
748 if( match && line.StartsWith(
"IdentityFile" ) )
750 wxString keyPath = line.AfterFirst(
' ' ).Trim(
false ).Trim(
true );
753 if( keyPath.StartsWith(
"~" ) )
754 keyPath.Replace(
"~", wxGetHomeDir(),
false );
757 if( wxFileName::FileExists( keyPath ) )
775 git_remote* remote =
nullptr;
781 if( git_remote_lookup( &remote,
m_repo, remote_name.ToStdString().c_str() ) == GIT_OK )
783 const char* url = git_remote_url( remote );
788 git_remote_free( remote );
800 if( remote.IsEmpty() )
803 if( remote.StartsWith(
"https://" ) || remote.StartsWith(
"http://" ) )
807 else if( remote.StartsWith(
"ssh://" ) || remote.StartsWith(
"git@" ) || remote.StartsWith(
"git+ssh://" )
808 || remote.EndsWith(
".git" ) )
829 size_t atPos = uri.find(
'@' );
831 if( atPos != wxString::npos )
833 size_t protoEnd = uri.find(
"//" );
835 if( protoEnd != wxString::npos )
837 wxString credentials = uri.Mid( protoEnd + 2, atPos - protoEnd - 2 );
838 size_t colonPos = credentials.find(
':' );
840 if( colonPos != wxString::npos )
843 m_password = credentials.Mid( colonPos + 1, credentials.Length() - colonPos - 1 );
859 size_t colonPos =
m_remote.find(
':' );
861 if( colonPos != wxString::npos )
867 size_t hostStart =
m_remote.find(
"://" ) + 2;
868 size_t hostEnd =
m_remote.find(
'/', hostStart );
871 if( hostEnd != wxString::npos )
872 host =
m_remote.Mid( hostStart, hostEnd - hostStart );
876 atPos = host.find(
'@' );
878 if( atPos != wxString::npos )
901 if( sshKey.IsEmpty() )
903 wxLogTrace(
traceGit,
"Finished testing all possible ssh keys" );
905 return GIT_PASSTHROUGH;
908 wxString sshPubKey = sshKey +
".pub";
911 wxLogTrace(
traceGit,
"Testing %s\n", sshKey );
913 if( git_credential_ssh_key_new( aOut, aUsername.mbc_str(), sshPubKey.mbc_str(), sshKey.mbc_str(),
914 password.mbc_str() ) != GIT_OK )
916 wxLogTrace(
traceGit,
"Failed to create SSH key credential for %s: %s",
918 return GIT_PASSTHROUGH;
929 git_credential_userpass_plaintext_new( aOut, aUsername.mbc_str(), password.mbc_str() );
940 if( git_credential_ssh_key_from_agent( aOut, aUsername.mbc_str() ) != GIT_OK )
942 wxLogTrace(
traceGit,
"Failed to create SSH agent credential for %s: %s",
944 return GIT_PASSTHROUGH;
952 const git_oid* aOID,
unsigned int aIsMerge,
void* aPayload )
955 git_oid_cpy( (git_oid*) aPayload, aOID );
961extern "C" void clone_progress_cb(
const char* aStr,
size_t aLen,
size_t aTotal,
void* aPayload )
965 wxString progressMessage( aStr );
970extern "C" int progress_cb(
const char* str,
int len,
void* aPayload )
976 wxLogTrace(
traceGit,
"Progress CB cancelled" );
980 wxString progressMessage( str, len );
991 wxString progressMessage = wxString::Format(
_(
"Received %u of %u objects" ),
992 aStats->received_objects,
993 aStats->total_objects );
996 wxLogTrace(
traceGit,
"Transfer progress cancelled" );
1000 parent->
UpdateProgress( aStats->received_objects, aStats->total_objects, progressMessage );
1006extern "C" int update_cb(
const char* aRefname,
const git_oid* aFirst,
const git_oid* aSecond,
1009 constexpr int cstring_len = 8;
1010 char a_str[cstring_len + 1];
1011 char b_str[cstring_len + 1];
1016 git_oid_tostr( b_str, cstring_len, aSecond );
1018#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
1019 if( !git_oid_is_zero( aFirst ) )
1021 if( !git_oid_iszero( aFirst ) )
1024 git_oid_tostr( a_str, cstring_len, aFirst );
1025 status = wxString::Format(
_(
"* [updated] %s..%s %s" ), a_str, b_str, aRefname );
1029 status = wxString::Format(
_(
"* [new] %s %s" ), b_str, aRefname );
1041 long long progress = 100;
1046 progress = ( aCurrent * 100ll ) / aTotal;
1049 wxString progressMessage = wxString::Format(
_(
"Writing objects: %lld%% (%u/%u), %zu bytes" ),
1050 progress, aCurrent, aTotal, aBytes );
1060 wxString status( aStatus );
1062 if( !status.IsEmpty() )
1064 wxString statusMessage = wxString::Format(
_(
"* [rejected] %s (%s)" ), aRefname, aStatus );
1069 wxString statusMessage = wxString::Format(
_(
"[updated] %s" ), aRefname );
1077extern "C" int credentials_cb( git_cred** aOut,
const char* aUrl,
const char* aUsername,
1078 unsigned int aAllowedTypes,
void* aPayload )
1083 wxLogTrace(
traceGit,
"Credentials callback for %s, testing %d", aUrl, aAllowedTypes );
1087 wxLogTrace(
traceGit,
"Local repository, no credentials needed" );
1088 return GIT_PASSTHROUGH;
1091 if( aAllowedTypes & GIT_CREDENTIAL_USERNAME
1092 && !( parent->
TestedTypes() & GIT_CREDENTIAL_USERNAME ) )
1094 wxString username = parent->
GetUsername().Trim().Trim(
false );
1095 wxLogTrace(
traceGit,
"Username credential for %s at %s with allowed type %d",
1096 username, aUrl, aAllowedTypes );
1098 if( git_credential_username_new( aOut, username.ToStdString().c_str() ) != GIT_OK )
1100 wxLogTrace(
traceGit,
"Failed to create username credential for %s: %s",
1105 wxLogTrace(
traceGit,
"Created username credential for %s", username );
1111 && ( aAllowedTypes & GIT_CREDENTIAL_USERPASS_PLAINTEXT )
1112 && !( parent->
TestedTypes() & GIT_CREDENTIAL_USERPASS_PLAINTEXT )
1116 wxLogTrace(
traceGit,
"Plaintext authentication for %s at %s with allowed type %d",
1121 && ( aAllowedTypes & GIT_CREDENTIAL_SSH_KEY )
1122 && !( parent->
TestedTypes() & GIT_CREDENTIAL_SSH_KEY ) )
1129 if(
result == GIT_PASSTHROUGH )
1132 git_error_set_str( GIT_ERROR_NET,
_(
"Unable to authenticate" ).mbc_str() );
1144 return GIT_PASSTHROUGH;
1147 git_error_set_str( GIT_ERROR_NET,
_(
"Unable to authenticate" ).mbc_str() );
1163 wxArrayString lines = wxSplit( aKeyText,
'\n',
'\0' );
1165 if( lines.IsEmpty() || !lines[0].Contains( wxS(
"PRIVATE KEY" ) ) )
1168 if( lines[0].Contains( wxS(
"ENCRYPTED" ) ) )
1172 for(
size_t ii = 1; ii < lines.GetCount(); ++ii )
1174 if( lines[ii].Trim().IsEmpty() )
1177 if( lines[ii].StartsWith( wxS(
"Proc-Type:" ) ) && lines[ii].Contains( wxS(
"ENCRYPTED" ) ) )
1181 if( !lines[0].Contains( wxS(
"OPENSSH PRIVATE KEY" ) ) )
1187 for(
size_t ii = 1; ii < lines.GetCount(); ++ii )
1189 if( lines[ii].StartsWith( wxS(
"-----" ) ) )
1195 wxMemoryBuffer raw = wxBase64Decode( body, wxBase64DecodeMode_SkipWS );
1197 const char magic[] =
"openssh-key-v1";
1198 const size_t magicLen =
sizeof( magic );
1199 const unsigned char* data =
static_cast<const unsigned char*
>( raw.GetData() );
1201 if( raw.GetDataLen() < magicLen + 4 || std::memcmp( data, magic, magicLen ) != 0 )
1205 (
static_cast<uint32_t
>( data[magicLen] ) << 24 ) | (
static_cast<uint32_t
>( data[magicLen + 1] ) << 16 )
1206 | (
static_cast<uint32_t
>( data[magicLen + 2] ) << 8 ) |
static_cast<uint32_t
>( data[magicLen + 3] );
1208 if( nameLen == 0 || raw.GetDataLen() < magicLen + 4 + nameLen )
1211 std::string cipher(
reinterpret_cast<const char*
>( data + magicLen + 4 ), nameLen );
1213 return cipher !=
"none";
1222 git_object* obj =
nullptr;
1223 std::string refStr( aRef.ToUTF8() );
1225 if( git_revparse_single( &obj, aRepo, refStr.c_str() ) != 0 )
1227 wxLogTrace(
traceGit,
"git_revparse_single failed for ref '%s': %s",
1233 git_tree* tree =
nullptr;
1235 if( git_object_peel(
reinterpret_cast<git_object**
>( &tree ), obj, GIT_OBJECT_TREE ) != 0 )
1237 wxLogTrace(
traceGit,
"git_object_peel to tree failed for '%s': %s",
1247 const std::function<
void(
const git_diff_delta& )>& aCallback )
1252 std::size_t numDeltas = git_diff_num_deltas( aDiff );
1254 for( std::size_t ii = 0; ii < numDeltas; ++ii )
1256 const git_diff_delta*
delta = git_diff_get_delta( aDiff, ii );
1261 aCallback( *
delta );
virtual void UpdateProgress(int aCurrent, int aTotal, const wxString &aMessage)
std::mutex m_gitActionMutex
std::vector< wxString > GetBranchNames() const
void updateConnectionType()
GIT_CONN_TYPE GetConnType() const
static wxString GetLastGitError()
wxString GetCurrentBranchName() const
wxString GetGitRootDirectory() const
void SetSSHKey(const wxString &aSSHKey)
static const unsigned KIGIT_CREDENTIAL_SSH_AGENT
int HandlePlaintextAuthentication(git_cred **aOut, const wxString &aUsername)
std::vector< wxString > GetProjectDirs()
Return a vector of project files in the repository.
wxString GetRemoteNameOrDefault() const
Returns GetRemotename() when non-empty, otherwise "origin".
git_repository * GetRepo() const
wxString GetProjectDir() const
Get the project directory path.
wxString GetNextPublicKey()
KIGIT_COMMON(git_repository *aRepo)
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 ...
std::vector< wxString > m_publicKeys
bool HasPushAndPullRemote() const
void UpdateCurrentBranchInfo()
bool HasLocalCommits() const
int HandleSSHAgentAuthentication(git_cred **aOut, const wxString &aUsername)
wxString GetUpstreamShorthand() const
Returns the upstream shorthand for the current branch (e.g.
wxString GetRemotename() const
int HandleSSHKeyAuthentication(git_cred **aOut, const wxString &aUsername)
wxString GetUsername() const
Get the username.
unsigned & TestedTypes()
Return the connection types that have been tested for authentication.
KIGIT_COMMON * GetCommon() const
Get the common object.
KIGIT_COMMON::GIT_CONN_TYPE GetConnType() const
Get the connection type.
const wxChar *const traceGit
Flag to enable Git debugging output.
int fetchhead_foreach_cb(const char *, const char *, const git_oid *aOID, unsigned int aIsMerge, void *aPayload)
int progress_cb(const char *str, int len, void *aPayload)
int 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)
std::unique_ptr< git_tree, decltype([](git_tree *aTree) { git_tree_free(aTree); })> GitTreePtr
A unique pointer for git_tree objects with automatic cleanup.
bool IsPrivateKeyEncrypted(const wxString &aKeyText)
Report whether an SSH private key is protected by a passphrase.
git_tree * ResolveRefToTree(git_repository *aRepo, const wxString &aRef)
Resolve a string ref (branch name, short OID, full OID, tag) to its tree.
std::unique_ptr< git_revwalk, decltype([](git_revwalk *aWalker) { git_revwalk_free(aWalker); })> GitRevWalkPtr
A unique pointer for git_revwalk objects with automatic cleanup.
std::unique_ptr< git_commit, decltype([](git_commit *aCommit) { git_commit_free(aCommit); })> GitCommitPtr
A unique pointer for git_commit objects with automatic cleanup.
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_reference, decltype([](git_reference *aRef) { git_reference_free(aRef); })> GitReferencePtr
A unique pointer for git_reference objects with automatic cleanup.
std::unique_ptr< git_strarray, decltype([](git_strarray *aArray) { git_strarray_free(aArray); })> GitStrArrayPtr
A unique pointer for git_strarray objects with automatic cleanup.
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_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.
wxString result
Test unit parsing edge cases and error handling.
wxLogTrace helper definitions.