38#include <wx/datetime.h>
42#include <wx/filename.h>
43#include <wx/stdpaths.h>
52 LIBGIT2_SCOPE() { git_libgit2_init(); }
53 ~LIBGIT2_SCOPE() { git_libgit2_shutdown(); }
57struct SCOPED_BOOL_OVERRIDE
59 explicit SCOPED_BOOL_OVERRIDE(
bool& aFlag ) : m_flag( aFlag ), m_original( aFlag ) {}
60 ~SCOPED_BOOL_OVERRIDE() { m_flag = m_original; }
69struct SCOPED_BACKUP_LOCATION_OVERRIDE
71 explicit SCOPED_BACKUP_LOCATION_OVERRIDE(
BACKUP_LOCATION& aLocation ) :
72 m_location( aLocation ), m_original( aLocation )
76 ~SCOPED_BACKUP_LOCATION_OVERRIDE() { m_location = m_original; }
83struct SCOPED_BACKUP_FORMAT_OVERRIDE
85 explicit SCOPED_BACKUP_FORMAT_OVERRIDE(
BACKUP_FORMAT& aFormat ) :
86 m_format( aFormat ), m_original( aFormat )
90 ~SCOPED_BACKUP_FORMAT_OVERRIDE() { m_format = m_original; }
99struct SCOPED_PROJECT_LOAD
101 SCOPED_PROJECT_LOAD( SETTINGS_MANAGER& aMgr,
const wxString& aProjectFile ) : m_mgr( aMgr )
103 m_mgr.LoadProject( aProjectFile.ToStdString() );
106 ~SCOPED_PROJECT_LOAD() { m_mgr.UnloadProject( &m_mgr.Prj(),
false ); }
108 SETTINGS_MANAGER& m_mgr;
112void writeTextFile(
const wxString& aPath,
const wxString& aContents )
114 wxFFile f( aPath, wxT(
"w" ) );
116 f.Write( aContents );
121std::string readTextFile(
const wxString& aPath )
123 wxFFile f( aPath, wxT(
"rb" ) );
129 return text.ToStdString();
141 return std::string();
146struct SNAPSHOT_PROJECT
148 SNAPSHOT_PROJECT(
const wxString& aDirPrefix,
const wxString& aFileName ) :
149 m_tempDir( aDirPrefix ),
150 m_proPath( m_tempDir.ChildPathStr( aFileName ) )
152 writeTextFile( m_proPath, wxS(
"{ \"meta\": { \"version\": 3 } }\n" ) );
153 m_project = std::make_unique<SCOPED_PROJECT_LOAD>( m_mgr, m_proPath );
156 PROJECT& Project() {
return m_mgr.Prj(); }
157 PROJECT_FILE& ProjectFile() {
return m_mgr.Prj().GetProjectFile(); }
159 SETTINGS_MANAGER m_mgr;
160 KI_TEST::SCOPED_TEMP_DIR m_tempDir;
162 std::unique_ptr<SCOPED_PROJECT_LOAD> m_project;
175std::string snapshotProjectBytes(
PROJECT& aProject )
177 std::vector<HISTORY_FILE_DATA> fileData;
180 auto it = std::find_if( fileData.begin(), fileData.end(),
183 return aEntry.relativePath.EndsWith( wxS(
".kicad_pro" ) );
188 return historyEntryBytes( *it );
208 std::vector<HISTORY_FILE_DATA> fileData;
212 BOOST_CHECK_NO_THROW( board.
SaveToHistory( wxS(
"/tmp/anywhere" ), fileData ) );
213 BOOST_CHECK( fileData.empty() );
225 wxString tempDir = wxStandardPaths::Get().GetTempDir();
226 wxString projectPath = tempDir + wxFileName::GetPathSeparator() + wxS(
"pcb_autosave.kicad_pro" );
233 std::vector<HISTORY_FILE_DATA> fileData;
237 BOOST_CHECK( fileData.empty() );
254 LIBGIT2_SCOPE libgit;
257 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
258 backupEnabled =
true;
265 wxString boardPath =
path + wxFileName::GetPathSeparator() + wxS(
"stray.kicad_pcb" );
266 writeTextFile( boardPath, wxS(
"(kicad_pcb (version 20240108))\n" ) );
270 BOOST_CHECK( !history.
Init(
path ) );
272 BOOST_CHECK( !history.
TagSave(
path, wxS(
"pcb" ) ) );
275 wxString historyDir =
path + wxFileName::GetPathSeparator() + wxS(
".history" );
276 BOOST_CHECK( !wxDirExists( historyDir ) );
289 LIBGIT2_SCOPE libgit;
292 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
293 backupEnabled =
true;
298 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"subdirs.kicad_pro" ), wxS(
"{}\n" ) );
299 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"subdirs.kicad_pcb" ),
300 wxS(
"(kicad_pcb (version 20240108))\n" ) );
304 wxString subDir =
path + wxFileName::GetPathSeparator() + wxS(
"libs" );
305 BOOST_REQUIRE( wxFileName::Mkdir( subDir, 0777, wxPATH_MKDIR_FULL ) );
306 writeTextFile( subDir + wxFileName::GetPathSeparator() + wxS(
"fp.kicad_mod" ),
307 wxS(
"(footprint test)\n" ) );
313 wxString historyDir =
path + wxFileName::GetPathSeparator() + wxS(
".history" );
314 BOOST_CHECK( wxDirExists( historyDir ) );
315 BOOST_CHECK( !wxDirExists( subDir + wxFileName::GetPathSeparator() + wxS(
".history" ) ) );
321 writeTextFile( subDir + wxFileName::GetPathSeparator() + wxS(
"fp.kicad_mod" ),
322 wxS(
"(footprint test (modified))\n" ) );
330 BOOST_CHECK( headBefore != headAfter );
337 LIBGIT2_SCOPE libgit;
341 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
342 backupEnabled =
true;
345 const wxString& projectPath = tempProject.
PathStr();
348 projectPath + wxFileName::GetPathSeparator() + wxS(
"issue24016.kicad_pcb" );
349 writeTextFile( boardPath, wxS(
"(kicad_pcb (version 20240108))\n" ) );
353 wxString projectFile =
354 projectPath + wxFileName::GetPathSeparator() + wxS(
"issue24016.kicad_pro" );
355 writeTextFile( projectFile, wxS(
"{}\n" ) );
360 wxString headHash = history.
GetHeadHash( projectPath );
365 wxString backupsDir =
366 projectPath + wxFileName::GetPathSeparator() + wxS(
"issue24016-backups" );
367 BOOST_REQUIRE( wxFileName::Mkdir( backupsDir, 0777, wxPATH_MKDIR_FULL ) );
369 wxString zipPath = backupsDir + wxFileName::GetPathSeparator()
370 + wxS(
"issue24016-2026-04-22_120000.zip" );
371 writeTextFile( zipPath, wxS(
"pretend-zip-contents" ) );
373 writeTextFile( boardPath, wxS(
"(kicad_pcb (version 20240108) (dirty yes))\n" ) );
377 BOOST_CHECK_MESSAGE( wxDirExists( backupsDir ),
378 "zip backups directory must survive RestoreCommit" );
379 BOOST_CHECK_MESSAGE( wxFileExists( zipPath ),
380 ".zip archive inside the backups directory must survive RestoreCommit" );
399 LIBGIT2_SCOPE libgit;
402 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
403 backupEnabled =
true;
406 const wxString& projectPath = tempProject.
PathStr();
409 wxString parentPro = projectPath + wxFileName::GetPathSeparator() + wxS(
"projectA.kicad_pro" );
410 wxString parentPcb = projectPath + wxFileName::GetPathSeparator() + wxS(
"projectA.kicad_pcb" );
411 writeTextFile( parentPro, wxS(
"{}\n" ) );
412 writeTextFile( parentPcb, wxS(
"(kicad_pcb (version 20240108))\n" ) );
417 wxString headHash = history.
GetHeadHash( projectPath );
423 wxString nestedDir = projectPath + wxFileName::GetPathSeparator() + wxS(
"projectB" );
424 BOOST_REQUIRE( wxFileName::Mkdir( nestedDir, 0777, wxPATH_MKDIR_FULL ) );
426 wxString nestedPro = nestedDir + wxFileName::GetPathSeparator() + wxS(
"projectB.kicad_pro" );
427 wxString nestedPcb = nestedDir + wxFileName::GetPathSeparator() + wxS(
"projectB.kicad_pcb" );
428 wxString nestedSch = nestedDir + wxFileName::GetPathSeparator() + wxS(
"projectB.kicad_sch" );
429 writeTextFile( nestedPro, wxS(
"{ \"nested\": true }\n" ) );
430 writeTextFile( nestedPcb, wxS(
"(kicad_pcb (version 20240108) (nested yes))\n" ) );
431 writeTextFile( nestedSch, wxS(
"(kicad_sch (version 20240108))\n" ) );
434 writeTextFile( parentPcb, wxS(
"(kicad_pcb (version 20240108) (dirty yes))\n" ) );
439 BOOST_CHECK_MESSAGE( wxDirExists( nestedDir ),
440 "nested project directory must survive RestoreCommit" );
441 BOOST_CHECK_MESSAGE( wxFileExists( nestedPro ),
442 "nested .kicad_pro must survive RestoreCommit" );
443 BOOST_CHECK_MESSAGE( wxFileExists( nestedPcb ),
444 "nested .kicad_pcb must survive RestoreCommit" );
445 BOOST_CHECK_MESSAGE( wxFileExists( nestedSch ),
446 "nested .kicad_sch must survive RestoreCommit" );
449 BOOST_CHECK( wxFileExists( parentPro ) );
450 BOOST_CHECK( wxFileExists( parentPcb ) );
461 LIBGIT2_SCOPE libgit;
464 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
465 backupEnabled =
true;
468 const wxString& projectPath = tempProject.
PathStr();
470 wxString boardPath = projectPath + wxFileName::GetPathSeparator() + wxS(
"rb.kicad_pcb" );
471 wxString projectFile = projectPath + wxFileName::GetPathSeparator() + wxS(
"rb.kicad_pro" );
472 writeTextFile( projectFile, wxS(
"{}\n" ) );
473 writeTextFile( boardPath, wxS(
"(kicad_pcb (version 20240108))\n" ) );
478 wxString headHash = history.
GetHeadHash( projectPath );
482 writeTextFile( boardPath, wxS(
"(kicad_pcb (version 20240108) (dirty yes))\n" ) );
488 wxString parentDir = wxFileName( projectPath ).GetPath();
489 wxString leafPrefix = wxFileName( projectPath ).GetFullName() + wxS(
"_restore_backup_" );
491 wxDir dir( parentDir );
494 wxString retainedBackup;
495 bool foundLegacyBackup =
false;
498 for(
bool cont = dir.GetFirst( &
name, wxEmptyString, wxDIR_DIRS ); cont;
499 cont = dir.GetNext( &
name ) )
501 if(
name.StartsWith( leafPrefix ) )
503 retainedBackup = parentDir + wxFileName::GetPathSeparator() +
name;
506 BOOST_CHECK_MESSAGE(
name.Find(
':' ) == wxNOT_FOUND,
507 "retained backup directory name must not contain ':' "
508 "(Windows-illegal in path components)" );
510 else if(
name == wxFileName( projectPath ).GetFullName() + wxS(
"_restore_backup" ) )
512 foundLegacyBackup =
true;
517 !retainedBackup.IsEmpty(),
518 "RestoreCommit must retain a timestamped _restore_backup_<ts>/ sibling directory" );
521 "RestoreCommit must not leave the legacy non-timestamped _restore_backup/ behind" );
524 if( !retainedBackup.IsEmpty() && wxDirExists( retainedBackup ) )
525 wxFileName::Rmdir( retainedBackup, wxPATH_RMDIR_RECURSIVE );
534 LIBGIT2_SCOPE libgit;
537 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
538 backupEnabled =
true;
543 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
544 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pcb" ),
545 wxS(
"(kicad_pcb (version 20240108))\n" ) );
546 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_sch" ),
547 wxS(
"(kicad_sch (version 20240108))\n" ) );
549 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"passwords.txt" ), wxS(
"secret\n" ) );
550 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"datasheet.pdf" ), wxS(
"fake pdf bytes\n" ) );
551 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"notes.md" ), wxS(
"personal notes\n" ) );
553 wxString subDir =
path + wxFileName::GetPathSeparator() + wxS(
"docs" );
554 BOOST_REQUIRE( wxFileName::Mkdir( subDir, 0777, wxPATH_MKDIR_FULL ) );
555 writeTextFile( subDir + wxFileName::GetPathSeparator() + wxS(
"manual.txt" ), wxS(
"irrelevant\n" ) );
560 wxString hist =
path + wxFileName::GetPathSeparator() + wxS(
".history" );
561 git_repository* repo =
nullptr;
562 BOOST_REQUIRE_EQUAL( git_repository_open( &repo, hist.mb_str().data() ), 0 );
565 BOOST_REQUIRE_EQUAL( git_reference_name_to_id( &head_oid, repo,
"HEAD" ), 0 );
567 git_commit* head =
nullptr;
568 BOOST_REQUIRE_EQUAL( git_commit_lookup( &head, repo, &head_oid ), 0 );
570 git_tree* tree =
nullptr;
571 BOOST_REQUIRE_EQUAL( git_commit_tree( &tree, head ), 0 );
573 std::vector<std::string> committedPaths;
575 tree, GIT_TREEWALK_PRE,
576 [](
const char* root,
const git_tree_entry* entry,
void* payload ) ->
int
578 auto* paths =
static_cast<std::vector<std::string>*
>( payload );
580 if( git_tree_entry_type( entry ) == GIT_OBJECT_BLOB )
581 paths->push_back( std::string( root ) + git_tree_entry_name( entry ) );
587 git_tree_free( tree );
588 git_commit_free( head );
589 git_repository_free( repo );
591 auto contains = [&](
const std::string& s )
593 return std::find( committedPaths.begin(), committedPaths.end(), s ) != committedPaths.end();
596 BOOST_CHECK_MESSAGE( contains(
"p.kicad_pro" ),
"kicad_pro must be committed" );
597 BOOST_CHECK_MESSAGE( contains(
"p.kicad_pcb" ),
"kicad_pcb must be committed" );
598 BOOST_CHECK_MESSAGE( contains(
"p.kicad_sch" ),
"kicad_sch must be committed" );
600 BOOST_CHECK_MESSAGE( !contains(
"passwords.txt" ),
"passwords.txt must NOT appear in history" );
601 BOOST_CHECK_MESSAGE( !contains(
"datasheet.pdf" ),
"datasheet.pdf must NOT appear in history" );
602 BOOST_CHECK_MESSAGE( !contains(
"notes.md" ),
"notes.md must NOT appear in history" );
603 BOOST_CHECK_MESSAGE( !contains(
"docs/manual.txt" ),
"subdirectory user content must NOT appear in history" );
613 LIBGIT2_SCOPE libgit;
616 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
617 backupEnabled =
true;
622 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
623 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pcb" ),
624 wxS(
"(kicad_pcb (version 20240108))\n" ) );
629 std::string inMemoryContent =
"(kicad_pcb (version 20240108))\n";
631 auto saver = [&inMemoryContent](
const wxString&, std::vector<HISTORY_FILE_DATA>& aFileData )
635 entry.
content = inMemoryContent;
636 aFileData.push_back( std::move( entry ) );
645 wxString histDir =
path + wxFileName::GetPathSeparator() + wxS(
".history" );
646 BOOST_CHECK( wxDirExists( histDir ) );
649 BOOST_CHECK_MESSAGE( head.IsEmpty(),
"no untagged HEAD should exist after an idle first save" );
652 inMemoryContent =
"(kicad_pcb (version 20240108) (edited yes))\n";
658 BOOST_CHECK_MESSAGE( !head.IsEmpty(),
"in-memory edits diverging from disk must produce a commit" );
672 LIBGIT2_SCOPE libgit;
675 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
676 backupEnabled =
true;
680 const wxString sep = wxFileName::GetPathSeparator();
682 writeTextFile(
path + sep + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
683 writeTextFile(
path + sep + wxS(
"p.kicad_pcb" ), wxS(
"(kicad_pcb (version 20240108))\n" ) );
689 auto runCount = std::make_shared<int>( 0 );
690 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
693 [runCount](
const wxString&, std::vector<HISTORY_FILE_DATA>& aFileData )
697 HISTORY_FILE_DATA entry;
698 entry.relativePath = wxS(
"p.kicad_pcb" );
699 entry.content =
"(kicad_pcb (version 20240108) (edited yes))\n";
700 aFileData.push_back( std::move( entry ) );
702 board->GetHistoryLifetimeToken() );
714 BOOST_CHECK_MESSAGE( *runCount == 1,
"commit runner invoked a saver whose board was destroyed" );
717 BOOST_CHECK_MESSAGE( *runCount == 1,
"autosave-file runner invoked a saver whose board was destroyed" );
728 LIBGIT2_SCOPE libgit;
731 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
732 backupEnabled =
true;
737 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
738 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pcb" ),
739 wxS(
"(kicad_pcb (version 20240108))\n" ) );
743 std::string inMemoryContent =
"(kicad_pcb (version 20240108))\n";
745 auto saver = [&inMemoryContent](
const wxString&, std::vector<HISTORY_FILE_DATA>& aFileData )
749 entry.
content = inMemoryContent;
750 aFileData.push_back( std::move( entry ) );
759 BOOST_CHECK_MESSAGE( !head.IsEmpty(),
"manual save on a fresh project must commit even when staged matches disk" );
770 LIBGIT2_SCOPE libgit;
773 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
774 backupEnabled =
true;
777 SCOPED_BACKUP_FORMAT_OVERRIDE restoreFormat( format );
783 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
784 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pcb" ),
785 wxS(
"(kicad_pcb (version 20240108))\n" ) );
789 auto saver = [](
const wxString&, std::vector<HISTORY_FILE_DATA>& aFileData )
793 entry.
content =
"(kicad_pcb (version 20240108) (edited yes))\n";
794 aFileData.push_back( std::move( entry ) );
803 "zip backup format must not create incremental autosave commits" );
814 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
815 backupEnabled =
true;
818 SCOPED_BACKUP_FORMAT_OVERRIDE restoreFormat( format );
822 SCOPED_BACKUP_LOCATION_OVERRIDE restoreLocation(
location );
827 const wxString sep = wxFileName::GetPathSeparator();
829 writeTextFile(
path + sep + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
832 SCOPED_PROJECT_LOAD loadedProject( mgr,
path + sep + wxS(
"p.kicad_pro" ) );
836 auto saver = [](
const wxString&, std::vector<HISTORY_FILE_DATA>& aFileData )
840 entry.
content =
"(kicad_pcb (version 20240108) (edited yes))\n";
841 aFileData.push_back( std::move( entry ) );
848 wxString autosavePath =
path + sep + wxS(
"_autosave-p.kicad_pcb" );
849 BOOST_CHECK_MESSAGE( wxFileExists( autosavePath ),
850 "zip backup format must write autosave recovery files" );
861 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
862 backupEnabled =
true;
865 SCOPED_BACKUP_LOCATION_OVERRIDE restoreLocation(
location );
870 const wxString sep = wxFileName::GetPathSeparator();
874 writeTextFile(
path + sep + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
877 SCOPED_PROJECT_LOAD loadedProject( mgr,
path + sep + wxS(
"p.kicad_pro" ) );
879 const wxString sourcePath =
path + sep + wxS(
"p.kicad_pcb" );
880 const wxString autosavePath =
path + sep + wxS(
"_autosave-p.kicad_pcb" );
881 const wxString boardContent = wxS(
"(kicad_pcb (version 20240108))\n" );
884 writeTextFile( sourcePath, boardContent );
885 writeTextFile( autosavePath, boardContent );
890 wxDateTime srcMtime = wxFileName( sourcePath ).GetModificationTime();
891 wxDateTime newerMtime = srcMtime + wxTimeSpan::Seconds( 60 );
892 BOOST_REQUIRE( wxFileName( autosavePath ).SetTimes( &newerMtime, &newerMtime,
nullptr ) );
894 std::vector<wxString> exts{ wxS(
"kicad_pcb" ) };
897 BOOST_CHECK_MESSAGE( stale.empty(),
898 "Content-identical autosave with newer mtime was flagged stale, "
899 "triggering a spurious recovery prompt (issue 24126)" );
908 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
909 backupEnabled =
true;
912 SCOPED_BACKUP_LOCATION_OVERRIDE restoreLocation(
location );
917 const wxString sep = wxFileName::GetPathSeparator();
919 writeTextFile(
path + sep + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
922 SCOPED_PROJECT_LOAD loadedProject( mgr,
path + sep + wxS(
"p.kicad_pro" ) );
924 const wxString sourcePath =
path + sep + wxS(
"p.kicad_pcb" );
925 const wxString autosavePath =
path + sep + wxS(
"_autosave-p.kicad_pcb" );
928 writeTextFile( sourcePath, wxS(
"(kicad_pcb (version 20240108))\n" ) );
929 writeTextFile( autosavePath, wxS(
"(kicad_pcb (version 20240108) (edited yes))\n" ) );
933 wxDateTime srcMtime = wxFileName( sourcePath ).GetModificationTime();
934 wxDateTime newerMtime = srcMtime + wxTimeSpan::Seconds( 60 );
935 BOOST_REQUIRE( wxFileName( autosavePath ).SetTimes( &newerMtime, &newerMtime,
nullptr ) );
937 std::vector<wxString> exts{ wxS(
"kicad_pcb" ) };
940 BOOST_CHECK_MESSAGE( stale.size() == 1,
941 "Genuinely divergent autosave must still be flagged stale for recovery" );
947 LIBGIT2_SCOPE libgit;
950 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
951 backupEnabled =
true;
955 const wxString sep = wxFileName::GetPathSeparator();
957 writeTextFile(
path + sep + wxS(
"trim.kicad_pro" ), wxS(
"{}\n" ) );
958 writeTextFile(
path + sep + wxS(
"trim.kicad_pcb" ), wxS(
"(kicad_pcb (version 20240108))\n" ) );
959 writeTextFile(
path + sep + wxS(
"trim.kicad_sch" ), wxS(
"(kicad_sch (version 20240108))\n" ) );
964 writeTextFile(
path + sep + wxS(
"trim.kicad_pcb" ), wxS(
"(kicad_pcb (version 20240108) (net 1))\n" ) );
969 writeTextFile(
path + sep + wxS(
"trim.kicad_sch" ), wxS(
"(kicad_sch (version 20240108) (mod))\n" ) );
972 wxString historyDir =
path + sep + wxS(
".history" );
973 git_repository* repo =
nullptr;
974 BOOST_REQUIRE( git_repository_open( &repo, historyDir.mb_str().data() ) == 0 );
977 git_commit* head =
nullptr;
978 git_tree* tree =
nullptr;
979 BOOST_REQUIRE( git_reference_name_to_id( &headOid, repo,
"HEAD" ) == 0 );
980 BOOST_REQUIRE( git_commit_lookup( &head, repo, &headOid ) == 0 );
983 BOOST_CHECK_MESSAGE( git_tree_entry_byname( tree,
"trim.kicad_sch" ),
984 "schematic missing from snapshot after trim" );
985 BOOST_CHECK_MESSAGE( git_tree_entry_byname( tree,
"trim.kicad_pcb" ),
"board dropped from snapshot after trim" );
986 BOOST_CHECK_MESSAGE( git_tree_entry_byname( tree,
"trim.kicad_pro" ),
987 "project file dropped from snapshot after trim" );
989 git_tree_free( tree );
990 git_commit_free( head );
991 git_repository_free( repo );
998 SNAPSHOT_PROJECT tc( wxS(
"kicad_qa_pro_snapshot" ), wxS(
"snap.kicad_pro" ) );
1002 addViaStackPreset( bds, wxS(
"History Preset" ) );
1004 std::string bytes = snapshotProjectBytes( tc.Project() );
1006 BOOST_CHECK_MESSAGE( bytes.find(
"History Preset" ) != std::string::npos,
1007 "project file snapshot must carry the unsaved via stack preset" );
1014 SNAPSHOT_PROJECT tc( wxS(
"kicad_qa_pro_snapshot_save" ), wxS(
"snapsave.kicad_pro" ) );
1019 addViaStackPreset( bds, wxS(
"History Preset" ) );
1021 projectFile.
m_TextVars[wxS(
"HISTORY_VAR" )] = wxS(
"kept" );
1023 snapshotProjectBytes( tc.Project() );
1026 "a real save after the snapshot must still write the file" );
1028 std::string onDisk = readTextFile( tc.m_proPath );
1030 BOOST_CHECK_MESSAGE( onDisk.find(
"History Preset" ) != std::string::npos,
1031 "the via stack preset must reach the file after the snapshot" );
1032 BOOST_CHECK_MESSAGE( onDisk.find(
"HISTORY_VAR" ) != std::string::npos,
1033 "the text variable must reach the file after the snapshot" );
1040 SNAPSHOT_PROJECT tc( wxS(
"kicad_qa_pro_snapshot_clean" ), wxS(
"snapclean.kicad_pro" ) );
1047 std::string before = readTextFile( tc.m_proPath );
1049 snapshotProjectBytes( tc.Project() );
1052 "an unchanged project must not be rewritten after a snapshot" );
Container for design settings for a BOARD object.
std::vector< VIA_STACK_PRESET > m_ViaStackPresets
Information pertinent to a Pcbnew printed circuit board.
void SaveToHistory(const wxString &aProjectPath, std::vector< HISTORY_FILE_DATA > &aFileData)
Serialize board into HISTORY_FILE_DATA for non-blocking history commit.
void SetProject(PROJECT *aProject, bool aReferenceOnly=false)
Link a board to a given project.
const wxString & GetFileName() const
PROJECT * GetProject() const
wxString PathStr() const
Get the path to the temporary directory as a wxString.
Simple local history manager built on libgit2.
bool EnforceSizeLimit(const wxString &aProjectPath, size_t aMaxBytes, PROGRESS_REPORTER *aReporter=nullptr)
Enforce total size limit by rebuilding trimmed history keeping newest commits whose cumulative unique...
bool TagSave(const wxString &aProjectPath, const wxString &aFileType)
Tag a manual save in the local history repository.
bool RunRegisteredSaversAndCommit(const wxString &aProjectPath, const wxString &aTitle, const wxString &aTagFileType=wxEmptyString)
Run all registered savers and, if any staged changes differ from HEAD, create a commit.
std::vector< std::pair< wxString, wxString > > FindStaleAutosaveFiles(const wxString &aProjectPath, const std::vector< wxString > &aExtensions) const
Enumerate autosave files newer than their corresponding source files for the project at aProjectPath,...
wxString GetHeadHash(const wxString &aProjectPath)
Return the current head commit hash.
void WaitForPendingSave()
Block until any pending background save completes.
bool RestoreCommit(const wxString &aProjectPath, const wxString &aHash, wxWindow *aParent=nullptr, bool aConfirm=true)
Restore the project files to the state recorded by the given commit hash.
bool Init(const wxString &aProjectPath)
Initialize the local history repository for the given project path.
bool CommitSnapshot(const std::vector< wxString > &aFiles, const wxString &aTitle)
Commit the given files to the local history repository.
bool RunRegisteredSaversAsAutosaveFiles(const wxString &aProjectPath)
Run all registered savers and write their output to autosave files instead of committing to the local...
bool CommitFullProjectSnapshot(const wxString &aProjectPath, const wxString &aTitle)
Commit a snapshot of the entire project directory (excluding the .history directory and ignored trans...
void RegisterSaver(const void *aSaverObject, const std::function< void(const wxString &, std::vector< HISTORY_FILE_DATA > &)> &aSaver, const std::weak_ptr< void > &aLifetime={})
Register a saver callback invoked during autosave history commits.
void UnregisterSaver(const void *aSaverObject)
Unregister a previously registered saver callback.
virtual COMMON_SETTINGS * GetCommonSettings() const
virtual SETTINGS_MANAGER & GetSettingsManager() const
The backing store for a PROJECT, in JSON format.
std::map< wxString, wxString > m_TextVars
bool SaveToFile(const wxString &aDirectory="", bool aForce=false) override
Calls Store() and then writes the contents of the JSON document to a file.
Container for project specific data.
virtual const wxString GetProjectFullName() const
Return the full path and name of the project.
virtual const wxString GetProjectPath() const
Return the full path of the project.
void SaveToHistory(const wxString &aProjectPath, std::vector< HISTORY_FILE_DATA > &aFileData)
Produce HISTORY_FILE_DATA entries for project files (.kicad_pro and .kicad_prl).
bool SaveProject(const wxString &aFullPath=wxEmptyString, PROJECT *aProject=nullptr)
Save a loaded project.
bool LoadProject(const wxString &aFullPath, bool aSetActive=true)
Load a project or sets up a new project with a specified path.
bool UnloadProject(PROJECT *aProject, bool aSave=true)
Save, unload and unregister the given PROJECT.
PROJECT & Prj() const
A helper while we are not MDI-capable – return the one and only project.
@ ZIP
Zip archive snapshots; autosave uses recovery files.
@ PROJECT_DIR
Inside the project directory (default)
PGM_BASE & Pgm()
The global program "get" accessor.
BACKUP_LOCATION location
Where backups, history, and autosave files live.
BACKUP_FORMAT format
Backup format (incremental git history vs zip archives)
bool enabled
Automatically back up the project when files are saved.
Data produced by a registered saver on the UI thread, consumed by either the background local-history...
wxString sourcePath
For file-copy savers (small files like .kicad_pro)
std::string content
Serialized content (mutually exclusive with sourcePath)
wxString relativePath
Destination path relative to the project root.
A named microvia stack definition, chosen while routing instead of entering the values each time.
BOOST_AUTO_TEST_CASE(HorizontalAlignment)
BOOST_AUTO_TEST_SUITE(CadstarPartParser)
BOOST_REQUIRE(intersection.has_value()==c.ExpectedIntersection.has_value())
BOOST_AUTO_TEST_SUITE_END()
BOOST_AUTO_TEST_CASE(SaveToHistoryWithNullProjectDoesNotCrash)
Regression test for https://gitlab.com/kicad/code/kicad/-/issues/23737.
BOOST_CHECK_EQUAL(result, "25.4")