Parse gitignore when building new project from template

Avoids cloning the .git subdirectory and will also avoid any files
listed as ignored in the .gitignore file.  It will copy both gitignore
and gitattributes into the new project

Fixes https://gitlab.com/kicad/code/kicad/-/issues/16507
This commit is contained in:
Seth Hillbrand 2025-08-11 09:56:48 -07:00
parent 09e1fca7e4
commit ac7ae87495

View File

@ -28,6 +28,8 @@
#include <wx/txtstrm.h> #include <wx/txtstrm.h>
#include <wx/wfstream.h> #include <wx/wfstream.h>
#include <wx/log.h> #include <wx/log.h>
#include <wx/textfile.h>
#include <unordered_map>
#include <wildcards_and_files_ext.h> #include <wildcards_and_files_ext.h>
#include "project_template.h" #include "project_template.h"
@ -76,13 +78,19 @@ public:
FILE_TRAVERSER( std::vector<wxFileName>& files, const wxString& exclude ) : FILE_TRAVERSER( std::vector<wxFileName>& files, const wxString& exclude ) :
m_files( files ), m_files( files ),
m_exclude( exclude ) m_exclude( exclude )
{ } {
}
virtual wxDirTraverseResult OnFile( const wxString& filename ) override virtual wxDirTraverseResult OnFile( const wxString& filename ) override
{ {
wxFileName fn( filename ); wxFileName fn( filename );
wxString path( fn.GetPathWithSep() ); wxString path( fn.GetPathWithSep() );
EnsureGitFiles( path );
if( IsIgnored( path, fn.GetFullName(), false ) )
return wxDIR_CONTINUE;
bool exclude = fn.GetName().Contains( "fp-info-cache" ) bool exclude = fn.GetName().Contains( "fp-info-cache" )
|| fn.GetName().StartsWith( FILEEXT::AutoSaveFilePrefix ) || fn.GetName().StartsWith( FILEEXT::AutoSaveFilePrefix )
|| fn.GetName().StartsWith( FILEEXT::LockFilePrefix ); || fn.GetName().StartsWith( FILEEXT::LockFilePrefix );
@ -90,24 +98,19 @@ public:
if( !exclude ) if( !exclude )
m_files.emplace_back( wxFileName( filename ) ); m_files.emplace_back( wxFileName( filename ) );
if( path != m_oldPath )
{
const wxString gitfiles[] = { wxT( ".gitignore" ), wxT( ".gitattributes" ) };
for( const wxString& file : gitfiles )
{
if( wxFileExists( path + file ) )
m_files.emplace_back( wxFileName( path + file ) );
}
m_oldPath = path;
}
return wxDIR_CONTINUE; return wxDIR_CONTINUE;
} }
virtual wxDirTraverseResult OnDir( const wxString& dirname ) override virtual wxDirTraverseResult OnDir( const wxString& dirname ) override
{ {
wxFileName dir( dirname );
wxString parent = dir.GetPathWithSep();
EnsureGitFiles( parent );
if( dir.GetFullName() == wxT( ".git" ) || IsIgnored( parent, dir.GetFullName(), true ) )
return wxDIR_IGNORE;
wxDirTraverseResult result = wxDIR_IGNORE; wxDirTraverseResult result = wxDIR_IGNORE;
bool exclude = dirname.StartsWith( m_exclude ) || dirname.EndsWith( "-backups" ); bool exclude = dirname.StartsWith( m_exclude ) || dirname.EndsWith( "-backups" );
@ -115,6 +118,7 @@ public:
if( !exclude ) if( !exclude )
{ {
m_files.emplace_back( wxFileName::DirName( dirname ) ); m_files.emplace_back( wxFileName::DirName( dirname ) );
EnsureGitFiles( dirname + wxFileName::GetPathSeparator() );
result = wxDIR_CONTINUE; result = wxDIR_CONTINUE;
} }
@ -122,9 +126,68 @@ public:
} }
private: private:
void EnsureGitFiles( const wxString& path )
{
if( m_gitIgnores.find( path ) != m_gitIgnores.end() )
return;
wxString gitignore = path + wxT( ".gitignore" );
if( wxFileExists( gitignore ) )
{
wxFileInputStream input( gitignore );
wxTextInputStream text( input, wxT( "\x9" ), wxConvUTF8 );
while( input.IsOk() && !input.Eof() )
{
wxString line = text.ReadLine();
line.Trim().Trim( false );
if( line.IsEmpty() || line.StartsWith( wxT( "#" ) ) )
continue;
m_gitIgnores[path].push_back( line );
}
m_files.emplace_back( wxFileName( gitignore ) );
}
else
{
m_gitIgnores[path] = {};
}
wxString gitattributes = path + wxT( ".gitattributes" );
if( wxFileExists( gitattributes ) )
m_files.emplace_back( wxFileName( gitattributes ) );
}
bool IsIgnored( const wxString& path, const wxString& name, bool isDir )
{
auto it = m_gitIgnores.find( path );
if( it == m_gitIgnores.end() )
return false;
for( const wxString& pattern : it->second )
{
bool dirOnly = pattern.EndsWith( wxT( "/" ) );
wxString pat = dirOnly ? pattern.substr( 0, pattern.length() - 1 ) : pattern;
if( dirOnly && !isDir )
continue;
if( wxMatchWild( pat, name ) )
return true;
}
return false;
}
std::vector<wxFileName>& m_files; std::vector<wxFileName>& m_files;
wxString m_exclude; wxString m_exclude;
wxString m_oldPath; std::unordered_map<wxString, std::vector<wxString>> m_gitIgnores;
}; };
@ -147,7 +210,6 @@ wxString PROJECT_TEMPLATE::GetPrjDirName()
PROJECT_TEMPLATE::~PROJECT_TEMPLATE() PROJECT_TEMPLATE::~PROJECT_TEMPLATE()
{ {
} }
@ -163,8 +225,7 @@ wxBitmap* PROJECT_TEMPLATE::GetIcon()
} }
size_t PROJECT_TEMPLATE::GetDestinationFiles( const wxFileName& aNewProjectPath, size_t PROJECT_TEMPLATE::GetDestinationFiles( const wxFileName& aNewProjectPath, std::vector<wxFileName>& aDestFiles )
std::vector< wxFileName >& aDestFiles )
{ {
std::vector<wxFileName> srcFiles = GetFileList(); std::vector<wxFileName> srcFiles = GetFileList();
@ -174,8 +235,7 @@ size_t PROJECT_TEMPLATE::GetDestinationFiles( const wxFileName& aNewProjectPath,
for( wxFileName& file : srcFiles ) for( wxFileName& file : srcFiles )
{ {
if( file.GetExt() == FILEEXT::ProjectFileExtension if( file.GetExt() == FILEEXT::ProjectFileExtension || file.GetExt() == FILEEXT::LegacyProjectFileExtension )
|| file.GetExt() == FILEEXT::LegacyProjectFileExtension )
{ {
if( !basename.IsEmpty() && basename != file.GetName() ) if( !basename.IsEmpty() && basename != file.GetName() )
multipleProjectFilesFound = true; multipleProjectFilesFound = true;
@ -224,8 +284,7 @@ bool PROJECT_TEMPLATE::CreateProject( wxFileName& aNewProjectPath, wxString* aEr
for( wxFileName& file : srcFiles ) for( wxFileName& file : srcFiles )
{ {
if( file.GetExt() == FILEEXT::ProjectFileExtension if( file.GetExt() == FILEEXT::ProjectFileExtension || file.GetExt() == FILEEXT::LegacyProjectFileExtension )
|| file.GetExt() == FILEEXT::LegacyProjectFileExtension )
{ {
if( !basename.IsEmpty() && basename != file.GetName() ) if( !basename.IsEmpty() && basename != file.GetName() )
multipleProjectFilesFound = true; multipleProjectFilesFound = true;
@ -249,8 +308,7 @@ bool PROJECT_TEMPLATE::CreateProject( wxFileName& aNewProjectPath, wxString* aEr
{ {
// Don't rename drawing sheet definitions; they're often shared // Don't rename drawing sheet definitions; they're often shared
} }
else if( destFile.GetName().EndsWith( "-cache" ) else if( destFile.GetName().EndsWith( "-cache" ) || destFile.GetName().EndsWith( "-rescue" ) )
|| destFile.GetName().EndsWith( "-rescue" ) )
{ {
currname.Replace( basename, aNewProjectPath.GetName() ); currname.Replace( basename, aNewProjectPath.GetName() );
} }