mirror of
https://gitlab.com/kicad/code/kicad.git
synced 2025-09-14 18:23:15 +02:00
988 lines
30 KiB
C++
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;
|
|
};
|