542 lines
16 KiB
C++
542 lines
16 KiB
C++
/*
|
|
* This program source code file is part of KiCad, a free EDA CAD application.
|
|
*
|
|
* Copyright (C) 2023 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 <wx/filename.h>
|
|
#include <wx/log.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 )
|
|
{}
|
|
|
|
KIGIT_COMMON::~KIGIT_COMMON()
|
|
{}
|
|
|
|
git_repository* KIGIT_COMMON::GetRepo() const
|
|
{
|
|
return m_repo;
|
|
}
|
|
|
|
wxString KIGIT_COMMON::GetCurrentBranchName() const
|
|
{
|
|
git_reference* head = nullptr;
|
|
|
|
int retval = git_repository_head( &head, m_repo );
|
|
|
|
if( retval && retval != GIT_EUNBORNBRANCH && retval != GIT_ENOTFOUND )
|
|
return wxEmptyString;
|
|
|
|
git_reference *branch;
|
|
|
|
if( git_reference_resolve( &branch, head ) )
|
|
{
|
|
git_reference_free( head );
|
|
return wxEmptyString;
|
|
}
|
|
|
|
git_reference_free( head );
|
|
const char* branchName = "";
|
|
|
|
if( git_branch_name( &branchName, branch ) )
|
|
{
|
|
git_reference_free( branch );
|
|
return wxEmptyString;
|
|
}
|
|
|
|
git_reference_free( branch );
|
|
|
|
return branchName;
|
|
}
|
|
|
|
|
|
std::vector<wxString> KIGIT_COMMON::GetBranchNames() const
|
|
{
|
|
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 ) )
|
|
return branchNames;
|
|
|
|
git_reference* branchReference = nullptr;
|
|
git_branch_t branchType;
|
|
|
|
while( git_branch_next( &branchReference, &branchType, branchIterator ) != GIT_ITEROVER )
|
|
{
|
|
const char* branchName = "";
|
|
|
|
if( git_branch_name( &branchName, branchReference ) )
|
|
continue;
|
|
|
|
const git_oid* commitId = git_reference_target( branchReference );
|
|
|
|
git_commit* commit = nullptr;
|
|
|
|
if( git_commit_lookup( &commit, m_repo, commitId ) )
|
|
continue;
|
|
|
|
git_time_t commitTime = git_commit_time( commit );
|
|
|
|
if( git_branch_is_head( branchReference ) )
|
|
firstName = branchName;
|
|
else
|
|
branchNamesMap.emplace( commitTime, branchName );
|
|
|
|
git_commit_free( commit );
|
|
git_reference_free( branchReference );
|
|
}
|
|
|
|
git_branch_iterator_free( branchIterator );
|
|
|
|
// 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()
|
|
{
|
|
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 )
|
|
{
|
|
wxLogError( "An error occurred: %s", git_error_last()->message );
|
|
return projDirs;
|
|
}
|
|
|
|
if( git_commit_lookup( &commit, m_repo, &oid ) != GIT_OK )
|
|
{
|
|
wxLogError( "An error occurred: %s", git_error_last()->message );
|
|
return projDirs;
|
|
}
|
|
|
|
if( git_commit_tree( &tree, commit ) != GIT_OK )
|
|
{
|
|
wxLogError( "An error occurred: %s", git_error_last()->message );
|
|
return projDirs;
|
|
}
|
|
|
|
// 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 );
|
|
|
|
git_tree_free( tree );
|
|
git_commit_free( commit );
|
|
|
|
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 = [&]( git_oid* from_oid, git_oid* to_oid ) -> std::set<wxString>
|
|
{
|
|
std::set<wxString> modified_set;
|
|
git_revwalk* walker = nullptr;
|
|
|
|
if( git_revwalk_new( &walker, m_repo ) != GIT_OK )
|
|
return modified_set;
|
|
|
|
if( ( git_revwalk_push( walker, from_oid ) != GIT_OK )
|
|
|| ( git_revwalk_hide( walker, to_oid ) != GIT_OK ) )
|
|
{
|
|
git_revwalk_free( walker );
|
|
return modified_set;
|
|
}
|
|
|
|
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 )
|
|
continue;
|
|
|
|
git_tree *tree, *parent_tree = nullptr;
|
|
if( git_commit_tree( &tree, commit ) != GIT_OK )
|
|
{
|
|
git_commit_free( commit );
|
|
continue;
|
|
}
|
|
|
|
// get parent commit tree to diff against
|
|
if( !git_commit_parentcount( commit ) )
|
|
{
|
|
git_tree_free( tree );
|
|
git_commit_free( commit );
|
|
continue;
|
|
}
|
|
|
|
|
|
git_commit* parent;
|
|
if( git_commit_parent( &parent, commit, 0 ) != GIT_OK )
|
|
{
|
|
git_tree_free( tree );
|
|
git_commit_free( commit );
|
|
continue;
|
|
}
|
|
|
|
|
|
if( git_commit_tree( &parent_tree, parent ) != GIT_OK )
|
|
{
|
|
git_tree_free( tree );
|
|
git_commit_free( commit );
|
|
git_commit_free( parent );
|
|
continue;
|
|
}
|
|
|
|
|
|
git_diff* diff;
|
|
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
|
|
|
|
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 );
|
|
}
|
|
|
|
git_tree_free( parent_tree );
|
|
git_commit_free( parent );
|
|
git_tree_free( tree );
|
|
git_commit_free( commit );
|
|
}
|
|
|
|
git_revwalk_free( walker );
|
|
|
|
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 )
|
|
return modified_files;
|
|
|
|
if( git_branch_upstream( &remote_head, head ) != GIT_OK )
|
|
{
|
|
git_reference_free( head );
|
|
return modified_files;
|
|
}
|
|
|
|
git_oid head_oid = *git_reference_target( head );
|
|
git_oid remote_oid = *git_reference_target( remote_head );
|
|
|
|
git_reference_free( head );
|
|
git_reference_free( 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 )
|
|
return false;
|
|
|
|
if( git_branch_upstream( &remote_head, head ) != GIT_OK )
|
|
{
|
|
git_reference_free( head );
|
|
return false;
|
|
}
|
|
|
|
git_oid head_oid = *git_reference_target( head );
|
|
git_oid remote_oid = *git_reference_target( remote_head );
|
|
|
|
git_reference_free( head );
|
|
git_reference_free( remote_head );
|
|
|
|
git_revwalk* walker = nullptr;
|
|
|
|
if( git_revwalk_new( &walker, m_repo ) != GIT_OK )
|
|
return false;
|
|
|
|
if( ( git_revwalk_push( walker, &head_oid ) != GIT_OK )
|
|
|| ( git_revwalk_hide( walker, &remote_oid ) != GIT_OK ) )
|
|
{
|
|
git_revwalk_free( walker );
|
|
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 )
|
|
{
|
|
git_revwalk_free( walker );
|
|
return false;
|
|
}
|
|
|
|
git_revwalk_free( walker );
|
|
return true;
|
|
}
|
|
|
|
|
|
bool KIGIT_COMMON::HasPushAndPullRemote() const
|
|
{
|
|
git_remote* remote = nullptr;
|
|
|
|
if( git_remote_lookup( &remote, m_repo, "origin" ) != GIT_OK )
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// 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 )
|
|
{
|
|
push_url = fetch_url;
|
|
}
|
|
|
|
// Clean up the remote object
|
|
git_remote_free( remote );
|
|
|
|
// Check if both URLs are valid (i.e., not NULL)
|
|
return fetch_url && push_url;
|
|
}
|
|
|
|
|
|
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* data )
|
|
{
|
|
KIGIT_COMMON* parent = (KIGIT_COMMON*) data;
|
|
|
|
wxString progressMessage( aStr );
|
|
parent->UpdateProgress( aLen, aTotal, progressMessage );
|
|
}
|
|
|
|
|
|
extern "C" int progress_cb( const char* str, int len, void* data )
|
|
{
|
|
KIGIT_COMMON* parent = (KIGIT_COMMON*) data;
|
|
|
|
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_COMMON* parent = (KIGIT_COMMON*) 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_COMMON* parent = (KIGIT_COMMON*) aPayload;
|
|
wxString status;
|
|
|
|
git_oid_tostr( b_str, cstring_len, aSecond );
|
|
|
|
if( !git_oid_is_zero( aFirst ) )
|
|
{
|
|
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 )
|
|
{
|
|
int64_t progress = 100;
|
|
KIGIT_COMMON* parent = (KIGIT_COMMON*) aPayload;
|
|
|
|
if( aTotal != 0 )
|
|
{
|
|
progress = ( aCurrent * 100 ) / aTotal;
|
|
}
|
|
|
|
wxString progressMessage = wxString::Format( _( "Writing objects: %d%% (%d/%d), %d 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_COMMON* parent = (KIGIT_COMMON*) 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_COMMON* parent = static_cast<KIGIT_COMMON*>( aPayload );
|
|
|
|
if( parent->GetConnType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_LOCAL )
|
|
return GIT_PASSTHROUGH;
|
|
|
|
if( aAllowedTypes & GIT_CREDENTIAL_USERNAME
|
|
&& !( parent->TestedTypes() & GIT_CREDTYPE_USERNAME ) )
|
|
{
|
|
wxString username = parent->GetUsername().Trim().Trim( false );
|
|
git_cred_username_new( aOut, username.ToStdString().c_str() );
|
|
parent->TestedTypes() |= GIT_CREDTYPE_USERNAME;
|
|
}
|
|
else if( parent->GetConnType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS
|
|
&& ( aAllowedTypes & GIT_CREDENTIAL_USERPASS_PLAINTEXT )
|
|
&& !( parent->TestedTypes() & GIT_CREDTYPE_USERPASS_PLAINTEXT ) )
|
|
{
|
|
wxString username = parent->GetUsername().Trim().Trim( false );
|
|
wxString password = parent->GetPassword().Trim().Trim( false );
|
|
|
|
git_cred_userpass_plaintext_new( aOut, username.ToStdString().c_str(),
|
|
password.ToStdString().c_str() );
|
|
parent->TestedTypes() |= GIT_CREDTYPE_USERPASS_PLAINTEXT;
|
|
}
|
|
else if( parent->GetConnType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH
|
|
&& ( aAllowedTypes & GIT_CREDENTIAL_SSH_KEY )
|
|
&& !( parent->TestedTypes() & GIT_CREDTYPE_SSH_KEY ) )
|
|
{
|
|
// SSH key authentication
|
|
wxString sshKey = parent->GetSSHKey();
|
|
wxString sshPubKey = sshKey + ".pub";
|
|
wxString username = parent->GetUsername().Trim().Trim( false );
|
|
wxString password = parent->GetPassword().Trim().Trim( false );
|
|
|
|
git_cred_ssh_key_new( aOut, username.ToStdString().c_str(),
|
|
sshPubKey.ToStdString().c_str(),
|
|
sshKey.ToStdString().c_str(),
|
|
password.ToStdString().c_str() );
|
|
parent->TestedTypes() |= GIT_CREDTYPE_SSH_KEY;
|
|
}
|
|
else
|
|
{
|
|
return GIT_PASSTHROUGH;
|
|
}
|
|
|
|
return GIT_OK;
|
|
}; |