diff --git a/common/git/git_clone_handler.cpp b/common/git/git_clone_handler.cpp index 226caf4a5a..03d44b22a5 100644 --- a/common/git/git_clone_handler.cpp +++ b/common/git/git_clone_handler.cpp @@ -63,6 +63,7 @@ bool GIT_CLONE_HANDLER::PerformClone() cloneOptions.fetch_opts.callbacks.payload = this; m_testedTypes = 0; + ResetNextKey(); if( git_clone( &m_repo, m_URL.ToStdString().c_str(), m_clonePath.ToStdString().c_str(), &cloneOptions ) != 0 ) diff --git a/common/git/git_pull_handler.cpp b/common/git/git_pull_handler.cpp index 816e93f5a4..434f2e84c7 100644 --- a/common/git/git_pull_handler.cpp +++ b/common/git/git_pull_handler.cpp @@ -29,7 +29,7 @@ #include #include -GIT_PULL_HANDLER::GIT_PULL_HANDLER( git_repository* aRepo ) : KIGIT_COMMON( aRepo ) +GIT_PULL_HANDLER::GIT_PULL_HANDLER( KIGIT_COMMON* aRepo ) : KIGIT_COMMON( *aRepo ) {} @@ -55,6 +55,9 @@ bool GIT_PULL_HANDLER::PerformFetch() remoteCallbacks.credentials = credentials_cb; remoteCallbacks.payload = this; + m_testedTypes = 0; + ResetNextKey(); + if( git_remote_connect( remote, GIT_DIRECTION_FETCH, &remoteCallbacks, nullptr, nullptr ) ) { git_remote_free( remote ); diff --git a/common/git/git_pull_handler.h b/common/git/git_pull_handler.h index 057eda0f20..125e460bc1 100644 --- a/common/git/git_pull_handler.h +++ b/common/git/git_pull_handler.h @@ -42,14 +42,14 @@ struct CommitDetails std::string m_date; }; -// Enum for result codes -enum class PullResult +// Enum for result codes, error codes are negative, success codes are positive +enum class PullResult : int { - Success, - Error, + MergeFailed = -2, + Error = -1, + Success = 0, UpToDate, - FastForward, - MergeFailed + FastForward }; struct ConflictData @@ -68,7 +68,7 @@ struct ConflictData class GIT_PULL_HANDLER : public KIGIT_COMMON, public GIT_PROGRESS { public: - GIT_PULL_HANDLER( git_repository* aRepo ); + GIT_PULL_HANDLER( KIGIT_COMMON* aCommon ); ~GIT_PULL_HANDLER(); PullResult PerformPull(); diff --git a/common/git/git_push_handler.cpp b/common/git/git_push_handler.cpp index 37dfb86283..a9b1754715 100644 --- a/common/git/git_push_handler.cpp +++ b/common/git/git_push_handler.cpp @@ -26,7 +26,7 @@ #include -GIT_PUSH_HANDLER::GIT_PUSH_HANDLER( git_repository* aRepo ) : KIGIT_COMMON( aRepo ) +GIT_PUSH_HANDLER::GIT_PUSH_HANDLER( KIGIT_COMMON* aRepo ) : KIGIT_COMMON( *aRepo ) {} diff --git a/common/git/git_push_handler.h b/common/git/git_push_handler.h index 873e200e11..a68f65bcff 100644 --- a/common/git/git_push_handler.h +++ b/common/git/git_push_handler.h @@ -43,7 +43,7 @@ enum class PushResult class GIT_PUSH_HANDLER : public KIGIT_COMMON, public GIT_PROGRESS { public: - GIT_PUSH_HANDLER( git_repository* aRepo ); + GIT_PUSH_HANDLER( KIGIT_COMMON* aRepo ); ~GIT_PUSH_HANDLER(); PushResult PerformPush(); diff --git a/common/git/kicad_git_common.cpp b/common/git/kicad_git_common.cpp index 3e4206b9fa..47d1fe2913 100644 --- a/common/git/kicad_git_common.cpp +++ b/common/git/kicad_git_common.cpp @@ -27,6 +27,8 @@ #include #include +#include +#include #include #include @@ -416,6 +418,90 @@ wxString KIGIT_COMMON::GetRemotename() const 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 ); +} + + +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(); + + wxString lastIdentityFile; + bool match = false; + + for( wxString line = configFile.GetFirstLine(); !configFile.Eof(); line = configFile.GetNextLine() ) + { + line.Trim( false ).Trim( true ); + + if( line.StartsWith( "Host " ) ) + { + lastIdentityFile.Clear(); + 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() { @@ -437,6 +523,77 @@ void KIGIT_COMMON::UpdateCurrentBranchInfo() // Find the stored password if it exists KIPLATFORM::SECRETS::GetSecret( m_remote, m_username, m_password ); + + updateConnectionType(); + updatePublicKeys(); +} + +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; + } + } } @@ -585,7 +742,14 @@ extern "C" int credentials_cb( git_cred** aOut, const char* aUrl, const char* aU && !( parent->TestedTypes() & GIT_CREDTYPE_SSH_KEY ) ) { // SSH key authentication - wxString sshKey = parent->GetSSHKey(); + wxString sshKey = parent->GetNextPublicKey(); + + if( sshKey.IsEmpty() ) + { + parent->TestedTypes() |= GIT_CREDTYPE_SSH_KEY; + return GIT_PASSTHROUGH; + } + wxString sshPubKey = sshKey + ".pub"; wxString username = parent->GetUsername().Trim().Trim( false ); wxString password = parent->GetPassword().Trim().Trim( false ); @@ -594,7 +758,6 @@ extern "C" int credentials_cb( git_cred** aOut, const char* aUrl, const char* aU sshPubKey.ToStdString().c_str(), sshKey.ToStdString().c_str(), password.ToStdString().c_str() ); - parent->TestedTypes() |= GIT_CREDTYPE_SSH_KEY; } else { diff --git a/common/git/kicad_git_common.h b/common/git/kicad_git_common.h index 1f552474fb..75706283f5 100644 --- a/common/git/kicad_git_common.h +++ b/common/git/kicad_git_common.h @@ -89,12 +89,11 @@ public: wxString GetUsername() const { return m_username; } wxString GetPassword() const { return m_password; } - wxString GetSSHKey() const { return m_sshKey; } GIT_CONN_TYPE GetConnType() const { return m_connType; } void SetUsername( const wxString& aUsername ) { m_username = aUsername; } void SetPassword( const wxString& aPassword ) { m_password = aPassword; } - void SetSSHKey( const wxString& aSSHKey ) { m_sshKey = aSSHKey; } + void SetSSHKey( const wxString& aSSHKey ); void SetConnType( GIT_CONN_TYPE aConnType ) { m_connType = aConnType; } void SetConnType( unsigned aConnType ) @@ -119,17 +118,34 @@ public: wxString GetRemotename() const; + void ResetNextKey() { m_nextPublicKey = 0; } + + wxString GetNextPublicKey() + { + if( m_nextPublicKey >= static_cast( m_publicKeys.size() ) ) + return wxEmptyString; + + return m_publicKeys[m_nextPublicKey++]; + } + protected: git_repository* m_repo; GIT_CONN_TYPE m_connType; - wxString m_remote; + wxString m_remote; // This is the full connection string + wxString m_hostname; // This is just the hostname without the protocol, username, or password wxString m_username; wxString m_password; - wxString m_sshKey; unsigned m_testedTypes; +private: + void updatePublicKeys(); + void updateConnectionType(); + + std::vector m_publicKeys; + int m_nextPublicKey; + }; diff --git a/kicad/project_tree_pane.cpp b/kicad/project_tree_pane.cpp index b63e287953..df58f20930 100644 --- a/kicad/project_tree_pane.cpp +++ b/kicad/project_tree_pane.cpp @@ -641,15 +641,6 @@ void PROJECT_TREE_PANE::ReCreateTreePrj() m_TreeProject->GitCommon()->SetUsername( Prj().GetLocalSettings().m_GitRepoUsername ); m_TreeProject->GitCommon()->SetSSHKey( Prj().GetLocalSettings().m_GitSSHKey ); m_TreeProject->GitCommon()->UpdateCurrentBranchInfo(); - - wxString conn_type = Prj().GetLocalSettings().m_GitRepoType; - - if( conn_type == "https" ) - m_TreeProject->GitCommon()->SetConnType( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS ); - else if( conn_type == "ssh" ) - m_TreeProject->GitCommon()->SetConnType( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH ); - else - m_TreeProject->GitCommon()->SetConnType( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_LOCAL ); } // We may have opened a legacy project, in which case GetProjectFileName will return the @@ -1694,12 +1685,7 @@ void PROJECT_TREE_PANE::onGitInitializeProject( wxCommandEvent& aEvent ) m_gitLastError = GIT_ERROR_NONE; - GIT_PULL_HANDLER handler( repo ); - - handler.SetConnType( m_TreeProject->GitCommon()->GetConnType() ); - handler.SetUsername( m_TreeProject->GitCommon()->GetUsername() ); - handler.SetPassword( m_TreeProject->GitCommon()->GetPassword() ); - handler.SetSSHKey( m_TreeProject->GitCommon()->GetSSHKey() ); + GIT_PULL_HANDLER handler( m_TreeProject->GitCommon() ); handler.SetProgressReporter( std::make_unique( this, _( "Fetching Remote" ), @@ -1733,18 +1719,13 @@ void PROJECT_TREE_PANE::onGitPullProject( wxCommandEvent& aEvent ) if( !repo ) return; - GIT_PULL_HANDLER handler( repo ); - - handler.SetConnType( m_TreeProject->GitCommon()->GetConnType() ); - handler.SetUsername( m_TreeProject->GitCommon()->GetUsername() ); - handler.SetPassword( m_TreeProject->GitCommon()->GetPassword() ); - handler.SetSSHKey( m_TreeProject->GitCommon()->GetSSHKey() ); + GIT_PULL_HANDLER handler( m_TreeProject->GitCommon() ); handler.SetProgressReporter( std::make_unique( this, _( "Fetching Remote" ), 1 ) ); - if( handler.PerformPull() != PullResult::Success ) + if( handler.PerformPull() < PullResult::Success ) { wxString errorMessage = handler.GetErrorString(); @@ -1760,12 +1741,7 @@ void PROJECT_TREE_PANE::onGitPushProject( wxCommandEvent& aEvent ) if( !repo ) return; - GIT_PUSH_HANDLER handler( repo ); - - handler.SetConnType( m_TreeProject->GitCommon()->GetConnType() ); - handler.SetUsername( m_TreeProject->GitCommon()->GetUsername() ); - handler.SetPassword( m_TreeProject->GitCommon()->GetPassword() ); - handler.SetSSHKey( m_TreeProject->GitCommon()->GetSSHKey() ); + GIT_PUSH_HANDLER handler( m_TreeProject->GitCommon() ); handler.SetProgressReporter( std::make_unique( this, _( "Fetching Remote" ), @@ -2410,12 +2386,12 @@ void PROJECT_TREE_PANE::onGitSyncProject( wxCommandEvent& aEvent ) void PROJECT_TREE_PANE::onGitFetch( wxCommandEvent& aEvent ) { - git_repository* repo = m_TreeProject->GetGitRepo(); + KIGIT_COMMON* gitCommon = m_TreeProject->GitCommon(); - if( !repo ) + if( !gitCommon ) return; - GIT_PULL_HANDLER handler( repo ); + GIT_PULL_HANDLER handler( gitCommon ); handler.PerformFetch(); }