34#include <wx/datetime.h>
38#include <wx/filename.h>
39#include <wx/stdpaths.h>
48 LIBGIT2_SCOPE() { git_libgit2_init(); }
49 ~LIBGIT2_SCOPE() { git_libgit2_shutdown(); }
53struct SCOPED_BOOL_OVERRIDE
55 explicit SCOPED_BOOL_OVERRIDE(
bool& aFlag ) : m_flag( aFlag ), m_original( aFlag ) {}
56 ~SCOPED_BOOL_OVERRIDE() { m_flag = m_original; }
65struct SCOPED_BACKUP_LOCATION_OVERRIDE
67 explicit SCOPED_BACKUP_LOCATION_OVERRIDE(
BACKUP_LOCATION& aLocation ) :
68 m_location( aLocation ), m_original( aLocation )
72 ~SCOPED_BACKUP_LOCATION_OVERRIDE() { m_location = m_original; }
79struct SCOPED_BACKUP_FORMAT_OVERRIDE
81 explicit SCOPED_BACKUP_FORMAT_OVERRIDE(
BACKUP_FORMAT& aFormat ) :
82 m_format( aFormat ), m_original( aFormat )
86 ~SCOPED_BACKUP_FORMAT_OVERRIDE() { m_format = m_original; }
95struct SCOPED_PROJECT_LOAD
97 SCOPED_PROJECT_LOAD( SETTINGS_MANAGER& aMgr,
const wxString& aProjectFile ) : m_mgr( aMgr )
99 m_mgr.LoadProject( aProjectFile.ToStdString() );
102 ~SCOPED_PROJECT_LOAD() { m_mgr.UnloadProject( &m_mgr.Prj(),
false ); }
104 SETTINGS_MANAGER& m_mgr;
110struct SCOPED_TEMP_DIR
112 explicit SCOPED_TEMP_DIR(
const wxString& aPrefix )
114 wxString base = wxStandardPaths::Get().GetTempDir();
115 m_path = base + wxFileName::GetPathSeparator() + aPrefix
116 + wxString::Format( wxS(
"_%lu_%ld" ),
117 static_cast<unsigned long>( ::wxGetProcessId() ),
118 static_cast<long>( wxDateTime::UNow().GetTicks() ) );
119 wxFileName::Mkdir( m_path, 0777, wxPATH_MKDIR_FULL );
124 if( !m_path.IsEmpty() && wxDirExists( m_path ) )
125 wxFileName::Rmdir( m_path, wxPATH_RMDIR_RECURSIVE );
128 const wxString& Path()
const {
return m_path; }
134void writeTextFile(
const wxString& aPath,
const wxString& aContents )
136 wxFFile f( aPath, wxT(
"w" ) );
138 f.Write( aContents );
159 std::vector<HISTORY_FILE_DATA> fileData;
163 BOOST_CHECK_NO_THROW( board.
SaveToHistory( wxS(
"/tmp/anywhere" ), fileData ) );
164 BOOST_CHECK( fileData.empty() );
176 wxString tempDir = wxStandardPaths::Get().GetTempDir();
177 wxString projectPath = tempDir + wxFileName::GetPathSeparator() + wxS(
"pcb_autosave.kicad_pro" );
184 std::vector<HISTORY_FILE_DATA> fileData;
188 BOOST_CHECK( fileData.empty() );
205 LIBGIT2_SCOPE libgit;
208 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
209 backupEnabled =
true;
211 SCOPED_TEMP_DIR notAProject( wxS(
"kicad_qa_no_project" ) );
212 const wxString&
path = notAProject.Path();
216 wxString boardPath =
path + wxFileName::GetPathSeparator() + wxS(
"stray.kicad_pcb" );
217 writeTextFile( boardPath, wxS(
"(kicad_pcb (version 20240108))\n" ) );
221 BOOST_CHECK( !history.
Init(
path ) );
223 BOOST_CHECK( !history.
TagSave(
path, wxS(
"pcb" ) ) );
226 wxString historyDir =
path + wxFileName::GetPathSeparator() + wxS(
".history" );
227 BOOST_CHECK( !wxDirExists( historyDir ) );
240 LIBGIT2_SCOPE libgit;
243 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
244 backupEnabled =
true;
246 SCOPED_TEMP_DIR
project( wxS(
"kicad_qa_subdirs" ) );
249 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"subdirs.kicad_pro" ), wxS(
"{}\n" ) );
250 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"subdirs.kicad_pcb" ),
251 wxS(
"(kicad_pcb (version 20240108))\n" ) );
255 wxString subDir =
path + wxFileName::GetPathSeparator() + wxS(
"libs" );
256 BOOST_REQUIRE( wxFileName::Mkdir( subDir, 0777, wxPATH_MKDIR_FULL ) );
257 writeTextFile( subDir + wxFileName::GetPathSeparator() + wxS(
"fp.kicad_mod" ),
258 wxS(
"(footprint test)\n" ) );
264 wxString historyDir =
path + wxFileName::GetPathSeparator() + wxS(
".history" );
265 BOOST_CHECK( wxDirExists( historyDir ) );
266 BOOST_CHECK( !wxDirExists( subDir + wxFileName::GetPathSeparator() + wxS(
".history" ) ) );
272 writeTextFile( subDir + wxFileName::GetPathSeparator() + wxS(
"fp.kicad_mod" ),
273 wxS(
"(footprint test (modified))\n" ) );
281 BOOST_CHECK( headBefore != headAfter );
288 LIBGIT2_SCOPE libgit;
292 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
293 backupEnabled =
true;
295 SCOPED_TEMP_DIR tempProject( wxS(
"kicad_qa_issue24016" ) );
296 const wxString& projectPath = tempProject.Path();
299 projectPath + wxFileName::GetPathSeparator() + wxS(
"issue24016.kicad_pcb" );
300 writeTextFile( boardPath, wxS(
"(kicad_pcb (version 20240108))\n" ) );
304 wxString projectFile =
305 projectPath + wxFileName::GetPathSeparator() + wxS(
"issue24016.kicad_pro" );
306 writeTextFile( projectFile, wxS(
"{}\n" ) );
311 wxString headHash = history.
GetHeadHash( projectPath );
316 wxString backupsDir =
317 projectPath + wxFileName::GetPathSeparator() + wxS(
"issue24016-backups" );
318 BOOST_REQUIRE( wxFileName::Mkdir( backupsDir, 0777, wxPATH_MKDIR_FULL ) );
320 wxString zipPath = backupsDir + wxFileName::GetPathSeparator()
321 + wxS(
"issue24016-2026-04-22_120000.zip" );
322 writeTextFile( zipPath, wxS(
"pretend-zip-contents" ) );
324 writeTextFile( boardPath, wxS(
"(kicad_pcb (version 20240108) (dirty yes))\n" ) );
328 BOOST_CHECK_MESSAGE( wxDirExists( backupsDir ),
329 "zip backups directory must survive RestoreCommit" );
330 BOOST_CHECK_MESSAGE( wxFileExists( zipPath ),
331 ".zip archive inside the backups directory must survive RestoreCommit" );
350 LIBGIT2_SCOPE libgit;
353 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
354 backupEnabled =
true;
356 SCOPED_TEMP_DIR tempProject( wxS(
"kicad_qa_nested_project" ) );
357 const wxString& projectPath = tempProject.Path();
360 wxString parentPro = projectPath + wxFileName::GetPathSeparator() + wxS(
"projectA.kicad_pro" );
361 wxString parentPcb = projectPath + wxFileName::GetPathSeparator() + wxS(
"projectA.kicad_pcb" );
362 writeTextFile( parentPro, wxS(
"{}\n" ) );
363 writeTextFile( parentPcb, wxS(
"(kicad_pcb (version 20240108))\n" ) );
368 wxString headHash = history.
GetHeadHash( projectPath );
374 wxString nestedDir = projectPath + wxFileName::GetPathSeparator() + wxS(
"projectB" );
375 BOOST_REQUIRE( wxFileName::Mkdir( nestedDir, 0777, wxPATH_MKDIR_FULL ) );
377 wxString nestedPro = nestedDir + wxFileName::GetPathSeparator() + wxS(
"projectB.kicad_pro" );
378 wxString nestedPcb = nestedDir + wxFileName::GetPathSeparator() + wxS(
"projectB.kicad_pcb" );
379 wxString nestedSch = nestedDir + wxFileName::GetPathSeparator() + wxS(
"projectB.kicad_sch" );
380 writeTextFile( nestedPro, wxS(
"{ \"nested\": true }\n" ) );
381 writeTextFile( nestedPcb, wxS(
"(kicad_pcb (version 20240108) (nested yes))\n" ) );
382 writeTextFile( nestedSch, wxS(
"(kicad_sch (version 20240108))\n" ) );
385 writeTextFile( parentPcb, wxS(
"(kicad_pcb (version 20240108) (dirty yes))\n" ) );
390 BOOST_CHECK_MESSAGE( wxDirExists( nestedDir ),
391 "nested project directory must survive RestoreCommit" );
392 BOOST_CHECK_MESSAGE( wxFileExists( nestedPro ),
393 "nested .kicad_pro must survive RestoreCommit" );
394 BOOST_CHECK_MESSAGE( wxFileExists( nestedPcb ),
395 "nested .kicad_pcb must survive RestoreCommit" );
396 BOOST_CHECK_MESSAGE( wxFileExists( nestedSch ),
397 "nested .kicad_sch must survive RestoreCommit" );
400 BOOST_CHECK( wxFileExists( parentPro ) );
401 BOOST_CHECK( wxFileExists( parentPcb ) );
412 LIBGIT2_SCOPE libgit;
415 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
416 backupEnabled =
true;
418 SCOPED_TEMP_DIR tempProject( wxS(
"kicad_qa_retained_backup" ) );
419 const wxString& projectPath = tempProject.Path();
421 wxString boardPath = projectPath + wxFileName::GetPathSeparator() + wxS(
"rb.kicad_pcb" );
422 wxString projectFile = projectPath + wxFileName::GetPathSeparator() + wxS(
"rb.kicad_pro" );
423 writeTextFile( projectFile, wxS(
"{}\n" ) );
424 writeTextFile( boardPath, wxS(
"(kicad_pcb (version 20240108))\n" ) );
429 wxString headHash = history.
GetHeadHash( projectPath );
433 writeTextFile( boardPath, wxS(
"(kicad_pcb (version 20240108) (dirty yes))\n" ) );
439 wxString parentDir = wxFileName( projectPath ).GetPath();
440 wxString leafPrefix = wxFileName( projectPath ).GetFullName() + wxS(
"_restore_backup_" );
442 wxDir dir( parentDir );
445 wxString retainedBackup;
446 bool foundLegacyBackup =
false;
449 for(
bool cont = dir.GetFirst( &
name, wxEmptyString, wxDIR_DIRS ); cont;
450 cont = dir.GetNext( &
name ) )
452 if(
name.StartsWith( leafPrefix ) )
454 retainedBackup = parentDir + wxFileName::GetPathSeparator() +
name;
457 BOOST_CHECK_MESSAGE(
name.Find(
':' ) == wxNOT_FOUND,
458 "retained backup directory name must not contain ':' "
459 "(Windows-illegal in path components)" );
461 else if(
name == wxFileName( projectPath ).GetFullName() + wxS(
"_restore_backup" ) )
463 foundLegacyBackup =
true;
468 !retainedBackup.IsEmpty(),
469 "RestoreCommit must retain a timestamped _restore_backup_<ts>/ sibling directory" );
472 "RestoreCommit must not leave the legacy non-timestamped _restore_backup/ behind" );
475 if( !retainedBackup.IsEmpty() && wxDirExists( retainedBackup ) )
476 wxFileName::Rmdir( retainedBackup, wxPATH_RMDIR_RECURSIVE );
485 LIBGIT2_SCOPE libgit;
488 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
489 backupEnabled =
true;
491 SCOPED_TEMP_DIR
project( wxS(
"kicad_qa_privacy" ) );
494 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
495 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pcb" ),
496 wxS(
"(kicad_pcb (version 20240108))\n" ) );
497 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_sch" ),
498 wxS(
"(kicad_sch (version 20240108))\n" ) );
500 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"passwords.txt" ), wxS(
"secret\n" ) );
501 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"datasheet.pdf" ), wxS(
"fake pdf bytes\n" ) );
502 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"notes.md" ), wxS(
"personal notes\n" ) );
504 wxString subDir =
path + wxFileName::GetPathSeparator() + wxS(
"docs" );
505 BOOST_REQUIRE( wxFileName::Mkdir( subDir, 0777, wxPATH_MKDIR_FULL ) );
506 writeTextFile( subDir + wxFileName::GetPathSeparator() + wxS(
"manual.txt" ), wxS(
"irrelevant\n" ) );
511 wxString hist =
path + wxFileName::GetPathSeparator() + wxS(
".history" );
512 git_repository* repo =
nullptr;
513 BOOST_REQUIRE_EQUAL( git_repository_open( &repo, hist.mb_str().data() ), 0 );
516 BOOST_REQUIRE_EQUAL( git_reference_name_to_id( &head_oid, repo,
"HEAD" ), 0 );
518 git_commit* head =
nullptr;
519 BOOST_REQUIRE_EQUAL( git_commit_lookup( &head, repo, &head_oid ), 0 );
521 git_tree* tree =
nullptr;
522 BOOST_REQUIRE_EQUAL( git_commit_tree( &tree, head ), 0 );
524 std::vector<std::string> committedPaths;
526 tree, GIT_TREEWALK_PRE,
527 [](
const char* root,
const git_tree_entry* entry,
void* payload ) ->
int
529 auto* paths =
static_cast<std::vector<std::string>*
>( payload );
531 if( git_tree_entry_type( entry ) == GIT_OBJECT_BLOB )
532 paths->push_back( std::string( root ) + git_tree_entry_name( entry ) );
538 git_tree_free( tree );
539 git_commit_free( head );
540 git_repository_free( repo );
542 auto contains = [&](
const std::string& s )
544 return std::find( committedPaths.begin(), committedPaths.end(), s ) != committedPaths.end();
547 BOOST_CHECK_MESSAGE( contains(
"p.kicad_pro" ),
"kicad_pro must be committed" );
548 BOOST_CHECK_MESSAGE( contains(
"p.kicad_pcb" ),
"kicad_pcb must be committed" );
549 BOOST_CHECK_MESSAGE( contains(
"p.kicad_sch" ),
"kicad_sch must be committed" );
551 BOOST_CHECK_MESSAGE( !contains(
"passwords.txt" ),
"passwords.txt must NOT appear in history" );
552 BOOST_CHECK_MESSAGE( !contains(
"datasheet.pdf" ),
"datasheet.pdf must NOT appear in history" );
553 BOOST_CHECK_MESSAGE( !contains(
"notes.md" ),
"notes.md must NOT appear in history" );
554 BOOST_CHECK_MESSAGE( !contains(
"docs/manual.txt" ),
"subdirectory user content must NOT appear in history" );
564 LIBGIT2_SCOPE libgit;
567 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
568 backupEnabled =
true;
570 SCOPED_TEMP_DIR
project( wxS(
"kicad_qa_first_idle_autosave" ) );
573 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
574 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pcb" ),
575 wxS(
"(kicad_pcb (version 20240108))\n" ) );
580 std::string inMemoryContent =
"(kicad_pcb (version 20240108))\n";
582 auto saver = [&inMemoryContent](
const wxString&, std::vector<HISTORY_FILE_DATA>& aFileData )
586 entry.
content = inMemoryContent;
587 aFileData.push_back( std::move( entry ) );
596 wxString histDir =
path + wxFileName::GetPathSeparator() + wxS(
".history" );
597 BOOST_CHECK( wxDirExists( histDir ) );
600 BOOST_CHECK_MESSAGE( head.IsEmpty(),
"no untagged HEAD should exist after an idle first save" );
603 inMemoryContent =
"(kicad_pcb (version 20240108) (edited yes))\n";
609 BOOST_CHECK_MESSAGE( !head.IsEmpty(),
"in-memory edits diverging from disk must produce a commit" );
623 LIBGIT2_SCOPE libgit;
626 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
627 backupEnabled =
true;
629 SCOPED_TEMP_DIR
project( wxS(
"kicad_qa_saver_lifetime" ) );
631 const wxString sep = wxFileName::GetPathSeparator();
633 writeTextFile(
path + sep + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
634 writeTextFile(
path + sep + wxS(
"p.kicad_pcb" ), wxS(
"(kicad_pcb (version 20240108))\n" ) );
640 auto runCount = std::make_shared<int>( 0 );
641 std::unique_ptr<BOARD> board = std::make_unique<BOARD>();
644 [runCount](
const wxString&, std::vector<HISTORY_FILE_DATA>& aFileData )
648 HISTORY_FILE_DATA entry;
649 entry.relativePath = wxS(
"p.kicad_pcb" );
650 entry.content =
"(kicad_pcb (version 20240108) (edited yes))\n";
651 aFileData.push_back( std::move( entry ) );
653 board->GetHistoryLifetimeToken() );
665 BOOST_CHECK_MESSAGE( *runCount == 1,
"commit runner invoked a saver whose board was destroyed" );
668 BOOST_CHECK_MESSAGE( *runCount == 1,
"autosave-file runner invoked a saver whose board was destroyed" );
679 LIBGIT2_SCOPE libgit;
682 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
683 backupEnabled =
true;
685 SCOPED_TEMP_DIR
project( wxS(
"kicad_qa_first_manual_save" ) );
688 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
689 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pcb" ),
690 wxS(
"(kicad_pcb (version 20240108))\n" ) );
694 std::string inMemoryContent =
"(kicad_pcb (version 20240108))\n";
696 auto saver = [&inMemoryContent](
const wxString&, std::vector<HISTORY_FILE_DATA>& aFileData )
700 entry.
content = inMemoryContent;
701 aFileData.push_back( std::move( entry ) );
710 BOOST_CHECK_MESSAGE( !head.IsEmpty(),
"manual save on a fresh project must commit even when staged matches disk" );
721 LIBGIT2_SCOPE libgit;
724 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
725 backupEnabled =
true;
728 SCOPED_BACKUP_FORMAT_OVERRIDE restoreFormat( format );
731 SCOPED_TEMP_DIR
project( wxS(
"kicad_qa_zip_skips_incremental" ) );
734 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
735 writeTextFile(
path + wxFileName::GetPathSeparator() + wxS(
"p.kicad_pcb" ),
736 wxS(
"(kicad_pcb (version 20240108))\n" ) );
740 auto saver = [](
const wxString&, std::vector<HISTORY_FILE_DATA>& aFileData )
744 entry.
content =
"(kicad_pcb (version 20240108) (edited yes))\n";
745 aFileData.push_back( std::move( entry ) );
754 "zip backup format must not create incremental autosave commits" );
765 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
766 backupEnabled =
true;
769 SCOPED_BACKUP_FORMAT_OVERRIDE restoreFormat( format );
773 SCOPED_BACKUP_LOCATION_OVERRIDE restoreLocation(
location );
776 SCOPED_TEMP_DIR
project( wxS(
"kicad_qa_zip_recovery_files" ) );
778 const wxString sep = wxFileName::GetPathSeparator();
780 writeTextFile(
path + sep + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
783 SCOPED_PROJECT_LOAD loadedProject( mgr,
path + sep + wxS(
"p.kicad_pro" ) );
787 auto saver = [](
const wxString&, std::vector<HISTORY_FILE_DATA>& aFileData )
791 entry.
content =
"(kicad_pcb (version 20240108) (edited yes))\n";
792 aFileData.push_back( std::move( entry ) );
799 wxString autosavePath =
path + sep + wxS(
"_autosave-p.kicad_pcb" );
800 BOOST_CHECK_MESSAGE( wxFileExists( autosavePath ),
801 "zip backup format must write autosave recovery files" );
812 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
813 backupEnabled =
true;
816 SCOPED_BACKUP_LOCATION_OVERRIDE restoreLocation(
location );
819 SCOPED_TEMP_DIR
project( wxS(
"kicad_qa_cloudsync_autosave" ) );
821 const wxString sep = wxFileName::GetPathSeparator();
825 writeTextFile(
path + sep + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
828 SCOPED_PROJECT_LOAD loadedProject( mgr,
path + sep + wxS(
"p.kicad_pro" ) );
830 const wxString sourcePath =
path + sep + wxS(
"p.kicad_pcb" );
831 const wxString autosavePath =
path + sep + wxS(
"_autosave-p.kicad_pcb" );
832 const wxString boardContent = wxS(
"(kicad_pcb (version 20240108))\n" );
835 writeTextFile( sourcePath, boardContent );
836 writeTextFile( autosavePath, boardContent );
841 wxDateTime srcMtime = wxFileName( sourcePath ).GetModificationTime();
842 wxDateTime newerMtime = srcMtime + wxTimeSpan::Seconds( 60 );
843 BOOST_REQUIRE( wxFileName( autosavePath ).SetTimes( &newerMtime, &newerMtime,
nullptr ) );
845 std::vector<wxString> exts{ wxS(
"kicad_pcb" ) };
848 BOOST_CHECK_MESSAGE( stale.empty(),
849 "Content-identical autosave with newer mtime was flagged stale, "
850 "triggering a spurious recovery prompt (issue 24126)" );
859 SCOPED_BOOL_OVERRIDE restoreBackupFlag( backupEnabled );
860 backupEnabled =
true;
863 SCOPED_BACKUP_LOCATION_OVERRIDE restoreLocation(
location );
866 SCOPED_TEMP_DIR
project( wxS(
"kicad_qa_divergent_autosave" ) );
868 const wxString sep = wxFileName::GetPathSeparator();
870 writeTextFile(
path + sep + wxS(
"p.kicad_pro" ), wxS(
"{}\n" ) );
873 SCOPED_PROJECT_LOAD loadedProject( mgr,
path + sep + wxS(
"p.kicad_pro" ) );
875 const wxString sourcePath =
path + sep + wxS(
"p.kicad_pcb" );
876 const wxString autosavePath =
path + sep + wxS(
"_autosave-p.kicad_pcb" );
879 writeTextFile( sourcePath, wxS(
"(kicad_pcb (version 20240108))\n" ) );
880 writeTextFile( autosavePath, wxS(
"(kicad_pcb (version 20240108) (edited yes))\n" ) );
884 wxDateTime srcMtime = wxFileName( sourcePath ).GetModificationTime();
885 wxDateTime newerMtime = srcMtime + wxTimeSpan::Seconds( 60 );
886 BOOST_REQUIRE( wxFileName( autosavePath ).SetTimes( &newerMtime, &newerMtime,
nullptr ) );
888 std::vector<wxString> exts{ wxS(
"kicad_pcb" ) };
891 BOOST_CHECK_MESSAGE( stale.size() == 1,
892 "Genuinely divergent autosave must still be flagged stale for recovery" );
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
Simple local history manager built on libgit2.
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 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
virtual const wxString GetProjectPath() const
Return the full path of the 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...
std::string content
Serialized content (mutually exclusive with sourcePath)
wxString relativePath
Destination path relative to the project root.
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")