kicad-source/common/git/kicad_git_common.cpp
2025-06-03 11:41:27 +01:00

988 lines
30 KiB
C++

/*
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright The KiCad Developers, see AUTHORS.TXT for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you may find one here:
* http://www.gnu.org/licenses/gpl-3.0.html
* or you may search the http://www.gnu.org website for the version 3 license,
* or you may write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
#include "kicad_git_common.h"
#include "kicad_git_memory.h"
#include "git_repo_mixin.h"
#include <git/git_progress.h>
#include <git/kicad_git_compat.h>
#include <kiplatform/secrets.h>
#include <trace_helpers.h>
#include <git2.h>
#include <wx/filename.h>
#include <wx/log.h>
#include <wx/textfile.h>
#include <wx/utils.h>
#include <map>
#include <vector>
KIGIT_COMMON::KIGIT_COMMON( git_repository* aRepo ) :
m_repo( aRepo ), m_connType( GIT_CONN_TYPE::GIT_CONN_LOCAL ), m_testedTypes( 0 ),
m_nextPublicKey( 0 )
{}
KIGIT_COMMON::KIGIT_COMMON( const KIGIT_COMMON& aOther ) :
// Initialize base class and member variables
m_repo( aOther.m_repo ),
m_connType( aOther.m_connType ),
m_remote( aOther.m_remote ),
m_hostname( aOther.m_hostname ),
m_username( aOther.m_username ),
m_password( aOther.m_password ),
m_testedTypes( aOther.m_testedTypes ),
// The mutex is default-initialized, not copied
m_gitActionMutex(),
m_publicKeys( aOther.m_publicKeys ),
m_nextPublicKey( aOther.m_nextPublicKey )
{
}
KIGIT_COMMON::~KIGIT_COMMON()
{}
git_repository* KIGIT_COMMON::GetRepo() const
{
return m_repo;
}
wxString KIGIT_COMMON::GetCurrentBranchName() const
{
wxCHECK( m_repo, wxEmptyString );
git_reference* head = nullptr;
int retval = git_repository_head( &head, m_repo );
if( retval && retval != GIT_EUNBORNBRANCH && retval != GIT_ENOTFOUND )
return wxEmptyString;
KIGIT::GitReferencePtr headPtr( head );
git_reference* branch;
if( git_reference_resolve( &branch, head ) )
{
wxLogTrace( traceGit, "Failed to resolve branch" );
return wxEmptyString;
}
KIGIT::GitReferencePtr branchPtr( branch );
const char* branchName = "";
if( git_branch_name( &branchName, branch ) )
{
wxLogTrace( traceGit, "Failed to get branch name" );
return wxEmptyString;
}
return wxString( branchName );
}
std::vector<wxString> KIGIT_COMMON::GetBranchNames() const
{
if( !m_repo )
return {};
std::vector<wxString> branchNames;
std::map<git_time_t, wxString> branchNamesMap;
wxString firstName;
git_branch_iterator* branchIterator = nullptr;
if( git_branch_iterator_new( &branchIterator, m_repo, GIT_BRANCH_LOCAL ) )
{
wxLogTrace( traceGit, "Failed to get branch iterator" );
return branchNames;
}
KIGIT::GitBranchIteratorPtr branchIteratorPtr( branchIterator );
git_reference* branchReference = nullptr;
git_branch_t branchType;
while( git_branch_next( &branchReference, &branchType, branchIterator ) != GIT_ITEROVER )
{
const char* branchName = "";
KIGIT::GitReferencePtr branchReferencePtr( branchReference );
if( git_branch_name( &branchName, branchReference ) )
{
wxLogTrace( traceGit, "Failed to get branch name in iter loop" );
continue;
}
const git_oid* commitId = git_reference_target( branchReference );
git_commit* commit = nullptr;
if( git_commit_lookup( &commit, m_repo, commitId ) )
{
wxLogTrace( traceGit, "Failed to get commit in iter loop" );
continue;
}
KIGIT::GitCommitPtr commitPtr( commit );
git_time_t commitTime = git_commit_time( commit );
if( git_branch_is_head( branchReference ) )
firstName = branchName;
else
branchNamesMap.emplace( commitTime, branchName );
}
// Add the current branch to the top of the list
if( !firstName.IsEmpty() )
branchNames.push_back( firstName );
// Add the remaining branches in order from newest to oldest
for( auto rit = branchNamesMap.rbegin(); rit != branchNamesMap.rend(); ++rit )
branchNames.push_back( rit->second );
return branchNames;
}
std::vector<wxString> KIGIT_COMMON::GetProjectDirs()
{
wxCHECK( m_repo, {} );
std::vector<wxString> projDirs;
git_oid oid;
git_commit* commit;
git_tree *tree;
if( git_reference_name_to_id( &oid, m_repo, "HEAD" ) != GIT_OK )
{
wxLogTrace( traceGit, "An error occurred: %s", KIGIT_COMMON::GetLastGitError() );
return projDirs;
}
if( git_commit_lookup( &commit, m_repo, &oid ) != GIT_OK )
{
wxLogTrace( traceGit, "An error occurred: %s", KIGIT_COMMON::GetLastGitError() );
return projDirs;
}
KIGIT::GitCommitPtr commitPtr( commit );
if( git_commit_tree( &tree, commit ) != GIT_OK )
{
wxLogTrace( traceGit, "An error occurred: %s", KIGIT_COMMON::GetLastGitError() );
return projDirs;
}
KIGIT::GitTreePtr treePtr( tree );
// Define callback
git_tree_walk(
tree, GIT_TREEWALK_PRE,
[]( const char* root, const git_tree_entry* entry, void* payload )
{
std::vector<wxString>* prjs = static_cast<std::vector<wxString>*>( payload );
wxFileName root_fn( git_tree_entry_name( entry ) );
root_fn.SetPath( root );
if( git_tree_entry_type( entry ) == GIT_OBJECT_BLOB
&& ( ( root_fn.GetExt() == "kicad_pro" ) || ( root_fn.GetExt() == "pro" ) ) )
{
prjs->push_back( root_fn.GetFullPath() );
}
return 0; // continue walking
},
&projDirs );
std::sort( projDirs.begin(), projDirs.end(),
[]( const wxString& a, const wxString& b )
{
int a_freq = a.Freq( wxFileName::GetPathSeparator() );
int b_freq = b.Freq( wxFileName::GetPathSeparator() );
if( a_freq == b_freq )
return a < b;
else
return a_freq < b_freq;
} );
return projDirs;
}
std::pair<std::set<wxString>,std::set<wxString>> KIGIT_COMMON::GetDifferentFiles() const
{
auto get_modified_files = [&]( const git_oid* from_oid, const git_oid* to_oid ) -> std::set<wxString>
{
std::set<wxString> modified_set;
git_revwalk* walker = nullptr;
if( !m_repo || !from_oid )
return modified_set;
if( git_revwalk_new( &walker, m_repo ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to create revwalker: %s", KIGIT_COMMON::GetLastGitError() );
return modified_set;
}
KIGIT::GitRevWalkPtr walkerPtr( walker );
if( git_revwalk_push( walker, from_oid ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to set from commit: %s", KIGIT_COMMON::GetLastGitError() );
return modified_set;
}
if( to_oid && git_revwalk_hide( walker, to_oid ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to set end commit (maybe new repo): %s", KIGIT_COMMON::GetLastGitError() );
}
git_oid oid;
git_commit* commit;
// iterate over all local commits not in remote
while( git_revwalk_next( &oid, walker ) == GIT_OK )
{
if( git_commit_lookup( &commit, m_repo, &oid ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to lookup commit: %s", KIGIT_COMMON::GetLastGitError() );
continue;
}
KIGIT::GitCommitPtr commitPtr( commit );
git_tree *tree, *parent_tree = nullptr;
if( git_commit_tree( &tree, commit ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to get commit tree: %s", KIGIT_COMMON::GetLastGitError() );
continue;
}
KIGIT::GitTreePtr treePtr( tree );
// get parent commit tree to diff against
if( !git_commit_parentcount( commit ) )
{
git_tree_walk(
tree, GIT_TREEWALK_PRE,
[]( const char* root, const git_tree_entry* entry, void* payload )
{
std::set<wxString>* modified_set_internal = static_cast<std::set<wxString>*>( payload );
wxString filePath = wxString::Format( "%s%s", root, git_tree_entry_name( entry ) );
modified_set_internal->insert( std::move( filePath ) );
return 0; // continue walking
},
&modified_set );
continue;
}
git_commit* parent;
if( git_commit_parent( &parent, commit, 0 ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to get parent commit: %s", KIGIT_COMMON::GetLastGitError() );
continue;
}
KIGIT::GitCommitPtr parentPtr( parent );
if( git_commit_tree( &parent_tree, parent ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to get parent commit tree: %s", KIGIT_COMMON::GetLastGitError() );
continue;
}
KIGIT::GitTreePtr parentTreePtr( parent_tree );
git_diff* diff;
git_diff_options diff_opts;
git_diff_init_options( &diff_opts, GIT_DIFF_OPTIONS_VERSION );
if( !m_repo || !parent_tree || !tree )
continue;
if( git_diff_tree_to_tree( &diff, m_repo, parent_tree, tree, &diff_opts ) == GIT_OK )
{
size_t num_deltas = git_diff_num_deltas( diff );
for( size_t i = 0; i < num_deltas; ++i )
{
const git_diff_delta* delta = git_diff_get_delta( diff, i );
modified_set.insert( delta->new_file.path );
}
git_diff_free( diff );
}
else
{
wxLogTrace( traceGit, "Failed to diff trees: %s", KIGIT_COMMON::GetLastGitError() );
}
}
wxLogTrace( traceGit, "Finished walking commits with end: %s", KIGIT_COMMON::GetLastGitError() );
return modified_set;
};
std::pair<std::set<wxString>,std::set<wxString>> modified_files;
if( !m_repo )
return modified_files;
git_reference* head = nullptr;
git_reference* remote_head = nullptr;
if( git_repository_head( &head, m_repo ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to get modified HEAD" );
return modified_files;
}
KIGIT::GitReferencePtr headPtr( head );
if( git_branch_upstream( &remote_head, head ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to get modified remote HEAD" );
}
KIGIT::GitReferencePtr remoteHeadPtr( remote_head );
if( remote_head != nullptr && head != nullptr )
{
const git_oid* head_oid = git_reference_target( head );
const git_oid* remote_oid = git_reference_target( remote_head );
modified_files.first = get_modified_files( head_oid, remote_oid );
modified_files.second = get_modified_files( remote_oid, head_oid );
}
return modified_files;
}
bool KIGIT_COMMON::HasLocalCommits() const
{
if( !m_repo )
return false;
git_reference* head = nullptr;
git_reference* remote_head = nullptr;
if( git_repository_head( &head, m_repo ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to get HEAD: %s", KIGIT_COMMON::GetLastGitError() );
return false;
}
KIGIT::GitReferencePtr headPtr( head );
if( git_branch_upstream( &remote_head, head ) != GIT_OK )
{
// No remote branch, so we have local commits (new repo?)
wxLogTrace( traceGit, "Failed to get remote HEAD: %s", KIGIT_COMMON::GetLastGitError() );
return true;
}
KIGIT::GitReferencePtr remoteHeadPtr( remote_head );
const git_oid* head_oid = git_reference_target( head );
const git_oid* remote_oid = git_reference_target( remote_head );
git_revwalk* walker = nullptr;
if( git_revwalk_new( &walker, m_repo ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to create revwalker: %s", KIGIT_COMMON::GetLastGitError() );
return false;
}
KIGIT::GitRevWalkPtr walkerPtr( walker );
if( !head_oid || git_revwalk_push( walker, head_oid ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to push commits: %s", KIGIT_COMMON::GetLastGitError() );
return false;
}
if( remote_oid && git_revwalk_hide( walker, remote_oid ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to push/hide commits: %s", KIGIT_COMMON::GetLastGitError() );
return false;
}
git_oid oid;
// If we can't walk to the next commit, then we are at or behind the remote
if( git_revwalk_next( &oid, walker ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to walk to next commit: %s", KIGIT_COMMON::GetLastGitError() );
return false;
}
return true;
}
bool KIGIT_COMMON::HasPushAndPullRemote() const
{
wxCHECK( m_repo, false );
git_remote* remote = nullptr;
if( git_remote_lookup( &remote, m_repo, "origin" ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to get remote for haspushpull" );
return false;
}
KIGIT::GitRemotePtr remotePtr( remote );
// Get the URLs associated with the remote
const char* fetch_url = git_remote_url( remote );
const char* push_url = git_remote_pushurl( remote );
// If no push URL is set, libgit2 defaults to using the fetch URL for pushing
if( !push_url )
{
wxLogTrace( traceGit, "No push URL set, using fetch URL" );
push_url = fetch_url;
}
return fetch_url && push_url;
}
wxString KIGIT_COMMON::GetRemotename() const
{
wxCHECK( m_repo, wxEmptyString );
wxString retval;
git_reference* head = nullptr;
git_reference* upstream = nullptr;
if( git_repository_head( &head, m_repo ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to get remote name: %s", KIGIT_COMMON::GetLastGitError() );
return retval;
}
KIGIT::GitReferencePtr headPtr( head );
if( git_branch_upstream( &upstream, head ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to get upstream branch: %s", KIGIT_COMMON::GetLastGitError() );
git_strarray remotes = { nullptr, 0 };
if( git_remote_list( &remotes, m_repo ) == GIT_OK )
{
if( remotes.count == 1 )
retval = remotes.strings[0];
git_strarray_dispose( &remotes );
}
else
{
wxLogTrace( traceGit, "Failed to list remotes: %s", KIGIT_COMMON::GetLastGitError() );
// If we can't get the remote name from the upstream branch or the list of remotes,
// just return the default remote name
git_remote* remote = nullptr;
if( git_remote_lookup( &remote, m_repo, "origin" ) == GIT_OK )
{
retval = git_remote_name( remote );
git_remote_free( remote );
}
else
{
wxLogTrace( traceGit, "Failed to get remote name from default remote: %s",
KIGIT_COMMON::GetLastGitError() );
}
}
return retval;
}
KIGIT::GitReferencePtr upstreamPtr( upstream );
git_buf remote_name = GIT_BUF_INIT_CONST( nullptr, 0 );
if( git_branch_remote_name( &remote_name, m_repo, git_reference_name( upstream ) ) == GIT_OK )
{
retval = remote_name.ptr;
git_buf_dispose( &remote_name );
}
else
{
wxLogTrace( traceGit,
"Failed to get remote name from upstream branch: %s",
KIGIT_COMMON::GetLastGitError() );
}
return retval;
}
void KIGIT_COMMON::SetSSHKey( const wxString& aKey )
{
auto it = std::find( m_publicKeys.begin(), m_publicKeys.end(), aKey );
if( it != m_publicKeys.end() )
m_publicKeys.erase( it );
m_publicKeys.insert( m_publicKeys.begin(), aKey );
}
wxString KIGIT_COMMON::GetGitRootDirectory() const
{
if( !m_repo )
return wxEmptyString;
const char *path = git_repository_path( m_repo );
wxString retval = path;
return retval;
}
void KIGIT_COMMON::updatePublicKeys()
{
m_publicKeys.clear();
wxFileName keyFile( wxGetHomeDir(), wxEmptyString );
keyFile.AppendDir( ".ssh" );
keyFile.SetFullName( "id_rsa" );
if( keyFile.FileExists() )
m_publicKeys.push_back( keyFile.GetFullPath() );
keyFile.SetFullName( "id_dsa" );
if( keyFile.FileExists() )
m_publicKeys.push_back( keyFile.GetFullPath() );
keyFile.SetFullName( "id_ecdsa" );
if( keyFile.FileExists() )
m_publicKeys.push_back( keyFile.GetFullPath() );
keyFile.SetFullName( "id_ed25519" );
if( keyFile.FileExists() )
m_publicKeys.push_back( keyFile.GetFullPath() );
// Parse SSH config file for hostname information
wxFileName sshConfig( wxGetHomeDir(), wxEmptyString );
sshConfig.AppendDir( ".ssh" );
sshConfig.SetFullName( "config" );
if( sshConfig.FileExists() )
{
wxTextFile configFile( sshConfig.GetFullPath() );
configFile.Open();
bool match = false;
for( wxString line = configFile.GetFirstLine(); !configFile.Eof(); line = configFile.GetNextLine() )
{
line.Trim( false ).Trim( true );
if( line.StartsWith( "Host " ) )
match = false;
// The difference here is that we are matching either "Hostname" or "Host" to get the
// match. This is because in the absence of a "Hostname" line, the "Host" line is used
if( line.StartsWith( "Host" ) && line.Contains( m_hostname ) )
match = true;
if( match && line.StartsWith( "IdentityFile" ) )
{
wxString keyPath = line.AfterFirst( ' ' ).Trim( false ).Trim( true );
// Expand ~ to home directory if present
if( keyPath.StartsWith( "~" ) )
keyPath.Replace( "~", wxGetHomeDir(), false );
// Add the public key to the beginning of the list
if( wxFileName::FileExists( keyPath ) )
SetSSHKey( keyPath );
}
}
configFile.Close();
}
}
void KIGIT_COMMON::UpdateCurrentBranchInfo()
{
wxCHECK( m_repo, /* void */ );
// We want to get the current branch's upstream url as well as the stored password
// if one exists given the url and username.
wxString remote_name = GetRemotename();
git_remote* remote = nullptr;
if( git_remote_lookup( &remote, m_repo, remote_name.ToStdString().c_str() ) == GIT_OK )
{
const char* url = git_remote_url( remote );
if( url )
m_remote = url;
git_remote_free( remote );
}
// Find the stored password if it exists
KIPLATFORM::SECRETS::GetSecret( m_remote, m_username, m_password );
updateConnectionType();
updatePublicKeys();
}
KIGIT_COMMON::GIT_CONN_TYPE KIGIT_COMMON::GetConnType() const
{
wxString remote = m_remote;
if( remote.IsEmpty() )
remote = GetRemotename();
if( remote.StartsWith( "https://" ) || remote.StartsWith( "http://" ) )
{
return GIT_CONN_TYPE::GIT_CONN_HTTPS;
}
else if( remote.StartsWith( "ssh://" ) || remote.StartsWith( "git@" ) || remote.StartsWith( "git+ssh://" )
|| remote.EndsWith( ".git" ) )
{
return GIT_CONN_TYPE::GIT_CONN_SSH;
}
return GIT_CONN_TYPE::GIT_CONN_LOCAL;
}
void KIGIT_COMMON::updateConnectionType()
{
if( m_remote.StartsWith( "https://" ) || m_remote.StartsWith( "http://" ) )
m_connType = GIT_CONN_TYPE::GIT_CONN_HTTPS;
else if( m_remote.StartsWith( "ssh://" ) || m_remote.StartsWith( "git@" ) || m_remote.StartsWith( "git+ssh://" ) )
m_connType = GIT_CONN_TYPE::GIT_CONN_SSH;
else
m_connType = GIT_CONN_TYPE::GIT_CONN_LOCAL;
if( m_connType != GIT_CONN_TYPE::GIT_CONN_LOCAL )
{
wxString uri = m_remote;
size_t atPos = uri.find( '@' );
if( atPos != wxString::npos )
{
size_t protoEnd = uri.find( "//" );
if( protoEnd != wxString::npos )
{
wxString credentials = uri.Mid( protoEnd + 2, atPos - protoEnd - 2 );
size_t colonPos = credentials.find( ':' );
if( colonPos != wxString::npos )
{
m_username = credentials.Left( colonPos );
m_password = credentials.Mid( colonPos + 1, credentials.Length() - colonPos - 1 );
}
else
{
m_username = credentials;
}
}
else
{
m_username = uri.Left( atPos );
}
}
if( m_remote.StartsWith( "git@" ) )
{
// SSH format: git@hostname:path
size_t colonPos = m_remote.find( ':' );
if( colonPos != wxString::npos )
m_hostname = m_remote.Mid( 4, colonPos - 4 );
}
else
{
// other URL format: proto://[user@]hostname/path
size_t hostStart = m_remote.find( "://" ) + 2;
size_t hostEnd = m_remote.find( '/', hostStart );
wxString host;
if( hostEnd != wxString::npos )
host = m_remote.Mid( hostStart, hostEnd - hostStart );
else
host = m_remote.Mid( hostStart );
atPos = host.find( '@' );
if( atPos != wxString::npos )
m_hostname = host.Mid( atPos + 1 );
else
m_hostname = host;
}
}
}
int KIGIT_COMMON::HandleSSHKeyAuthentication( git_cred** aOut, const wxString& aUsername )
{
if( !( m_testedTypes & KIGIT_CREDENTIAL_SSH_AGENT ) )
return HandleSSHAgentAuthentication( aOut, aUsername );
// SSH key authentication with password
wxString sshKey = GetNextPublicKey();
if( sshKey.IsEmpty() )
{
wxLogTrace( traceGit, "Finished testing all possible ssh keys" );
m_testedTypes |= GIT_CREDENTIAL_SSH_KEY;
return GIT_PASSTHROUGH;
}
wxString sshPubKey = sshKey + ".pub";
wxString password = GetPassword();
wxLogTrace( traceGit, "Testing %s\n", sshKey );
if( git_credential_ssh_key_new( aOut, aUsername.mbc_str(), sshPubKey.mbc_str(), sshKey.mbc_str(),
password.mbc_str() ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to create SSH key credential for %s: %s",
aUsername, KIGIT_COMMON::GetLastGitError() );
return GIT_PASSTHROUGH;
}
return GIT_OK;
}
int KIGIT_COMMON::HandlePlaintextAuthentication( git_cred** aOut, const wxString& aUsername )
{
wxString password = GetPassword();
git_credential_userpass_plaintext_new( aOut, aUsername.mbc_str(), password.mbc_str() );
m_testedTypes |= GIT_CREDENTIAL_USERPASS_PLAINTEXT;
return GIT_OK;
}
int KIGIT_COMMON::HandleSSHAgentAuthentication( git_cred** aOut, const wxString& aUsername )
{
if( git_credential_ssh_key_from_agent( aOut, aUsername.mbc_str() ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to create SSH agent credential for %s: %s",
aUsername, KIGIT_COMMON::GetLastGitError() );
return GIT_PASSTHROUGH;
}
m_testedTypes |= KIGIT_CREDENTIAL_SSH_AGENT;
return GIT_OK;
}
extern "C" int fetchhead_foreach_cb( const char*, const char*,
const git_oid* aOID, unsigned int aIsMerge, void* aPayload )
{
if( aIsMerge )
git_oid_cpy( (git_oid*) aPayload, aOID );
return 0;
}
extern "C" void clone_progress_cb( const char* aStr, size_t aLen, size_t aTotal, void* aPayload )
{
KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
wxString progressMessage( aStr );
parent->UpdateProgress( aLen, aTotal, progressMessage );
}
extern "C" int progress_cb( const char* str, int len, void* aPayload )
{
KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
wxString progressMessage( str, len );
parent->UpdateProgress( 0, 0, progressMessage );
return 0;
}
extern "C" int transfer_progress_cb( const git_transfer_progress* aStats, void* aPayload )
{
KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
wxString progressMessage = wxString::Format( _( "Received %u of %u objects" ),
aStats->received_objects,
aStats->total_objects );
parent->UpdateProgress( aStats->received_objects, aStats->total_objects, progressMessage );
return 0;
}
extern "C" int update_cb( const char* aRefname, const git_oid* aFirst, const git_oid* aSecond,
void* aPayload )
{
constexpr int cstring_len = 8;
char a_str[cstring_len + 1];
char b_str[cstring_len + 1];
KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
wxString status;
git_oid_tostr( b_str, cstring_len, aSecond );
#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 )
if( !git_oid_is_zero( aFirst ) )
#else
if( !git_oid_iszero( aFirst ) )
#endif
{
git_oid_tostr( a_str, cstring_len, aFirst );
status = wxString::Format( _( "* [updated] %s..%s %s" ), a_str, b_str, aRefname );
}
else
{
status = wxString::Format( _( "* [new] %s %s" ), b_str, aRefname );
}
parent->UpdateProgress( 0, 0, status );
return 0;
}
extern "C" int push_transfer_progress_cb( unsigned int aCurrent, unsigned int aTotal, size_t aBytes,
void* aPayload )
{
long long progress = 100;
KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
if( aTotal != 0 )
{
progress = ( aCurrent * 100ll ) / aTotal;
}
wxString progressMessage = wxString::Format( _( "Writing objects: %lld%% (%u/%u), %zu bytes" ),
progress, aCurrent, aTotal, aBytes );
parent->UpdateProgress( aCurrent, aTotal, progressMessage );
return 0;
}
extern "C" int push_update_reference_cb( const char* aRefname, const char* aStatus, void* aPayload )
{
KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
wxString status( aStatus );
if( !status.IsEmpty() )
{
wxString statusMessage = wxString::Format( _( "* [rejected] %s (%s)" ), aRefname, aStatus );
parent->UpdateProgress( 0, 0, statusMessage );
}
else
{
wxString statusMessage = wxString::Format( _( "[updated] %s" ), aRefname );
parent->UpdateProgress( 0, 0, statusMessage );
}
return 0;
}
extern "C" int credentials_cb( git_cred** aOut, const char* aUrl, const char* aUsername,
unsigned int aAllowedTypes, void* aPayload )
{
KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
KIGIT_COMMON* common = parent->GetCommon();
wxLogTrace( traceGit, "Credentials callback for %s, testing %d", aUrl, aAllowedTypes );
if( parent->GetConnType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_LOCAL )
{
wxLogTrace( traceGit, "Local repository, no credentials needed" );
return GIT_PASSTHROUGH;
}
if( aAllowedTypes & GIT_CREDENTIAL_USERNAME
&& !( parent->TestedTypes() & GIT_CREDENTIAL_USERNAME ) )
{
wxString username = parent->GetUsername().Trim().Trim( false );
wxLogTrace( traceGit, "Username credential for %s at %s with allowed type %d",
username, aUrl, aAllowedTypes );
if( git_credential_username_new( aOut, username.ToStdString().c_str() ) != GIT_OK )
{
wxLogTrace( traceGit, "Failed to create username credential for %s: %s",
username, KIGIT_COMMON::GetLastGitError() );
}
else
{
wxLogTrace( traceGit, "Created username credential for %s", username );
}
parent->TestedTypes() |= GIT_CREDENTIAL_USERNAME;
}
else if( parent->GetConnType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS
&& ( aAllowedTypes & GIT_CREDENTIAL_USERPASS_PLAINTEXT )
&& !( parent->TestedTypes() & GIT_CREDENTIAL_USERPASS_PLAINTEXT ) )
{
// Plaintext authentication
wxLogTrace( traceGit, "Plaintext authentication for %s at %s with allowed type %d",
parent->GetUsername(), aUrl, aAllowedTypes );
return common->HandlePlaintextAuthentication( aOut, parent->GetUsername() );
}
else if( parent->GetConnType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH
&& ( aAllowedTypes & GIT_CREDENTIAL_SSH_KEY )
&& !( parent->TestedTypes() & GIT_CREDENTIAL_SSH_KEY ) )
{
// SSH key authentication
return common->HandleSSHKeyAuthentication( aOut, parent->GetUsername() );
}
else
{
// If we didn't find anything to try, then we don't have a callback set that the
// server likes
if( !parent->TestedTypes() )
return GIT_PASSTHROUGH;
git_error_clear();
git_error_set_str( GIT_ERROR_NET, _( "Unable to authenticate" ).mbc_str() );
// Otherwise, we did try something but we failed, so return an authentication error
return GIT_EAUTH;
}
return GIT_OK;
};