diff --git a/common/CMakeLists.txt b/common/CMakeLists.txt index b2cc01015f..1c7a1ea444 100644 --- a/common/CMakeLists.txt +++ b/common/CMakeLists.txt @@ -633,7 +633,8 @@ set( COMMON_GIT_SRCS git/project_git_utils.cpp git/kicad_git_common.cpp git/kicad_git_errors.cpp - git/project_git_utils.cpp + git/git_backend.cpp + git/libgit_backend.cpp ) set( COMMON_SRCS diff --git a/common/git/git_add_to_index_handler.cpp b/common/git/git_add_to_index_handler.cpp index f97bb263be..b8f17650d9 100644 --- a/common/git/git_add_to_index_handler.cpp +++ b/common/git/git_add_to_index_handler.cpp @@ -22,16 +22,13 @@ */ #include "git_add_to_index_handler.h" -#include +#include "git_backend.h" -#include - -#include #include -GIT_ADD_TO_INDEX_HANDLER::GIT_ADD_TO_INDEX_HANDLER( git_repository* aRepository ) +GIT_ADD_TO_INDEX_HANDLER::GIT_ADD_TO_INDEX_HANDLER( git_repository* aRepository ) : + KIGIT_COMMON( aRepository ) { - m_repository = aRepository; m_filesToAdd.clear(); } @@ -43,66 +40,11 @@ GIT_ADD_TO_INDEX_HANDLER::~GIT_ADD_TO_INDEX_HANDLER() bool GIT_ADD_TO_INDEX_HANDLER::AddToIndex( const wxString& aFilePath ) { - // Test if file is currently in the index - - git_index* index = nullptr; - size_t at_pos = 0; - - if( git_repository_index( &index, m_repository ) != 0 ) - { - wxLogError( "Failed to get repository index" ); - return false; - } - - KIGIT::GitIndexPtr indexPtr( index ); - - if( git_index_find( &at_pos, index, aFilePath.ToUTF8().data() ) == GIT_OK ) - { - wxLogError( "%s already in index", aFilePath ); - return false; - } - - // Add file to index if not already there - m_filesToAdd.push_back( aFilePath ); - - return true; + return GetGitBackend()->AddToIndex( this, aFilePath ); } bool GIT_ADD_TO_INDEX_HANDLER::PerformAddToIndex() { - git_index* index = nullptr; - - m_filesFailedToAdd.clear(); - - if( git_repository_index( &index, m_repository ) != 0 ) - { - wxLogError( "Failed to get repository index" ); - std::copy( m_filesToAdd.begin(), m_filesToAdd.end(), std::back_inserter( m_filesFailedToAdd ) ); - return false; - } - - KIGIT::GitIndexPtr indexPtr( index ); - - for( auto& file : m_filesToAdd ) - { - if( git_index_add_bypath( index, file.ToUTF8().data() ) != 0 ) - { - wxLogError( "Failed to add %s to index", file ); - m_filesFailedToAdd.push_back( file ); - continue; - } - } - - - if( git_index_write( index ) != 0 ) - { - wxLogError( "Failed to write index" ); - m_filesFailedToAdd.clear(); - std::copy( m_filesToAdd.begin(), m_filesToAdd.end(), - std::back_inserter( m_filesFailedToAdd ) ); - return false; - } - - return true; + return GetGitBackend()->PerformAddToIndex( this ); } diff --git a/common/git/git_add_to_index_handler.h b/common/git/git_add_to_index_handler.h index 6adc67e565..09fe901f21 100644 --- a/common/git/git_add_to_index_handler.h +++ b/common/git/git_add_to_index_handler.h @@ -24,12 +24,13 @@ #ifndef GIT_ADD_TO_INDEX_HANDLER_H_ #define GIT_ADD_TO_INDEX_HANDLER_H_ -#include +#include #include +#include -class wxString; +class LIBGIT_BACKEND; -class GIT_ADD_TO_INDEX_HANDLER +class GIT_ADD_TO_INDEX_HANDLER : public KIGIT_COMMON { public: GIT_ADD_TO_INDEX_HANDLER( git_repository* aRepository ); @@ -40,8 +41,7 @@ public: bool PerformAddToIndex(); private: - git_repository* m_repository; - + friend class LIBGIT_BACKEND; std::vector m_filesToAdd; std::vector m_filesFailedToAdd; }; diff --git a/common/git/git_backend.cpp b/common/git/git_backend.cpp new file mode 100644 index 0000000000..563ab4abb1 --- /dev/null +++ b/common/git/git_backend.cpp @@ -0,0 +1,36 @@ +/* + * 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 "git_backend.h" + +static GIT_BACKEND* s_backend = nullptr; + +GIT_BACKEND* GetGitBackend() +{ + return s_backend; +} + +void SetGitBackend( GIT_BACKEND* aBackend ) +{ + s_backend = aBackend; +} diff --git a/common/git/git_backend.h b/common/git/git_backend.h new file mode 100644 index 0000000000..3df3101330 --- /dev/null +++ b/common/git/git_backend.h @@ -0,0 +1,127 @@ +/* + * 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 + */ + +#ifndef GIT_BACKEND_H_ +#define GIT_BACKEND_H_ + +#include +#include +#include +#include + +class GIT_CLONE_HANDLER; +class GIT_COMMIT_HANDLER; +class GIT_PUSH_HANDLER; +class GIT_STATUS_HANDLER; +class GIT_ADD_TO_INDEX_HANDLER; +class GIT_REMOVE_FROM_INDEX_HANDLER; +class GIT_CONFIG_HANDLER; +class GIT_INIT_HANDLER; +class GIT_BRANCH_HANDLER; +class GIT_PULL_HANDLER; +class GIT_REVERT_HANDLER; +struct RemoteConfig; +struct git_repository; +enum class InitResult; +enum class BranchResult; +enum class PullResult; +struct FileStatus; +enum class PushResult; + +// Commit result shared across backend and handlers +enum class CommitResult +{ + Success, + Error, + Cancelled +}; + +class GIT_BACKEND +{ +public: + virtual ~GIT_BACKEND() = default; + + virtual void Init() = 0; + virtual void Shutdown() = 0; + + // Whether the libgit2 library is available/initialized enough for use + virtual bool IsLibraryAvailable() = 0; + + virtual bool Clone( GIT_CLONE_HANDLER* aHandler ) = 0; + + virtual CommitResult Commit( GIT_COMMIT_HANDLER* aHandler, + const std::vector& aFiles, + const wxString& aMessage, + const wxString& aAuthorName, + const wxString& aAuthorEmail ) = 0; + + virtual PushResult Push( GIT_PUSH_HANDLER* aHandler ) = 0; + + virtual bool HasChangedFiles( GIT_STATUS_HANDLER* aHandler ) = 0; + + virtual std::map GetFileStatus( GIT_STATUS_HANDLER* aHandler, + const wxString& aPathspec ) = 0; + + virtual wxString GetCurrentBranchName( GIT_STATUS_HANDLER* aHandler ) = 0; + + virtual void UpdateRemoteStatus( GIT_STATUS_HANDLER* aHandler, + const std::set& aLocalChanges, + const std::set& aRemoteChanges, + std::map& aFileStatus ) = 0; + + virtual wxString GetWorkingDirectory( GIT_STATUS_HANDLER* aHandler ) = 0; + + virtual wxString GetWorkingDirectory( GIT_CONFIG_HANDLER* aHandler ) = 0; + virtual bool GetConfigString( GIT_CONFIG_HANDLER* aHandler, const wxString& aKey, + wxString& aValue ) = 0; + + virtual bool IsRepository( GIT_INIT_HANDLER* aHandler, const wxString& aPath ) = 0; + virtual InitResult InitializeRepository( GIT_INIT_HANDLER* aHandler, const wxString& aPath ) = 0; + virtual bool SetupRemote( GIT_INIT_HANDLER* aHandler, const RemoteConfig& aConfig ) = 0; + + virtual BranchResult SwitchToBranch( GIT_BRANCH_HANDLER* aHandler, const wxString& aBranchName ) = 0; + virtual bool BranchExists( GIT_BRANCH_HANDLER* aHandler, const wxString& aBranchName ) = 0; + + virtual bool PerformFetch( GIT_PULL_HANDLER* aHandler, bool aSkipLock ) = 0; + virtual PullResult PerformPull( GIT_PULL_HANDLER* aHandler ) = 0; + + virtual void PerformRevert( GIT_REVERT_HANDLER* aHandler ) = 0; + + virtual git_repository* GetRepositoryForFile( const char* aFilename ) = 0; + virtual int CreateBranch( git_repository* aRepo, const wxString& aBranchName ) = 0; + virtual bool RemoveVCS( git_repository*& aRepo, const wxString& aProjectPath, + bool aRemoveGitDir, wxString* aErrors ) = 0; + + virtual bool AddToIndex( GIT_ADD_TO_INDEX_HANDLER* aHandler, const wxString& aFilePath ) = 0; + + virtual bool PerformAddToIndex( GIT_ADD_TO_INDEX_HANDLER* aHandler ) = 0; + + virtual bool RemoveFromIndex( GIT_REMOVE_FROM_INDEX_HANDLER* aHandler, const wxString& aFilePath ) = 0; + + virtual void PerformRemoveFromIndex( GIT_REMOVE_FROM_INDEX_HANDLER* aHandler ) = 0; +}; + +GIT_BACKEND* GetGitBackend(); +void SetGitBackend( GIT_BACKEND* aBackend ); + +#endif diff --git a/common/git/git_branch_handler.cpp b/common/git/git_branch_handler.cpp index 9cb0ab66be..a4e362db19 100644 --- a/common/git/git_branch_handler.cpp +++ b/common/git/git_branch_handler.cpp @@ -22,6 +22,7 @@ */ #include "git_branch_handler.h" +#include "git_backend.h" #include #include #include @@ -35,89 +36,12 @@ GIT_BRANCH_HANDLER::~GIT_BRANCH_HANDLER() bool GIT_BRANCH_HANDLER::BranchExists( const wxString& aBranchName ) { - git_repository* repo = GetRepo(); - - if( !repo ) - return false; - - git_reference* branchRef = nullptr; - bool exists = LookupBranchReference( aBranchName, &branchRef ); - - if( branchRef ) - git_reference_free( branchRef ); - - return exists; -} - -bool GIT_BRANCH_HANDLER::LookupBranchReference( const wxString& aBranchName, git_reference** aReference ) -{ - git_repository* repo = GetRepo(); - - if( !repo ) - return false; - - // Try direct lookup first - if( git_reference_lookup( aReference, repo, aBranchName.mb_str() ) == GIT_OK ) - return true; - - // Try dwim (Do What I Mean) lookup for short branch names - if( git_reference_dwim( aReference, repo, aBranchName.mb_str() ) == GIT_OK ) - return true; - - return false; + return GetGitBackend()->BranchExists( this, aBranchName ); } BranchResult GIT_BRANCH_HANDLER::SwitchToBranch( const wxString& aBranchName ) { - git_repository* repo = GetRepo(); - - if( !repo ) - { - AddErrorString( _( "No repository available" ) ); - return BranchResult::Error; - } - - // Look up the branch reference - git_reference* branchRef = nullptr; - - if( !LookupBranchReference( aBranchName, &branchRef ) ) - { - AddErrorString( wxString::Format( _( "Failed to lookup branch '%s': %s" ), - aBranchName, KIGIT_COMMON::GetLastGitError() ) ); - return BranchResult::BranchNotFound; - } - - KIGIT::GitReferencePtr branchRefPtr( branchRef ); - const char* branchRefName = git_reference_name( branchRef ); - git_object* branchObj = nullptr; - - if( git_revparse_single( &branchObj, repo, aBranchName.mb_str() ) != GIT_OK ) - { - AddErrorString( wxString::Format( _( "Failed to find branch head for '%s': %s" ), - aBranchName, KIGIT_COMMON::GetLastGitError() ) ); - return BranchResult::Error; - } - - KIGIT::GitObjectPtr branchObjPtr( branchObj ); - - // Switch to the branch - if( git_checkout_tree( repo, branchObj, nullptr ) != GIT_OK ) - { - AddErrorString( wxString::Format( _( "Failed to switch to branch '%s': %s" ), - aBranchName, KIGIT_COMMON::GetLastGitError() ) ); - return BranchResult::CheckoutFailed; - } - - // Update the HEAD reference - if( git_repository_set_head( repo, branchRefName ) != GIT_OK ) - { - AddErrorString( wxString::Format( _( "Failed to update HEAD reference for branch '%s': %s" ), - aBranchName, KIGIT_COMMON::GetLastGitError() ) ); - return BranchResult::Error; - } - - wxLogTrace( traceGit, "Successfully switched to branch '%s'", aBranchName ); - return BranchResult::Success; + return GetGitBackend()->SwitchToBranch( this, aBranchName ); } void GIT_BRANCH_HANDLER::UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage ) diff --git a/common/git/git_branch_handler.h b/common/git/git_branch_handler.h index 7555fd28dd..d12e752c57 100644 --- a/common/git/git_branch_handler.h +++ b/common/git/git_branch_handler.h @@ -57,15 +57,6 @@ public: bool BranchExists( const wxString& aBranchName ); void UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage ) override; - -private: - /** - * Look up a branch reference by name - * @param aBranchName Name of the branch - * @param aReference Output parameter for the reference - * @return True if successful, false otherwise - */ - bool LookupBranchReference( const wxString& aBranchName, git_reference** aReference ); }; #endif // GIT_BRANCH_HANDLER_H \ No newline at end of file diff --git a/common/git/git_clone_handler.cpp b/common/git/git_clone_handler.cpp index a58c35bd0e..14a03cbed5 100644 --- a/common/git/git_clone_handler.cpp +++ b/common/git/git_clone_handler.cpp @@ -23,13 +23,8 @@ #include "git_clone_handler.h" -#include -#include #include - -#include -#include -#include +#include "git_backend.h" GIT_CLONE_HANDLER::GIT_CLONE_HANDLER( KIGIT_COMMON* aCommon ) : KIGIT_REPO_MIXIN( aCommon ) {} @@ -41,55 +36,7 @@ GIT_CLONE_HANDLER::~GIT_CLONE_HANDLER() bool GIT_CLONE_HANDLER::PerformClone() { - std::unique_lock lock( GetCommon()->m_gitActionMutex, std::try_to_lock ); - - if( !lock.owns_lock() ) - { - wxLogTrace( traceGit, "GIT_CLONE_HANDLER::PerformClone() could not lock" ); - return false; - } - - wxFileName clonePath( m_clonePath ); - - if( !clonePath.DirExists() ) - { - if( !clonePath.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) ) - { - AddErrorString( wxString::Format( _( "Could not create directory '%s'" ), - m_clonePath ) ); - return false; - } - } - - git_clone_options cloneOptions; - git_clone_init_options( &cloneOptions, GIT_CLONE_OPTIONS_VERSION ); - cloneOptions.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - cloneOptions.checkout_opts.progress_cb = clone_progress_cb; - cloneOptions.checkout_opts.progress_payload = this; - cloneOptions.fetch_opts.callbacks.transfer_progress = transfer_progress_cb; - cloneOptions.fetch_opts.callbacks.credentials = credentials_cb; - cloneOptions.fetch_opts.callbacks.payload = this; - - TestedTypes() = 0; - ResetNextKey(); - git_repository* newRepo = nullptr; - wxString remote = GetCommon()->m_remote; - - if( git_clone( &newRepo, remote.mbc_str(), m_clonePath.mbc_str(), - &cloneOptions ) != 0 ) - { - AddErrorString( wxString::Format( _( "Could not clone repository '%s'" ), remote ) ); - return false; - } - - GetCommon()->SetRepo( newRepo ); - - if( m_progressReporter ) - m_progressReporter->Hide(); - - m_previousProgress = 0; - - return true; + return GetGitBackend()->Clone( this ); } diff --git a/common/git/git_commit_handler.cpp b/common/git/git_commit_handler.cpp index ca4916037a..c11d65ec98 100644 --- a/common/git/git_commit_handler.cpp +++ b/common/git/git_commit_handler.cpp @@ -22,8 +22,7 @@ */ #include "git_commit_handler.h" -#include -#include +#include "git_backend.h" GIT_COMMIT_HANDLER::GIT_COMMIT_HANDLER( git_repository* aRepo ) : KIGIT_COMMON( aRepo ) @@ -34,121 +33,13 @@ GIT_COMMIT_HANDLER::~GIT_COMMIT_HANDLER() {} -GIT_COMMIT_HANDLER::CommitResult +CommitResult GIT_COMMIT_HANDLER::PerformCommit( const std::vector& aFiles, const wxString& aMessage, const wxString& aAuthorName, const wxString& aAuthorEmail ) { - git_repository* repo = GetRepo(); - - if( !repo ) - return CommitResult::Error; - - git_index* index = nullptr; - - if( git_repository_index( &index, repo ) != 0 ) - { - AddErrorString( wxString::Format( _( "Failed to get repository index: %s" ), - KIGIT_COMMON::GetLastGitError() ) ); - return CommitResult::Error; - } - - KIGIT::GitIndexPtr indexPtr( index ); - - for( const wxString& file : aFiles ) - { - if( git_index_add_bypath( index, file.mb_str() ) != 0 ) - { - AddErrorString( wxString::Format( _( "Failed to add file to index: %s" ), - KIGIT_COMMON::GetLastGitError() ) ); - return CommitResult::Error; - } - } - - if( git_index_write( index ) != 0 ) - { - AddErrorString( wxString::Format( _( "Failed to write index: %s" ), - KIGIT_COMMON::GetLastGitError() ) ); - return CommitResult::Error; - } - - git_oid tree_id; - - if( git_index_write_tree( &tree_id, index ) != 0 ) - { - AddErrorString( wxString::Format( _( "Failed to write tree: %s" ), - KIGIT_COMMON::GetLastGitError() ) ); - return CommitResult::Error; - } - - git_tree* tree = nullptr; - - if( git_tree_lookup( &tree, repo, &tree_id ) != 0 ) - { - AddErrorString( wxString::Format( _( "Failed to lookup tree: %s" ), - KIGIT_COMMON::GetLastGitError() ) ); - return CommitResult::Error; - } - - KIGIT::GitTreePtr treePtr( tree ); - git_commit* parent = nullptr; - - if( git_repository_head_unborn( repo ) == 0 ) - { - git_reference* headRef = nullptr; - - if( git_repository_head( &headRef, repo ) != 0 ) - { - AddErrorString( wxString::Format( _( "Failed to get HEAD reference: %s" ), - KIGIT_COMMON::GetLastGitError() ) ); - return CommitResult::Error; - } - - KIGIT::GitReferencePtr headRefPtr( headRef ); - - if( git_reference_peel( (git_object**) &parent, headRef, GIT_OBJECT_COMMIT ) != 0 ) - { - AddErrorString( wxString::Format( _( "Failed to get commit: %s" ), - KIGIT_COMMON::GetLastGitError() ) ); - return CommitResult::Error; - } - } - - KIGIT::GitCommitPtr parentPtr( parent ); - - git_signature* author = nullptr; - - if( git_signature_now( &author, aAuthorName.mb_str(), aAuthorEmail.mb_str() ) != 0 ) - { - AddErrorString( wxString::Format( _( "Failed to create author signature: %s" ), - KIGIT_COMMON::GetLastGitError() ) ); - return CommitResult::Error; - } - - KIGIT::GitSignaturePtr authorPtr( author ); - git_oid oid; - size_t parentsCount = parent ? 1 : 0; -#if( LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR == 8 \ - && ( LIBGIT2_VER_REVISION < 2 || LIBGIT2_VER_REVISION == 3 ) ) - git_commit* const parents[1] = { parent }; - git_commit** const parentsPtr = parent ? parents : nullptr; -#else - const git_commit* parents[1] = { parent }; - const git_commit** parentsPtr = parent ? parents : nullptr; -#endif - - - - if( git_commit_create( &oid, repo, "HEAD", author, author, nullptr, - aMessage.mb_str(), tree, parentsCount, parentsPtr ) != 0 ) - { - AddErrorString( wxString::Format( _( "Failed to create commit: %s" ), - KIGIT_COMMON::GetLastGitError() ) ); - return CommitResult::Error; - } - - return CommitResult::Success; + return GetGitBackend()->Commit( this, aFiles, aMessage, aAuthorName, aAuthorEmail ); } diff --git a/common/git/git_commit_handler.h b/common/git/git_commit_handler.h index 3fe6f25226..fd8f87eb41 100644 --- a/common/git/git_commit_handler.h +++ b/common/git/git_commit_handler.h @@ -27,25 +27,20 @@ // Define a class to handle git commit operations #include -#include +#include "git_backend.h" #include #include #include +class LIBGIT_BACKEND; + class GIT_COMMIT_HANDLER : public KIGIT_COMMON { public: GIT_COMMIT_HANDLER( git_repository* aRepo ); virtual ~GIT_COMMIT_HANDLER(); - enum class CommitResult - { - Success, - Error, - Cancelled - }; - CommitResult PerformCommit( const std::vector& aFiles, const wxString& aMessage, const wxString& aAuthorName, @@ -54,6 +49,7 @@ public: wxString GetErrorString() const; private: + friend class LIBGIT_BACKEND; void AddErrorString( const wxString& aErrorString ); wxString m_errorString; diff --git a/common/git/git_config_handler.cpp b/common/git/git_config_handler.cpp index 86284aaab4..67daf5b8da 100644 --- a/common/git/git_config_handler.cpp +++ b/common/git/git_config_handler.cpp @@ -22,6 +22,7 @@ */ #include "git_config_handler.h" +#include "git_backend.h" #include #include #include @@ -59,48 +60,12 @@ GitUserConfig GIT_CONFIG_HANDLER::GetUserConfig() wxString GIT_CONFIG_HANDLER::GetWorkingDirectory() { - git_repository* repo = GetRepo(); - - if( !repo ) - return wxEmptyString; - - const char* workdir = git_repository_workdir( repo ); - - if( !workdir ) - return wxEmptyString; - - return wxString( workdir ); + return GetGitBackend()->GetWorkingDirectory( this ); } bool GIT_CONFIG_HANDLER::GetConfigString( const wxString& aKey, wxString& aValue ) { - git_repository* repo = GetRepo(); - - if( !repo ) - return false; - - git_config* config = nullptr; - - if( git_repository_config( &config, repo ) != GIT_OK ) - { - wxLogTrace( traceGit, "Failed to get repository config: %s", KIGIT_COMMON::GetLastGitError() ); - return false; - } - - KIGIT::GitConfigPtr configPtr( config ); - - git_config_entry* entry = nullptr; - int result = git_config_get_entry( &entry, config, aKey.mb_str() ); - KIGIT::GitConfigEntryPtr entryPtr( entry ); - - if( result != GIT_OK || entry == nullptr ) - { - wxLogTrace( traceGit, "Config key '%s' not found", aKey ); - return false; - } - - aValue = wxString( entry->value ); - return true; + return GetGitBackend()->GetConfigString( this, aKey, aValue ); } void GIT_CONFIG_HANDLER::UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage ) diff --git a/common/git/git_init_handler.cpp b/common/git/git_init_handler.cpp index f2d902128a..0309c6c2ef 100644 --- a/common/git/git_init_handler.cpp +++ b/common/git/git_init_handler.cpp @@ -22,6 +22,7 @@ */ #include "git_init_handler.h" +#include "git_backend.h" #include #include #include @@ -35,118 +36,17 @@ GIT_INIT_HANDLER::~GIT_INIT_HANDLER() bool GIT_INIT_HANDLER::IsRepository( const wxString& aPath ) { - git_repository* repo = nullptr; - int error = git_repository_open( &repo, aPath.mb_str() ); - - if( error == 0 ) - { - git_repository_free( repo ); - return true; - } - - return false; + return GetGitBackend()->IsRepository( this, aPath ); } InitResult GIT_INIT_HANDLER::InitializeRepository( const wxString& aPath ) { - // Check if directory is already a git repository - if( IsRepository( aPath ) ) - { - return InitResult::AlreadyExists; - } - - git_repository* repo = nullptr; - - if( git_repository_init( &repo, aPath.mb_str(), 0 ) != GIT_OK ) - { - if( repo ) - git_repository_free( repo ); - - AddErrorString( wxString::Format( _( "Failed to initialize Git repository: %s" ), - KIGIT_COMMON::GetLastGitError() ) ); - return InitResult::Error; - } - - // Update the common repository pointer - GetCommon()->SetRepo( repo ); - - wxLogTrace( traceGit, "Successfully initialized Git repository at %s", aPath ); - return InitResult::Success; + return GetGitBackend()->InitializeRepository( this, aPath ); } bool GIT_INIT_HANDLER::SetupRemote( const RemoteConfig& aConfig ) { - // This is an optional step - if( aConfig.url.IsEmpty() ) - return true; - - git_repository* repo = GetRepo(); - - if( !repo ) - { - AddErrorString( _( "No repository available to set up remote" ) ); - return false; - } - - // Update connection settings in common - GetCommon()->SetUsername( aConfig.username ); - GetCommon()->SetPassword( aConfig.password ); - GetCommon()->SetSSHKey( aConfig.sshKey ); - - git_remote* remote = nullptr; - wxString fullURL; - - // Build the full URL based on connection type - if( aConfig.connType == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH ) - { - fullURL = aConfig.username + "@" + aConfig.url; - } - else if( aConfig.connType == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS ) - { - fullURL = aConfig.url.StartsWith( "https" ) ? "https://" : "http://"; - - if( !aConfig.username.empty() ) - { - fullURL.append( aConfig.username ); - - if( !aConfig.password.empty() ) - { - fullURL.append( wxS( ":" ) ); - fullURL.append( aConfig.password ); - } - - fullURL.append( wxS( "@" ) ); - } - - // Extract the bare URL (without protocol prefix) - wxString bareURL = aConfig.url; - if( bareURL.StartsWith( "https://" ) ) - bareURL = bareURL.Mid( 8 ); - else if( bareURL.StartsWith( "http://" ) ) - bareURL = bareURL.Mid( 7 ); - - fullURL.append( bareURL ); - } - else - { - fullURL = aConfig.url; - } - - int error = git_remote_create_with_fetchspec( &remote, repo, "origin", - fullURL.ToStdString().c_str(), - "+refs/heads/*:refs/remotes/origin/*" ); - - KIGIT::GitRemotePtr remotePtr( remote ); - - if( error != GIT_OK ) - { - AddErrorString( wxString::Format( _( "Failed to create remote: %s" ), - KIGIT_COMMON::GetLastGitError() ) ); - return false; - } - - wxLogTrace( traceGit, "Successfully set up remote origin" ); - return true; + return GetGitBackend()->SetupRemote( this, aConfig ); } void GIT_INIT_HANDLER::UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage ) diff --git a/common/git/git_pull_handler.cpp b/common/git/git_pull_handler.cpp index b08043f8d0..9520cf985c 100644 --- a/common/git/git_pull_handler.cpp +++ b/common/git/git_pull_handler.cpp @@ -22,188 +22,22 @@ */ #include "git_pull_handler.h" -#include -#include -#include - -#include - -#include -#include -#include +#include "git_backend.h" GIT_PULL_HANDLER::GIT_PULL_HANDLER( KIGIT_COMMON* aCommon ) : KIGIT_REPO_MIXIN( aCommon ) -{ -} - +{} 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 lock( GetCommon()->m_gitActionMutex, std::try_to_lock ); - - if( !aSkipLock && !lock.owns_lock() ) - { - wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Could not lock mutex" ); - return false; - } - - // Fetch updates from remote repository - git_remote* remote = nullptr; - - 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; - } - - KIGIT::GitRemotePtr remotePtr( remote ); - - git_remote_callbacks remoteCallbacks; - git_remote_init_callbacks( &remoteCallbacks, GIT_REMOTE_CALLBACKS_VERSION ); - remoteCallbacks.sideband_progress = progress_cb; - remoteCallbacks.transfer_progress = transfer_progress_cb; - remoteCallbacks.credentials = credentials_cb; - remoteCallbacks.payload = this; - GetCommon()->SetCancelled( false ); - - TestedTypes() = 0; - ResetNextKey(); - - if( git_remote_connect( remote, GIT_DIRECTION_FETCH, &remoteCallbacks, nullptr, nullptr ) ) - { - 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; - } - - git_fetch_options fetchOptions; - git_fetch_init_options( &fetchOptions, GIT_FETCH_OPTIONS_VERSION ); - fetchOptions.callbacks = remoteCallbacks; - - if( git_remote_fetch( remote, nullptr, &fetchOptions, nullptr ) ) - { - 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; + return GetGitBackend()->PerformFetch( this, aSkipLock ); } - PullResult GIT_PULL_HANDLER::PerformPull() { - PullResult result = PullResult::Success; - std::unique_lock lock( GetCommon()->m_gitActionMutex, std::try_to_lock ); - - if( !lock.owns_lock() ) - { - wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Could not lock mutex" ); - return PullResult::Error; - } - - if( !PerformFetch( true ) ) - return PullResult::Error; - - git_oid pull_merge_oid = {}; - - if( git_repository_fetchhead_foreach( GetRepo(), fetchhead_foreach_cb, - &pull_merge_oid ) ) - { - AddErrorString( _( "Could not read 'FETCH_HEAD'" ) ); - return PullResult::Error; - } - - git_annotated_commit* fetchhead_commit; - - if( git_annotated_commit_lookup( &fetchhead_commit, GetRepo(), &pull_merge_oid ) ) - { - AddErrorString( _( "Could not lookup commit" ) ); - return PullResult::Error; - } - - 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; - - if( git_merge_analysis( &merge_analysis, &merge_preference, GetRepo(), merge_commits, 1 ) ) - { - AddErrorString( _( "Could not analyze merge" ) ); - return PullResult::Error; - } - - if( merge_analysis & GIT_MERGE_ANALYSIS_UNBORN ) - { - AddErrorString( _( "Invalid HEAD. Cannot merge." ) ); - return PullResult::MergeFailed; - } - - // Nothing to do if the repository is up to date - if( merge_analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE ) - { - wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Repository is up to date" ); - git_repository_state_cleanup( GetRepo() ); - return PullResult::UpToDate; - } - - // Fast-forward is easy, just update the local reference - if( merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD ) - { - wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Fast-forward merge" ); - return handleFastForward(); - } - - if( merge_analysis & GIT_MERGE_ANALYSIS_NORMAL ) - { - wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Normal merge" ); - - // Check git config to determine if we should rebase or merge - git_config* config = nullptr; - - if( git_repository_config( &config, GetRepo() ) != GIT_OK ) - { - wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Failed to get repository config" ); - AddErrorString( _( "Could not access repository configuration" ) ); - return PullResult::Error; - } - - KIGIT::GitConfigPtr configPtr( config ); - - // Check for pull.rebase config - int rebase_value = 0; - int ret = git_config_get_bool( &rebase_value, config, "pull.rebase" ); - - // If pull.rebase is set to true, use rebase; otherwise use merge - if( ret == GIT_OK && rebase_value ) - { - wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Using rebase based on config" ); - return handleRebase( merge_commits, 1 ); - } - - wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Using merge based on config" ); - return handleMerge( merge_commits, 1 ); - } - - wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Merge needs resolution" ); - //TODO: handle merges when they need to be resolved - - return result; + return GetGitBackend()->PerformPull( this ); } const std::vector>>& @@ -212,384 +46,7 @@ GIT_PULL_HANDLER::GetFetchResults() const return m_fetchResults; } - -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 ) - return aMessage.substr( 0, firstLineEnd ); - - return aMessage; -} - - -std::string GIT_PULL_HANDLER::getFormattedCommitDate( const git_time& aTime ) -{ - char dateBuffer[64]; - time_t time = static_cast( aTime.time ); - struct tm timeInfo; - #ifdef _WIN32 - localtime_s( &timeInfo, &time ); - #else - gmtime_r( &time, &timeInfo ); - #endif - strftime( dateBuffer, sizeof( dateBuffer ), "%Y-%b-%d %H:%M:%S", &timeInfo ); - return dateBuffer; -} - - -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" ) ); - return PullResult::Error; - } - - KIGIT::GitReferencePtr headRef( rawRef ); - - git_oid updatedRefOid; - const char* currentBranchName = git_reference_name( rawRef ); - const char* branch_shorthand = git_reference_shorthand( rawRef ); - wxString remote_name = GetRemotename(); - wxString remoteBranchName = wxString::Format( "refs/remotes/%s/%s", - remote_name, branch_shorthand ); - - // 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'" ), - 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 ); - 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; - } - - 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( targetTree ), &checkoutOptions ) != GIT_OK ) - { - AddErrorString( _( "Failed to perform checkout operation." ) ); - return PullResult::Error; - } - - 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; - - // 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 ); - - if( git_revwalk_push_glob( revWalker, currentBranchName ) != GIT_OK ) - { - AddErrorString( _( "Failed to push reference to revision walker." ) ); - return PullResult::Error; - } - - std::pair>& branchCommits = m_fetchResults.emplace_back(); - branchCommits.first = currentBranchName; - - git_oid commitOid; - - 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 '%s'" ), - git_oid_tostr_s( &commitOid ) ) ); - return PullResult::Error; - } - - KIGIT::GitCommitPtr commitPtr( commit ); - - CommitDetails details; - details.m_sha = git_oid_tostr_s( &commitOid ); - details.m_firstLine = getFirstLineFromCommitMessage( git_commit_message( commit ) ); - details.m_author = git_commit_author( commit )->name; - details.m_date = getFormattedCommitDate( git_commit_author( commit )->when ); - - branchCommits.second.push_back( details ); - } - - return PullResult::FastForward; -} - - -PullResult GIT_PULL_HANDLER::handleMerge( const git_annotated_commit** aMergeHeads, - size_t aMergeHeadsCount ) -{ - git_merge_options merge_opts; - git_merge_options_init( &merge_opts, GIT_MERGE_OPTIONS_VERSION ); - - git_checkout_options checkout_opts; - git_checkout_init_options( &checkout_opts, GIT_CHECKOUT_OPTIONS_VERSION ); - - checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; - - if( git_merge( GetRepo(), aMergeHeads, aMergeHeadsCount, &merge_opts, &checkout_opts ) ) - { - AddErrorString( _( "Could not merge commits" ) ); - return PullResult::Error; - } - - // Get the repository index - git_index* index = nullptr; - - if( git_repository_index( &index, GetRepo() ) ) - { - AddErrorString( _( "Could not get repository index" ) ); - return PullResult::Error; - } - - KIGIT::GitIndexPtr indexPtr( index ); - - // Check for conflicts - git_index_conflict_iterator* conflicts = nullptr; - - if( git_index_conflict_iterator_new( &conflicts, index ) ) - { - AddErrorString( _( "Could not get conflict iterator" ) ); - return PullResult::Error; - } - - KIGIT::GitIndexConflictIteratorPtr conflictsPtr( conflicts ); - - const git_index_entry* ancestor = nullptr; - const git_index_entry* our = nullptr; - const git_index_entry* their = nullptr; - std::vector conflict_data; - - while( git_index_conflict_next( &ancestor, &our, &their, conflicts ) == 0 ) - { - // Case 3: Both files have changed - if( ancestor && our && their ) - { - ConflictData conflict_datum; - conflict_datum.filename = our->path; - conflict_datum.our_oid = our->id; - conflict_datum.their_oid = their->id; - conflict_datum.our_commit_time = our->mtime.seconds; - conflict_datum.their_commit_time = their->mtime.seconds; - conflict_datum.our_status = _( "Changed" ); - conflict_datum.their_status = _( "Changed" ); - conflict_datum.use_ours = true; - - conflict_data.push_back( conflict_datum ); - } - // Case 4: File added in both ours and theirs - else if( !ancestor && our && their ) - { - ConflictData conflict_datum; - conflict_datum.filename = our->path; - conflict_datum.our_oid = our->id; - conflict_datum.their_oid = their->id; - conflict_datum.our_commit_time = our->mtime.seconds; - conflict_datum.their_commit_time = their->mtime.seconds; - conflict_datum.our_status = _( "Added" ); - conflict_datum.their_status = _( "Added" ); - conflict_datum.use_ours = true; - - conflict_data.push_back( conflict_datum ); - } - // Case 1: Remote file has changed or been added, local file has not - else if( their && !our ) - { - // Accept their changes - git_index_add( index, their ); - } - // Case 2: Local file has changed or been added, remote file has not - else if( our && !their ) - { - // Accept our changes - git_index_add( index, our ); - } - else - { - wxLogError( wxS( "Unexpected conflict state" ) ); - } - } - - if( conflict_data.empty() ) - { - git_index_conflict_cleanup( index ); - git_index_write( index ); - } - - return conflict_data.empty() ? PullResult::Success : PullResult::MergeFailed; -} - - -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 ) { - + ReportProgress( aCurrent, aTotal, aMessage ); } diff --git a/common/git/git_pull_handler.h b/common/git/git_pull_handler.h index c553567c75..7f95155f00 100644 --- a/common/git/git_pull_handler.h +++ b/common/git/git_pull_handler.h @@ -29,7 +29,6 @@ #include #include #include -#include // Structure to store commit details struct CommitDetails @@ -50,22 +49,12 @@ enum class PullResult : int FastForward }; -struct ConflictData -{ - std::string filename; - std::string our_status; - std::string their_status; - git_oid our_oid; - git_oid their_oid; - git_time_t our_commit_time; - git_time_t their_commit_time; - bool use_ours; // Flag indicating user's choice (true = ours, false = theirs) -}; - +class LIBGIT_BACKEND; class GIT_PULL_HANDLER : public KIGIT_REPO_MIXIN { public: + friend class LIBGIT_BACKEND; GIT_PULL_HANDLER( KIGIT_COMMON* aCommon ); ~GIT_PULL_HANDLER(); @@ -77,15 +66,8 @@ public: // Implementation for progress updates void UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage ) override; - private: std::vector>> m_fetchResults; - - std::string getFirstLineFromCommitMessage( const std::string& aMessage ); - 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_ diff --git a/common/git/git_push_handler.cpp b/common/git/git_push_handler.cpp index 316011936e..41ac3f8c0b 100644 --- a/common/git/git_push_handler.cpp +++ b/common/git/git_push_handler.cpp @@ -22,13 +22,8 @@ */ #include "git_push_handler.h" -#include -#include #include - -#include - -#include +#include "git_backend.h" GIT_PUSH_HANDLER::GIT_PUSH_HANDLER( KIGIT_COMMON* aRepo ) : KIGIT_REPO_MIXIN( aRepo ) {} @@ -38,79 +33,7 @@ GIT_PUSH_HANDLER::~GIT_PUSH_HANDLER() PushResult GIT_PUSH_HANDLER::PerformPush() { - std::unique_lock lock( GetCommon()->m_gitActionMutex, std::try_to_lock ); - - if(!lock.owns_lock()) - { - wxLogTrace(traceGit, "GIT_PUSH_HANDLER::PerformPush: Could not lock mutex"); - return PushResult::Error; - } - - PushResult result = PushResult::Success; - - // Fetch updates from remote repository - git_remote* remote = nullptr; - - if(git_remote_lookup(&remote, GetRepo(), "origin") != 0) - { - AddErrorString(_("Could not lookup remote")); - return PushResult::Error; - } - - KIGIT::GitRemotePtr remotePtr(remote); - - git_remote_callbacks remoteCallbacks; - git_remote_init_callbacks(&remoteCallbacks, GIT_REMOTE_CALLBACKS_VERSION); - remoteCallbacks.sideband_progress = progress_cb; - remoteCallbacks.transfer_progress = transfer_progress_cb; - remoteCallbacks.update_tips = update_cb; - remoteCallbacks.push_transfer_progress = push_transfer_progress_cb; - remoteCallbacks.credentials = credentials_cb; - remoteCallbacks.payload = this; - GetCommon()->SetCancelled( false ); - - TestedTypes() = 0; - ResetNextKey(); - - if( git_remote_connect( remote, GIT_DIRECTION_PUSH, &remoteCallbacks, nullptr, nullptr ) ) - { - AddErrorString( wxString::Format( _( "Could not connect to remote: %s" ), - KIGIT_COMMON::GetLastGitError() ) ); - return PushResult::Error; - } - - git_push_options pushOptions; - git_push_init_options( &pushOptions, GIT_PUSH_OPTIONS_VERSION ); - pushOptions.callbacks = remoteCallbacks; - - // Get the current HEAD reference - git_reference* head = nullptr; - - if( git_repository_head( &head, GetRepo() ) != 0 ) - { - git_remote_disconnect( remote ); - AddErrorString( _( "Could not get repository head" ) ); - return PushResult::Error; - } - - KIGIT::GitReferencePtr headPtr( head ); - - // Create refspec for current branch - const char* refs[1]; - refs[0] = git_reference_name( head ); - const git_strarray refspecs = { (char**) refs, 1 }; - - if( git_remote_push( remote, &refspecs, &pushOptions ) ) - { - AddErrorString( wxString::Format( _( "Could not push to remote: %s" ), - KIGIT_COMMON::GetLastGitError() ) ); - git_remote_disconnect( remote ); - return PushResult::Error; - } - - git_remote_disconnect( remote ); - - return result; + return GetGitBackend()->Push( this ); } diff --git a/common/git/git_remove_from_index_handler.cpp b/common/git/git_remove_from_index_handler.cpp index f94c0a9116..45cb501e01 100644 --- a/common/git/git_remove_from_index_handler.cpp +++ b/common/git/git_remove_from_index_handler.cpp @@ -21,15 +21,14 @@ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ -#include +#include "git_remove_from_index_handler.h" +#include "git_backend.h" + #include -#include -#include "git_remove_from_index_handler.h" - -GIT_REMOVE_FROM_INDEX_HANDLER::GIT_REMOVE_FROM_INDEX_HANDLER( git_repository* aRepository ) +GIT_REMOVE_FROM_INDEX_HANDLER::GIT_REMOVE_FROM_INDEX_HANDLER( git_repository* aRepository ) : + KIGIT_COMMON( aRepository ) { - m_repository = aRepository; m_filesToRemove.clear(); } @@ -41,61 +40,11 @@ GIT_REMOVE_FROM_INDEX_HANDLER::~GIT_REMOVE_FROM_INDEX_HANDLER() bool GIT_REMOVE_FROM_INDEX_HANDLER::RemoveFromIndex( const wxString& aFilePath ) { - // Test if file is currently in the index - - git_index* index = nullptr; - size_t at_pos = 0; - - if( git_repository_index( &index, m_repository ) != 0 ) - { - wxLogError( "Failed to get repository index" ); - return false; - } - - KIGIT::GitIndexPtr indexPtr( index ); - - if( git_index_find( &at_pos, index, aFilePath.ToUTF8().data() ) != 0 ) - { - wxLogError( "Failed to find index entry for %s", aFilePath ); - return false; - } - - m_filesToRemove.push_back( aFilePath ); - return true; + return GetGitBackend()->RemoveFromIndex( this, aFilePath ); } void GIT_REMOVE_FROM_INDEX_HANDLER::PerformRemoveFromIndex() { - for( auto& file : m_filesToRemove ) - { - git_index* index = nullptr; - git_oid oid; - - if( git_repository_index( &index, m_repository ) != 0 ) - { - wxLogError( "Failed to get repository index" ); - return; - } - - KIGIT::GitIndexPtr indexPtr( index ); - - if( git_index_remove_bypath( index, file.ToUTF8().data() ) != 0 ) - { - wxLogError( "Failed to remove index entry for %s", file ); - return; - } - - if( git_index_write( index ) != 0 ) - { - wxLogError( "Failed to write index" ); - return; - } - - if( git_index_write_tree( &oid, index ) != 0 ) - { - wxLogError( "Failed to write index tree" ); - return; - } - } + GetGitBackend()->PerformRemoveFromIndex( this ); } diff --git a/common/git/git_remove_from_index_handler.h b/common/git/git_remove_from_index_handler.h index f2c2b02e54..53a60108ef 100644 --- a/common/git/git_remove_from_index_handler.h +++ b/common/git/git_remove_from_index_handler.h @@ -24,12 +24,13 @@ #ifndef GIT_REMOVE_FROM_INDEX_HANDLER_H_ #define GIT_REMOVE_FROM_INDEX_HANDLER_H_ -#include +#include #include +#include -class wxString; +class LIBGIT_BACKEND; -class GIT_REMOVE_FROM_INDEX_HANDLER +class GIT_REMOVE_FROM_INDEX_HANDLER : public KIGIT_COMMON { public: GIT_REMOVE_FROM_INDEX_HANDLER( git_repository* aRepository ); @@ -41,7 +42,7 @@ public: private: - git_repository* m_repository; + friend class LIBGIT_BACKEND; std::vector m_filesToRemove; }; diff --git a/common/git/git_revert_handler.cpp b/common/git/git_revert_handler.cpp index a432856aba..2f2f052070 100644 --- a/common/git/git_revert_handler.cpp +++ b/common/git/git_revert_handler.cpp @@ -22,11 +22,7 @@ */ #include "git_revert_handler.h" - -#include -#include - -#include +#include "git_backend.h" GIT_REVERT_HANDLER::GIT_REVERT_HANDLER( git_repository* aRepository ) @@ -46,74 +42,8 @@ bool GIT_REVERT_HANDLER::Revert( const wxString& aFilePath ) return true; } - -static void checkout_progress_cb( const char *path, size_t cur, size_t tot, void *payload ) -{ - wxLogTrace( traceGit, wxS( "checkout_progress_cb: %s %zu/%zu" ), path, cur, tot ); -} - - -static int checkout_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 ) -{ - GIT_REVERT_HANDLER* handler = static_cast(payload); - - if( why & ( GIT_CHECKOUT_NOTIFY_CONFLICT | GIT_CHECKOUT_NOTIFY_IGNORED - | GIT_CHECKOUT_NOTIFY_UPDATED ) ) - handler->PushFailedFile( path ); - - return 0; -} - - void GIT_REVERT_HANDLER::PerformRevert() { - git_object* head_commit = NULL; - git_checkout_options opts; - git_checkout_init_options( &opts, GIT_CHECKOUT_OPTIONS_VERSION ); - - // Get the HEAD commit - if( git_revparse_single( &head_commit, m_repository, "HEAD" ) != 0 ) - { - // Handle error. If we cannot get the HEAD, then there's no point proceeding. - return; - } - - opts.checkout_strategy = GIT_CHECKOUT_FORCE; - char** paths = new char*[m_filesToRevert.size()]; - - for( size_t ii = 0; ii < m_filesToRevert.size(); ii++ ) - { - // Set paths to the specific file - paths[ii] = wxStrdup( m_filesToRevert[ii].ToUTF8() ); - } - - git_strarray arr = { paths, m_filesToRevert.size() }; - - opts.paths = arr; - opts.progress_cb = checkout_progress_cb; - opts.notify_cb = checkout_notify_cb; - opts.notify_payload = static_cast( this ); - - // Attempt to checkout the file(s) - if( git_checkout_tree(m_repository, head_commit, &opts ) != 0 ) - { - const git_error *e = git_error_last(); - - if( e ) - { - wxLogTrace( traceGit, wxS( "Checkout failed: %d: %s" ), e->klass, e->message ); - } - } - - // Free the HEAD commit - for( size_t ii = 0; ii < m_filesToRevert.size(); ii++ ) - delete( paths[ii] ); - - delete[] paths; - - git_object_free( head_commit ); + GetGitBackend()->PerformRevert( this ); } diff --git a/common/git/git_revert_handler.h b/common/git/git_revert_handler.h index 3c4e3ddb26..5b65fbaf52 100644 --- a/common/git/git_revert_handler.h +++ b/common/git/git_revert_handler.h @@ -28,6 +28,8 @@ #include #include +class LIBGIT_BACKEND; + class GIT_REVERT_HANDLER { public: @@ -44,6 +46,7 @@ public: } private: + friend class LIBGIT_BACKEND; git_repository* m_repository; std::vector m_filesToRevert; diff --git a/common/git/git_status_handler.cpp b/common/git/git_status_handler.cpp index d3394e055c..3acde10727 100644 --- a/common/git/git_status_handler.cpp +++ b/common/git/git_status_handler.cpp @@ -22,10 +22,8 @@ */ #include "git_status_handler.h" -#include -#include #include -#include +#include "git_backend.h" GIT_STATUS_HANDLER::GIT_STATUS_HANDLER( KIGIT_COMMON* aCommon ) : KIGIT_REPO_MIXIN( aCommon ) {} @@ -35,171 +33,29 @@ GIT_STATUS_HANDLER::~GIT_STATUS_HANDLER() bool GIT_STATUS_HANDLER::HasChangedFiles() { - git_repository* repo = GetRepo(); - - if( !repo ) - return false; - - git_status_options opts; - git_status_init_options( &opts, GIT_STATUS_OPTIONS_VERSION ); - - opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX - | GIT_STATUS_OPT_SORT_CASE_SENSITIVELY; - - git_status_list* status_list = nullptr; - - if( git_status_list_new( &status_list, repo, &opts ) != GIT_OK ) - { - wxLogTrace( traceGit, "Failed to get status list: %s", KIGIT_COMMON::GetLastGitError() ); - return false; - } - - KIGIT::GitStatusListPtr status_list_ptr( status_list ); - bool hasChanges = ( git_status_list_entrycount( status_list ) > 0 ); - - return hasChanges; + return GetGitBackend()->HasChangedFiles( this ); } std::map GIT_STATUS_HANDLER::GetFileStatus( const wxString& aPathspec ) { - std::map fileStatusMap; - git_repository* repo = GetRepo(); - - if( !repo ) - return fileStatusMap; - - git_status_options status_options; - git_status_init_options( &status_options, GIT_STATUS_OPTIONS_VERSION ); - status_options.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; - status_options.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_INCLUDE_UNMODIFIED; - - // Set up pathspec if provided - std::string pathspec_str; - std::vector pathspec_ptrs; - - if( !aPathspec.IsEmpty() ) - { - pathspec_str = aPathspec.ToStdString(); - pathspec_ptrs.push_back( pathspec_str.c_str() ); - - status_options.pathspec.strings = const_cast( pathspec_ptrs.data() ); - status_options.pathspec.count = pathspec_ptrs.size(); - } - - git_status_list* status_list = nullptr; - - if( git_status_list_new( &status_list, repo, &status_options ) != GIT_OK ) - { - wxLogTrace( traceGit, "Failed to get git status list: %s", KIGIT_COMMON::GetLastGitError() ); - return fileStatusMap; - } - - KIGIT::GitStatusListPtr statusListPtr( status_list ); - - size_t count = git_status_list_entrycount( status_list ); - wxString repoWorkDir( git_repository_workdir( repo ) ); - - for( size_t ii = 0; ii < count; ++ii ) - { - const git_status_entry* entry = git_status_byindex( status_list, ii ); - std::string path( entry->head_to_index ? entry->head_to_index->old_file.path - : entry->index_to_workdir->old_file.path ); - - wxString absPath = repoWorkDir + path; - - FileStatus fileStatus; - fileStatus.filePath = absPath; - fileStatus.gitStatus = entry->status; - fileStatus.status = ConvertStatus( entry->status ); - - fileStatusMap[absPath] = fileStatus; - } - - return fileStatusMap; + return GetGitBackend()->GetFileStatus( this, aPathspec ); } wxString GIT_STATUS_HANDLER::GetCurrentBranchName() { - git_repository* repo = GetRepo(); - - if( !repo ) - return wxEmptyString; - - git_reference* currentBranchReference = nullptr; - int rc = git_repository_head( ¤tBranchReference, repo ); - KIGIT::GitReferencePtr currentBranchReferencePtr( currentBranchReference ); - - if( currentBranchReference ) - { - return git_reference_shorthand( currentBranchReference ); - } - else if( rc == GIT_EUNBORNBRANCH ) - { - // Unborn branch - could return empty or a default name - return wxEmptyString; - } - else - { - wxLogTrace( traceGit, "Failed to lookup current branch: %s", KIGIT_COMMON::GetLastGitError() ); - return wxEmptyString; - } + return GetGitBackend()->GetCurrentBranchName( this ); } void GIT_STATUS_HANDLER::UpdateRemoteStatus( const std::set& aLocalChanges, - const std::set& aRemoteChanges, - std::map& aFileStatus ) + const std::set& aRemoteChanges, + std::map& aFileStatus ) { - git_repository* repo = GetRepo(); - - if( !repo ) - return; - - wxString repoWorkDir( git_repository_workdir( repo ) ); - - // Update status based on local/remote changes - for( auto& [absPath, fileStatus] : aFileStatus ) - { - // Convert absolute path to relative path for comparison - wxString relativePath = absPath; - if( relativePath.StartsWith( repoWorkDir ) ) - { - relativePath = relativePath.Mid( repoWorkDir.length() ); -#ifdef _WIN32 - relativePath.Replace( wxS( "\\" ), wxS( "/" ) ); -#endif - } - - std::string relativePathStd = relativePath.ToStdString(); - - // Only update if the file is not already modified/added/deleted - if( fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CURRENT ) - { - if( aLocalChanges.count( relativePathStd ) ) - { - fileStatus.status = KIGIT_COMMON::GIT_STATUS::GIT_STATUS_AHEAD; - } - else if( aRemoteChanges.count( relativePathStd ) ) - { - fileStatus.status = KIGIT_COMMON::GIT_STATUS::GIT_STATUS_BEHIND; - } - } - } + GetGitBackend()->UpdateRemoteStatus( this, aLocalChanges, aRemoteChanges, aFileStatus ); } wxString GIT_STATUS_HANDLER::GetWorkingDirectory() { - git_repository* repo = GetRepo(); - - if( !repo ) - return wxEmptyString; - - const char* workdir = git_repository_workdir( repo ); - - if( !workdir ) - return wxEmptyString; - - return wxString( workdir ); + return GetGitBackend()->GetWorkingDirectory( this ); } KIGIT_COMMON::GIT_STATUS GIT_STATUS_HANDLER::ConvertStatus( unsigned int aGitStatus ) diff --git a/common/git/git_status_handler.h b/common/git/git_status_handler.h index 4c8833bf8a..8aac4cbbf7 100644 --- a/common/git/git_status_handler.h +++ b/common/git/git_status_handler.h @@ -29,6 +29,8 @@ #include #include +class LIBGIT_BACKEND; + struct FileStatus { wxString filePath; @@ -80,6 +82,7 @@ public: void UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage ) override; private: + friend class LIBGIT_BACKEND; /** * Convert git status flags to KIGIT_COMMON::GIT_STATUS * @param aGitStatus Raw git status flags diff --git a/common/git/kicad_git_common.h b/common/git/kicad_git_common.h index e3150339fa..d6d5bf4c7d 100644 --- a/common/git/kicad_git_common.h +++ b/common/git/kicad_git_common.h @@ -33,6 +33,8 @@ #include +class LIBGIT_BACKEND; + class KIGIT_COMMON { @@ -174,6 +176,7 @@ protected: friend class GIT_PUSH_HANDLER; friend class GIT_PULL_HANDLER; friend class GIT_CLONE_HANDLER; + friend class LIBGIT_BACKEND; friend class PROJECT_TREE_PANE; private: diff --git a/common/git/libgit_backend.cpp b/common/git/libgit_backend.cpp new file mode 100644 index 0000000000..00a6da838f --- /dev/null +++ b/common/git/libgit_backend.cpp @@ -0,0 +1,1358 @@ +/* + * 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 "libgit_backend.h" + +#include "git_clone_handler.h" +#include "git_commit_handler.h" +#include "git_push_handler.h" +#include "git_add_to_index_handler.h" +#include "git_remove_from_index_handler.h" +#include "git_status_handler.h" +#include "git_config_handler.h" +#include "git_init_handler.h" +#include "git_branch_handler.h" +#include "git_pull_handler.h" +#include "git_revert_handler.h" +#include "project_git_utils.h" +#include "kicad_git_common.h" +#include "kicad_git_memory.h" +#include "trace_helpers.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +static std::string getFirstLineFromCommitMessage( const std::string& aMessage ) +{ + if( aMessage.empty() ) + return aMessage; + + size_t firstLineEnd = aMessage.find_first_of( '\n' ); + + if( firstLineEnd != std::string::npos ) + return aMessage.substr( 0, firstLineEnd ); + + return aMessage; +} + +static std::string getFormattedCommitDate( const git_time& aTime ) +{ + char dateBuffer[64]; + time_t time = static_cast( aTime.time ); + struct tm timeInfo; +#ifdef _WIN32 + localtime_s( &timeInfo, &time ); +#else + gmtime_r( &time, &timeInfo ); +#endif + strftime( dateBuffer, sizeof( dateBuffer ), "%Y-%b-%d %H:%M:%S", &timeInfo ); + return dateBuffer; +} + +void LIBGIT_BACKEND::Init() +{ + git_libgit2_init(); +} + +void LIBGIT_BACKEND::Shutdown() +{ + git_libgit2_shutdown(); +} + +bool LIBGIT_BACKEND::IsLibraryAvailable() +{ +#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 ) + int major = 0, minor = 0, rev = 0; + return git_libgit2_version( &major, &minor, &rev ) == GIT_OK; +#else + // On older platforms, assume available when building with libgit2 + return true; +#endif +} + +bool LIBGIT_BACKEND::Clone( GIT_CLONE_HANDLER* aHandler ) +{ + KIGIT_COMMON* common = aHandler->GetCommon(); + std::unique_lock lock( common->m_gitActionMutex, std::try_to_lock ); + + if( !lock.owns_lock() ) + { + wxLogTrace( traceGit, "GIT_CLONE_HANDLER::PerformClone() could not lock" ); + return false; + } + + wxFileName clonePath( aHandler->GetClonePath() ); + + if( !clonePath.DirExists() ) + { + if( !clonePath.Mkdir( wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) ) + { + aHandler->AddErrorString( wxString::Format( _( "Could not create directory '%s'" ), + aHandler->GetClonePath() ) ); + return false; + } + } + + git_clone_options cloneOptions; + git_clone_init_options( &cloneOptions, GIT_CLONE_OPTIONS_VERSION ); + cloneOptions.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE; + cloneOptions.checkout_opts.progress_cb = clone_progress_cb; + cloneOptions.checkout_opts.progress_payload = aHandler; + cloneOptions.fetch_opts.callbacks.transfer_progress = transfer_progress_cb; + cloneOptions.fetch_opts.callbacks.credentials = credentials_cb; + cloneOptions.fetch_opts.callbacks.payload = aHandler; + + aHandler->TestedTypes() = 0; + aHandler->ResetNextKey(); + git_repository* newRepo = nullptr; + wxString remote = common->m_remote; + + if( git_clone( &newRepo, remote.mbc_str(), aHandler->GetClonePath().mbc_str(), + &cloneOptions ) != 0 ) + { + aHandler->AddErrorString( wxString::Format( _( "Could not clone repository '%s'" ), remote ) ); + return false; + } + + common->SetRepo( newRepo ); + + return true; +} + +CommitResult LIBGIT_BACKEND::Commit( GIT_COMMIT_HANDLER* aHandler, + const std::vector& aFiles, + const wxString& aMessage, + const wxString& aAuthorName, + const wxString& aAuthorEmail ) +{ + git_repository* repo = aHandler->GetRepo(); + + if( !repo ) + return CommitResult::Error; + + git_index* index = nullptr; + + if( git_repository_index( &index, repo ) != 0 ) + { + aHandler->AddErrorString( wxString::Format( _( "Failed to get repository index: %s" ), + KIGIT_COMMON::GetLastGitError() ) ); + return CommitResult::Error; + } + + KIGIT::GitIndexPtr indexPtr( index ); + + for( const wxString& file : aFiles ) + { + if( git_index_add_bypath( index, file.mb_str() ) != 0 ) + { + aHandler->AddErrorString( wxString::Format( _( "Failed to add file to index: %s" ), + KIGIT_COMMON::GetLastGitError() ) ); + return CommitResult::Error; + } + } + + if( git_index_write( index ) != 0 ) + { + aHandler->AddErrorString( wxString::Format( _( "Failed to write index: %s" ), + KIGIT_COMMON::GetLastGitError() ) ); + return CommitResult::Error; + } + + git_oid tree_id; + + if( git_index_write_tree( &tree_id, index ) != 0 ) + { + aHandler->AddErrorString( wxString::Format( _( "Failed to write tree: %s" ), + KIGIT_COMMON::GetLastGitError() ) ); + return CommitResult::Error; + } + + git_tree* tree = nullptr; + + if( git_tree_lookup( &tree, repo, &tree_id ) != 0 ) + { + aHandler->AddErrorString( wxString::Format( _( "Failed to lookup tree: %s" ), + KIGIT_COMMON::GetLastGitError() ) ); + return CommitResult::Error; + } + + KIGIT::GitTreePtr treePtr( tree ); + git_commit* parent = nullptr; + + if( git_repository_head_unborn( repo ) == 0 ) + { + git_reference* headRef = nullptr; + + if( git_repository_head( &headRef, repo ) != 0 ) + { + aHandler->AddErrorString( wxString::Format( _( "Failed to get HEAD reference: %s" ), + KIGIT_COMMON::GetLastGitError() ) ); + return CommitResult::Error; + } + + KIGIT::GitReferencePtr headRefPtr( headRef ); + + if( git_reference_peel( (git_object**) &parent, headRef, GIT_OBJECT_COMMIT ) != 0 ) + { + aHandler->AddErrorString( wxString::Format( _( "Failed to get commit: %s" ), + KIGIT_COMMON::GetLastGitError() ) ); + return CommitResult::Error; + } + } + + KIGIT::GitCommitPtr parentPtr( parent ); + + git_signature* author = nullptr; + + if( git_signature_now( &author, aAuthorName.mb_str(), aAuthorEmail.mb_str() ) != 0 ) + { + aHandler->AddErrorString( wxString::Format( _( "Failed to create author signature: %s" ), + KIGIT_COMMON::GetLastGitError() ) ); + return CommitResult::Error; + } + + KIGIT::GitSignaturePtr authorPtr( author ); + git_oid oid; + size_t parentsCount = parent ? 1 : 0; +#if( LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR == 8 \ + && ( LIBGIT2_VER_REVISION < 2 || LIBGIT2_VER_REVISION == 3 ) ) + git_commit* const parents[1] = { parent }; + git_commit** const parentsPtr = parent ? parents : nullptr; +#else + const git_commit* parents[1] = { parent }; + const git_commit** parentsPtr = parent ? parents : nullptr; +#endif + + + + if( git_commit_create( &oid, repo, "HEAD", author, author, nullptr, + aMessage.mb_str(), tree, parentsCount, parentsPtr ) != 0 ) + { + aHandler->AddErrorString( wxString::Format( _( "Failed to create commit: %s" ), + KIGIT_COMMON::GetLastGitError() ) ); + return CommitResult::Error; + } + + return CommitResult::Success; +} + +PushResult LIBGIT_BACKEND::Push( GIT_PUSH_HANDLER* aHandler ) +{ + KIGIT_COMMON* common = aHandler->GetCommon(); + std::unique_lock lock( common->m_gitActionMutex, std::try_to_lock ); + + if(!lock.owns_lock()) + { + wxLogTrace(traceGit, "GIT_PUSH_HANDLER::PerformPush: Could not lock mutex"); + return PushResult::Error; + } + + PushResult result = PushResult::Success; + + git_remote* remote = nullptr; + + if(git_remote_lookup(&remote, aHandler->GetRepo(), "origin") != 0) + { + aHandler->AddErrorString(_("Could not lookup remote")); + return PushResult::Error; + } + + KIGIT::GitRemotePtr remotePtr(remote); + + git_remote_callbacks remoteCallbacks; + git_remote_init_callbacks(&remoteCallbacks, GIT_REMOTE_CALLBACKS_VERSION); + remoteCallbacks.sideband_progress = progress_cb; + remoteCallbacks.transfer_progress = transfer_progress_cb; + remoteCallbacks.update_tips = update_cb; + remoteCallbacks.push_transfer_progress = push_transfer_progress_cb; + remoteCallbacks.credentials = credentials_cb; + remoteCallbacks.payload = aHandler; + common->SetCancelled( false ); + + aHandler->TestedTypes() = 0; + aHandler->ResetNextKey(); + + if( git_remote_connect( remote, GIT_DIRECTION_PUSH, &remoteCallbacks, nullptr, nullptr ) ) + { + aHandler->AddErrorString( wxString::Format( _( "Could not connect to remote: %s" ), + KIGIT_COMMON::GetLastGitError() ) ); + return PushResult::Error; + } + + git_push_options pushOptions; + git_push_init_options( &pushOptions, GIT_PUSH_OPTIONS_VERSION ); + pushOptions.callbacks = remoteCallbacks; + + git_reference* head = nullptr; + + if( git_repository_head( &head, aHandler->GetRepo() ) != 0 ) + { + git_remote_disconnect( remote ); + aHandler->AddErrorString( _( "Could not get repository head" ) ); + return PushResult::Error; + } + + KIGIT::GitReferencePtr headPtr( head ); + + const char* refs[1]; + refs[0] = git_reference_name( head ); + const git_strarray refspecs = { (char**) refs, 1 }; + + if( git_remote_push( remote, &refspecs, &pushOptions ) ) + { + aHandler->AddErrorString( wxString::Format( _( "Could not push to remote: %s" ), + KIGIT_COMMON::GetLastGitError() ) ); + git_remote_disconnect( remote ); + return PushResult::Error; + } + + git_remote_disconnect( remote ); + + return result; +} + +bool LIBGIT_BACKEND::HasChangedFiles( GIT_STATUS_HANDLER* aHandler ) +{ + git_repository* repo = aHandler->GetRepo(); + + if( !repo ) + return false; + + git_status_options opts; + git_status_init_options( &opts, GIT_STATUS_OPTIONS_VERSION ); + + opts.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX + | GIT_STATUS_OPT_SORT_CASE_SENSITIVELY; + + git_status_list* status_list = nullptr; + + if( git_status_list_new( &status_list, repo, &opts ) != GIT_OK ) + { + wxLogTrace( traceGit, "Failed to get status list: %s", KIGIT_COMMON::GetLastGitError() ); + return false; + } + + KIGIT::GitStatusListPtr status_list_ptr( status_list ); + bool hasChanges = ( git_status_list_entrycount( status_list ) > 0 ); + + return hasChanges; +} + +std::map LIBGIT_BACKEND::GetFileStatus( GIT_STATUS_HANDLER* aHandler, + const wxString& aPathspec ) +{ + std::map fileStatusMap; + git_repository* repo = aHandler->GetRepo(); + + if( !repo ) + return fileStatusMap; + + git_status_options status_options; + git_status_init_options( &status_options, GIT_STATUS_OPTIONS_VERSION ); + status_options.show = GIT_STATUS_SHOW_INDEX_AND_WORKDIR; + status_options.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_INCLUDE_UNMODIFIED; + + std::string pathspec_str; + std::vector pathspec_ptrs; + + if( !aPathspec.IsEmpty() ) + { + pathspec_str = aPathspec.ToStdString(); + pathspec_ptrs.push_back( pathspec_str.c_str() ); + + status_options.pathspec.strings = const_cast( pathspec_ptrs.data() ); + status_options.pathspec.count = pathspec_ptrs.size(); + } + + git_status_list* status_list = nullptr; + + if( git_status_list_new( &status_list, repo, &status_options ) != GIT_OK ) + { + wxLogTrace( traceGit, "Failed to get git status list: %s", KIGIT_COMMON::GetLastGitError() ); + return fileStatusMap; + } + + KIGIT::GitStatusListPtr statusListPtr( status_list ); + + size_t count = git_status_list_entrycount( status_list ); + wxString repoWorkDir( git_repository_workdir( repo ) ); + + for( size_t ii = 0; ii < count; ++ii ) + { + const git_status_entry* entry = git_status_byindex( status_list, ii ); + std::string path( entry->head_to_index ? entry->head_to_index->old_file.path + : entry->index_to_workdir->old_file.path ); + + wxString absPath = repoWorkDir + path; + + FileStatus fileStatus; + fileStatus.filePath = absPath; + fileStatus.gitStatus = entry->status; + fileStatus.status = aHandler->ConvertStatus( entry->status ); + + fileStatusMap[absPath] = fileStatus; + } + + return fileStatusMap; +} + +wxString LIBGIT_BACKEND::GetCurrentBranchName( GIT_STATUS_HANDLER* aHandler ) +{ + git_repository* repo = aHandler->GetRepo(); + + if( !repo ) + return wxEmptyString; + + git_reference* currentBranchReference = nullptr; + int rc = git_repository_head( ¤tBranchReference, repo ); + KIGIT::GitReferencePtr currentBranchReferencePtr( currentBranchReference ); + + if( currentBranchReference ) + { + return git_reference_shorthand( currentBranchReference ); + } + else if( rc == GIT_EUNBORNBRANCH ) + { + return wxEmptyString; + } + else + { + wxLogTrace( traceGit, "Failed to lookup current branch: %s", KIGIT_COMMON::GetLastGitError() ); + return wxEmptyString; + } +} + +void LIBGIT_BACKEND::UpdateRemoteStatus( GIT_STATUS_HANDLER* aHandler, + const std::set& aLocalChanges, + const std::set& aRemoteChanges, + std::map& aFileStatus ) +{ + git_repository* repo = aHandler->GetRepo(); + + if( !repo ) + return; + + wxString repoWorkDir( git_repository_workdir( repo ) ); + + for( auto& [absPath, fileStatus] : aFileStatus ) + { + wxString relativePath = absPath; + if( relativePath.StartsWith( repoWorkDir ) ) + { + relativePath = relativePath.Mid( repoWorkDir.length() ); +#ifdef _WIN32 + relativePath.Replace( wxS( "\\" ), wxS( "/" ) ); +#endif + } + + std::string relativePathStd = relativePath.ToStdString(); + + if( fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CURRENT ) + { + if( aLocalChanges.count( relativePathStd ) ) + { + fileStatus.status = KIGIT_COMMON::GIT_STATUS::GIT_STATUS_AHEAD; + } + else if( aRemoteChanges.count( relativePathStd ) ) + { + fileStatus.status = KIGIT_COMMON::GIT_STATUS::GIT_STATUS_BEHIND; + } + } + } +} + +wxString LIBGIT_BACKEND::GetWorkingDirectory( GIT_STATUS_HANDLER* aHandler ) +{ + git_repository* repo = aHandler->GetRepo(); + + if( !repo ) + return wxEmptyString; + + const char* workdir = git_repository_workdir( repo ); + + if( !workdir ) + return wxEmptyString; + + return wxString( workdir ); +} + +wxString LIBGIT_BACKEND::GetWorkingDirectory( GIT_CONFIG_HANDLER* aHandler ) +{ + git_repository* repo = aHandler->GetRepo(); + + if( !repo ) + return wxEmptyString; + + const char* workdir = git_repository_workdir( repo ); + + if( !workdir ) + return wxEmptyString; + + return wxString( workdir ); +} + +bool LIBGIT_BACKEND::GetConfigString( GIT_CONFIG_HANDLER* aHandler, const wxString& aKey, + wxString& aValue ) +{ + git_repository* repo = aHandler->GetRepo(); + + if( !repo ) + return false; + + git_config* config = nullptr; + + if( git_repository_config( &config, repo ) != GIT_OK ) + { + wxLogTrace( traceGit, "Failed to get repository config: %s", KIGIT_COMMON::GetLastGitError() ); + return false; + } + + KIGIT::GitConfigPtr configPtr( config ); + + git_config_entry* entry = nullptr; + int result = git_config_get_entry( &entry, config, aKey.mb_str() ); + KIGIT::GitConfigEntryPtr entryPtr( entry ); + + if( result != GIT_OK || entry == nullptr ) + { + wxLogTrace( traceGit, "Config key '%s' not found", aKey ); + return false; + } + + aValue = wxString( entry->value ); + return true; +} + +bool LIBGIT_BACKEND::IsRepository( GIT_INIT_HANDLER* aHandler, const wxString& aPath ) +{ + git_repository* repo = nullptr; + int error = git_repository_open( &repo, aPath.mb_str() ); + + if( error == 0 ) + { + git_repository_free( repo ); + return true; + } + + return false; +} + +InitResult LIBGIT_BACKEND::InitializeRepository( GIT_INIT_HANDLER* aHandler, const wxString& aPath ) +{ + if( IsRepository( aHandler, aPath ) ) + { + return InitResult::AlreadyExists; + } + + git_repository* repo = nullptr; + + if( git_repository_init( &repo, aPath.mb_str(), 0 ) != GIT_OK ) + { + if( repo ) + git_repository_free( repo ); + + aHandler->AddErrorString( wxString::Format( _( "Failed to initialize Git repository: %s" ), + KIGIT_COMMON::GetLastGitError() ) ); + return InitResult::Error; + } + + aHandler->GetCommon()->SetRepo( repo ); + + wxLogTrace( traceGit, "Successfully initialized Git repository at %s", aPath ); + return InitResult::Success; +} + +bool LIBGIT_BACKEND::SetupRemote( GIT_INIT_HANDLER* aHandler, const RemoteConfig& aConfig ) +{ + if( aConfig.url.IsEmpty() ) + return true; + + git_repository* repo = aHandler->GetRepo(); + + if( !repo ) + { + aHandler->AddErrorString( _( "No repository available to set up remote" ) ); + return false; + } + + aHandler->GetCommon()->SetUsername( aConfig.username ); + aHandler->GetCommon()->SetPassword( aConfig.password ); + aHandler->GetCommon()->SetSSHKey( aConfig.sshKey ); + + git_remote* remote = nullptr; + wxString fullURL; + + if( aConfig.connType == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH ) + { + fullURL = aConfig.username + "@" + aConfig.url; + } + else if( aConfig.connType == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS ) + { + fullURL = aConfig.url.StartsWith( "https" ) ? "https://" : "http://"; + + if( !aConfig.username.empty() ) + { + fullURL.append( aConfig.username ); + + if( !aConfig.password.empty() ) + { + fullURL.append( wxS( ":" ) ); + fullURL.append( aConfig.password ); + } + + fullURL.append( wxS( "@" ) ); + } + + wxString bareURL = aConfig.url; + if( bareURL.StartsWith( "https://" ) ) + bareURL = bareURL.Mid( 8 ); + else if( bareURL.StartsWith( "http://" ) ) + bareURL = bareURL.Mid( 7 ); + + fullURL.append( bareURL ); + } + else + { + fullURL = aConfig.url; + } + + int error = git_remote_create_with_fetchspec( &remote, repo, "origin", + fullURL.ToStdString().c_str(), + "+refs/heads/*:refs/remotes/origin/*" ); + + KIGIT::GitRemotePtr remotePtr( remote ); + + if( error != GIT_OK ) + { + aHandler->AddErrorString( wxString::Format( _( "Failed to create remote: %s" ), + KIGIT_COMMON::GetLastGitError() ) ); + return false; + } + + wxLogTrace( traceGit, "Successfully set up remote origin" ); + return true; +} + +static bool lookup_branch_reference( git_repository* repo, const wxString& aBranchName, + git_reference** aReference ) +{ + if( git_reference_lookup( aReference, repo, aBranchName.mb_str() ) == GIT_OK ) + return true; + + if( git_reference_dwim( aReference, repo, aBranchName.mb_str() ) == GIT_OK ) + return true; + + return false; +} + +BranchResult LIBGIT_BACKEND::SwitchToBranch( GIT_BRANCH_HANDLER* aHandler, const wxString& aBranchName ) +{ + git_repository* repo = aHandler->GetRepo(); + + if( !repo ) + { + aHandler->AddErrorString( _( "No repository available" ) ); + return BranchResult::Error; + } + + git_reference* branchRef = nullptr; + + if( !lookup_branch_reference( repo, aBranchName, &branchRef ) ) + { + aHandler->AddErrorString( wxString::Format( _( "Failed to lookup branch '%s': %s" ), + aBranchName, KIGIT_COMMON::GetLastGitError() ) ); + return BranchResult::BranchNotFound; + } + + KIGIT::GitReferencePtr branchRefPtr( branchRef ); + const char* branchRefName = git_reference_name( branchRef ); + git_object* branchObj = nullptr; + + if( git_revparse_single( &branchObj, repo, aBranchName.mb_str() ) != GIT_OK ) + { + aHandler->AddErrorString( wxString::Format( _( "Failed to find branch head for '%s': %s" ), + aBranchName, KIGIT_COMMON::GetLastGitError() ) ); + return BranchResult::Error; + } + + KIGIT::GitObjectPtr branchObjPtr( branchObj ); + + if( git_checkout_tree( repo, branchObj, nullptr ) != GIT_OK ) + { + aHandler->AddErrorString( wxString::Format( _( "Failed to switch to branch '%s': %s" ), + aBranchName, KIGIT_COMMON::GetLastGitError() ) ); + return BranchResult::CheckoutFailed; + } + + if( git_repository_set_head( repo, branchRefName ) != GIT_OK ) + { + aHandler->AddErrorString( wxString::Format( _( "Failed to update HEAD reference for branch '%s': %s" ), + aBranchName, KIGIT_COMMON::GetLastGitError() ) ); + return BranchResult::Error; + } + + wxLogTrace( traceGit, "Successfully switched to branch '%s'", aBranchName ); + return BranchResult::Success; +} + +bool LIBGIT_BACKEND::BranchExists( GIT_BRANCH_HANDLER* aHandler, const wxString& aBranchName ) +{ + git_repository* repo = aHandler->GetRepo(); + + if( !repo ) + return false; + + git_reference* branchRef = nullptr; + bool exists = lookup_branch_reference( repo, aBranchName, &branchRef ); + + if( branchRef ) + git_reference_free( branchRef ); + + return exists; +} + +// Use callbacks declared/implemented in kicad_git_common.h/.cpp + +bool LIBGIT_BACKEND::PerformFetch( GIT_PULL_HANDLER* aHandler, bool aSkipLock ) +{ + if( !aHandler->GetRepo() ) + { + wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - No repository found" ); + return false; + } + + std::unique_lock lock( aHandler->GetCommon()->m_gitActionMutex, std::try_to_lock ); + + if( !aSkipLock && !lock.owns_lock() ) + { + wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Could not lock mutex" ); + return false; + } + + git_remote* remote = nullptr; + + if( git_remote_lookup( &remote, aHandler->GetRepo(), "origin" ) != 0 ) + { + wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Failed to lookup remote 'origin'" ); + aHandler->AddErrorString( wxString::Format( _( "Could not lookup remote '%s'" ), "origin" ) ); + return false; + } + + KIGIT::GitRemotePtr remotePtr( remote ); + + git_remote_callbacks remoteCallbacks; + git_remote_init_callbacks( &remoteCallbacks, GIT_REMOTE_CALLBACKS_VERSION ); + remoteCallbacks.sideband_progress = progress_cb; + remoteCallbacks.transfer_progress = transfer_progress_cb; + remoteCallbacks.credentials = credentials_cb; + remoteCallbacks.payload = aHandler; + aHandler->GetCommon()->SetCancelled( false ); + + aHandler->TestedTypes() = 0; + aHandler->ResetNextKey(); + + if( git_remote_connect( remote, GIT_DIRECTION_FETCH, &remoteCallbacks, nullptr, nullptr ) ) + { + wxString errorMsg = KIGIT_COMMON::GetLastGitError(); + wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Failed to connect to remote: %s", errorMsg ); + aHandler->AddErrorString( wxString::Format( _( "Could not connect to remote '%s': %s" ), "origin", errorMsg ) ); + return false; + } + + git_fetch_options fetchOptions; + git_fetch_init_options( &fetchOptions, GIT_FETCH_OPTIONS_VERSION ); + fetchOptions.callbacks = remoteCallbacks; + + if( git_remote_fetch( remote, nullptr, &fetchOptions, nullptr ) ) + { + wxString errorMsg = KIGIT_COMMON::GetLastGitError(); + wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformFetch() - Failed to fetch from remote: %s", errorMsg ); + aHandler->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; +} + +PullResult LIBGIT_BACKEND::PerformPull( GIT_PULL_HANDLER* aHandler ) +{ + PullResult result = PullResult::Success; + std::unique_lock lock( aHandler->GetCommon()->m_gitActionMutex, std::try_to_lock ); + + if( !lock.owns_lock() ) + { + wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Could not lock mutex" ); + return PullResult::Error; + } + + if( !PerformFetch( aHandler, true ) ) + return PullResult::Error; + + git_oid pull_merge_oid = {}; + + if( git_repository_fetchhead_foreach( aHandler->GetRepo(), fetchhead_foreach_cb, + &pull_merge_oid ) ) + { + aHandler->AddErrorString( _( "Could not read 'FETCH_HEAD'" ) ); + return PullResult::Error; + } + + git_annotated_commit* fetchhead_commit; + + if( git_annotated_commit_lookup( &fetchhead_commit, aHandler->GetRepo(), &pull_merge_oid ) ) + { + aHandler->AddErrorString( _( "Could not lookup commit" ) ); + return PullResult::Error; + } + + 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; + + if( git_merge_analysis( &merge_analysis, &merge_preference, aHandler->GetRepo(), merge_commits, 1 ) ) + { + aHandler->AddErrorString( _( "Could not analyze merge" ) ); + return PullResult::Error; + } + + if( merge_analysis & GIT_MERGE_ANALYSIS_UNBORN ) + { + aHandler->AddErrorString( _( "Invalid HEAD. Cannot merge." ) ); + return PullResult::MergeFailed; + } + + if( merge_analysis & GIT_MERGE_ANALYSIS_UP_TO_DATE ) + { + wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Repository is up to date" ); + git_repository_state_cleanup( aHandler->GetRepo() ); + return PullResult::UpToDate; + } + + if( merge_analysis & GIT_MERGE_ANALYSIS_FASTFORWARD ) + { + wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Fast-forward merge" ); + return handleFastForward( aHandler ); + } + + if( merge_analysis & GIT_MERGE_ANALYSIS_NORMAL ) + { + wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Normal merge" ); + + git_config* config = nullptr; + + if( git_repository_config( &config, aHandler->GetRepo() ) != GIT_OK ) + { + wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Failed to get repository config" ); + aHandler->AddErrorString( _( "Could not access repository configuration" ) ); + return PullResult::Error; + } + + KIGIT::GitConfigPtr configPtr( config ); + + int rebase_value = 0; + int ret = git_config_get_bool( &rebase_value, config, "pull.rebase" ); + + if( ret == GIT_OK && rebase_value ) + { + wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Using rebase based on config" ); + return handleRebase( aHandler, merge_commits, 1 ); + } + + wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Using merge based on config" ); + return handleMerge( aHandler, merge_commits, 1 ); + } + + wxLogTrace( traceGit, "GIT_PULL_HANDLER::PerformPull() - Merge needs resolution" ); + return result; +} + +PullResult LIBGIT_BACKEND::handleFastForward( GIT_PULL_HANDLER* aHandler ) +{ + git_reference* rawRef = nullptr; + + if( git_repository_head( &rawRef, aHandler->GetRepo() ) ) + { + aHandler->AddErrorString( _( "Could not get repository head" ) ); + return PullResult::Error; + } + + KIGIT::GitReferencePtr headRef( rawRef ); + + git_oid updatedRefOid; + const char* currentBranchName = git_reference_name( rawRef ); + const char* branch_shorthand = git_reference_shorthand( rawRef ); + wxString remote_name = aHandler->GetRemotename(); + wxString remoteBranchName = wxString::Format( "refs/remotes/%s/%s", remote_name, branch_shorthand ); + + if( git_reference_name_to_id( &updatedRefOid, aHandler->GetRepo(), remoteBranchName.c_str() ) != GIT_OK ) + { + aHandler->AddErrorString( wxString::Format( _( "Could not get reference OID for reference '%s'" ), + remoteBranchName ) ); + return PullResult::Error; + } + + git_commit* targetCommit = nullptr; + + if( git_commit_lookup( &targetCommit, aHandler->GetRepo(), &updatedRefOid ) != GIT_OK ) + { + aHandler->AddErrorString( _( "Could not look up target commit" ) ); + return PullResult::Error; + } + + KIGIT::GitCommitPtr targetCommitPtr( targetCommit ); + + git_tree* targetTree = nullptr; + + if( git_commit_tree( &targetTree, targetCommit ) != GIT_OK ) + { + git_commit_free( targetCommit ); + aHandler->AddErrorString( _( "Could not get tree from target commit" ) ); + return PullResult::Error; + } + + KIGIT::GitTreePtr targetTreePtr( targetTree ); + + git_checkout_options checkoutOptions; + git_checkout_init_options( &checkoutOptions, GIT_CHECKOUT_OPTIONS_VERSION ); + 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; + } + + 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( aHandler->GetRepo(), reinterpret_cast( targetTree ), &checkoutOptions ) != GIT_OK ) + { + aHandler->AddErrorString( _( "Failed to perform checkout operation." ) ); + return PullResult::Error; + } + + git_reference* updatedRef = nullptr; + + if( git_reference_set_target( &updatedRef, rawRef, &updatedRefOid, nullptr ) != GIT_OK ) + { + aHandler->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 ); + + if( git_repository_state_cleanup( aHandler->GetRepo() ) != GIT_OK ) + { + aHandler->AddErrorString( _( "Failed to clean up repository state after fast-forward." ) ); + return PullResult::Error; + } + + git_revwalk* revWalker = nullptr; + + if( git_revwalk_new( &revWalker, aHandler->GetRepo() ) != GIT_OK ) + { + aHandler->AddErrorString( _( "Failed to initialize revision walker." ) ); + return PullResult::Error; + } + + KIGIT::GitRevWalkPtr revWalkerPtr( revWalker ); + git_revwalk_sorting( revWalker, GIT_SORT_TIME ); + + if( git_revwalk_push_glob( revWalker, currentBranchName ) != GIT_OK ) + { + aHandler->AddErrorString( _( "Failed to push reference to revision walker." ) ); + return PullResult::Error; + } + + std::pair>& branchCommits = + aHandler->m_fetchResults.emplace_back(); + branchCommits.first = currentBranchName; + + git_oid commitOid; + + while( git_revwalk_next( &commitOid, revWalker ) == GIT_OK ) + { + git_commit* commit = nullptr; + + if( git_commit_lookup( &commit, aHandler->GetRepo(), &commitOid ) ) + { + aHandler->AddErrorString( wxString::Format( _( "Could not lookup commit '%s'" ), + git_oid_tostr_s( &commitOid ) ) ); + return PullResult::Error; + } + + KIGIT::GitCommitPtr commitPtr( commit ); + + CommitDetails details; + details.m_sha = git_oid_tostr_s( &commitOid ); + details.m_firstLine = getFirstLineFromCommitMessage( git_commit_message( commit ) ); + details.m_author = git_commit_author( commit )->name; + details.m_date = getFormattedCommitDate( git_commit_author( commit )->when ); + + branchCommits.second.push_back( details ); + } + + return PullResult::FastForward; +} + +PullResult LIBGIT_BACKEND::handleMerge( GIT_PULL_HANDLER* aHandler, + const git_annotated_commit** aMergeHeads, + size_t aMergeHeadsCount ) +{ + if( git_merge( aHandler->GetRepo(), aMergeHeads, aMergeHeadsCount, nullptr, nullptr ) ) + { + aHandler->AddErrorString( _( "Merge failed" ) ); + return PullResult::MergeFailed; + } + + return PullResult::Success; +} + +PullResult LIBGIT_BACKEND::handleRebase( GIT_PULL_HANDLER* aHandler, + const git_annotated_commit** aMergeHeads, + size_t aMergeHeadsCount ) +{ + git_rebase_options rebase_opts; + git_rebase_init_options( &rebase_opts, GIT_REBASE_OPTIONS_VERSION ); + + git_rebase* rebase = nullptr; + + if( git_rebase_init( &rebase, aHandler->GetRepo(), nullptr, aMergeHeads[0], nullptr, &rebase_opts ) ) + { + aHandler->AddErrorString( _( "Rebase failed to start" ) ); + return PullResult::MergeFailed; + } + + KIGIT::GitRebasePtr rebasePtr( rebase ); + + while( true ) + { + git_rebase_operation* op = nullptr; + if( git_rebase_next( &op, rebase ) != 0 ) + break; + + if( git_rebase_commit( nullptr, rebase, nullptr, nullptr, nullptr, nullptr ) ) + { + aHandler->AddErrorString( _( "Rebase commit failed" ) ); + return PullResult::MergeFailed; + } + } + + if( git_rebase_finish( rebase, nullptr ) ) + { + aHandler->AddErrorString( _( "Rebase finish failed" ) ); + return PullResult::MergeFailed; + } + + return PullResult::Success; +} + +void LIBGIT_BACKEND::PerformRevert( GIT_REVERT_HANDLER* aHandler ) +{ + git_object* head_commit = NULL; + git_checkout_options opts; + git_checkout_init_options( &opts, GIT_CHECKOUT_OPTIONS_VERSION ); + + if( git_revparse_single( &head_commit, aHandler->m_repository, "HEAD" ) != 0 ) + { + return; + } + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + char** paths = new char*[aHandler->m_filesToRevert.size()]; + + for( size_t ii = 0; ii < aHandler->m_filesToRevert.size(); ii++ ) + { + paths[ii] = wxStrdup( aHandler->m_filesToRevert[ii].ToUTF8() ); + } + + git_strarray arr = { paths, aHandler->m_filesToRevert.size() }; + + opts.paths = arr; + opts.progress_cb = nullptr; + opts.notify_cb = nullptr; + opts.notify_payload = static_cast( aHandler ); + + if( git_checkout_tree( aHandler->m_repository, head_commit, &opts ) != 0 ) + { + const git_error* e = git_error_last(); + + if( e ) + { + wxLogTrace( traceGit, wxS( "Checkout failed: %d: %s" ), e->klass, e->message ); + } + } + + for( size_t ii = 0; ii < aHandler->m_filesToRevert.size(); ii++ ) + delete( paths[ii] ); + + delete[] paths; + + git_object_free( head_commit ); +} + +git_repository* LIBGIT_BACKEND::GetRepositoryForFile( const char* aFilename ) +{ + git_repository* repo = nullptr; + git_buf repo_path = GIT_BUF_INIT; + + if( git_repository_discover( &repo_path, aFilename, 0, nullptr ) != GIT_OK ) + { + wxLogTrace( traceGit, "Can't repo discover %s: %s", aFilename, + KIGIT_COMMON::GetLastGitError() ); + return nullptr; + } + + KIGIT::GitBufPtr repo_path_ptr( &repo_path ); + + if( git_repository_open( &repo, repo_path.ptr ) != GIT_OK ) + { + wxLogTrace( traceGit, "Can't open repo for %s: %s", repo_path.ptr, + KIGIT_COMMON::GetLastGitError() ); + return nullptr; + } + + return repo; +} + +int LIBGIT_BACKEND::CreateBranch( git_repository* aRepo, const wxString& aBranchName ) +{ + git_oid head_oid; + + if( int error = git_reference_name_to_id( &head_oid, aRepo, "HEAD" ); error != GIT_OK ) + { + wxLogTrace( traceGit, "Failed to lookup HEAD reference: %s", + KIGIT_COMMON::GetLastGitError() ); + return error; + } + + git_commit* commit = nullptr; + + if( int error = git_commit_lookup( &commit, aRepo, &head_oid ); error != GIT_OK ) + { + wxLogTrace( traceGit, "Failed to lookup commit: %s", + KIGIT_COMMON::GetLastGitError() ); + return error; + } + + KIGIT::GitCommitPtr commitPtr( commit ); + git_reference* branchRef = nullptr; + + if( int error = git_branch_create( &branchRef, aRepo, aBranchName.mb_str(), commit, 0 ); error != GIT_OK ) + { + wxLogTrace( traceGit, "Failed to create branch: %s", + KIGIT_COMMON::GetLastGitError() ); + return error; + } + + git_reference_free( branchRef ); + return 0; +} + +bool LIBGIT_BACKEND::RemoveVCS( git_repository*& aRepo, const wxString& aProjectPath, + bool aRemoveGitDir, wxString* aErrors ) +{ + if( aRepo ) + { + git_repository_free( aRepo ); + aRepo = nullptr; + } + + if( aRemoveGitDir ) + { + wxFileName gitDir( aProjectPath, wxEmptyString ); + gitDir.AppendDir( ".git" ); + + if( gitDir.DirExists() ) + { + wxString errors; + if( !RmDirRecursive( gitDir.GetPath(), &errors ) ) + { + if( aErrors ) + *aErrors = errors; + + wxLogTrace( traceGit, "Failed to remove .git directory: %s", errors ); + return false; + } + } + } + + wxLogTrace( traceGit, "Successfully removed VCS from project" ); + return true; +} + +bool LIBGIT_BACKEND::AddToIndex( GIT_ADD_TO_INDEX_HANDLER* aHandler, const wxString& aFilePath ) +{ + git_repository* repo = aHandler->GetRepo(); + + git_index* index = nullptr; + size_t at_pos = 0; + + if( git_repository_index( &index, repo ) != 0 ) + { + wxLogError( "Failed to get repository index" ); + return false; + } + + KIGIT::GitIndexPtr indexPtr( index ); + + if( git_index_find( &at_pos, index, aFilePath.ToUTF8().data() ) == GIT_OK ) + { + wxLogError( "%s already in index", aFilePath ); + return false; + } + + aHandler->m_filesToAdd.push_back( aFilePath ); + return true; +} + +bool LIBGIT_BACKEND::PerformAddToIndex( GIT_ADD_TO_INDEX_HANDLER* aHandler ) +{ + git_repository* repo = aHandler->GetRepo(); + git_index* index = nullptr; + + aHandler->m_filesFailedToAdd.clear(); + + if( git_repository_index( &index, repo ) != 0 ) + { + wxLogError( "Failed to get repository index" ); + std::copy( aHandler->m_filesToAdd.begin(), aHandler->m_filesToAdd.end(), + std::back_inserter( aHandler->m_filesFailedToAdd ) ); + return false; + } + + KIGIT::GitIndexPtr indexPtr( index ); + + for( auto& file : aHandler->m_filesToAdd ) + { + if( git_index_add_bypath( index, file.ToUTF8().data() ) != 0 ) + { + wxLogError( "Failed to add %s to index", file ); + aHandler->m_filesFailedToAdd.push_back( file ); + continue; + } + } + + if( git_index_write( index ) != 0 ) + { + wxLogError( "Failed to write index" ); + aHandler->m_filesFailedToAdd.clear(); + std::copy( aHandler->m_filesToAdd.begin(), aHandler->m_filesToAdd.end(), + std::back_inserter( aHandler->m_filesFailedToAdd ) ); + return false; + } + + return true; +} + +bool LIBGIT_BACKEND::RemoveFromIndex( GIT_REMOVE_FROM_INDEX_HANDLER* aHandler, + const wxString& aFilePath ) +{ + git_repository* repo = aHandler->GetRepo(); + git_index* index = nullptr; + size_t at_pos = 0; + + if( git_repository_index( &index, repo ) != 0 ) + { + wxLogError( "Failed to get repository index" ); + return false; + } + + KIGIT::GitIndexPtr indexPtr( index ); + + if( git_index_find( &at_pos, index, aFilePath.ToUTF8().data() ) != 0 ) + { + wxLogError( "Failed to find index entry for %s", aFilePath ); + return false; + } + + aHandler->m_filesToRemove.push_back( aFilePath ); + return true; +} + +void LIBGIT_BACKEND::PerformRemoveFromIndex( GIT_REMOVE_FROM_INDEX_HANDLER* aHandler ) +{ + git_repository* repo = aHandler->GetRepo(); + + for( auto& file : aHandler->m_filesToRemove ) + { + git_index* index = nullptr; + git_oid oid; + + if( git_repository_index( &index, repo ) != 0 ) + { + wxLogError( "Failed to get repository index" ); + return; + } + + KIGIT::GitIndexPtr indexPtr( index ); + + if( git_index_remove_bypath( index, file.ToUTF8().data() ) != 0 ) + { + wxLogError( "Failed to remove index entry for %s", file ); + return; + } + + if( git_index_write( index ) != 0 ) + { + wxLogError( "Failed to write index" ); + return; + } + + if( git_index_write_tree( &oid, index ) != 0 ) + { + wxLogError( "Failed to write index tree" ); + return; + } + } +} diff --git a/common/git/libgit_backend.h b/common/git/libgit_backend.h new file mode 100644 index 0000000000..350e0e1b56 --- /dev/null +++ b/common/git/libgit_backend.h @@ -0,0 +1,100 @@ +/* + * 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 + */ + +#ifndef LIBGIT_BACKEND_H_ +#define LIBGIT_BACKEND_H_ + +#include "git_backend.h" + +// Forward declarations to avoid exposing libgit2 headers +struct git_annotated_commit; + +class LIBGIT_BACKEND : public GIT_BACKEND +{ +public: + void Init() override; + void Shutdown() override; + bool IsLibraryAvailable() override; + + bool Clone( GIT_CLONE_HANDLER* aHandler ) override; + + CommitResult Commit( GIT_COMMIT_HANDLER* aHandler, + const std::vector& aFiles, + const wxString& aMessage, + const wxString& aAuthorName, + const wxString& aAuthorEmail ) override; + + PushResult Push( GIT_PUSH_HANDLER* aHandler ) override; + + bool HasChangedFiles( GIT_STATUS_HANDLER* aHandler ) override; + + std::map GetFileStatus( GIT_STATUS_HANDLER* aHandler, + const wxString& aPathspec ) override; + + wxString GetCurrentBranchName( GIT_STATUS_HANDLER* aHandler ) override; + + void UpdateRemoteStatus( GIT_STATUS_HANDLER* aHandler, + const std::set& aLocalChanges, + const std::set& aRemoteChanges, + std::map& aFileStatus ) override; + + wxString GetWorkingDirectory( GIT_STATUS_HANDLER* aHandler ) override; + + wxString GetWorkingDirectory( GIT_CONFIG_HANDLER* aHandler ) override; + bool GetConfigString( GIT_CONFIG_HANDLER* aHandler, const wxString& aKey, + wxString& aValue ) override; + + bool IsRepository( GIT_INIT_HANDLER* aHandler, const wxString& aPath ) override; + InitResult InitializeRepository( GIT_INIT_HANDLER* aHandler, const wxString& aPath ) override; + bool SetupRemote( GIT_INIT_HANDLER* aHandler, const RemoteConfig& aConfig ) override; + + BranchResult SwitchToBranch( GIT_BRANCH_HANDLER* aHandler, const wxString& aBranchName ) override; + bool BranchExists( GIT_BRANCH_HANDLER* aHandler, const wxString& aBranchName ) override; + + bool PerformFetch( GIT_PULL_HANDLER* aHandler, bool aSkipLock ) override; + PullResult PerformPull( GIT_PULL_HANDLER* aHandler ) override; + + void PerformRevert( GIT_REVERT_HANDLER* aHandler ) override; + + git_repository* GetRepositoryForFile( const char* aFilename ) override; + int CreateBranch( git_repository* aRepo, const wxString& aBranchName ) override; + bool RemoveVCS( git_repository*& aRepo, const wxString& aProjectPath, + bool aRemoveGitDir, wxString* aErrors ) override; + + bool AddToIndex( GIT_ADD_TO_INDEX_HANDLER* aHandler, const wxString& aFilePath ) override; + + bool PerformAddToIndex( GIT_ADD_TO_INDEX_HANDLER* aHandler ) override; + + bool RemoveFromIndex( GIT_REMOVE_FROM_INDEX_HANDLER* aHandler, const wxString& aFilePath ) override; + + void PerformRemoveFromIndex( GIT_REMOVE_FROM_INDEX_HANDLER* aHandler ) override; + +private: + PullResult handleFastForward( GIT_PULL_HANDLER* aHandler ); + PullResult handleMerge( GIT_PULL_HANDLER* aHandler, const git_annotated_commit** aMergeHeads, + size_t aMergeHeadsCount ); + PullResult handleRebase( GIT_PULL_HANDLER* aHandler, const git_annotated_commit** aMergeHeads, + size_t aMergeHeadsCount ); +}; + +#endif diff --git a/common/git/project_git_utils.cpp b/common/git/project_git_utils.cpp index 6b80036094..c3e1c2cbc7 100644 --- a/common/git/project_git_utils.cpp +++ b/common/git/project_git_utils.cpp @@ -22,105 +22,25 @@ */ #include "project_git_utils.h" -#include "kicad_git_common.h" -#include "kicad_git_memory.h" -#include -#include -#include -#include -#include +#include "git_backend.h" namespace KIGIT { git_repository* PROJECT_GIT_UTILS::GetRepositoryForFile( const char* aFilename ) { - git_repository* repo = nullptr; - git_buf repo_path = GIT_BUF_INIT; - - if( git_repository_discover( &repo_path, aFilename, 0, nullptr ) != GIT_OK ) - { - wxLogTrace( traceGit, "Can't repo discover %s: %s", aFilename, - KIGIT_COMMON::GetLastGitError() ); - return nullptr; - } - - KIGIT::GitBufPtr repo_path_ptr( &repo_path ); - - if( git_repository_open( &repo, repo_path.ptr ) != GIT_OK ) - { - wxLogTrace( traceGit, "Can't open repo for %s: %s", repo_path.ptr, - KIGIT_COMMON::GetLastGitError() ); - return nullptr; - } - - return repo; + return GetGitBackend()->GetRepositoryForFile( aFilename ); } int PROJECT_GIT_UTILS::CreateBranch( git_repository* aRepo, const wxString& aBranchName ) { - git_oid head_oid; - - if( int error = git_reference_name_to_id( &head_oid, aRepo, "HEAD" ); error != GIT_OK ) - { - wxLogTrace( traceGit, "Failed to lookup HEAD reference: %s", - KIGIT_COMMON::GetLastGitError() ); - return error; - } - - git_commit* commit = nullptr; - - if( int error = git_commit_lookup( &commit, aRepo, &head_oid ); error != GIT_OK ) - { - wxLogTrace( traceGit, "Failed to lookup commit: %s", - KIGIT_COMMON::GetLastGitError() ); - return error; - } - - KIGIT::GitCommitPtr commitPtr( commit ); - git_reference* branchRef = nullptr; - - if( int error = git_branch_create( &branchRef, aRepo, aBranchName.mb_str(), commit, 0 ); error != GIT_OK ) - { - wxLogTrace( traceGit, "Failed to create branch: %s", - KIGIT_COMMON::GetLastGitError() ); - return error; - } - - git_reference_free( branchRef ); - return 0; + return GetGitBackend()->CreateBranch( aRepo, aBranchName ); } bool PROJECT_GIT_UTILS::RemoveVCS( git_repository*& aRepo, const wxString& aProjectPath, bool aRemoveGitDir, wxString* aErrors ) { - if( aRepo ) - { - git_repository_free( aRepo ); - aRepo = nullptr; - } - - if( aRemoveGitDir ) - { - wxFileName gitDir( aProjectPath, wxEmptyString ); - gitDir.AppendDir( ".git" ); - - if( gitDir.DirExists() ) - { - wxString errors; - if( !RmDirRecursive( gitDir.GetPath(), &errors ) ) - { - if( aErrors ) - *aErrors = errors; - - wxLogTrace( traceGit, "Failed to remove .git directory: %s", errors ); - return false; - } - } - } - - wxLogTrace( traceGit, "Successfully removed VCS from project" ); - return true; + return GetGitBackend()->RemoveVCS( aRepo, aProjectPath, aRemoveGitDir, aErrors ); } -} // namespace KIGIT \ No newline at end of file +} // namespace KIGIT diff --git a/kicad/kicad.cpp b/kicad/kicad.cpp index c1b44198e4..e3bcdfaed0 100644 --- a/kicad/kicad.cpp +++ b/kicad/kicad.cpp @@ -49,7 +49,8 @@ #include #include -#include +#include +#include #include #include "pgm_kicad.h" @@ -97,8 +98,9 @@ bool PGM_KICAD::OnPgmInit() } #endif - // Initialize the git library before trying to initialize individual programs - git_libgit2_init(); + // Initialize the git backend before trying to initialize individual programs + SetGitBackend( new LIBGIT_BACKEND() ); + GetGitBackend()->Init(); static const wxCmdLineEntryDesc desc[] = { { wxCMD_LINE_OPTION, "f", "frame", "Frame to load", wxCMD_LINE_VAL_STRING, 0 }, @@ -401,7 +403,9 @@ void PGM_KICAD::OnPgmExit() // especially wxSingleInstanceCheckerImpl earlier than wxApp and earlier // than static destruction would. Destroy(); - git_libgit2_shutdown(); + GetGitBackend()->Shutdown(); + delete GetGitBackend(); + SetGitBackend( nullptr ); } diff --git a/kicad/project_tree_pane.cpp b/kicad/project_tree_pane.cpp index 043940a735..5e03b49b65 100644 --- a/kicad/project_tree_pane.cpp +++ b/kicad/project_tree_pane.cpp @@ -24,7 +24,7 @@ */ #include -#include +#include #include #include @@ -802,14 +802,8 @@ void PROJECT_TREE_PANE::onRight( wxTreeEvent& Event ) bool vcs_can_switch = vcs_has_repo; bool vcs_menu = Pgm().GetCommonSettings()->m_Git.enableGit; - // Check if the libgit2 library has been successfully initialized -#if ( LIBGIT2_VER_MAJOR >= 1 ) || ( LIBGIT2_VER_MINOR >= 99 ) - int major, minor, rev; - bool libgit_init = ( git_libgit2_version( &major, &minor, &rev ) == GIT_OK ); -#else - //Work around libgit2 API change for supporting older platforms - bool libgit_init = true; -#endif + // Check if the libgit2 library is available via backend + bool libgit_init = GetGitBackend() && GetGitBackend()->IsLibraryAvailable(); vcs_menu &= libgit_init; @@ -2176,7 +2170,7 @@ void PROJECT_TREE_PANE::onGitCommit( wxCommandEvent& aEvent ) auto result = commitHandler.PerformCommit( files, dlg.GetCommitMessage(), dlg.GetAuthorName(), dlg.GetAuthorEmail() ); - if( result != GIT_COMMIT_HANDLER::CommitResult::Success ) + if( result != CommitResult::Success ) { wxMessageBox( wxString::Format( _( "Failed to create commit: %s" ), commitHandler.GetErrorString() ) );