Browse Source

Refactor git calls into their own namespace

pull/18/head
Seth Hillbrand 3 months ago
parent
commit
f2b6ac5d18
  1. 5
      common/CMakeLists.txt
  2. 126
      common/git/git_branch_handler.cpp
  3. 71
      common/git/git_branch_handler.h
  4. 109
      common/git/git_config_handler.cpp
  5. 69
      common/git/git_config_handler.h
  6. 151
      common/git/git_init_handler.cpp
  7. 76
      common/git/git_init_handler.h
  8. 232
      common/git/git_status_handler.cpp
  9. 91
      common/git/git_status_handler.h
  10. 35
      common/git/project_git_utils.cpp
  11. 15
      common/git/project_git_utils.h
  12. 495
      kicad/project_tree_pane.cpp

5
common/CMakeLists.txt

@ -601,17 +601,22 @@ set( COMMON_IMPORT_GFX_SRCS
set( COMMON_GIT_SRCS
git/git_add_to_index_handler.cpp
git/git_branch_handler.cpp
git/git_clone_handler.cpp
git/git_commit_handler.cpp
git/git_config_handler.cpp
git/git_init_handler.cpp
git/git_pull_handler.cpp
git/git_push_handler.cpp
git/git_remove_from_index_handler.cpp
git/git_resolve_conflict_handler.cpp
git/git_revert_handler.cpp
git/git_status_handler.cpp
git/git_sync_handler.cpp
git/project_git_utils.cpp
git/kicad_git_common.cpp
git/kicad_git_errors.cpp
git/project_git_utils.cpp
)
set( COMMON_SRCS

126
common/git/git_branch_handler.cpp

@ -0,0 +1,126 @@
/*
* 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_branch_handler.h"
#include <git/kicad_git_common.h>
#include <git/kicad_git_memory.h>
#include <trace_helpers.h>
#include <wx/log.h>
GIT_BRANCH_HANDLER::GIT_BRANCH_HANDLER( KIGIT_COMMON* aCommon ) : KIGIT_REPO_MIXIN( aCommon )
{}
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;
}
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;
}
void GIT_BRANCH_HANDLER::UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage )
{
ReportProgress( aCurrent, aTotal, aMessage );
}

71
common/git/git_branch_handler.h

@ -0,0 +1,71 @@
/*
* 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_BRANCH_HANDLER_H
#define GIT_BRANCH_HANDLER_H
#include <git/git_repo_mixin.h>
#include <wx/string.h>
#include <vector>
enum class BranchResult
{
Success,
BranchNotFound,
CheckoutFailed,
Error
};
class GIT_BRANCH_HANDLER : public KIGIT_REPO_MIXIN
{
public:
GIT_BRANCH_HANDLER( KIGIT_COMMON* aCommon );
virtual ~GIT_BRANCH_HANDLER();
/**
* Switch to the specified branch
* @param aBranchName Name of the branch to switch to
* @return BranchResult indicating the outcome of the operation
*/
BranchResult SwitchToBranch( const wxString& aBranchName );
/**
* Check if a branch exists
* @param aBranchName Name of the branch to check
* @return True if branch exists, false otherwise
*/
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

109
common/git/git_config_handler.cpp

@ -0,0 +1,109 @@
/*
* 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_config_handler.h"
#include <git/kicad_git_common.h>
#include <git/kicad_git_memory.h>
#include <pgm_base.h>
#include <settings/common_settings.h>
#include <trace_helpers.h>
#include <wx/log.h>
GIT_CONFIG_HANDLER::GIT_CONFIG_HANDLER( KIGIT_COMMON* aCommon ) : KIGIT_REPO_MIXIN( aCommon )
{}
GIT_CONFIG_HANDLER::~GIT_CONFIG_HANDLER()
{}
GitUserConfig GIT_CONFIG_HANDLER::GetUserConfig()
{
GitUserConfig userConfig;
// Try to get from git config first
userConfig.hasName = GetConfigString( "user.name", userConfig.authorName );
userConfig.hasEmail = GetConfigString( "user.email", userConfig.authorEmail );
// Fall back to common settings if not found in git config
if( !userConfig.hasName )
{
userConfig.authorName = Pgm().GetCommonSettings()->m_Git.authorName;
}
if( !userConfig.hasEmail )
{
userConfig.authorEmail = Pgm().GetCommonSettings()->m_Git.authorEmail;
}
return userConfig;
}
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 );
}
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;
}
void GIT_CONFIG_HANDLER::UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage )
{
ReportProgress( aCurrent, aTotal, aMessage );
}

69
common/git/git_config_handler.h

@ -0,0 +1,69 @@
/*
* 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_CONFIG_HANDLER_H
#define GIT_CONFIG_HANDLER_H
#include <git/git_repo_mixin.h>
#include <wx/string.h>
struct GitUserConfig
{
wxString authorName;
wxString authorEmail;
bool hasName = false;
bool hasEmail = false;
};
class GIT_CONFIG_HANDLER : public KIGIT_REPO_MIXIN
{
public:
GIT_CONFIG_HANDLER( KIGIT_COMMON* aCommon );
virtual ~GIT_CONFIG_HANDLER();
/**
* Get user configuration (name and email) from git config
* Falls back to common settings if not found in git config
* @return GitUserConfig structure with author information
*/
GitUserConfig GetUserConfig();
/**
* Get the repository working directory path
* @return Working directory path, or empty string if not available
*/
wxString GetWorkingDirectory();
void UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage ) override;
private:
/**
* Get a string value from git config
* @param aKey Configuration key to retrieve
* @param aValue Output parameter for the value
* @return True if value was found, false otherwise
*/
bool GetConfigString( const wxString& aKey, wxString& aValue );
};
#endif // GIT_CONFIG_HANDLER_H

151
common/git/git_init_handler.cpp

@ -0,0 +1,151 @@
/*
* 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_init_handler.h"
#include <git/kicad_git_common.h>
#include <git/kicad_git_memory.h>
#include <trace_helpers.h>
#include <wx/log.h>
GIT_INIT_HANDLER::GIT_INIT_HANDLER( KIGIT_COMMON* aCommon ) : KIGIT_REPO_MIXIN( aCommon )
{}
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;
}
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;
}
bool GIT_INIT_HANDLER::SetupRemote( const RemoteConfig& aConfig )
{
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;
}
void GIT_INIT_HANDLER::UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage )
{
ReportProgress( aCurrent, aTotal, aMessage );
}

76
common/git/git_init_handler.h

@ -0,0 +1,76 @@
/*
* 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_INIT_HANDLER_H
#define GIT_INIT_HANDLER_H
#include <git/git_repo_mixin.h>
#include <wx/string.h>
enum class InitResult
{
Success,
AlreadyExists,
Error
};
struct RemoteConfig
{
wxString url;
wxString username;
wxString password;
wxString sshKey;
KIGIT_COMMON::GIT_CONN_TYPE connType;
};
class GIT_INIT_HANDLER : public KIGIT_REPO_MIXIN
{
public:
GIT_INIT_HANDLER( KIGIT_COMMON* aCommon );
virtual ~GIT_INIT_HANDLER();
/**
* Check if a directory is already a git repository
* @param aPath Directory path to check
* @return True if directory contains a git repository
*/
bool IsRepository( const wxString& aPath );
/**
* Initialize a new git repository in the specified directory
* @param aPath Directory path where to initialize the repository
* @return InitResult indicating success, already exists, or error
*/
InitResult InitializeRepository( const wxString& aPath );
/**
* Set up a remote for the repository
* @param aConfig Remote configuration parameters
* @return True on success, false on error
*/
bool SetupRemote( const RemoteConfig& aConfig );
void UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage ) override;
};
#endif // GIT_INIT_HANDLER_H

232
common/git/git_status_handler.cpp

@ -0,0 +1,232 @@
/*
* 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_status_handler.h"
#include <git/kicad_git_common.h>
#include <git/kicad_git_memory.h>
#include <trace_helpers.h>
#include <wx/log.h>
GIT_STATUS_HANDLER::GIT_STATUS_HANDLER( KIGIT_COMMON* aCommon ) : KIGIT_REPO_MIXIN( aCommon )
{}
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;
}
std::map<wxString, FileStatus> GIT_STATUS_HANDLER::GetFileStatus( const wxString& aPathspec )
{
std::map<wxString, FileStatus> 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
const char* pathspec_cstr = nullptr;
if( !aPathspec.IsEmpty() )
{
pathspec_cstr = aPathspec.c_str().AsChar();
const char* pathspec[] = { pathspec_cstr };
status_options.pathspec = { (char**) pathspec, 1 };
}
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;
}
wxString GIT_STATUS_HANDLER::GetCurrentBranchName()
{
git_repository* repo = GetRepo();
if( !repo )
return wxEmptyString;
git_reference* currentBranchReference = nullptr;
int rc = git_repository_head( &currentBranchReference, 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;
}
}
void GIT_STATUS_HANDLER::UpdateRemoteStatus( const std::set<wxString>& aLocalChanges,
const std::set<wxString>& aRemoteChanges,
std::map<wxString, FileStatus>& 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;
}
}
}
}
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 );
}
KIGIT_COMMON::GIT_STATUS GIT_STATUS_HANDLER::ConvertStatus( unsigned int aGitStatus )
{
if( aGitStatus & GIT_STATUS_IGNORED )
{
return KIGIT_COMMON::GIT_STATUS::GIT_STATUS_IGNORED;
}
else if( aGitStatus & ( GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED ) )
{
return KIGIT_COMMON::GIT_STATUS::GIT_STATUS_MODIFIED;
}
else if( aGitStatus & ( GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_NEW ) )
{
return KIGIT_COMMON::GIT_STATUS::GIT_STATUS_ADDED;
}
else if( aGitStatus & ( GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_DELETED ) )
{
return KIGIT_COMMON::GIT_STATUS::GIT_STATUS_DELETED;
}
else if( aGitStatus & ( GIT_STATUS_CONFLICTED ) )
{
return KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CONFLICTED;
}
else
{
return KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CURRENT;
}
}
void GIT_STATUS_HANDLER::UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage )
{
ReportProgress( aCurrent, aTotal, aMessage );
}

91
common/git/git_status_handler.h

@ -0,0 +1,91 @@
/*
* 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_STATUS_HANDLER_H
#define GIT_STATUS_HANDLER_H
#include <git/git_repo_mixin.h>
#include <wx/string.h>
#include <map>
#include <set>
struct FileStatus
{
wxString filePath;
KIGIT_COMMON::GIT_STATUS status;
unsigned int gitStatus; // Raw git status flags
};
class GIT_STATUS_HANDLER : public KIGIT_REPO_MIXIN
{
public:
GIT_STATUS_HANDLER( KIGIT_COMMON* aCommon );
virtual ~GIT_STATUS_HANDLER();
/**
* Check if the repository has any changed files
* @return True if there are uncommitted changes, false otherwise
*/
bool HasChangedFiles();
/**
* Get detailed file status for all files in the specified path
* @param aPathspec Path specification to filter files (empty for all files)
* @return Map of absolute file paths to their status information
*/
std::map<wxString, FileStatus> GetFileStatus( const wxString& aPathspec = wxEmptyString );
/**
* Get the current branch name
* @return Current branch name, or empty string if not available
*/
wxString GetCurrentBranchName();
/**
* Get status for modified files based on local/remote changes
* @param aLocalChanges Set of files with local changes
* @param aRemoteChanges Set of files with remote changes
* @param aFileStatus Map to update with ahead/behind status
*/
void UpdateRemoteStatus( const std::set<wxString>& aLocalChanges,
const std::set<wxString>& aRemoteChanges,
std::map<wxString, FileStatus>& aFileStatus );
/**
* Get the repository working directory path
* @return Working directory path, or empty string if not available
*/
wxString GetWorkingDirectory();
void UpdateProgress( int aCurrent, int aTotal, const wxString& aMessage ) override;
private:
/**
* Convert git status flags to KIGIT_COMMON::GIT_STATUS
* @param aGitStatus Raw git status flags
* @return Converted status
*/
KIGIT_COMMON::GIT_STATUS ConvertStatus( unsigned int aGitStatus );
};
#endif // GIT_STATUS_HANDLER_H

35
common/git/project_git_utils.cpp

@ -26,6 +26,8 @@
#include "kicad_git_memory.h"
#include <trace_helpers.h>
#include <wx/log.h>
#include <wx/filename.h>
#include <gestfich.h>
namespace KIGIT
{
@ -88,5 +90,36 @@ int PROJECT_GIT_UTILS::CreateBranch( git_repository* aRepo, const wxString& aBra
return 0;
}
} // namespace KIGIT
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;
}
} // namespace KIGIT

15
common/git/project_git_utils.h

@ -50,8 +50,21 @@ public:
* @return 0 on success, libgit2 error code on failure.
*/
static int CreateBranch( git_repository* aRepo, const wxString& aBranchName );
/**
* Remove version control from a directory by freeing the repository and
* optionally removing the .git directory.
*
* @param aRepo Repository to free (will be set to nullptr)
* @param aProjectPath Path to the project directory
* @param aRemoveGitDir If true, also remove the .git directory from disk
* @param aErrors Output parameter for any error messages
* @return True on success, false on failure
*/
static bool RemoveVCS( git_repository*& aRepo, const wxString& aProjectPath = wxEmptyString,
bool aRemoveGitDir = false, wxString* aErrors = nullptr );
};
} // namespace KIGIT
#endif // PROJECT_GIT_UTILS_H
#endif // PROJECT_GIT_UTILS_H

495
kicad/project_tree_pane.cpp

@ -57,6 +57,7 @@
#include <wx/settings.h>
#include <git/git_commit_handler.h>
#include <git/git_config_handler.h>
#include <git/git_pull_handler.h>
#include <git/git_push_handler.h>
#include <git/git_resolve_conflict_handler.h>
@ -65,6 +66,9 @@
#include <git/git_sync_handler.h>
#include <git/git_clone_handler.h>
#include <git/kicad_git_compat.h>
#include <git/git_init_handler.h>
#include <git/git_branch_handler.h>
#include <git/git_status_handler.h>
#include <git/kicad_git_memory.h>
#include <git/project_git_utils.h>
@ -650,7 +654,8 @@ void PROJECT_TREE_PANE::ReCreateTreePrj()
if( m_TreeProject->GetGitRepo() )
{
git_repository_free( m_TreeProject->GetGitRepo() );
git_repository* repo = m_TreeProject->GetGitRepo();
KIGIT::PROJECT_GIT_UTILS::RemoveVCS( repo );
m_TreeProject->SetGitRepo( nullptr );
m_gitIconsInitialized = false;
}
@ -753,28 +758,11 @@ void PROJECT_TREE_PANE::ReCreateTreePrj()
bool PROJECT_TREE_PANE::hasChangedFiles()
{
git_repository* repo = m_TreeProject->GetGitRepo();
if( !repo )
if( !m_TreeProject->GetGitRepo() )
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 )
{
wxLogError( _( "Failed to get status list: %s" ), KIGIT_COMMON::GetLastGitError() );
return false;
}
KIGIT::GitStatusListPtr status_list_ptr( status_list );
return ( git_status_list_entrycount( status_list ) > 0 );
GIT_STATUS_HANDLER statusHandler( m_TreeProject->GitCommon() );
return statusHandler.HasChangedFiles();
}
@ -1626,7 +1614,8 @@ void PROJECT_TREE_PANE::EmptyTreePrj()
}
}
git_repository_free( m_TreeProject->GetGitRepo() );
git_repository* repo = m_TreeProject->GetGitRepo();
KIGIT::PROJECT_GIT_UTILS::RemoveVCS( repo );
m_TreeProject->SetGitRepo( nullptr );
}
}
@ -1673,110 +1662,51 @@ void PROJECT_TREE_PANE::onGitInitializeProject( wxCommandEvent& aEvent )
return;
}
// Check if the directory is already a git repository
git_repository* repo = nullptr;
int error = git_repository_open( &repo, dir.mb_str() );
GIT_INIT_HANDLER initHandler( m_TreeProject->GitCommon() );
if( error == 0 )
if( initHandler.IsRepository( dir ) )
{
// Directory is already a git repository
wxWindow* topLevelParent = wxGetTopLevelParent( this );
DisplayInfoMessage( topLevelParent,
_( "The selected directory is already a Git project." ) );
git_repository_free( repo );
return;
}
DIALOG_GIT_REPOSITORY dlg( wxGetTopLevelParent( this ), nullptr );
dlg.SetTitle( _( "Set default remote" ) );
if( dlg.ShowModal() != wxID_OK )
return;
// Directory is not a git repository
if( git_repository_init( &repo, dir.mb_str(), 0 ) != GIT_OK )
InitResult result = initHandler.InitializeRepository( dir );
if( result != InitResult::Success )
{
if( repo )
git_repository_free( repo );
if( m_gitLastError != git_error_last()->klass )
{
m_gitLastError = git_error_last()->klass;
DisplayErrorMessage( m_parent, _( "Failed to initialize Git project." ),
KIGIT_COMMON::GetLastGitError() );
}
DisplayErrorMessage( m_parent, _( "Failed to initialize Git project." ),
initHandler.GetErrorString() );
return;
}
else
{
m_TreeProject->SetGitRepo( repo );
m_gitLastError = GIT_ERROR_NONE;
}
//Set up the git remote
m_TreeProject->GitCommon()->SetPassword( dlg.GetPassword() );
m_TreeProject->GitCommon()->SetUsername( dlg.GetUsername() );
m_TreeProject->GitCommon()->SetSSHKey( dlg.GetRepoSSHPath() );
git_remote* remote = nullptr;
wxString fullURL;
if( dlg.GetRepoType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_SSH )
{
fullURL = dlg.GetUsername() + "@" + dlg.GetRepoURL();
}
else if( dlg.GetRepoType() == KIGIT_COMMON::GIT_CONN_TYPE::GIT_CONN_HTTPS )
{
fullURL = dlg.GetRepoURL().StartsWith( "https" ) ? "https://" : "http://";
if( !dlg.GetUsername().empty() )
{
fullURL.append( dlg.GetUsername() );
if( !dlg.GetPassword().empty() )
{
fullURL.append( wxS( ":" ) );
fullURL.append( dlg.GetPassword() );
}
fullURL.append( wxS( "@" ) );
}
fullURL.append( dlg.GetBareRepoURL() );
}
else
{
fullURL = dlg.GetRepoURL();
}
error = git_remote_create_with_fetchspec( &remote, repo, "origin",
fullURL.ToStdString().c_str(),
"+refs/heads/*:refs/remotes/origin/*" );
m_gitLastError = GIT_ERROR_NONE;
KIGIT::GitRemotePtr remotePtr( remote );
// Set up the remote
RemoteConfig remoteConfig;
remoteConfig.url = dlg.GetRepoURL();
remoteConfig.username = dlg.GetUsername();
remoteConfig.password = dlg.GetPassword();
remoteConfig.sshKey = dlg.GetRepoSSHPath();
remoteConfig.connType = dlg.GetRepoType();
if( error != GIT_OK )
if( !initHandler.SetupRemote( remoteConfig ) )
{
if( m_gitLastError != git_error_last()->klass )
{
m_gitLastError = git_error_last()->klass;
DisplayErrorMessage( m_parent, _( "Failed to set default remote." ),
KIGIT_COMMON::GetLastGitError() );
}
DisplayErrorMessage( m_parent, _( "Failed to set default remote." ),
initHandler.GetErrorString() );
return;
}
m_gitLastError = GIT_ERROR_NONE;
GIT_PULL_HANDLER handler( m_TreeProject->GitCommon() );
handler.SetProgressReporter( std::make_unique<WX_PROGRESS_REPORTER>( this, _( "Fetch Remote" ), 1, PR_NO_ABORT ) );
handler.PerformFetch();
KIPLATFORM::SECRETS::StoreSecret( dlg.GetRepoURL(), dlg.GetUsername(), dlg.GetPassword() );
@ -1844,27 +1774,23 @@ void PROJECT_TREE_PANE::onGitPushProject( wxCommandEvent& aEvent )
}
void PROJECT_TREE_PANE::onGitSwitchBranch( wxCommandEvent& aEvent )
{
git_repository* repo = m_TreeProject->GetGitRepo();
if( !repo )
if( !m_TreeProject->GetGitRepo() )
return;
GIT_BRANCH_HANDLER branchHandler( m_TreeProject->GitCommon() );
wxString branchName;
if( aEvent.GetId() == ID_GIT_SWITCH_BRANCH )
{
DIALOG_GIT_SWITCH dlg( wxGetTopLevelParent( this ), repo );
DIALOG_GIT_SWITCH dlg( wxGetTopLevelParent( this ), m_TreeProject->GetGitRepo() );
int retval = dlg.ShowModal();
branchName = dlg.GetBranchName();
if( retval == wxID_ADD )
KIGIT::PROJECT_GIT_UTILS::CreateBranch( repo, branchName );
KIGIT::PROJECT_GIT_UTILS::CreateBranch( m_TreeProject->GetGitRepo(), branchName );
else if( retval != wxID_OK )
return;
}
@ -1879,48 +1805,10 @@ void PROJECT_TREE_PANE::onGitSwitchBranch( wxCommandEvent& aEvent )
branchName = branches[branchIndex];
}
// Retrieve the reference to the existing branch using libgit2
git_reference* branchRef = nullptr;
if( git_reference_lookup( &branchRef, repo, branchName.mb_str() ) != GIT_OK &&
git_reference_dwim( &branchRef, repo, branchName.mb_str() ) != GIT_OK )
{
wxString errorMessage = wxString::Format( _( "Failed to lookup branch '%s': %s" ),
branchName, KIGIT_COMMON::GetLastGitError() );
DisplayError( m_parent, errorMessage );
return;
}
KIGIT::GitReferencePtr branchRefPtr( branchRef );
const char* branchRefName = git_reference_name( branchRef );
git_object* branchObj = nullptr;
if( git_revparse_single( &branchObj, repo, branchName.mb_str() ) != 0 )
wxLogTrace( traceGit, wxS( "onGitSwitchBranch: Switching to branch '%s'" ), branchName );
if( branchHandler.SwitchToBranch( branchName ) != BranchResult::Success )
{
wxString errorMessage =
wxString::Format( _( "Failed to find branch head for '%s'" ), branchName );
DisplayError( m_parent, errorMessage );
return;
}
KIGIT::GitObjectPtr branchObjPtr( branchObj );
// Switch to the branch
if( git_checkout_tree( repo, branchObj, nullptr ) != 0 )
{
wxString errorMessage =
wxString::Format( _( "Failed to switch to branch '%s'" ), branchName );
DisplayError( m_parent, errorMessage );
return;
}
// Update the HEAD reference
if( git_repository_set_head( repo, branchRefName ) != 0 )
{
wxString errorMessage = wxString::Format(
_( "Failed to update HEAD reference for branch '%s'" ), branchName );
DisplayError( m_parent, errorMessage );
return;
DisplayError( m_parent, branchHandler.GetErrorString() );
}
}
@ -1936,21 +1824,18 @@ void PROJECT_TREE_PANE::onGitRemoveVCS( wxCommandEvent& aEvent )
return;
}
// Remove the VCS (git) from the project directory
git_repository_free( repo );
m_TreeProject->SetGitRepo( nullptr );
// Remove the .git directory
wxFileName fn( m_Parent->GetProjectFileName() );
fn.AppendDir( ".git" );
// Fully delete the git repository on disk
wxLogTrace( traceGit, wxS( "onGitRemoveVCS: Removing VCS from project" ) );
wxString errors;
if( !RmDirRecursive( fn.GetPath(), &errors ) )
if( !KIGIT::PROJECT_GIT_UTILS::RemoveVCS( repo, Prj().GetProjectPath(), true, &errors ) )
{
DisplayErrorMessage( m_parent, _( "Failed to remove Git directory" ), errors );
DisplayErrorMessage( m_parent, _( "Failed to remove VCS" ), errors );
return;
}
m_TreeProject->SetGitRepo( nullptr );
// Clear all item states
std::stack<wxTreeItemId> items;
items.push( m_TreeProject->GetRootItem() );
@ -2118,17 +2003,17 @@ void PROJECT_TREE_PANE::updateGitStatusIconMap()
return;
}
git_repository* repo = m_TreeProject->GetGitRepo();
if( !repo )
if( !m_TreeProject->GetGitRepo() )
{
wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: No git repository found" ) );
return;
}
// Get Current Branch
GIT_STATUS_HANDLER statusHandler( m_TreeProject->GitCommon() );
// Set up pathspec for project files
wxFileName rootFilename( Prj().GetProjectFullName() );
wxString repoWorkDir( git_repository_workdir( repo ) );
wxString repoWorkDir = statusHandler.GetWorkingDirectory();
wxFileName relative = rootFilename;
relative.MakeRelativeTo( repoWorkDir );
@ -2138,164 +2023,35 @@ void PROJECT_TREE_PANE::updateGitStatusIconMap()
pathspecStr.Replace( wxS( "\\" ), wxS( "/" ) );
#endif
const char* pathspec[] = { pathspecStr.c_str().AsChar() };
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;
status_options.pathspec = { (char**) pathspec, 1 };
git_index* index = nullptr;
if( git_repository_index( &index, repo ) != GIT_OK )
{
m_gitLastError = giterr_last()->klass;
wxLogTrace( traceGit, wxS( "Failed to get git index: %s" ), KIGIT_COMMON::GetLastGitError() );
return;
}
KIGIT::GitIndexPtr indexPtr( index );
git_status_list* status_list = nullptr;
if( git_status_list_new( &status_list, repo, &status_options ) != GIT_OK )
{
wxLogTrace( traceGit, wxS( "Failed to get git status list: %s" ), KIGIT_COMMON::GetLastGitError() );
return;
}
KIGIT::GitStatusListPtr statusListPtr( status_list );
// Get file status
auto fileStatusMap = statusHandler.GetFileStatus( pathspecStr );
auto [localChanges, remoteChanges] = m_TreeProject->GitCommon()->GetDifferentFiles();
statusHandler.UpdateRemoteStatus( localChanges, remoteChanges, fileStatusMap );
size_t count = git_status_list_entrycount( status_list );
bool updated = false;
bool updated = false;
for( size_t ii = 0; ii < count; ++ii )
// Update status icons based on file status
for( const auto& [absPath, fileStatus] : fileStatusMap )
{
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;
absPath << path;
auto iter = m_gitTreeCache.find( absPath );
if( iter == m_gitTreeCache.end() )
{
wxLogTrace( traceGit, wxS( "File '%s' not found in tree cache" ), absPath );
continue;
}
// If we are current, don't continue because we still need to check to see if the
// current commit is ahead/behind the remote. If the file is modified/added/deleted,
// that is the main status we want to show.
if( entry->status & GIT_STATUS_IGNORED )
{
wxLogTrace( traceGit, wxS( "File '%s' is ignored" ), absPath );
auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
KIGIT_COMMON::GIT_STATUS::GIT_STATUS_IGNORED );
if( inserted || it->second != KIGIT_COMMON::GIT_STATUS::GIT_STATUS_IGNORED )
updated = true;
it->second = KIGIT_COMMON::GIT_STATUS::GIT_STATUS_IGNORED;
}
else if( entry->status & ( GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_MODIFIED ) )
{
wxLogTrace( traceGit, wxS( "File '%s' is modified in %s" ),
absPath, ( entry->status & GIT_STATUS_INDEX_MODIFIED )? "index" : "working tree" );
auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
KIGIT_COMMON::GIT_STATUS::GIT_STATUS_MODIFIED );
if( inserted || it->second != KIGIT_COMMON::GIT_STATUS::GIT_STATUS_MODIFIED )
updated = true;
it->second = KIGIT_COMMON::GIT_STATUS::GIT_STATUS_MODIFIED;
}
else if( entry->status & ( GIT_STATUS_INDEX_NEW | GIT_STATUS_WT_NEW ) )
{
wxLogTrace( traceGit, wxS( "File '%s' is new in %s" ),
absPath, ( entry->status & GIT_STATUS_INDEX_NEW )? "index" : "working tree" );
auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
KIGIT_COMMON::GIT_STATUS::GIT_STATUS_ADDED );
if( inserted || it->second != KIGIT_COMMON::GIT_STATUS::GIT_STATUS_ADDED )
updated = true;
it->second = KIGIT_COMMON::GIT_STATUS::GIT_STATUS_ADDED;
}
else if( entry->status & ( GIT_STATUS_INDEX_DELETED | GIT_STATUS_WT_DELETED ) )
{
wxLogTrace( traceGit, wxS( "File '%s' is deleted in %s" ),
absPath, ( entry->status & GIT_STATUS_INDEX_DELETED )? "index" : "working tree" );
auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
KIGIT_COMMON::GIT_STATUS::GIT_STATUS_DELETED );
if( inserted || it->second != KIGIT_COMMON::GIT_STATUS::GIT_STATUS_DELETED )
updated = true;
it->second = KIGIT_COMMON::GIT_STATUS::GIT_STATUS_DELETED;
}
else if( localChanges.count( path ) )
{
wxLogTrace( traceGit, wxS( "File '%s' is ahead of remote" ), absPath );
auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
KIGIT_COMMON::GIT_STATUS::GIT_STATUS_AHEAD );
if( inserted || it->second != KIGIT_COMMON::GIT_STATUS::GIT_STATUS_AHEAD )
updated = true;
it->second = KIGIT_COMMON::GIT_STATUS::GIT_STATUS_AHEAD;
}
else if( remoteChanges.count( path ) )
{
wxLogTrace( traceGit, wxS( "File '%s' is behind remote" ), absPath );
auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
KIGIT_COMMON::GIT_STATUS::GIT_STATUS_BEHIND );
if( inserted || it->second != KIGIT_COMMON::GIT_STATUS::GIT_STATUS_BEHIND )
updated = true;
it->second = KIGIT_COMMON::GIT_STATUS::GIT_STATUS_BEHIND;
}
else
{
// If we are here, the file is unmodified and not ignored
auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second,
KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CURRENT );
if( inserted || it->second != KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CURRENT )
updated = true;
it->second = KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CURRENT;
}
auto [it, inserted] = m_gitStatusIcons.try_emplace( iter->second, fileStatus.status );
if( inserted || it->second != fileStatus.status )
updated = true;
it->second = fileStatus.status;
}
git_reference* currentBranchReference = nullptr;
int rc = git_repository_head( &currentBranchReference, repo );
KIGIT::GitReferencePtr currentBranchReferencePtr( currentBranchReference );
// Get the current branch name
if( currentBranchReference )
{
m_gitCurrentBranchName = git_reference_shorthand( currentBranchReference );
}
else if( rc == GIT_EUNBORNBRANCH )
{
// TODO: couldn't immediately figure out if libgit2 can return the name of an unborn branch
// For now, just do nothing
}
else
{
if( giterr_last()->klass != m_gitLastError )
wxLogTrace( "git", "Failed to lookup current branch: %s", KIGIT_COMMON::GetLastGitError() );
m_gitLastError = giterr_last()->klass;
}
m_gitCurrentBranchName = statusHandler.GetCurrentBranchName();
wxLogTrace( traceGit, wxS( "updateGitStatusIconMap: Updated git status icons" ) );
// If the icons are not changed, queue an event to update in the main thread
// Update UI if icons changed
if( updated || !m_gitIconsInitialized )
{
CallAfter(
@ -2319,55 +2075,15 @@ void PROJECT_TREE_PANE::onGitCommit( wxCommandEvent& aEvent )
return;
}
git_config* config = nullptr;
git_repository_config( &config, repo );
KIGIT::GitConfigPtr configPtr( config );
// Read relevant data from the git config
wxString authorName;
wxString authorEmail;
// Read author name
git_config_entry* name_c = nullptr;
git_config_entry* email_c = nullptr;
int authorNameError = git_config_get_entry( &name_c, config, "user.name" );
KIGIT::GitConfigEntryPtr namePtr( name_c );
if( authorNameError != 0 || name_c == nullptr )
{
authorName = Pgm().GetCommonSettings()->m_Git.authorName;
}
else
{
authorName = name_c->value;
}
// Read author email
int authorEmailError = git_config_get_entry( &email_c, config, "user.email" );
if( authorEmailError != 0 || email_c == nullptr )
{
authorEmail = Pgm().GetCommonSettings()->m_Git.authorEmail;
}
else
{
authorEmail = email_c->value;
}
// Get git configuration
GIT_CONFIG_HANDLER configHandler( m_TreeProject->GitCommon() );
GitUserConfig userConfig = configHandler.GetUserConfig();
// Collect modified files in the repository
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_list* status_list = nullptr;
git_status_list_new( &status_list, repo, &status_options );
KIGIT::GitStatusListPtr statusListPtr( status_list );
GIT_STATUS_HANDLER statusHandler( m_TreeProject->GitCommon() );
auto fileStatusMap = statusHandler.GetFileStatus();
std::map<wxString, int> modifiedFiles;
size_t count = git_status_list_entrycount( status_list );
std::set<wxString> selected_files;
for( PROJECT_TREE_ITEM* item : tree_data )
@ -2376,45 +2092,33 @@ void PROJECT_TREE_PANE::onGitCommit( wxCommandEvent& aEvent )
selected_files.emplace( item->GetFileName() );
}
for( size_t i = 0; i < count; ++i )
{
const git_status_entry* entry = git_status_byindex( status_list, i );
wxString repoWorkDir = statusHandler.GetWorkingDirectory();
// Check if the file is modified (index or workdir changes)
if( entry->status == GIT_STATUS_CURRENT
|| ( entry->status & ( GIT_STATUS_CONFLICTED | GIT_STATUS_IGNORED ) ) )
for( const auto& [absPath, fileStatus] : fileStatusMap )
{
// Skip current, conflicted, or ignored files
if( fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CURRENT
|| fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_CONFLICTED
|| fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_IGNORED )
{
continue;
}
wxFileName fn;
wxString filePath;
wxFileName fn( absPath );
// TODO: we are kind of erasing the difference between workdir and index here,
// because the Commit dialog doesn't show that difference.
// Entry may only have a head_to_index if it was previously staged
if( entry->index_to_workdir )
{
fn.Assign( entry->index_to_workdir->old_file.path );
fn.MakeAbsolute( git_repository_workdir( repo ) );
filePath = wxString( entry->index_to_workdir->old_file.path, wxConvUTF8 );
}
else if( entry->head_to_index )
{
fn.Assign( entry->head_to_index->old_file.path );
fn.MakeAbsolute( git_repository_workdir( repo ) );
filePath = wxString( entry->head_to_index->old_file.path, wxConvUTF8 );
}
else
// Convert to relative path for the modifiedFiles map
wxString relativePath = absPath;
if( relativePath.StartsWith( repoWorkDir ) )
{
wxCHECK2_MSG( false, continue, "File status with neither git_status_entry set!" );
relativePath = relativePath.Mid( repoWorkDir.length() );
#ifdef _WIN32
relativePath.Replace( wxS( "\\" ), wxS( "/" ) );
#endif
}
// Do not commit files outside the project directory
wxString projectPath = Prj().GetProjectPath();
wxString fileName = fn.GetFullPath();
if( !fileName.StartsWith( projectPath ) )
if( !absPath.StartsWith( projectPath ) )
continue;
// Skip lock files
@ -2435,23 +2139,22 @@ void PROJECT_TREE_PANE::onGitCommit( wxCommandEvent& aEvent )
if( aEvent.GetId() == ID_GIT_COMMIT_PROJECT )
{
modifiedFiles.emplace( filePath, entry->status );
modifiedFiles.emplace( relativePath, fileStatus.gitStatus );
}
else if( selected_files.count( fn.GetFullPath() ) )
else if( selected_files.count( absPath ) )
{
modifiedFiles.emplace( filePath, entry->status );
modifiedFiles.emplace( relativePath, fileStatus.gitStatus );
}
}
// Create a commit dialog
DIALOG_GIT_COMMIT dlg( wxGetTopLevelParent( this ), repo, authorName, authorEmail,
DIALOG_GIT_COMMIT dlg( wxGetTopLevelParent( this ), repo, userConfig.authorName, userConfig.authorEmail,
modifiedFiles );
auto ret = dlg.ShowModal();
if( ret != wxID_OK )
return;
std::vector<wxString> files = dlg.GetSelectedFiles();
if( dlg.GetCommitMessage().IsEmpty() )
@ -2490,29 +2193,23 @@ void PROJECT_TREE_PANE::onGitAddToIndex( wxCommandEvent& aEvent )
bool PROJECT_TREE_PANE::canFileBeAddedToVCS( const wxString& aFile )
{
git_index *index;
size_t entry_pos;
git_repository* repo = m_TreeProject->GetGitRepo();
if( !repo )
return false;
if( git_repository_index( &index, repo ) != 0 )
{
wxLogTrace( traceGit, "Failed to get git index: %s", KIGIT_COMMON::GetLastGitError() );
if( !m_TreeProject->GetGitRepo() )
return false;
}
KIGIT::GitIndexPtr indexPtr( index );
GIT_STATUS_HANDLER statusHandler( m_TreeProject->GitCommon() );
auto fileStatusMap = statusHandler.GetFileStatus();
// If we successfully find the file in the index, we may not add it to the VCS
if( git_index_find( &entry_pos, index, aFile.mb_str() ) == 0 )
// Check if file is already tracked or staged
for( const auto& [filePath, fileStatus] : fileStatusMap )
{
wxLogTrace( traceGit, "File already in index: %s", aFile );
return false;
if( filePath.EndsWith( aFile ) || filePath == aFile )
{
// File can be added if it's untracked
return fileStatus.status == KIGIT_COMMON::GIT_STATUS::GIT_STATUS_UNTRACKED;
}
}
// If file not found in status, it might be addable
return true;
}

Loading…
Cancel
Save