7
mirror of https://gitlab.com/kicad/code/kicad.git synced 2025-04-11 10:00:13 +00:00

Fix some regressions in git handling

Set default to rebase for pull
Update error string on auth failure
Improve trace logging
This commit is contained in:
Seth Hillbrand 2025-03-18 17:40:03 -07:00
parent 0d6838b803
commit b8225ba2d6
12 changed files with 461 additions and 193 deletions

View File

@ -27,6 +27,7 @@
#include <git2.h>
#include <git/kicad_git_memory.h>
#include <git/kicad_git_common.h>
#include <git/git_repo_mixin.h>
#include <gestfich.h>
#include <cerrno>
@ -240,6 +241,7 @@ void DIALOG_GIT_REPOSITORY::updateURLData()
if( valid )
{
m_fullURL = url;
m_ConnType->SetSelection( static_cast<int>( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS ) );
SetUsername( username );
SetPassword( password );
@ -255,6 +257,7 @@ void DIALOG_GIT_REPOSITORY::updateURLData()
if( valid )
{
m_fullURL = url;
m_ConnType->SetSelection( static_cast<int>( KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH ) );
m_txtUsername->SetValue( username );
m_txtURL->SetValue( repoAddress );
@ -284,12 +287,13 @@ void DIALOG_GIT_REPOSITORY::OnTestClick( wxCommandEvent& event )
// type, so we need to keep track of how many times we have tried.
KIGIT_COMMON common( m_repository );
common.SetRemote( m_txtURL->GetValue() );
callbacks.credentials = credentials_cb;
callbacks.payload = &common;
common.SetPassword( m_txtPassword->GetValue() );
common.SetUsername( m_txtUsername->GetValue() );
common.SetSSHKey( m_fpSSHKey->GetFileName().GetFullPath() );
KIGIT_REPO_MIXIN repoMixin( &common );
callbacks.payload = &repoMixin;
wxString txtURL = m_txtURL->GetValue();
git_remote_create_with_fetchspec( &remote, m_repository, "origin", txtURL.mbc_str(),

View File

@ -72,6 +72,8 @@ public:
return url;
}
const wxString& GetFullURL() const { return m_fullURL; }
void SetUsername( const wxString& aUsername ) { m_txtUsername->SetValue( aUsername ); }
wxString GetUsername() const { return m_txtUsername->GetValue(); }
@ -105,6 +107,7 @@ private:
private:
git_repository* m_repository;
wxString m_fullURL;
wxString m_prevFile;

View File

@ -73,11 +73,12 @@ bool GIT_CLONE_HANDLER::PerformClone()
TestedTypes() = 0;
ResetNextKey();
git_repository* newRepo = nullptr;
wxString remote = GetCommon()->m_remote;
if( git_clone( &newRepo, m_URL.ToStdString().c_str(), m_clonePath.ToStdString().c_str(),
if( git_clone( &newRepo, remote.mbc_str(), m_clonePath.mbc_str(),
&cloneOptions ) != 0 )
{
AddErrorString( wxString::Format( _( "Could not clone repository '%s'" ), m_URL ) );
AddErrorString( wxString::Format( _( "Could not clone repository '%s'" ), remote ) );
return false;
}

View File

@ -36,19 +36,17 @@ public:
bool PerformClone();
void SetURL( const wxString& aURL ) { m_URL = aURL; }
wxString GetURL() const { return m_URL; }
void SetBranch( const wxString& aBranch ) { m_branch = aBranch; }
wxString GetBranch() const { return m_branch; }
void SetClonePath( const wxString& aPath ) { m_clonePath = aPath; }
wxString GetClonePath() const { return m_clonePath; }
void SetRemote( const wxString& aRemote ) { GetCommon()->m_remote = aRemote; }
void UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage ) override;
private:
wxString m_URL;
wxString m_branch;
wxString m_clonePath;
};

View File

@ -45,7 +45,10 @@ GIT_PULL_HANDLER::~GIT_PULL_HANDLER()
bool GIT_PULL_HANDLER::PerformFetch( bool aSkipLock )
{
if( !GetRepo() )
{
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - No repository found" );
return false;
}
std::unique_lock<std::mutex> lock( GetCommon()->m_gitActionMutex, std::try_to_lock );
@ -60,6 +63,7 @@ bool GIT_PULL_HANDLER::PerformFetch( bool aSkipLock )
if( git_remote_lookup( &remote, GetRepo(), "origin" ) != 0 )
{
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Failed to lookup remote 'origin'" );
AddErrorString( wxString::Format( _( "Could not lookup remote '%s'" ), "origin" ) );
return false;
}
@ -78,8 +82,9 @@ bool GIT_PULL_HANDLER::PerformFetch( bool aSkipLock )
if( git_remote_connect( remote, GIT_DIRECTION_FETCH, &remoteCallbacks, nullptr, nullptr ) )
{
AddErrorString( wxString::Format( _( "Could not connect to remote '%s': %s" ), "origin",
KIGIT_COMMON::GetLastGitError() ) );
wxString errorMsg = KIGIT_COMMON::GetLastGitError();
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Failed to connect to remote: %s", errorMsg );
AddErrorString( wxString::Format( _( "Could not connect to remote '%s': %s" ), "origin", errorMsg ) );
return false;
}
@ -89,11 +94,13 @@ bool GIT_PULL_HANDLER::PerformFetch( bool aSkipLock )
if( git_remote_fetch( remote, nullptr, &fetchOptions, nullptr ) )
{
AddErrorString( wxString::Format( _( "Could not fetch data from remote '%s': %s" ),
"origin", KIGIT_COMMON::GetLastGitError() ) );
wxString errorMsg = KIGIT_COMMON::GetLastGitError();
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Failed to fetch from remote: %s", errorMsg );
AddErrorString( wxString::Format( _( "Could not fetch data from remote '%s': %s" ), "origin", errorMsg ) );
return false;
}
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Fetch completed successfully" );
return true;
}
@ -130,12 +137,11 @@ PullResult GIT_PULL_HANDLER::PerformPull()
}
KIGIT::GitAnnotatedCommitPtr fetchheadCommitPtr( fetchhead_commit );
const git_annotated_commit* merge_commits[] = { fetchhead_commit };
git_merge_analysis_t merge_analysis;
git_merge_preference_t merge_preference = GIT_MERGE_PREFERENCE_NONE;
const git_annotated_commit* merge_commits[] = { fetchhead_commit };
git_merge_analysis_t merge_analysis;
git_merge_preference_t merge_preference = GIT_MERGE_PREFERENCE_NONE;
if( git_merge_analysis( &merge_analysis, &merge_preference, GetRepo(), merge_commits,
1 ) )
if( git_merge_analysis( &merge_analysis, &merge_preference, GetRepo(), merge_commits, 1 ) )
{
AddErrorString( _( "Could not analyze merge" ) );
return PullResult::Error;
@ -165,7 +171,8 @@ PullResult GIT_PULL_HANDLER::PerformPull()
if( merge_analysis & GIT_MERGE_ANALYSIS_NORMAL )
{
wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Normal merge" );
PullResult ret = handleMerge( merge_commits, 1 );
PullResult ret = handleRebase( merge_commits, 1 );
// PullResult ret = handleMerge( merge_commits, 1 );
return ret;
}
@ -184,6 +191,9 @@ GIT_PULL_HANDLER::GetFetchResults() const
std::string GIT_PULL_HANDLER::getFirstLineFromCommitMessage( const std::string& aMessage )
{
if( aMessage.empty() )
return aMessage;
size_t firstLineEnd = aMessage.find_first_of( '\n' );
if( firstLineEnd != std::string::npos )
@ -197,7 +207,9 @@ std::string GIT_PULL_HANDLER::getFormattedCommitDate( const git_time& aTime )
{
char dateBuffer[64];
time_t time = static_cast<time_t>( aTime.time );
strftime( dateBuffer, sizeof( dateBuffer ), "%Y-%b-%d %H:%M:%S", gmtime( &time ) );
struct tm timeInfo;
gmtime_r( &time, &timeInfo );
strftime( dateBuffer, sizeof( dateBuffer ), "%Y-%b-%d %H:%M:%S", &timeInfo );
return dateBuffer;
}
@ -206,6 +218,7 @@ PullResult GIT_PULL_HANDLER::handleFastForward()
{
git_reference* rawRef = nullptr;
// Get the current HEAD reference
if( git_repository_head( &rawRef, GetRepo() ) )
{
AddErrorString( _( "Could not get repository head" ) );
@ -214,43 +227,131 @@ PullResult GIT_PULL_HANDLER::handleFastForward()
KIGIT::GitReferencePtr headRef( rawRef );
const char* updatedRefName = git_reference_name( rawRef );
git_oid updatedRefOid;
const char* currentBranchName = git_reference_name( rawRef );
wxString remoteBranchName = wxString::Format( "refs/remotes/origin/%s",
currentBranchName + strlen( "refs/heads/" ) );
git_oid updatedRefOid;
if( git_reference_name_to_id( &updatedRefOid, GetRepo(), updatedRefName ) )
// Get the OID of the updated reference (remote-tracking branch)
if( git_reference_name_to_id( &updatedRefOid, GetRepo(), remoteBranchName.c_str() ) != GIT_OK )
{
AddErrorString( wxString::Format( _( "Could not get reference OID for reference '%s'" ),
updatedRefName ) );
remoteBranchName ) );
return PullResult::Error;
}
// Get the target commit object
git_commit* targetCommit = nullptr;
if( git_commit_lookup( &targetCommit, GetRepo(), &updatedRefOid ) != GIT_OK )
{
AddErrorString( _( "Could not look up target commit" ) );
return PullResult::Error;
}
KIGIT::GitCommitPtr targetCommitPtr( targetCommit );
// Get the tree from the target commit
git_tree* targetTree = nullptr;
if( git_commit_tree( &targetTree, targetCommit ) != GIT_OK )
{
git_commit_free( targetCommit );
AddErrorString( _( "Could not get tree from target commit" ) );
return PullResult::Error;
}
KIGIT::GitTreePtr targetTreePtr( targetTree );
// Perform a checkout to update the working directory
git_checkout_options checkoutOptions;
git_checkout_init_options( &checkoutOptions, GIT_CHECKOUT_OPTIONS_VERSION );
checkoutOptions.checkout_strategy = GIT_CHECKOUT_SAFE;
auto notify_cb = []( git_checkout_notify_t why, const char* path, const git_diff_file* baseline,
const git_diff_file* target, const git_diff_file* workdir, void* payload ) -> int
{
switch( why )
{
case GIT_CHECKOUT_NOTIFY_CONFLICT:
wxLogTrace( traceGit, "Checkout conflict: %s", path ? path : "unknown" );
break;
case GIT_CHECKOUT_NOTIFY_DIRTY:
wxLogTrace( traceGit, "Checkout dirty: %s", path ? path : "unknown" );
break;
case GIT_CHECKOUT_NOTIFY_UPDATED:
wxLogTrace( traceGit, "Checkout updated: %s", path ? path : "unknown" );
break;
case GIT_CHECKOUT_NOTIFY_UNTRACKED:
wxLogTrace( traceGit, "Checkout untracked: %s", path ? path : "unknown" );
break;
case GIT_CHECKOUT_NOTIFY_IGNORED:
wxLogTrace( traceGit, "Checkout ignored: %s", path ? path : "unknown" );
break;
default:
break;
}
if( git_checkout_head( GetRepo(), &checkoutOptions ) )
return 0;
};
checkoutOptions.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_ALLOW_CONFLICTS;
checkoutOptions.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
checkoutOptions.notify_cb = notify_cb;
if( git_checkout_tree( GetRepo(), reinterpret_cast<git_object*>( targetTree ), &checkoutOptions ) != GIT_OK )
{
AddErrorString( _( "Failed to perform checkout operation." ) );
return PullResult::Error;
}
// Collect commit details for updated references
git_reference* updatedRef = nullptr;
// Update the current branch to point to the new commit
if (git_reference_set_target(&updatedRef, rawRef, &updatedRefOid, nullptr) != GIT_OK)
{
AddErrorString( wxString::Format( _( "Failed to update reference '%s' to point to '%s'" ), currentBranchName,
git_oid_tostr_s( &updatedRefOid ) ) );
return PullResult::Error;
}
KIGIT::GitReferencePtr updatedRefPtr( updatedRef );
// Clean up the repository state
if( git_repository_state_cleanup( GetRepo() ) != GIT_OK )
{
AddErrorString( _( "Failed to clean up repository state after fast-forward." ) );
return PullResult::Error;
}
git_revwalk* revWalker = nullptr;
git_revwalk_new( &revWalker, GetRepo() );
// Collect commit details for updated references
if( git_revwalk_new( &revWalker, GetRepo() ) != GIT_OK )
{
AddErrorString( _( "Failed to initialize revision walker." ) );
return PullResult::Error;
}
KIGIT::GitRevWalkPtr revWalkerPtr( revWalker );
git_revwalk_sorting( revWalker, GIT_SORT_TIME );
git_revwalk_push_glob( revWalker, updatedRefName );
if( git_revwalk_push_glob( revWalker, currentBranchName ) != GIT_OK )
{
AddErrorString( _( "Failed to push reference to revision walker." ) );
return PullResult::Error;
}
std::pair<std::string, std::vector<CommitDetails>>& branchCommits = m_fetchResults.emplace_back();
branchCommits.first = currentBranchName;
git_oid commitOid;
while( git_revwalk_next( &commitOid, revWalker ) == 0 )
while( git_revwalk_next( &commitOid, revWalker ) == GIT_OK )
{
git_commit* commit = nullptr;
if( git_commit_lookup( &commit, GetRepo(), &commitOid ) )
{
AddErrorString( wxString::Format( _( "Could not lookup commit '{}'" ),
AddErrorString( wxString::Format( _( "Could not lookup commit '%s'" ),
git_oid_tostr_s( &commitOid ) ) );
return PullResult::Error;
}
@ -263,13 +364,9 @@ PullResult GIT_PULL_HANDLER::handleFastForward()
details.m_author = git_commit_author( commit )->name;
details.m_date = getFormattedCommitDate( git_commit_author( commit )->when );
std::pair<std::string, std::vector<CommitDetails>>& branchCommits =
m_fetchResults.emplace_back();
branchCommits.first = updatedRefName;
branchCommits.second.push_back( details );
}
git_repository_state_cleanup( GetRepo() );
return PullResult::FastForward;
}
@ -378,6 +475,90 @@ PullResult GIT_PULL_HANDLER::handleMerge( const git_annotated_commit** aMergeHea
}
PullResult GIT_PULL_HANDLER::handleRebase( const git_annotated_commit** aMergeHeads, size_t aMergeHeadsCount )
{
// Get the current branch reference
git_reference* head_ref = nullptr;
if( git_repository_head( &head_ref, GetRepo() ) )
{
wxString errorMsg = KIGIT_COMMON::GetLastGitError();
wxLogTrace( traceGit, "GIT_PULL_HANDLER::handleRebase() - Failed to get HEAD: %s", errorMsg );
return PullResult::Error;
}
KIGIT::GitReferencePtr headRefPtr(head_ref);
// Initialize rebase operation
git_rebase* rebase = nullptr;
git_rebase_options rebase_opts = GIT_REBASE_OPTIONS_INIT;
rebase_opts.checkout_options.checkout_strategy = GIT_CHECKOUT_SAFE;
if( git_rebase_init( &rebase, GetRepo(), nullptr, nullptr, aMergeHeads[0], &rebase_opts ) )
{
wxString errorMsg = KIGIT_COMMON::GetLastGitError();
wxLogTrace( traceGit, "GIT_PULL_HANDLER::handleRebase() - Failed to initialize rebase: %s", errorMsg );
return PullResult::Error;
}
KIGIT::GitRebasePtr rebasePtr( rebase );
git_rebase_operation* operation = nullptr;
while( git_rebase_next( &operation, rebase ) != GIT_ITEROVER )
{
// Check for conflicts
git_index* index = nullptr;
if( git_repository_index( &index, GetRepo() ) )
{
wxLogTrace( traceGit, "GIT_PULL_HANDLER::handleRebase() - Failed to get index: %s",
KIGIT_COMMON::GetLastGitError() );
return PullResult::Error;
}
KIGIT::GitIndexPtr indexPtr( index );
if( git_index_has_conflicts( index ) )
{
// Abort the rebase if there are conflicts because we need to merge manually
git_rebase_abort( rebase );
AddErrorString( _( "Conflicts detected during rebase" ) );
return PullResult::MergeFailed;
}
git_oid commit_id;
git_signature* committer = nullptr;
if( git_signature_default( &committer, GetRepo() ) )
{
wxLogTrace( traceGit, "GIT_PULL_HANDLER::handleRebase() - Failed to create signature: %s",
KIGIT_COMMON::GetLastGitError() );
return PullResult::Error;
}
KIGIT::GitSignaturePtr committerPtr( committer );
if( git_rebase_commit( &commit_id, rebase, nullptr, committer, nullptr, nullptr ) != GIT_OK )
{
wxString errorMsg = KIGIT_COMMON::GetLastGitError();
wxLogTrace( traceGit, "GIT_PULL_HANDLER::handleRebase() - Failed to commit operation: %s", errorMsg );
git_rebase_abort( rebase );
return PullResult::Error;
}
}
// Finish the rebase
if( git_rebase_finish( rebase, nullptr ) )
{
wxLogTrace( traceGit, "GIT_PULL_HANDLER::handleRebase() - Failed to finish rebase: %s",
KIGIT_COMMON::GetLastGitError() );
return PullResult::Error;
}
wxLogTrace( traceGit, "GIT_PULL_HANDLER::handleRebase() - Rebase completed successfully" );
git_repository_state_cleanup( GetRepo() );
return PullResult::Success;
}
void GIT_PULL_HANDLER::UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage )
{

View File

@ -85,6 +85,7 @@ private:
std::string getFormattedCommitDate( const git_time& aTime );
PullResult handleFastForward();
PullResult handleMerge( const git_annotated_commit** aMergeHeads, size_t aMergeHeadsCount );
PullResult handleRebase( const git_annotated_commit** aMergeHeads, size_t aMergeHeadsCount );
};
#endif // _GIT_PULL_HANDLER_H_

View File

@ -321,7 +321,7 @@ std::pair<std::set<wxString>,std::set<wxString>> KIGIT_COMMON::GetDifferentFiles
git_diff_options diff_opts;
git_diff_init_options( &diff_opts, GIT_DIFF_OPTIONS_VERSION );
if( !diff_opts.flags || !m_repo || !parent_tree || !tree )
if( !m_repo || !parent_tree || !tree )
continue;
if( git_diff_tree_to_tree( &diff, m_repo, parent_tree, tree, &diff_opts ) == GIT_OK )
@ -664,9 +664,14 @@ KIGIT_COMMON::GIT_CONN_TYPE KIGIT_COMMON::GetConnType() const
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://" ) )
}
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;
}
@ -750,6 +755,7 @@ int KIGIT_COMMON::HandleSSHKeyAuthentication( git_cred** aOut, const wxString& a
if( sshKey.IsEmpty() )
{
wxLogTrace( traceGit, "Finished testing all possible ssh keys" );
m_testedTypes |= GIT_CREDENTIAL_SSH_KEY;
return GIT_PASSTHROUGH;
}
@ -757,12 +763,14 @@ int KIGIT_COMMON::HandleSSHKeyAuthentication( git_cred** aOut, const wxString& a
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_ERROR;
return GIT_PASSTHROUGH;
}
return GIT_OK;
@ -785,7 +793,7 @@ int KIGIT_COMMON::HandleSSHAgentAuthentication( git_cred** aOut, const wxString&
{
wxLogTrace( traceGit, "Failed to create SSH agent credential for %s: %s",
aUsername, KIGIT_COMMON::GetLastGitError() );
return GIT_ERROR;
return GIT_PASSTHROUGH;
}
m_testedTypes |= KIGIT_CREDENTIAL_SSH_AGENT;
@ -803,18 +811,18 @@ extern "C" int fetchhead_foreach_cb( const char*, const char*,
}
extern "C" void clone_progress_cb( const char* aStr, size_t aLen, size_t aTotal, void* data )
extern "C" void clone_progress_cb( const char* aStr, size_t aLen, size_t aTotal, void* aPayload )
{
GIT_PROGRESS* parent = (GIT_PROGRESS*) data;
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* data )
extern "C" int progress_cb( const char* str, int len, void* aPayload )
{
GIT_PROGRESS* parent = (GIT_PROGRESS*) data;
KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
wxString progressMessage( str, len );
parent->UpdateProgress( 0, 0, progressMessage );
@ -825,10 +833,11 @@ extern "C" int progress_cb( const char* str, int len, void* data )
extern "C" int transfer_progress_cb( const git_transfer_progress* aStats, void* aPayload )
{
GIT_PROGRESS* parent = (GIT_PROGRESS*) aPayload;
wxString progressMessage = wxString::Format( _( "Received %u of %u objects" ),
aStats->received_objects,
aStats->total_objects );
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 );
@ -843,8 +852,8 @@ extern "C" int update_cb( const char* aRefname, const git_oid* aFirst, const git
char a_str[cstring_len + 1];
char b_str[cstring_len + 1];
GIT_PROGRESS* parent = (GIT_PROGRESS*) aPayload;
wxString status;
KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
wxString status;
git_oid_tostr( b_str, cstring_len, aSecond );
@ -871,8 +880,8 @@ extern "C" int update_cb( const char* aRefname, const git_oid* aFirst, const git
extern "C" int push_transfer_progress_cb( unsigned int aCurrent, unsigned int aTotal, size_t aBytes,
void* aPayload )
{
long long progress = 100;
GIT_PROGRESS* parent = (GIT_PROGRESS*) aPayload;
long long progress = 100;
KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
if( aTotal != 0 )
{
@ -889,8 +898,8 @@ extern "C" int push_transfer_progress_cb( unsigned int aCurrent, unsigned int aT
extern "C" int push_update_reference_cb( const char* aRefname, const char* aStatus, void* aPayload )
{
GIT_PROGRESS* parent = (GIT_PROGRESS*) aPayload;
wxString status( aStatus );
KIGIT_REPO_MIXIN* parent = reinterpret_cast<KIGIT_REPO_MIXIN*>( aPayload );
wxString status( aStatus );
if( !status.IsEmpty() )
{
@ -913,14 +922,30 @@ extern "C" int credentials_cb( git_cred** aOut, const char* aUrl, const char* aU
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 );
git_credential_username_new( aOut, username.ToStdString().c_str() );
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
@ -928,18 +953,28 @@ extern "C" int credentials_cb( git_cred** aOut, const char* aUrl, const char* aU
&& !( parent->TestedTypes() & GIT_CREDENTIAL_USERPASS_PLAINTEXT ) )
{
// Plaintext authentication
return common->HandlePlaintextAuthentication( aOut, aUsername );
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, aUsername );
return common->HandleSSHKeyAuthentication( aOut, parent->GetUsername() );
}
else
{
return GIT_PASSTHROUGH;
// 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;

View File

@ -124,6 +124,12 @@ public:
return m_publicKeys[m_nextPublicKey++];
}
void SetRemote( const wxString& aRemote )
{
m_remote = aRemote;
updateConnectionType();
}
int HandleSSHKeyAuthentication( git_cred** aOut, const wxString& aUsername );
int HandlePlaintextAuthentication( git_cred** aOut, const wxString& aUsername );

View File

@ -66,6 +66,14 @@ using GitIndexPtr = std::unique_ptr<git_index,
git_index_free(aIndex);
})>;
/**
* @brief A unique pointer for git_rebase objects with automatic cleanup.
*/
using GitRebasePtr = std::unique_ptr<git_rebase,
decltype([](git_rebase* aRebase) {
git_rebase_free(aRebase);
})>;
/**
* @brief A unique pointer for git_revwalk objects with automatic cleanup.
*/

View File

@ -2152,6 +2152,7 @@ void PROJECT_TREE_PANE::updateGitStatusIconMap()
// that is the main status we want to show.
if( entry->status & GIT_STATUS_IGNORED )
{
wxLogTrace( traceGit, wxS( "File '%s' is ignored" ), absPath );
auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
KIGIT_COMMON::GIT_STATUS::GIT_STATUS_IGNORED );
@ -2162,6 +2163,8 @@ void PROJECT_TREE_PANE::updateGitStatusIconMap()
}
else if( entry->status & ( GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED ) )
{
wxLogTrace( traceGit, wxS( "File '%s' is modified in %s" ),
absPath, ( entry->status & GIT_STATUS_INDEX_MODIFIED )? "index" : "working tree" );
auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
KIGIT_COMMON::GIT_STATUS::GIT_STATUS_MODIFIED );
@ -2172,6 +2175,8 @@ void PROJECT_TREE_PANE::updateGitStatusIconMap()
}
else if( entry->status & ( GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_NEW ) )
{
wxLogTrace( traceGit, wxS( "File '%s' is new in %s" ),
absPath, ( entry->status & GIT_STATUS_INDEX_NEW )? "index" : "working tree" );
auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
KIGIT_COMMON::GIT_STATUS::GIT_STATUS_ADDED );
@ -2182,6 +2187,8 @@ void PROJECT_TREE_PANE::updateGitStatusIconMap()
}
else if( entry->status & ( GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_DELETED ) )
{
wxLogTrace( traceGit, wxS( "File '%s' is deleted in %s" ),
absPath, ( entry->status & GIT_STATUS_INDEX_DELETED )? "index" : "working tree" );
auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
KIGIT_COMMON::GIT_STATUS::GIT_STATUS_DELETED );
@ -2192,6 +2199,7 @@ void PROJECT_TREE_PANE::updateGitStatusIconMap()
}
else if( localChanges.count( path ) )
{
wxLogTrace( traceGit, wxS( "File '%s' is ahead of remote" ), absPath );
auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
KIGIT_COMMON::GIT_STATUS::GIT_STATUS_AHEAD );
@ -2202,6 +2210,7 @@ void PROJECT_TREE_PANE::updateGitStatusIconMap()
}
else if( remoteChanges.count( path ) )
{
wxLogTrace( traceGit, wxS( "File '%s' is behind remote" ), absPath );
auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
KIGIT_COMMON::GIT_STATUS::GIT_STATUS_BEHIND );
@ -2212,6 +2221,9 @@ void PROJECT_TREE_PANE::updateGitStatusIconMap()
}
else
{
wxLogTrace( traceGit, wxS( "File '%s' is status %d" ), absPath, entry->status );
// If we are here, the file is unmodified and not ignored
auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CURRENT );
@ -2397,141 +2409,146 @@ void PROJECT_TREE_PANE::onGitCommit( wxCommandEvent& aEvent )
modifiedFiles );
auto ret = dlg.ShowModal();
if( ret == wxID_OK )
if( ret != wxID_OK )
return;
// Commit the changes
git_oid tree_id;
git_tree* tree = nullptr;
git_commit* parent = nullptr;
git_index* index = nullptr;
std::vector<wxString> files = dlg.GetSelectedFiles();
if( dlg.GetCommitMessage().IsEmpty() )
{
// Commit the changes
git_oid tree_id;
git_tree* tree = nullptr;
git_commit* parent = nullptr;
git_index* index = nullptr;
wxMessageBox( _( "Discarding commit due to empty commit message." ) );
return;
}
std::vector<wxString> files = dlg.GetSelectedFiles();
if( files.empty() )
{
wxMessageBox( _( "Discarding commit due to empty file selection." ) );
return;
}
if( dlg.GetCommitMessage().IsEmpty() )
if( git_repository_index( &index, repo ) != 0 )
{
wxLogTrace( traceGit, wxString::Format( _( "Failed to get repository index: %s" ),
KIGIT_COMMON::GetLastGitError() ) );
return;
}
KIGIT::GitIndexPtr indexPtr( index );
for( wxString& file : files )
{
if( git_index_add_bypath( index, file.mb_str() ) != 0 )
{
wxMessageBox( _( "Discarding commit due to empty commit message." ) );
return;
}
if( files.empty() )
{
wxMessageBox( _( "Discarding commit due to empty file selection." ) );
return;
}
if( git_repository_index( &index, repo ) != 0 )
{
wxLogTrace( traceGit, wxString::Format( _( "Failed to get repository index: %s" ),
KIGIT_COMMON::GetLastGitError() ) );
return;
}
KIGIT::GitIndexPtr indexPtr( index );
for( wxString& file : files )
{
if( git_index_add_bypath( index, file.mb_str() ) != 0 )
{
wxMessageBox( wxString::Format( _( "Failed to add file to index: %s" ),
KIGIT_COMMON::GetLastGitError() ) );
return;
}
}
if( git_index_write( index ) != 0 )
{
wxLogTrace( traceGit, wxString::Format( _( "Failed to write index: %s" ),
KIGIT_COMMON::GetLastGitError() ) );
return;
}
if( git_index_write_tree( &tree_id, index ) != 0)
{
wxLogTrace( traceGit, wxString::Format( _( "Failed to write tree: %s" ),
KIGIT_COMMON::GetLastGitError() ) );
return;
}
if( git_tree_lookup( &tree, repo, &tree_id ) != 0 )
{
wxLogTrace( traceGit, wxString::Format( _( "Failed to lookup tree: %s" ),
KIGIT_COMMON::GetLastGitError() ) );
return;
}
KIGIT::GitTreePtr treePtr( tree );
git_reference* headRef = nullptr;
if( git_repository_head_unborn( repo ) == 0 )
{
if( git_repository_head( &headRef, repo ) != 0 )
{
wxLogTrace( traceGit, wxString::Format( _( "Failed to get HEAD reference: %s" ),
KIGIT_COMMON::GetLastGitError() ) );
return;
}
KIGIT::GitReferencePtr headRefPtr( headRef );
if( git_reference_peel( (git_object**) &parent, headRef, GIT_OBJECT_COMMIT ) != 0 )
{
wxLogTrace( traceGit, wxString::Format( _( "Failed to get commit: %s" ),
KIGIT_COMMON::GetLastGitError() ) );
return;
}
}
KIGIT::GitCommitPtr parentPtr( parent );
const wxString& commit_msg = dlg.GetCommitMessage();
const wxString& author_name = dlg.GetAuthorName();
const wxString& author_email = dlg.GetAuthorEmail();
git_signature* author = nullptr;
if( git_signature_now( &author, author_name.mb_str(), author_email.mb_str() ) != 0 )
{
wxLogTrace( traceGit, wxString::Format( _( "Failed to create author signature: %s" ),
KIGIT_COMMON::GetLastGitError() ) );
return;
}
KIGIT::GitSignaturePtr authorPtr( author );
git_oid oid;
#if( LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR == 8 \
&& ( LIBGIT2_VER_REVISION < 2 || LIBGIT2_VER_REVISION == 3 ) )
/*
* For libgit2 versions 1.8.0, 1.8.1. (cf19ddc52)
* This change was reverted for 1.8.2 (49d3fadfc, main branch)
* The revert for 1.8.2 was not included for 1.8.3 (which is on the maint/v1.8 branch, not main)
* This change was also reverted for 1.8.4 (94ba816f6, also maint/v1.8 branch)
*
* As of 1.8.4, the history is like this:
*
* * 3f4182d15 (tag: v1.8.4, maint/v1.8)
* * 94ba816f6 Revert "commit: fix const declaration" [puts const back]
* * 3353f78e8 (tag: v1.8.3)
* | * 4ce872a0f (tag: v1.8.2-rc1, tag: v1.8.2)
* | * 49d3fadfc Revert "commit: fix const declaration" [puts const back]
* |/
* * 36f7e21ad (tag: v1.8.1)
* * d74d49148 (tag: v1.8.0)
* * cf19ddc52 commit: fix const declaration [removes const]
*/
git_commit* const parents[1] = { parent };
#else
// For libgit2 versions older than 1.8.0, or equal to 1.8.2, or 1.8.4+
const git_commit* parents[1] = { parent };
#endif
if( git_commit_create( &oid, repo, "HEAD", author, author, nullptr, commit_msg.mb_str(), tree,
1, parents ) != 0 )
{
wxMessageBox( wxString::Format( _( "Failed to create commit: %s" ),
wxMessageBox( wxString::Format( _( "Failed to add file to index: %s" ),
KIGIT_COMMON::GetLastGitError() ) );
return;
}
}
if( git_index_write( index ) != 0 )
{
wxLogTrace( traceGit, wxString::Format( _( "Failed to write index: %s" ),
KIGIT_COMMON::GetLastGitError() ) );
return;
}
if( git_index_write_tree( &tree_id, index ) != 0)
{
wxLogTrace( traceGit, wxString::Format( _( "Failed to write tree: %s" ),
KIGIT_COMMON::GetLastGitError() ) );
return;
}
if( git_tree_lookup( &tree, repo, &tree_id ) != 0 )
{
wxLogTrace( traceGit, wxString::Format( _( "Failed to lookup tree: %s" ),
KIGIT_COMMON::GetLastGitError() ) );
return;
}
KIGIT::GitTreePtr treePtr( tree );
git_reference* headRef = nullptr;
if( git_repository_head_unborn( repo ) == 0 )
{
if( git_repository_head( &headRef, repo ) != 0 )
{
wxLogTrace( traceGit, wxString::Format( _( "Failed to get HEAD reference: %s" ),
KIGIT_COMMON::GetLastGitError() ) );
return;
}
KIGIT::GitReferencePtr headRefPtr( headRef );
if( git_reference_peel( (git_object**) &parent, headRef, GIT_OBJECT_COMMIT ) != 0 )
{
wxLogTrace( traceGit, wxString::Format( _( "Failed to get commit: %s" ),
KIGIT_COMMON::GetLastGitError() ) );
return;
}
}
KIGIT::GitCommitPtr parentPtr( parent );
const wxString& commit_msg = dlg.GetCommitMessage();
const wxString& author_name = dlg.GetAuthorName();
const wxString& author_email = dlg.GetAuthorEmail();
git_signature* author = nullptr;
if( git_signature_now( &author, author_name.mb_str(), author_email.mb_str() ) != 0 )
{
wxLogTrace( traceGit, wxString::Format( _( "Failed to create author signature: %s" ),
KIGIT_COMMON::GetLastGitError() ) );
return;
}
KIGIT::GitSignaturePtr authorPtr( author );
git_oid oid;
#if( LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR == 8 \
&& ( LIBGIT2_VER_REVISION < 2 || LIBGIT2_VER_REVISION == 3 ) )
/*
* For libgit2 versions 1.8.0, 1.8.1. (cf19ddc52)
* This change was reverted for 1.8.2 (49d3fadfc, main branch)
* The revert for 1.8.2 was not included for 1.8.3 (which is on the maint/v1.8 branch, not main)
* This change was also reverted for 1.8.4 (94ba816f6, also maint/v1.8 branch)
*
* As of 1.8.4, the history is like this:
*
* * 3f4182d15 (tag: v1.8.4, maint/v1.8)
* * 94ba816f6 Revert "commit: fix const declaration" [puts const back]
* * 3353f78e8 (tag: v1.8.3)
* | * 4ce872a0f (tag: v1.8.2-rc1, tag: v1.8.2)
* | * 49d3fadfc Revert "commit: fix const declaration" [puts const back]
* |/
* * 36f7e21ad (tag: v1.8.1)
* * d74d49148 (tag: v1.8.0)
* * cf19ddc52 commit: fix const declaration [removes const]
*/
git_commit* const parents[1] = { parent };
#else
// For libgit2 versions older than 1.8.0, or equal to 1.8.2, or 1.8.4+
const git_commit* parents[1] = { parent };
#endif
if( git_commit_create( &oid, repo, "HEAD", author, author, nullptr, commit_msg.mb_str(), tree,
1, parents ) != 0 )
{
wxMessageBox( wxString::Format( _( "Failed to create commit: %s" ),
KIGIT_COMMON::GetLastGitError() ) );
return;
}
wxLogTrace( traceGit, wxString::Format( _( "Created commit with id: %s" ),
git_oid_tostr_s( &oid ) ) );
m_gitStatusTimer.Start( 500, wxTIMER_ONE_SHOT );
}
@ -2662,27 +2679,39 @@ void PROJECT_TREE_PANE::onGitSyncTimer( wxTimerEvent& aEvent )
GIT_PULL_HANDLER handler( gitCommon );
handler.PerformFetch();
CallAfter( [this]()
{
gitStatusTimerHandler();
} );
} );
if( ADVANCED_CFG::GetCfg().m_GitProjectStatusRefreshInterval > 0 )
{
wxLogTrace( traceGit, "onGitSyncTimer: Starting git status timer" );
wxLogTrace( traceGit, "onGitSyncTimer: Restarting git sync timer" );
m_gitSyncTimer.Start( ADVANCED_CFG::GetCfg().m_GitProjectStatusRefreshInterval,
wxTIMER_ONE_SHOT );
}
}
void PROJECT_TREE_PANE::gitStatusTimerHandler()
{
updateTreeCache();
thread_pool& tp = GetKiCadThreadPool();
tp.push_task(
[this]()
{
updateGitStatusIconMap();
} );
}
void PROJECT_TREE_PANE::onGitStatusTimer( wxTimerEvent& aEvent )
{
wxLogTrace( traceGit, "onGitStatusTimer" );
if( ADVANCED_CFG::GetCfg().m_EnableGit == false || !m_TreeProject )
return;
updateTreeCache();
thread_pool& tp = GetKiCadThreadPool();
tp.push_task( [this]()
{
updateGitStatusIconMap();
} );
gitStatusTimerHandler();
}

View File

@ -303,6 +303,8 @@ private:
void onGitStatusTimer( wxTimerEvent& event );
void gitStatusTimerHandler();
public:
KICAD_MANAGER_FRAME* m_Parent;
PROJECT_TREE* m_TreeProject;

View File

@ -170,7 +170,7 @@ int KICAD_MANAGER_CONTROL::NewFromRepository( const TOOL_EVENT& aEvent )
GIT_CLONE_HANDLER cloneHandler( pane->m_TreeProject->GitCommon() );
cloneHandler.SetURL( dlg.GetRepoURL() );
cloneHandler.SetRemote( dlg.GetFullURL() );
cloneHandler.SetClonePath( pro.GetPath() );
cloneHandler.SetUsername( dlg.GetUsername() );
cloneHandler.SetPassword( dlg.GetPassword() );