39#include <wx/datetime.h>
43#include <wx/filename.h>
44#include <wx/stdpaths.h>
53 LIBGIT2_SCOPE() { git_libgit2_init(); }
54 ~LIBGIT2_SCOPE() { git_libgit2_shutdown(); }
58struct SCOPED_PGM_BACKUP_SETTINGS
67 explicit SCOPED_PGM_BACKUP_SETTINGS() :
68 m_backupReset(
Pgm().GetCommonSettings()->m_Backup )
71 static const COMMON_SETTINGS::AUTO_BACKUP s_defaultState = {
81 SCOPED_SET_RESET<COMMON_SETTINGS::AUTO_BACKUP> m_backupReset;
87struct SCOPED_PROJECT_LOAD
89 SCOPED_PROJECT_LOAD( SETTINGS_MANAGER& aMgr,
const wxString& aProjectFile ) : m_mgr( aMgr )
91 m_mgr.LoadProject( aProjectFile.ToStdString() );
94 ~SCOPED_PROJECT_LOAD() { m_mgr.UnloadProject( &m_mgr.Prj(),
false ); }
96 SETTINGS_MANAGER& m_mgr;
100void writeTextFile(
const wxString& aPath,
const wxString& aContents )
102 wxFFile f( aPath, wxT(
"wb" ) );
104 f.Write( aContents );
109std::string readTextFile(
const wxString& aPath )
111 wxFFile f( aPath, wxT(
"rb" ) );
117 return text.ToStdString();
129 return std::string();
134struct SNAPSHOT_PROJECT
136 SNAPSHOT_PROJECT(
const wxString& aDirPrefix,
const wxString& aFileName ) :
137 m_tempDir( aDirPrefix ),
138 m_proPath( m_tempDir.ChildPathStr( aFileName ) )
140 writeTextFile( m_proPath, wxS(
"{ \"meta\": { \"version\": 3 } }\n" ) );
141 m_project = std::make_unique<SCOPED_PROJECT_LOAD>( m_mgr, m_proPath );
144 PROJECT& Project() {
return m_mgr.Prj(); }
145 PROJECT_FILE& ProjectFile() {
return m_mgr.Prj().GetProjectFile(); }
147 SETTINGS_MANAGER m_mgr;
148 KI_TEST::SCOPED_TEMP_DIR m_tempDir;
150 std::unique_ptr<SCOPED_PROJECT_LOAD> m_project;
163std::string snapshotProjectBytes(
PROJECT& aProject )
165 std::vector<HISTORY_FILE_DATA> fileData;
168 auto it = std::find_if( fileData.begin(), fileData.end(),
171 return aEntry.relativePath.EndsWith( wxS(
".kicad_pro" ) );
176 return historyEntryBytes( *it );
196 std::vector<HISTORY_FILE_DATA> fileData;
200 BOOST_CHECK_NO_THROW( board.
SaveToHistory( wxS(
"/tmp/anywhere" ), fileData ) );
201 BOOST_CHECK( fileData.empty() );
213 wxString tempDir = wxStandardPaths::Get().GetTempDir();
214 wxString projectPath = tempDir + wxFileName::GetPathSeparator() + wxS(
"pcb_autosave.kicad_pro" );
221 std::vector<HISTORY_FILE_DATA> fileData;
225 BOOST_CHECK( fileData.empty() );
242 LIBGIT2_SCOPE libgit;
244 SCOPED_PGM_BACKUP_SETTINGS restorePgmBackup;
251 wxString boardPath =
path + wxFileName::GetPathSeparator() + wxS(
"stray.kicad_pcb" );
252 writeTextFile( boardPath, wxS(
"(kicad_pcb (version 20240108))\n" ) );
256 BOOST_CHECK( !history.
Init(
path ) );
258 BOOST_CHECK( !history.
TagSave(
path, wxS(
"pcb" ) ) );
261 wxString historyDir =
path + wxFileName::GetPathSeparator() + wxS(
".history" );
262 BOOST_CHECK( !wxDirExists( historyDir ) );
275 LIBGIT2_SCOPE libgit;
277 SCOPED_PGM_BACKUP_SETTINGS restorePgmBackup;
282 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"subdirs.kicad_pro" ), wxS(
"{}\n" ) );
283 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"subdirs.kicad_pcb" ),
284 wxS(
"(kicad_pcb (version 20240108))\n" ) );
288 wxString subDir =
path + wxFileName::GetPathSeparator() + wxS(
"libs" );
289 BOOST_REQUIRE( wxFileName::Mkdir( subDir, 0777, wxPATH_MKDIR_FULL ) );
290 writeTextFile( subDir + wxFileName::GetPathSeparator() + wxS(
"fp.kicad_mod" ),
291 wxS(
"(footprint test)\n" ) );
297 wxString historyDir =
path + wxFileName::GetPathSeparator() + wxS(
".history" );
298 BOOST_CHECK( wxDirExists( historyDir ) );
299 BOOST_CHECK( !wxDirExists( subDir + wxFileName::GetPathSeparator() + wxS(
".history" ) ) );
305 writeTextFile( subDir + wxFileName::GetPathSeparator() + wxS(
"fp.kicad_mod" ),
306 wxS(
"(footprint test (modified))\n" ) );
314 BOOST_CHECK( headBefore != headAfter );
321 LIBGIT2_SCOPE libgit;
324 SCOPED_PGM_BACKUP_SETTINGS restorePgmBackup;
327 const wxString& projectPath = tempProject.
PathStr();
330 projectPath + wxFileName::GetPathSeparator() + wxS(
"issue24016.kicad_pcb" );
331 writeTextFile( boardPath, wxS(
"(kicad_pcb (version 20240108))\n" ) );
335 wxString projectFile =
336 projectPath + wxFileName::GetPathSeparator() + wxS(
"issue24016.kicad_pro" );
337 writeTextFile( projectFile, wxS(
"{}\n" ) );
342 wxString headHash = history.
GetHeadHash( projectPath );
347 wxString backupsDir =
348 projectPath + wxFileName::GetPathSeparator() + wxS(
"issue24016-backups" );
349 BOOST_REQUIRE( wxFileName::Mkdir( backupsDir, 0777, wxPATH_MKDIR_FULL ) );
351 wxString zipPath = backupsDir + wxFileName::GetPathSeparator()
352 + wxS(
"issue24016-2026-04-22_120000.zip" );
353 writeTextFile( zipPath, wxS(
"pretend-zip-contents" ) );
355 writeTextFile( boardPath, wxS(
"(kicad_pcb (version 20240108) (dirty yes))\n" ) );
359 BOOST_CHECK_MESSAGE( wxDirExists( backupsDir ),
360 "zip backups directory must survive RestoreCommit" );
361 BOOST_CHECK_MESSAGE( wxFileExists( zipPath ),
362 ".zip archive inside the backups directory must survive RestoreCommit" );
381 LIBGIT2_SCOPE libgit;
383 SCOPED_PGM_BACKUP_SETTINGS restorePgmBackup;
386 const wxString& projectPath = tempProject.
PathStr();
389 wxString parentPro = projectPath + wxFileName::GetPathSeparator() + wxS(
"projectA.kicad_pro" );
390 wxString parentPcb = projectPath + wxFileName::GetPathSeparator() + wxS(
"projectA.kicad_pcb" );
391 writeTextFile( parentPro, wxS(
"{}\n" ) );
392 writeTextFile( parentPcb, wxS(
"(kicad_pcb (version 20240108))\n" ) );
397 wxString headHash = history.
GetHeadHash( projectPath );
403 wxString nestedDir = projectPath + wxFileName::GetPathSeparator() + wxS(
"projectB" );
404 BOOST_REQUIRE( wxFileName::Mkdir( nestedDir, 0777, wxPATH_MKDIR_FULL ) );
406 wxString nestedPro = nestedDir + wxFileName::GetPathSeparator() + wxS(
"projectB.kicad_pro" );
407 wxString nestedPcb = nestedDir + wxFileName::GetPathSeparator() + wxS(
"projectB.kicad_pcb" );
408 wxString nestedSch = nestedDir + wxFileName::GetPathSeparator() + wxS(
"projectB.kicad_sch" );
409 writeTextFile( nestedPro, wxS(
"{ \"nested\": true }\n" ) );
410 writeTextFile( nestedPcb, wxS(
"(kicad_pcb (version 20240108) (nested yes))\n" ) );
411 writeTextFile( nestedSch, wxS(
"(kicad_sch (version 20240108))\n" ) );
414 writeTextFile( parentPcb, wxS(
"(kicad_pcb (version 20240108) (dirty yes))\n" ) );
419 BOOST_CHECK_MESSAGE( wxDirExists( nestedDir ),
420 "nested project directory must survive RestoreCommit" );
421 BOOST_CHECK_MESSAGE( wxFileExists( nestedPro ),
422 "nested .kicad_pro must survive RestoreCommit" );
423 BOOST_CHECK_MESSAGE( wxFileExists( nestedPcb ),
424 "nested .kicad_pcb must survive RestoreCommit" );
425 BOOST_CHECK_MESSAGE( wxFileExists( nestedSch ),
426 "nested .kicad_sch must survive RestoreCommit" );
429 BOOST_CHECK( wxFileExists( parentPro ) );
430 BOOST_CHECK( wxFileExists( parentPcb ) );
441 LIBGIT2_SCOPE libgit;
443 SCOPED_PGM_BACKUP_SETTINGS restorePgmBackup;
446 const wxString& projectPath = tempProject.
PathStr();
448 wxString boardPath = projectPath + wxFileName::GetPathSeparator() + wxS(
"rb.kicad_pcb" );
449 wxString projectFile = projectPath + wxFileName::GetPathSeparator() + wxS(
"rb.kicad_pro" );
450 writeTextFile( projectFile, wxS(
"{}\n" ) );
451 writeTextFile( boardPath, wxS(
"(kicad_pcb (version 20240108))\n" ) );
456 wxString headHash = history.
GetHeadHash( projectPath );
460 writeTextFile( boardPath, wxS(
"(kicad_pcb (version 20240108) (dirty yes))\n" ) );
466 wxString parentDir = wxFileName( projectPath ).GetPath();
467 wxString leafPrefix = wxFileName( projectPath ).GetFullName() + wxS(
"_restore_backup_" );
469 wxDir dir( parentDir );
472 wxString retainedBackup;
473 bool foundLegacyBackup =
false;
476 for(
bool cont = dir.GetFirst( &
name, wxEmptyString, wxDIR_DIRS ); cont;
477 cont = dir.GetNext( &
name ) )
479 if(
name.StartsWith( leafPrefix ) )
481 retainedBackup = parentDir + wxFileName::GetPathSeparator() +
name;
484 BOOST_CHECK_MESSAGE(
name.Find(
':' ) == wxNOT_FOUND,
485 "retained backup directory name must not contain ':' "
486 "(Windows-illegal in path components)" );
488 else if(
name == wxFileName( projectPath ).GetFullName() + wxS(
"_restore_backup" ) )
490 foundLegacyBackup =
true;
495 !retainedBackup.IsEmpty(),
496 "RestoreCommit must retain a timestamped _restore_backup_<ts>/ sibling directory" );
499 "RestoreCommit must not leave the legacy non-timestamped _restore_backup/ behind" );
502 if( !retainedBackup.IsEmpty() && wxDirExists( retainedBackup ) )
503 wxFileName::Rmdir( retainedBackup, wxPATH_RMDIR_RECURSIVE );
512 LIBGIT2_SCOPE libgit;
514 SCOPED_PGM_BACKUP_SETTINGS restorePgmBackup;
519 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
520 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pcb" ),
521 wxS(
"(kicad_pcb (version 20240108))\n" ) );
522 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_sch" ),
523 wxS(
"(kicad_sch (version 20240108))\n" ) );
525 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"passwords.txt" ), wxS(
"secret\n" ) );
526 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"datasheet.pdf" ), wxS(
"fake pdf bytes\n" ) );
527 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"notes.md" ), wxS(
"personal notes\n" ) );
529 wxString subDir =
path + wxFileName::GetPathSeparator() + wxS(
"docs" );
530 BOOST_REQUIRE( wxFileName::Mkdir( subDir, 0777, wxPATH_MKDIR_FULL ) );
531 writeTextFile( subDir + wxFileName::GetPathSeparator() + wxS(
"manual.txt" ), wxS(
"irrelevant\n" ) );
536 wxString hist =
path + wxFileName::GetPathSeparator() + wxS(
".history" );
538 BOOST_REQUIRE_EQUAL( git_repository_open( &repo, hist.mb_str().data() ), 0 );
541 BOOST_REQUIRE_EQUAL( git_reference_name_to_id( &head_oid, repo,
"HEAD" ), 0 );
543 git_commit* head =
nullptr;
544 BOOST_REQUIRE_EQUAL( git_commit_lookup( &head, repo, &head_oid ), 0 );
546 git_tree* tree =
nullptr;
547 BOOST_REQUIRE_EQUAL( git_commit_tree( &tree, head ), 0 );
549 std::vector<std::string> committedPaths;
551 tree, GIT_TREEWALK_PRE,
552 [](
const char* root,
const git_tree_entry* entry,
void* payload ) ->
int
554 auto* paths =
static_cast<std::vector<std::string>*
>( payload );
556 if( git_tree_entry_type( entry ) == GIT_OBJECT_BLOB )
557 paths->push_back( std::string( root ) + git_tree_entry_name( entry ) );
563 git_tree_free( tree );
564 git_commit_free( head );
565 git_repository_free( repo );
567 auto contains = [&](
const std::string& s )
569 return std::find( committedPaths.begin(), committedPaths.end(), s ) != committedPaths.end();
572 BOOST_CHECK_MESSAGE( contains(
"p.kicad_pro" ),
"kicad_pro must be committed" );
573 BOOST_CHECK_MESSAGE( contains(
"p.kicad_pcb" ),
"kicad_pcb must be committed" );
574 BOOST_CHECK_MESSAGE( contains(
"p.kicad_sch" ),
"kicad_sch must be committed" );
576 BOOST_CHECK_MESSAGE( !contains(
"passwords.txt" ),
"passwords.txt must NOT appear in history" );
577 BOOST_CHECK_MESSAGE( !contains(
"datasheet.pdf" ),
"datasheet.pdf must NOT appear in history" );
578 BOOST_CHECK_MESSAGE( !contains(
"notes.md" ),
"notes.md must NOT appear in history" );
579 BOOST_CHECK_MESSAGE( !contains(
"docs/manual.txt" ),
"subdirectory user content must NOT appear in history" );
589 LIBGIT2_SCOPE libgit;
591 SCOPED_PGM_BACKUP_SETTINGS restorePgmBackup;
596 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
597 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pcb" ),
598 wxS(
"(kicad_pcb (version 20240108))\n" ) );
603 std::string inMemoryContent =
"(kicad_pcb (version 20240108))\n";
604 BOOST_REQUIRE_EQUAL( readTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pcb" ) ),
607 auto saver = [&inMemoryContent](
const wxString&, std::vector<HISTORY_FILE_DATA>& aFileData )
611 entry.
content = inMemoryContent;
612 aFileData.push_back( std::move( entry ) );
621 wxString histDir =
path + wxFileName::GetPathSeparator() + wxS(
".history" );
622 BOOST_CHECK( wxDirExists( histDir ) );
625 BOOST_CHECK_MESSAGE( head.IsEmpty(),
"no untagged HEAD should exist after an idle first save" );
628 inMemoryContent =
"(kicad_pcb (version 20240108) (edited yes))\n";
634 BOOST_CHECK_MESSAGE( !head.IsEmpty(),
"in-memory edits diverging from disk must produce a commit" );
648 LIBGIT2_SCOPE libgit;
650 SCOPED_PGM_BACKUP_SETTINGS restorePgmBackup;
654 const wxString sep = wxFileName::GetPathSeparator();
656 writeTextFile(
path + sep + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
657 writeTextFile(
path + sep + wxS(
"p.kicad_pcb" ), wxS(
"(kicad_pcb (version 20240108))\n" ) );
663 auto runCount = std::make_shared<int>( 0 );
664 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
667 [runCount](
const wxString&, std::vector<HISTORY_FILE_DATA>& aFileData )
671 HISTORY_FILE_DATA entry;
672 entry.relativePath = wxS(
"p.kicad_pcb" );
673 entry.content =
"(kicad_pcb (version 20240108) (edited yes))\n";
674 aFileData.push_back( std::move( entry ) );
676 board->GetHistoryLifetimeToken() );
688 BOOST_CHECK_MESSAGE( *runCount == 1,
"commit runner invoked a saver whose board was destroyed" );
691 BOOST_CHECK_MESSAGE( *runCount == 1,
"autosave-file runner invoked a saver whose board was destroyed" );
702 LIBGIT2_SCOPE libgit;
704 SCOPED_PGM_BACKUP_SETTINGS restorePgmBackup;
709 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
710 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pcb" ),
711 wxS(
"(kicad_pcb (version 20240108))\n" ) );
715 std::string inMemoryContent =
"(kicad_pcb (version 20240108))\n";
717 auto saver = [&inMemoryContent](
const wxString&, std::vector<HISTORY_FILE_DATA>& aFileData )
721 entry.
content = inMemoryContent;
722 aFileData.push_back( std::move( entry ) );
731 BOOST_CHECK_MESSAGE( !head.IsEmpty(),
"manual save on a fresh project must commit even when staged matches disk" );
742 LIBGIT2_SCOPE libgit;
744 SCOPED_PGM_BACKUP_SETTINGS restorePgmBackup;
751 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
752 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pcb" ),
753 wxS(
"(kicad_pcb (version 20240108))\n" ) );
757 auto saver = [](
const wxString&, std::vector<HISTORY_FILE_DATA>& aFileData )
761 entry.
content =
"(kicad_pcb (version 20240108) (edited yes))\n";
762 aFileData.push_back( std::move( entry ) );
771 "zip backup format must not create incremental autosave commits" );
781 SCOPED_PGM_BACKUP_SETTINGS restorePgmBackup;
787 const wxString sep = wxFileName::GetPathSeparator();
789 writeTextFile(
path + sep + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
792 SCOPED_PROJECT_LOAD loadedProject( mgr,
path + sep + wxS(
"p.kicad_pro" ) );
796 auto saver = [](
const wxString&, std::vector<HISTORY_FILE_DATA>& aFileData )
800 entry.
content =
"(kicad_pcb (version 20240108) (edited yes))\n";
801 aFileData.push_back( std::move( entry ) );
808 wxString autosavePath =
path + sep + wxS(
"_autosave-p.kicad_pcb" );
809 BOOST_CHECK_MESSAGE( wxFileExists( autosavePath ),
810 "zip backup format must write autosave recovery files" );
820 SCOPED_PGM_BACKUP_SETTINGS restorePgmBackup;
825 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" ) );
834 const wxString sourcePath =
path + sep + wxS(
"p.kicad_pcb" );
835 const wxString autosavePath =
path + sep + wxS(
"_autosave-p.kicad_pcb" );
836 const wxString boardContent = wxS(
"(kicad_pcb (version 20240108))\n" );
839 writeTextFile( sourcePath, boardContent );
840 writeTextFile( autosavePath, boardContent );
845 wxDateTime srcMtime = wxFileName( sourcePath ).GetModificationTime();
846 wxDateTime newerMtime = srcMtime + wxTimeSpan::Seconds( 60 );
847 BOOST_REQUIRE( wxFileName( autosavePath ).SetTimes( &newerMtime, &newerMtime,
nullptr ) );
849 std::vector<wxString> exts{ wxS(
"kicad_pcb" ) };
852 BOOST_CHECK_MESSAGE( stale.empty(),
853 "Content-identical autosave with newer mtime was flagged stale, "
854 "triggering a spurious recovery prompt (issue 24126)" );
862 SCOPED_PGM_BACKUP_SETTINGS restorePgmBackup;
867 const wxString sep = wxFileName::GetPathSeparator();
869 writeTextFile(
path + sep + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
872 SCOPED_PROJECT_LOAD loadedProject( mgr,
path + sep + wxS(
"p.kicad_pro" ) );
874 const wxString sourcePath =
path + sep + wxS(
"p.kicad_pcb" );
875 const wxString autosavePath =
path + sep + wxS(
"_autosave-p.kicad_pcb" );
878 writeTextFile( sourcePath, wxS(
"(kicad_pcb (version 20240108))\n" ) );
879 writeTextFile( autosavePath, wxS(
"(kicad_pcb (version 20240108) (edited yes))\n" ) );
883 wxDateTime srcMtime = wxFileName( sourcePath ).GetModificationTime();
884 wxDateTime newerMtime = srcMtime + wxTimeSpan::Seconds( 60 );
885 BOOST_REQUIRE( wxFileName( autosavePath ).SetTimes( &newerMtime, &newerMtime,
nullptr ) );
887 std::vector<wxString> exts{ wxS(
"kicad_pcb" ) };
890 BOOST_CHECK_MESSAGE( stale.size() == 1,
891 "Genuinely divergent autosave must still be flagged stale for recovery" );
897 LIBGIT2_SCOPE libgit;
899 SCOPED_PGM_BACKUP_SETTINGS restorePgmBackup;
903 const wxString sep = wxFileName::GetPathSeparator();
905 writeTextFile(
path + sep + wxS(
"trim.kicad_pro" ), wxS(
"{}\n" ) );
906 writeTextFile(
path + sep + wxS(
"trim.kicad_pcb" ), wxS(
"(kicad_pcb (version 20240108))\n" ) );
907 writeTextFile(
path + sep + wxS(
"trim.kicad_sch" ), wxS(
"(kicad_sch (version 20240108))\n" ) );
912 writeTextFile(
path + sep + wxS(
"trim.kicad_pcb" ), wxS(
"(kicad_pcb (version 20240108) (net 1))\n" ) );
917 writeTextFile(
path + sep + wxS(
"trim.kicad_sch" ), wxS(
"(kicad_sch (version 20240108) (mod))\n" ) );
920 wxString historyDir =
path + sep + wxS(
".history" );
922 BOOST_REQUIRE( git_repository_open( &repo, historyDir.mb_str().data() ) == 0 );
925 git_commit* head =
nullptr;
926 git_tree* tree =
nullptr;
927 BOOST_REQUIRE( git_reference_name_to_id( &headOid, repo,
"HEAD" ) == 0 );
928 BOOST_REQUIRE( git_commit_lookup( &head, repo, &headOid ) == 0 );
931 BOOST_CHECK_MESSAGE( git_tree_entry_byname( tree,
"trim.kicad_sch" ),
932 "schematic missing from snapshot after trim" );
933 BOOST_CHECK_MESSAGE( git_tree_entry_byname( tree,
"trim.kicad_pcb" ),
"board dropped from snapshot after trim" );
934 BOOST_CHECK_MESSAGE( git_tree_entry_byname( tree,
"trim.kicad_pro" ),
935 "project file dropped from snapshot after trim" );
937 git_tree_free( tree );
938 git_commit_free( head );
939 git_repository_free( repo );
946 SNAPSHOT_PROJECT tc( wxS(
"kicad_qa_pro_snapshot" ), wxS(
"snap.kicad_pro" ) );
950 addViaStackPreset( bds, wxS(
"History Preset" ) );
952 std::string
bytes = snapshotProjectBytes( tc.Project() );
954 BOOST_CHECK_MESSAGE(
bytes.find(
"History Preset" ) != std::string::npos,
955 "project file snapshot must carry the unsaved via stack preset" );
962 SNAPSHOT_PROJECT tc( wxS(
"kicad_qa_pro_snapshot_save" ), wxS(
"snapsave.kicad_pro" ) );
967 addViaStackPreset( bds, wxS(
"History Preset" ) );
969 projectFile.
m_TextVars[wxS(
"HISTORY_VAR" )] = wxS(
"kept" );
971 snapshotProjectBytes( tc.Project() );
974 "a real save after the snapshot must still write the file" );
976 std::string onDisk = readTextFile( tc.m_proPath );
978 BOOST_CHECK_MESSAGE( onDisk.find(
"History Preset" ) != std::string::npos,
979 "the via stack preset must reach the file after the snapshot" );
980 BOOST_CHECK_MESSAGE( onDisk.find(
"HISTORY_VAR" ) != std::string::npos,
981 "the text variable must reach the file after the snapshot" );
988 SNAPSHOT_PROJECT tc( wxS(
"kicad_qa_pro_snapshot_clean" ), wxS(
"snapclean.kicad_pro" ) );
995 std::string before = readTextFile( tc.m_proPath );
997 snapshotProjectBytes( tc.Project() );
1000 "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.
@ INCREMENTAL
Git-based local history (default)
@ PROJECT_DIR
Inside the project directory (default)
constexpr char PROJECT_FILE[]
Source project file, present when the importer is handed a project's sheets rather than one file.
PGM_BASE & Pgm()
The global program "get" accessor.
BACKUP_LOCATION location
Where backups, history, and autosave files live.
static constexpr unsigned long long UNLIMITED_SIZE
Value representing unlimited total size.
BACKUP_FORMAT format
Backup format (incremental git history vs zip archives)
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)
static std::vector< uint8_t > bytes(const std::string &aText)
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")