39#include <git2/sys/odb_backend.h>
41#include <wx/filename.h>
45#include <wx/datetime.h>
73 const wxString& aRelativePath )
75 wxFileName fn( aRelativePath );
78 return fn.GetFullPath();
83 wxArrayString dirs = fn.GetDirs();
86 dst.AssignDir( aHistoryRoot );
88 for(
const wxString& d : dirs )
91 dst.SetFullName( fn.GetFullName() );
92 return dst.GetFullPath();
105 std::ifstream file( aPath.fn_str(), std::ios::binary );
110 aContent.assign( std::istreambuf_iterator<char>( file ), std::istreambuf_iterator<char>() );
120 wxDir histDir( aHistoryRoot );
122 bool hasCopies =
false;
123 bool cont = histDir.IsOpened()
124 && histDir.GetFirst( &
name, wxEmptyString, wxDIR_FILES | wxDIR_DIRS | wxDIR_HIDDEN );
127 while( cont && !hasCopies )
129 hasCopies =
name != wxS(
".git" ) &&
name != wxS(
".gitignore" ) &&
name != wxS(
"README.txt" );
130 cont = histDir.GetNext( &
name );
137 git_commit* head =
nullptr;
138 git_tree* tree =
nullptr;
140 if( git_reference_name_to_id( &headOid, aRepo,
"HEAD" ) != 0 || git_commit_lookup( &head, aRepo, &headOid ) != 0 )
143 if( git_commit_tree( &tree, head ) == 0 )
148 std::vector<wxString> dirs;
149 } state{ aHistoryRoot, {} };
152 tree, GIT_TREEWALK_POST,
153 [](
const char* aDir,
const git_tree_entry* aEntry,
void* aPayload ) ->
int
155 auto* walkState =
static_cast<WALK_STATE*
>( aPayload );
156 wxString rel = wxString::FromUTF8( aDir ) + wxString::FromUTF8( git_tree_entry_name( aEntry ) );
158 if( rel.Contains( wxS(
".." ) ) )
163 if( git_tree_entry_type( aEntry ) == GIT_OBJECT_TREE )
164 walkState->dirs.push_back(
path );
165 else if( wxFileExists(
path ) )
166 wxRemoveFile(
path );
174 wxLogNull suppressRmdirErrors;
176 for(
const wxString& dir : state.dirs )
178 if( wxDirExists( dir ) )
182 git_tree_free( tree );
185 git_commit_free( head );
192 wxFFile fileA( aPathA, wxS(
"rb" ) );
193 wxFFile fileB( aPathB, wxS(
"rb" ) );
195 if( !fileA.IsOpened() || !fileB.IsOpened() )
198 wxFileOffset lenA = fileA.Length();
199 wxFileOffset lenB = fileB.Length();
201 if( lenA < 0 || lenB < 0 || lenA != lenB )
204 constexpr size_t chunkSize = 64 * 1024;
205 std::vector<char> bufA( chunkSize );
206 std::vector<char> bufB( chunkSize );
208 while( !fileA.Eof() )
210 size_t readA = fileA.Read( bufA.data(), chunkSize );
211 size_t readB = fileB.Read( bufB.data(), chunkSize );
216 if( readA > 0 && std::memcmp( bufA.data(), bufB.data(), readA ) != 0 )
219 if( fileA.Error() || fileB.Error() )
233 const wxString& aRelativePath,
236 wxFileName rel( aRelativePath );
238 dst.AssignDir( aAutosaveRoot );
240 for(
const wxString& d : rel.GetDirs() )
246 dst.SetFullName( rel.GetFullName() );
248 return dst.GetFullPath();
256 const wxString& aProjectPath,
257 const wxString& aAutosaveRoot,
260 wxFileName autosave( aAutosavePath );
264 wxString
name = autosave.GetFullName();
267 return wxEmptyString;
270 return autosave.GetFullPath();
273 if( !aAutosavePath.StartsWith( aAutosaveRoot ) )
274 return wxEmptyString;
276 wxString rel = aAutosavePath.Mid( aAutosaveRoot.length() );
277 wxFileName projFn( aProjectPath, wxEmptyString );
279 return projFn.GetPathWithSep() + rel;
284 const wxString& aTitle );
305 if( aProjectPath.IsEmpty() || !wxDirExists( aProjectPath ) )
308 wxDir dir( aProjectPath );
311 return dir.IsOpened()
322 return aName == wxS(
".history" ) || aName == wxS(
".history_old" )
323 || aName.StartsWith( wxS(
".history_old_" ) )
324 || aName == wxS(
".git" ) || aName == wxS(
"_restore_backup" )
325 || aName.StartsWith( wxS(
"_restore_backup_" ) ) || aName == wxS(
"_restore_temp" )
347 wxFileName fn( aFile );
357 const void* aSaverObject,
358 const std::function<
void(
const wxString&, std::vector<HISTORY_FILE_DATA>& )>& aSaver,
359 const std::weak_ptr<void>& aLifetime )
363 wxLogTrace(
traceAutoSave, wxS(
"[history] Saver %p already registered, skipping" ), aSaverObject );
368 entry.
saver = aSaver;
373 entry.
tracked = aLifetime.lock() !=
nullptr;
375 m_savers[aSaverObject] = std::move( entry );
376 wxLogTrace(
traceAutoSave, wxS(
"[history] Registered saver %p (total=%zu)" ), aSaverObject,
m_savers.size() );
382 std::vector<const void*> expired;
386 for(
const auto& [saverObject, entry] :
m_savers )
388 if( entry.tracked && entry.lifetime.expired() )
389 expired.push_back( saverObject );
392 for(
const void* obj : expired )
401 auto it =
m_savers.find( aSaverObject );
406 wxLogTrace(
traceAutoSave, wxS(
"[history] Unregistered saver %p (total=%zu)" ),
416 wxLogTrace(
traceAutoSave, wxS(
"[history] Cleared all savers" ) );
421 const wxString& aTagFileType )
425 wxLogTrace(
traceAutoSave, wxS(
"Local history disabled, returning" ) );
432 Init( aProjectPath );
435 wxS(
"[history] RunRegisteredSaversAndCommit start project='%s' title='%s' savers=%zu tag='%s'" ),
436 aProjectPath, aTitle,
m_savers.size(), aTagFileType );
440 wxLogTrace(
traceAutoSave, wxS(
"[history] no savers registered; skipping") );
445 if( !aTagFileType.IsEmpty() )
451 wxLogTrace(
traceAutoSave, wxS(
"[history] previous save still in progress; skipping cycle" ) );
458 std::vector<HISTORY_FILE_DATA> fileData;
460 for(
const auto& [saverObject, entry] :
m_savers )
462 size_t before = fileData.size();
463 entry.saver( aProjectPath, fileData );
464 wxLogTrace(
traceAutoSave, wxS(
"[history] saver %p produced %zu entries (total=%zu)" ),
465 saverObject, fileData.size() - before, fileData.size() );
471 fileData.erase( std::remove_if( fileData.begin(), fileData.end(),
474 if( entry.relativePath.IsEmpty() || wxFileName( entry.relativePath ).IsAbsolute() )
476 wxLogTrace( traceAutoSave, wxS(
"[history] filtered out entry with invalid path: '%s'" ),
477 entry.relativePath );
484 if( fileData.empty() )
486 wxLogTrace(
traceAutoSave, wxS(
"[history] saver set produced no entries; skipping" ) );
491 m_saveInProgress.store(
true, std::memory_order_release );
494 [
this, projectPath = aProjectPath, title = aTitle, tagFileType = aTagFileType,
495 data = std::move( fileData )]()
mutable ->
bool
498 !tagFileType.IsEmpty() );
502 TagSave( projectPath, tagFileType );
504 m_saveInProgress.store(
false, std::memory_order_release );
509 if( !aTagFileType.IsEmpty() )
511 WaitForPendingSave();
526 if( !
Pgm().GetCommonSettings()->m_Backup.enabled )
531 wxLogTrace(
traceAutoSave, wxS(
"[autosave] no savers registered; skipping" ) );
541 wxLogTrace(
traceAutoSave, wxS(
"[autosave] cannot create autosave root '%s'" ), autosaveRoot );
547 std::vector<HISTORY_FILE_DATA> fileData;
549 for(
const auto& [saverObject, entry] :
m_savers )
550 entry.saver( aProjectPath, fileData );
552 bool anyWritten =
false;
556 if( entry.relativePath.IsEmpty() || wxFileName( entry.relativePath ).IsAbsolute() )
560 wxFileName dstFn( dst );
564 wxLogTrace(
traceAutoSave, wxS(
"[autosave] cannot create dir '%s'" ), dstFn.GetPath() );
570 if( !entry.content.empty() )
572 buf = std::move( entry.content );
577 else if( !entry.sourcePath.IsEmpty() )
579 wxFFile src( entry.sourcePath, wxS(
"rb" ) );
581 if( !src.IsOpened() )
584 wxFileOffset len = src.Length();
589 buf.resize(
static_cast<size_t>( len ) );
591 if( len > 0 && src.Read( buf.data(), buf.size() ) != buf.size() )
607 wxLogTrace(
traceAutoSave, wxS(
"[autosave] wrote %zu bytes to '%s'" ), buf.size(), dst );
611 wxLogTrace(
traceAutoSave, wxS(
"[autosave] write failed for '%s': %s" ), dst, err );
624std::vector<std::pair<wxString, wxString>>
628 std::vector<std::pair<wxString, wxString>> results;
630 if( !wxDirExists( aAutosaveRoot ) )
638 std::function<void(
const wxString& )> walk =
639 [&](
const wxString& aDir )
647 bool cont = d.GetFirst( &
name );
651 wxFileName fn( aDir,
name );
652 wxString fullPath = fn.GetFullPath();
654 if( wxDirExists( fullPath ) )
657 && (
name == wxS(
".history" ) ||
name.EndsWith( wxS(
"-backups" ) ) ) )
659 cont = d.GetNext( &
name );
673 results.emplace_back( fullPath, src );
676 cont = d.GetNext( &
name );
680 walk( aAutosaveRoot );
685static std::vector<std::pair<wxString, wxString>>
696std::vector<std::pair<wxString, wxString>>
699 std::vector<std::pair<wxString, wxString>> results;
701 if( aExtensions.empty() )
706 wxFileName srcFn( pair.second );
709 for(
const wxString& ext : aExtensions )
711 if( srcFn.GetExt().IsSameAs( ext,
false ) )
723 if( srcFn.FileExists() )
724 srcTime = srcFn.GetModificationTime();
726 wxDateTime autosaveTime = wxFileName( pair.first ).GetModificationTime();
730 bool stale = !srcTime.IsValid()
731 || ( autosaveTime.IsLaterThan( srcTime )
735 results.emplace_back( std::move( pair ) );
749 if( wxFileExists( autosavePath ) )
750 wxRemoveFile( autosavePath );
756 const std::vector<wxString>& aSourcePaths )
const
758 if( aSourcePaths.empty() )
761 std::vector<wxFileName> targets;
762 targets.reserve( aSourcePaths.size() );
764 for(
const wxString& src : aSourcePaths )
767 targets.emplace_back( src );
770 if( targets.empty() )
775 wxFileName srcFn( srcPath );
778 for(
const wxFileName& target : targets )
780 if( srcFn.SameAs( target ) )
787 if( match && wxFileExists( autosavePath ) )
788 wxRemoveFile( autosavePath );
794 const std::vector<HISTORY_FILE_DATA>& aFileData,
797 wxLogTrace(
traceAutoSave, wxS(
"[history] background: writing %zu entries for '%s'" ),
798 aFileData.size(), aProjectPath );
804 wxLogTrace(
traceAutoSave, wxS(
"[history] background: cannot create history root '%s'" ), hist );
810 wxString relativePath;
814 std::vector<STAGED_FILE> staged;
818 STAGED_FILE file{ entry.relativePath, std::string() };
820 if( !entry.content.empty() )
822 file.content = entry.content;
827 else if( entry.sourcePath.IsEmpty() )
834 wxLogTrace(
traceAutoSave, wxS(
"[history] background: cannot read '%s'" ), entry.sourcePath );
838 staged.push_back( std::move( file ) );
856 for(
const STAGED_FILE& file : staged )
858 wxString rel = file.relativePath;
859 rel.Replace( wxS(
"\\" ), wxS(
"/" ) );
861 std::string
path = rel.utf8_string();
862 git_index_entry indexEntry = {};
863 indexEntry.mode = GIT_FILEMODE_BLOB;
864 indexEntry.path =
path.c_str();
866 if( git_index_add_from_buffer(
index, &indexEntry, file.content.data(), file.content.size() ) != 0 )
869 wxLogTrace(
traceAutoSave, wxS(
"[history] background: failed to stage '%s'" ), rel );
870 git_index_read(
index,
true );
877 git_commit* head_commit =
nullptr;
878 git_tree* head_tree =
nullptr;
880 bool headExists = ( git_reference_name_to_id( &head_oid, repo,
"HEAD" ) == 0 )
881 && ( git_commit_lookup( &head_commit, repo, &head_oid ) == 0 )
882 && ( git_commit_tree( &head_tree, head_commit ) == 0 );
884 git_tree* rawIndexTree =
nullptr;
885 git_oid index_tree_oid;
887 if( git_index_write_tree( &index_tree_oid,
index ) != 0 )
890 git_tree_free( head_tree );
893 git_commit_free( head_commit );
895 wxLogTrace(
traceAutoSave, wxS(
"[history] background: failed to write index tree" ) );
899 git_tree_lookup( &rawIndexTree, repo, &index_tree_oid );
900 std::unique_ptr<git_tree,
decltype( &git_tree_free )> indexTree( rawIndexTree, &git_tree_free );
902 bool hasChanges =
true;
906 git_diff* diff =
nullptr;
908 if( git_diff_tree_to_tree( &diff, repo, head_tree, indexTree.get(),
nullptr ) == 0 )
910 hasChanges = git_diff_num_deltas( diff ) > 0;
911 wxLogTrace(
traceAutoSave, wxS(
"[history] background: diff deltas=%u" ),
912 (
unsigned) git_diff_num_deltas( diff ) );
913 git_diff_free( diff );
920 bool stagedMatchesDisk =
true;
922 for(
const STAGED_FILE& file : staged )
924 wxFileName diskPath( file.relativePath );
925 std::string diskContent;
927 diskPath.MakeAbsolute( aProjectPath );
929 if( !
readFileContent( diskPath.GetFullPath(), diskContent ) || diskContent != file.content )
931 stagedMatchesDisk =
false;
936 if( stagedMatchesDisk && !aIsManualSave )
938 wxLogTrace(
traceAutoSave, wxS(
"[history] background: first commit; staged matches disk -- skipping" ) );
944 git_tree_free( head_tree );
947 git_commit_free( head_commit );
951 wxLogTrace(
traceAutoSave, wxS(
"[history] background: no changes detected; no commit") );
955 if( !aTitle.IsEmpty() && aTitle != wxS(
"Autosave" ) )
957 git_oid head_oid_amend;
959 if( git_reference_name_to_id( &head_oid_amend, repo,
"HEAD" ) == 0 )
961 git_commit* head_commit_amend =
nullptr;
963 if( git_commit_lookup( &head_commit_amend, repo, &head_oid_amend ) == 0 )
965 wxString existingMsg = wxString::FromUTF8( git_commit_message( head_commit_amend ) );
966 existingMsg.Trim(
true ).Trim(
false );
968 if( existingMsg != aTitle )
971 int amend_rc = git_commit_amend( &amended_oid, head_commit_amend,
"HEAD",
nullptr,
nullptr,
972 nullptr, aTitle.mb_str().data(),
nullptr );
975 wxLogTrace(
traceAutoSave, wxS(
"[history] background: amended HEAD message '%s' -> '%s'" ),
976 existingMsg, aTitle );
978 wxLogTrace(
traceAutoSave, wxS(
"[history] background: amend failed rc=%d" ), amend_rc );
981 git_commit_free( head_commit_amend );
989 git_signature* rawSig =
nullptr;
991 std::unique_ptr<git_signature,
decltype( &git_signature_free )> sig( rawSig, &git_signature_free );
993 git_commit* parent =
nullptr;
997 if( git_reference_name_to_id( &parent_id, repo,
"HEAD" ) == 0 )
999 if( git_commit_lookup( &parent, repo, &parent_id ) == 0 )
1003 wxString msg = aTitle.IsEmpty() ? wxString(
"Autosave" ) : aTitle;
1005 const git_commit* constParent = parent;
1007 int rc = git_commit_create( &commit_id, repo,
"HEAD", sig.get(), sig.get(),
nullptr,
1008 msg.mb_str().data(), indexTree.get(), parents,
1009 parents ? &constParent :
nullptr );
1013 wxLogTrace(
traceAutoSave, wxS(
"[history] background: commit created %s (%s entries=%zu)" ),
1014 wxString::FromUTF8( git_oid_tostr_s( &commit_id ) ), msg, aFileData.size() );
1018 wxLogTrace(
traceAutoSave, wxS(
"[history] background: commit failed rc=%d" ), rc );
1022 git_commit_free( parent );
1024 git_index_write(
index );
1033 wxLogTrace(
traceAutoSave, wxS(
"[history] waiting for pending background save" ) );
1057 if( !wxDirExists( hist ) )
1059 wxLogNull suppressSysErrorPopups;
1070 if( git_repository_open( &rawRepo, hist.mb_str().data() ) != 0 )
1072 if( git_repository_init( &rawRepo, hist.mb_str().data(), 0 ) != 0 )
1075 wxFileName ignoreFile( hist, wxS(
".gitignore" ) );
1076 if( !ignoreFile.FileExists() )
1078 wxFFile f( ignoreFile.GetFullPath(), wxT(
"w" ) );
1081 f.Write( wxS(
"# KiCad local history exclusions. Edit to add your own rules.\n"
1088 wxFileName readmeFile( hist, wxS(
"README.txt" ) );
1090 if( !readmeFile.FileExists() )
1092 wxFFile f( readmeFile.GetFullPath(), wxT(
"w" ) );
1096 f.Write( wxS(
"KiCad Local History Directory\n"
1097 "=============================\n\n"
1098 "This directory contains automatic snapshots of your project files.\n"
1099 "KiCad periodically saves copies of your work here, allowing you to\n"
1100 "recover from accidental changes or data loss.\n\n"
1101 "You can browse and restore previous versions through KiCad's\n"
1102 "File > Local History menu.\n\n"
1103 "To disable this feature:\n"
1104 " Preferences > Common > Project Backup > Enable automatic backups\n\n"
1105 "This directory can be safely deleted if you no longer need the\n"
1106 "history, but doing so will permanently remove all saved snapshots.\n" ) );
1112 git_repository_free( rawRepo );
1122 const wxString& aHistoryPath,
const wxString& aProjectPath,
1123 const std::vector<wxString>& aFiles,
const wxString& aTitle )
1125 std::vector<std::string> filesArrStr;
1127 for(
const wxString& file : aFiles )
1129 wxFileName src( file );
1132 if( src.GetFullPath().StartsWith( aProjectPath + wxFILE_SEP_PATH ) )
1133 relPath = src.GetFullPath().Mid( aProjectPath.length() + 1 );
1135 relPath = src.GetFullName();
1137 relPath.Replace(
"\\",
"/" );
1138 std::string relPathStr = relPath.ToStdString();
1140 unsigned int status = 0;
1141 int rc = git_status_file( &status, repo, relPathStr.data() );
1143 if( rc == 0 && status != 0 )
1145 wxLogTrace(
traceAutoSave, wxS(
"File %s status %d " ), relPath, status );
1146 filesArrStr.emplace_back( relPathStr );
1150 wxLogTrace(
traceAutoSave, wxS(
"File %s status error %d " ), relPath, rc );
1151 filesArrStr.emplace_back( relPathStr );
1155 std::vector<char*> cStrings( filesArrStr.size() );
1157 for(
size_t i = 0; i < filesArrStr.size(); i++ )
1158 cStrings[i] = filesArrStr[i].data();
1160 git_strarray filesArrGit;
1161 filesArrGit.count = filesArrStr.size();
1162 filesArrGit.strings = cStrings.data();
1164 if( filesArrStr.size() == 0 )
1170 int rc = git_index_add_all(
index, &filesArrGit, GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH | GIT_INDEX_ADD_FORCE, NULL,
1172 wxLogTrace(
traceAutoSave, wxS(
"Adding %zu files, rc %d" ), filesArrStr.size(), rc );
1178 if( git_index_write_tree( &tree_id,
index ) != 0 )
1181 git_tree* rawTree =
nullptr;
1182 git_tree_lookup( &rawTree, repo, &tree_id );
1183 std::unique_ptr<git_tree,
decltype( &git_tree_free )> tree( rawTree, &git_tree_free );
1185 git_signature* rawSig =
nullptr;
1187 std::unique_ptr<git_signature,
decltype( &git_signature_free )> sig( rawSig,
1188 &git_signature_free );
1190 git_commit* rawParent =
nullptr;
1194 if( git_reference_name_to_id( &parent_id, repo,
"HEAD" ) == 0 )
1196 git_commit_lookup( &rawParent, repo, &parent_id );
1200 std::unique_ptr<git_commit,
decltype( &git_commit_free )> parent( rawParent,
1203 git_tree* rawParentTree =
nullptr;
1206 git_commit_tree( &rawParentTree, parent.get() );
1208 std::unique_ptr<git_tree,
decltype( &git_tree_free )> parentTree( rawParentTree, &git_tree_free );
1210 git_diff* rawDiff =
nullptr;
1211 git_diff_tree_to_index( &rawDiff, repo, parentTree.get(),
index,
nullptr );
1212 std::unique_ptr<git_diff,
decltype( &git_diff_free )> diff( rawDiff, &git_diff_free );
1214 size_t numChangedFiles = git_diff_num_deltas( diff.get() );
1216 if( numChangedFiles == 0 )
1218 wxLogTrace(
traceAutoSave, wxS(
"No actual changes in tree, skipping commit" ) );
1224 if( !aTitle.IsEmpty() )
1225 msg << aTitle << wxS(
": " );
1227 msg << numChangedFiles << wxS(
" files changed" );
1229 for(
size_t i = 0; i < numChangedFiles; ++i )
1231 const git_diff_delta*
delta = git_diff_get_delta( diff.get(), i );
1232 git_patch* rawPatch =
nullptr;
1233 git_patch_from_diff( &rawPatch, diff.get(), i );
1234 std::unique_ptr<git_patch,
decltype( &git_patch_free )> patch( rawPatch,
1236 size_t context = 0, adds = 0, dels = 0;
1237 git_patch_line_stats( &context, &adds, &dels, patch.get() );
1238 size_t updated = std::min( adds, dels );
1241 msg << wxS(
"\n" ) << wxString::FromUTF8(
delta->new_file.path )
1242 << wxS(
" " ) << adds << wxS(
"/" ) << dels << wxS(
"/" ) << updated;
1246 git_commit* parentPtr = parent.get();
1247 const git_commit* constParentPtr = parentPtr;
1248 if( git_commit_create( &commit_id, repo,
"HEAD", sig.get(), sig.get(),
nullptr, msg.mb_str().data(), tree.get(),
1249 parents, parentPtr ? &constParentPtr :
nullptr )
1255 git_index_write(
index );
1264 const wxString& aTitle )
1272 wxLogTrace(
traceAutoSave, wxS(
"[history] commitSnapshotForProject failed to acquire lock: %s" ),
1289 wxString proj = wxFileName( aFiles[0] ).GetPath();
1303 wxString
name = aFile.GetFullName();
1305 if(
name == wxS(
"sym-lib-table" ) ||
name == wxS(
"fp-lib-table" ) )
1308 return aFile.GetExt().StartsWith( wxS(
"kicad_" ) );
1317 wxDir dir( aProjectPath );
1319 if( !dir.IsOpened() )
1330 std::function<void(
const wxString&,
bool )> collect =
1331 [&](
const wxString&
path,
bool topLevel )
1336 wxS(
"[history] collectProjectFiles: Skipping nested project at %s" ),
1347 bool cont = d.GetFirst( &
name );
1353 cont = d.GetNext( &
name );
1358 wxString fullPath = fn.GetFullPath();
1360 if( wxFileName::DirExists( fullPath ) )
1363 collect( fullPath,
false );
1365 else if( fn.FileExists() && fn.GetFullName() != wxS(
"fp-info-cache" ) &&
isKiCadProjectFile( fn ) )
1367 aFiles.push_back( fn.GetFullPath() );
1370 cont = d.GetNext( &
name );
1374 collect( aProjectPath,
true );
1383 std::vector<wxString> files;
1389 Init( aProjectPath );
1395 return wxDirExists(
historyPath( aProjectPath ) );
1407 if( git_reference_name_to_id( &head, repo,
"HEAD" ) != 0 )
1412 git_reference* ref =
nullptr;
1415 tagName.Printf( wxS(
"Save_%s_%d" ), aFileType, i++ );
1416 }
while( git_reference_lookup( &ref, repo, ( wxS(
"refs/tags/" ) + tagName ).mb_str().data() ) == 0 );
1419 git_object* head_obj =
nullptr;
1420 git_object_lookup( &head_obj, repo, &head, GIT_OBJECT_COMMIT );
1421 git_tag_create_lightweight( &tag_oid, repo, tagName.mb_str().data(), head_obj, 0 );
1422 git_object_free( head_obj );
1425 lastName.Printf( wxS(
"Last_Save_%s" ), aFileType );
1426 if( git_reference_lookup( &ref, repo, ( wxS(
"refs/tags/" ) + lastName ).mb_str().data() ) == 0 )
1428 git_reference_delete( ref );
1429 git_reference_free( ref );
1432 git_oid last_tag_oid;
1433 git_object* head_obj2 =
nullptr;
1434 git_object_lookup( &head_obj2, repo, &head, GIT_OBJECT_COMMIT );
1435 git_tag_create_lightweight( &last_tag_oid, repo, lastName.mb_str().data(), head_obj2, 0 );
1436 git_object_free( head_obj2 );
1454 wxLogTrace(
traceAutoSave, wxS(
"[history] TagSave: Failed to acquire lock for %s" ), aProjectPath );
1466 if( git_repository_open( &repo, hist.mb_str().data() ) != 0 )
1470 if( git_reference_name_to_id( &head_oid, repo,
"HEAD" ) != 0 )
1472 git_repository_free( repo );
1476 git_commit* head_commit =
nullptr;
1477 git_commit_lookup( &head_commit, repo, &head_oid );
1478 git_time_t head_time = git_commit_time( head_commit );
1481 git_tag_list_match( &tags,
"Last_Save_*", repo );
1482 git_time_t save_time = 0;
1484 for(
size_t i = 0; i < tags.count; ++i )
1486 git_reference* ref =
nullptr;
1487 if( git_reference_lookup( &ref, repo,
1488 ( wxS(
"refs/tags/" ) +
1489 wxString::FromUTF8( tags.strings[i] ) ).mb_str().data() ) == 0 )
1491 const git_oid* oid = git_reference_target( ref );
1492 git_commit* c =
nullptr;
1493 if( git_commit_lookup( &c, repo, oid ) == 0 )
1495 git_time_t t = git_commit_time( c );
1498 git_commit_free( c );
1500 git_reference_free( ref );
1504 git_strarray_free( &tags );
1505 git_commit_free( head_commit );
1506 git_repository_free( repo );
1510 if( save_time == 0 )
1513 return head_time > save_time;
1517 const wxString& aMessage )
1529 wxLogTrace(
traceAutoSave, wxS(
"[history] CommitDuplicateOfLastSave: Failed to acquire lock for %s" ), aProjectPath );
1538 wxString lastName; lastName.Printf( wxS(
"Last_Save_%s"), aFileType );
1539 git_reference* lastRef =
nullptr;
1540 if( git_reference_lookup( &lastRef, repo, ( wxS(
"refs/tags/") + lastName ).mb_str().data() ) != 0 )
1542 std::unique_ptr<git_reference,
decltype( &git_reference_free )> lastRefPtr( lastRef, &git_reference_free );
1544 const git_oid* lastOid = git_reference_target( lastRef );
1545 git_commit* lastCommit =
nullptr;
1546 if( git_commit_lookup( &lastCommit, repo, lastOid ) != 0 )
1548 std::unique_ptr<git_commit,
decltype( &git_commit_free )> lastCommitPtr( lastCommit, &git_commit_free );
1550 git_tree* lastTree =
nullptr;
1551 git_commit_tree( &lastTree, lastCommit );
1552 std::unique_ptr<git_tree,
decltype( &git_tree_free )> lastTreePtr( lastTree, &git_tree_free );
1556 git_commit* headCommit =
nullptr;
1558 const git_commit* parentArray[1];
1559 if( git_reference_name_to_id( &headOid, repo,
"HEAD" ) == 0 &&
1560 git_commit_lookup( &headCommit, repo, &headOid ) == 0 )
1562 parentArray[0] = headCommit;
1566 git_signature* sigRaw =
nullptr;
1568 std::unique_ptr<git_signature,
decltype( &git_signature_free )> sig( sigRaw, &git_signature_free );
1570 wxString msg = aMessage.IsEmpty() ? wxS(
"Discard unsaved ") + aFileType : aMessage;
1571 git_oid newCommitOid;
1572 int rc = git_commit_create( &newCommitOid, repo,
"HEAD", sig.get(), sig.get(),
nullptr,
1573 msg.mb_str().data(), lastTree, parents, parents ? parentArray :
nullptr );
1574 if( headCommit ) git_commit_free( headCommit );
1579 git_reference* existing =
nullptr;
1580 if( git_reference_lookup( &existing, repo, ( wxS(
"refs/tags/") + lastName ).mb_str().data() ) == 0 )
1582 git_reference_delete( existing );
1583 git_reference_free( existing );
1585 git_object* newCommitObj =
nullptr;
1586 if( git_object_lookup( &newCommitObj, repo, &newCommitOid, GIT_OBJECT_COMMIT ) == 0 )
1588 git_tag_create_lightweight( &newCommitOid, repo, lastName.mb_str().data(), newCommitObj, 0 );
1589 git_object_free( newCommitObj );
1598 if( !dir.IsOpened() )
1601 bool cont = dir.GetFirst( &
name );
1605 wxString fullPath = fn.GetFullPath();
1607 if( wxFileName::DirExists( fullPath ) )
1609 else if( fn.FileExists() )
1610 total += (size_t) fn.GetSize().GetValue();
1611 cont = dir.GetNext( &
name );
1618 std::vector<wxString> packs;
1619 wxDir packDir( aPackDir );
1621 if( !packDir.IsOpened() )
1625 bool cont = packDir.GetFirst( &
name, wxEmptyString, wxDIR_FILES );
1629 if(
name.EndsWith( wxS(
".pack" ) ) ||
name.EndsWith( wxS(
".idx" ) ) )
1630 packs.push_back( aPackDir + wxFileName::GetPathSeparator() +
name );
1632 cont = packDir.GetNext( &
name );
1635 std::sort( packs.begin(), packs.end() );
1641 const std::atomic<bool>* aCancel =
nullptr )
1643 struct PROGRESS_STATE
1646 const std::atomic<bool>*
cancel;
1647 } state{ aReporter, aCancel };
1651 if( aReporter || aCancel )
1653 git_packbuilder_set_callbacks(
1655 [](
int aStage, uint32_t aCurrent, uint32_t aTotal,
void* aPayload )
1657 auto* progress =
static_cast<PROGRESS_STATE*
>( aPayload );
1659 if( progress->reporter )
1662 progress->reporter->SetCurrentProgress( (
double) aCurrent / aTotal );
1664 progress->reporter->KeepRefreshing();
1667 return progress->cancel && progress->cancel->load() ? -1 : 0;
1672 std::string packDir = aPackDir.utf8_string();
1674 return git_packbuilder_write( aPb, aPackDir.IsEmpty() ?
nullptr : packDir.c_str(), 0,
nullptr,
nullptr ) == 0;
1685static void pruneLooseObjects(
const wxString& aObjectsPath,
const wxString& aPackIndex, time_t aWalkStart )
1687 git_odb* packOdb =
nullptr;
1688 git_odb_backend* backend =
nullptr;
1690 if( git_odb_new( &packOdb ) != 0 )
1693 if( git_odb_backend_one_pack( &backend, aPackIndex.utf8_string().c_str() ) != 0 )
1695 git_odb_free( packOdb );
1699 if( git_odb_add_backend( packOdb, backend, 1 ) != 0 )
1701 backend->free( backend );
1702 git_odb_free( packOdb );
1706 wxLogNull suppressRmdirErrors;
1707 wxDir objDir( aObjectsPath );
1709 bool contDir = objDir.IsOpened() && objDir.GetFirst( &fanout, wxEmptyString, wxDIR_DIRS );
1713 wxString fanoutPath = aObjectsPath + wxFileName::GetPathSeparator() + fanout;
1714 wxDir looseDir( fanoutPath );
1716 bool contFile = fanout.length() == 2 && looseDir.IsOpened()
1717 && looseDir.GetFirst( &rest, wxEmptyString, wxDIR_FILES );
1722 wxString loosePath = fanoutPath + wxFileName::GetPathSeparator() + rest;
1724 if( git_oid_fromstr( &oid, ( fanout + rest ).utf8_string().c_str() ) == 0
1725 && ( git_odb_exists( packOdb, &oid )
1728 wxRemoveFile( loosePath );
1731 contFile = looseDir.GetNext( &rest );
1734 if( fanout.length() == 2 && !wxDir( fanoutPath ).HasFiles() )
1735 wxRmdir( fanoutPath );
1737 contDir = objDir.GetNext( &fanout );
1740 git_odb_free( packOdb );
1747 const std::atomic<bool>* aCancel )
1749 git_packbuilder* pb =
nullptr;
1750 git_revwalk* walk =
nullptr;
1752 if( git_packbuilder_new( &pb, aRepo ) != 0 )
1753 return wxEmptyString;
1755 if( git_revwalk_new( &walk, aRepo ) != 0 )
1757 git_packbuilder_free( pb );
1758 return wxEmptyString;
1762 bool ok = git_revwalk_push_glob( walk,
"refs/*" ) == 0 && git_revwalk_push_head( walk ) == 0;
1766 while( ok && ( rc = git_revwalk_next( &oid, walk ) ) == 0 )
1767 ok = git_packbuilder_insert_commit( pb, &oid ) == 0;
1769 git_revwalk_free( walk );
1774 if( ok && rc == GIT_ITEROVER &&
writePack( pb, aPackDir, aReporter, aCancel ) )
1776#if ( LIBGIT2_VER_MAJOR > 1 ) || ( LIBGIT2_VER_MAJOR == 1 && LIBGIT2_VER_MINOR >= 4 )
1777 const char* newPackName = git_packbuilder_name( pb );
1779 char hex[GIT_OID_HEXSZ + 1];
1780 hex[GIT_OID_HEXSZ] =
'\0';
1781 git_oid_fmt( hex, git_packbuilder_hash( pb ) );
1783 const char* newPackName = strlen( hex ) > 0 ? hex :
nullptr;
1787 stem = wxS(
"pack-" ) + wxString::FromUTF8( newPackName );
1790 git_packbuilder_free( pb );
1796static std::vector<wxString>
supersededBy(
const std::vector<wxString>& aPriorPacks,
const wxString& aNewStem )
1798 std::vector<wxString> superseded;
1800 for(
const wxString& pack : aPriorPacks )
1802 if( wxFileName( pack ).GetName() != aNewStem )
1803 superseded.push_back( pack );
1813 std::vector<wxString>* aSupersededPacks )
1815 wxString sep = wxFileName::GetPathSeparator();
1816 wxString objPath = wxString::FromUTF8( git_repository_path( aRepo ) ) + wxS(
"objects" );
1817 wxString packDir = objPath + sep + wxS(
"pack" );
1819 std::vector<wxString> priorPacks =
listPackFiles( packDir );
1820 time_t walkStart = wxDateTime::Now().GetTicks();
1821 wxString stem =
buildPack( aRepo, wxEmptyString, aReporter,
nullptr );
1823 if( stem.IsEmpty() )
1841 if( aMaxBytes == 0 )
1844 if( !wxDirExists( aHistoryPath ) )
1849 if( current <= aMaxBytes )
1856 wxLogTrace(
traceAutoSave, wxS(
"[history] EnforceSizeLimit: Failed to acquire lock for %s" ), aProjectPath );
1868 if( current <= aMaxBytes )
1872 aReporter->
Report(
_(
"Compacting local history..." ) );
1875 std::vector<wxString> supersededPacks;
1881 for(
const wxString& pack : supersededPacks )
1883 if( !wxRemoveFile( pack ) )
1884 wxLogTrace(
traceAutoSave, wxS(
"[history] could not remove %s" ), pack );
1890 const size_t target = aMaxBytes / 4 * 3;
1892 if( current <= target )
1901 git_revwalk* walk =
nullptr;
1902 git_revwalk_new( &walk, repo );
1903 git_revwalk_sorting( walk, GIT_SORT_TIME );
1904 git_revwalk_push_head( walk );
1905 std::vector<git_oid> commits;
1908 while( git_revwalk_next( &oid, walk ) == 0 )
1909 commits.push_back( oid );
1911 git_revwalk_free( walk );
1913 if( commits.empty() )
1917 std::set<git_oid, bool ( * )(
const git_oid&,
const git_oid& )> seenBlobs(
1918 [](
const git_oid& a,
const git_oid& b )
1920 return memcmp( &a, &b,
sizeof( git_oid ) ) < 0;
1924 wxString gitDir = wxString::FromUTF8( git_repository_path( repo ) );
1926 size_t copyBytes = current > gitBytes ? current - gitBytes : 0;
1929 size_t keptBytes = 0;
1930 std::vector<git_oid> keep;
1932 git_odb* odb =
nullptr;
1933 git_repository_odb( &odb, repo );
1935 std::function<size_t( git_tree* )> accountTree =
1936 [&]( git_tree* tree )
1939 size_t cnt = git_tree_entrycount( tree );
1941 for(
size_t i = 0; i < cnt; ++i )
1943 const git_tree_entry* entry = git_tree_entry_byindex( tree, i );
1945 if( git_tree_entry_type( entry ) == GIT_OBJECT_BLOB )
1947 const git_oid* bid = git_tree_entry_id( entry );
1949 if( seenBlobs.find( *bid ) == seenBlobs.end() )
1952 git_object_t type = GIT_OBJECT_ANY;
1954 if( odb && git_odb_read_header( &len, &type, odb, bid ) == 0 )
1957 seenBlobs.insert( *bid );
1960 else if( git_tree_entry_type( entry ) == GIT_OBJECT_TREE )
1962 git_tree* sub =
nullptr;
1964 if( git_tree_lookup( &sub, repo, git_tree_entry_id( entry ) ) == 0 )
1966 added += accountTree( sub );
1967 git_tree_free( sub );
1975 for(
const git_oid& cOid : commits )
1977 git_commit* c =
nullptr;
1979 if( git_commit_lookup( &c, repo, &cOid ) != 0 )
1982 git_tree* tree =
nullptr;
1983 git_commit_tree( &tree, c );
1984 size_t add = accountTree( tree );
1985 git_tree_free( tree );
1986 git_commit_free( c );
1988 if( keep.empty() || keptBytes + add <= budget )
1990 keep.push_back( cOid );
1998 keep.push_back( commits.front() );
2001 git_odb_free( odb );
2005 std::vector<std::pair<wxString, git_oid>> tagTargets;
2006 std::set<git_oid, bool ( * )(
const git_oid&,
const git_oid& )> taggedCommits(
2007 [](
const git_oid& a,
const git_oid& b )
2009 return memcmp( &a, &b,
sizeof( git_oid ) ) < 0;
2011 git_strarray tagList;
2013 if( git_tag_list( &tagList, repo ) == 0 )
2015 for(
size_t i = 0; i < tagList.count; ++i )
2017 wxString
name = wxString::FromUTF8( tagList.strings[i] );
2018 if(
name.StartsWith( wxS(
"Save_") ) ||
name.StartsWith( wxS(
"Last_Save_") ) )
2020 git_reference* tref =
nullptr;
2022 if( git_reference_lookup( &tref, repo, ( wxS(
"refs/tags/" ) +
name ).mb_str().data() ) == 0 )
2024 const git_oid* toid = git_reference_target( tref );
2028 tagTargets.emplace_back(
name, *toid );
2029 taggedCommits.insert( *toid );
2033 for(
const auto& k : keep )
2035 if( memcmp( &k, toid,
sizeof( git_oid ) ) == 0 )
2045 keep.push_back( *toid );
2046 wxLogTrace(
traceAutoSave, wxS(
"[history] EnforceSizeLimit: Preserving tagged commit %s" ),
2051 git_reference_free( tref );
2055 git_strarray_free( &tagList );
2059 wxFileName trimFn( aHistoryPath + wxS(
"_trim"), wxEmptyString );
2060 wxString trimPath = trimFn.GetPath();
2062 if( wxDirExists( trimPath ) )
2063 wxFileName::Rmdir( trimPath, wxPATH_RMDIR_RECURSIVE );
2065 wxMkdir( trimPath );
2068 if( git_repository_init( &newRepo, trimPath.mb_str().data(), 0 ) != 0 )
2071 wxString newPackDir = wxString::FromUTF8( git_repository_path( newRepo ) ) + wxS(
"objects" )
2072 + wxFileName::GetPathSeparator() + wxS(
"pack" );
2073 git_repository_free( newRepo );
2078 git_packbuilder* pb =
nullptr;
2080 if( git_packbuilder_new( &pb, repo ) != 0 )
2084 std::reverse( keep.begin(), keep.end() );
2085 git_commit* parent =
nullptr;
2086 struct MAP_ENTRY { git_oid orig; git_oid neu; };
2087 std::vector<MAP_ENTRY> commitMap;
2088 bool rewriteOk =
true;
2092 aReporter->
AdvancePhase(
_(
"Trimming local history..." ) );
2096 for(
size_t idx = 0; idx < keep.size() && rewriteOk; ++idx )
2104 const git_oid& co = keep[idx];
2105 git_commit* orig =
nullptr;
2107 if( git_commit_lookup( &orig, repo, &co ) != 0 )
2110 git_tree* tree =
nullptr;
2112 if( git_commit_tree( &tree, orig ) != 0 )
2114 git_commit_free( orig );
2119 const git_signature* origAuthor = git_commit_author( orig );
2120 const git_signature* origCommitter = git_commit_committer( orig );
2121 git_signature* sigAuthor =
nullptr;
2122 git_signature* sigCommitter =
nullptr;
2124 git_signature_new( &sigAuthor, origAuthor->name, origAuthor->email,
2125 origAuthor->when.time, origAuthor->when.offset );
2126 git_signature_new( &sigCommitter, origCommitter->name, origCommitter->email,
2127 origCommitter->when.time, origCommitter->when.offset );
2129 const git_commit* parents[1];
2130 int parentCount = 0;
2134 parents[0] = parent;
2138 git_oid newCommitOid;
2140 if( git_commit_create( &newCommitOid, repo,
nullptr, sigAuthor, sigCommitter,
nullptr,
2141 git_commit_message( orig ), tree, parentCount, parentCount ? parents :
nullptr )
2149 git_commit_free( parent );
2152 git_commit_lookup( &parent, repo, &newCommitOid );
2153 commitMap.emplace_back( co, newCommitOid );
2156 git_signature_free( sigAuthor );
2157 git_signature_free( sigCommitter );
2158 git_tree_free( tree );
2159 git_commit_free( orig );
2163 git_commit_free( parent );
2166 for(
auto it = commitMap.rbegin(); it != commitMap.rend() && rewriteOk; ++it )
2168 if( git_packbuilder_insert_commit( pb, &it->neu ) != 0 )
2172 if( !rewriteOk || commitMap.empty() )
2174 git_packbuilder_free( pb );
2179 aReporter->
AdvancePhase(
_(
"Compacting trimmed history..." ) );
2181 bool packed =
writePack( pb, newPackDir, aReporter );
2182 git_packbuilder_free( pb );
2184 if( !packed || git_repository_open( &newRepo, trimPath.mb_str().data() ) != 0 )
2187 const git_oid& newHeadOid = commitMap.back().neu;
2188 git_reference* headRef =
nullptr;
2189 bool headOk =
false;
2191 if( git_reference_lookup( &headRef, newRepo,
"HEAD" ) == 0 )
2193 const char* branch = git_reference_symbolic_target( headRef );
2194 git_reference* branchRef =
nullptr;
2196 if( branch && git_reference_create( &branchRef, newRepo, branch, &newHeadOid, 1,
nullptr ) == 0 )
2199 git_reference_free( branchRef );
2202 git_reference_free( headRef );
2207 git_repository_free( newRepo );
2211 git_commit* newHead =
nullptr;
2213 if( git_commit_lookup( &newHead, newRepo, &newHeadOid ) == 0 )
2215 git_tree* newHeadTree =
nullptr;
2216 git_index* newIndex =
nullptr;
2218 if( git_commit_tree( &newHeadTree, newHead ) == 0 && git_repository_index( &newIndex, newRepo ) == 0 )
2220 git_index_read_tree( newIndex, newHeadTree );
2221 git_index_write( newIndex );
2225 git_index_free( newIndex );
2228 git_tree_free( newHeadTree );
2230 git_commit_free( newHead );
2234 for(
const auto& tt : tagTargets )
2237 const git_oid* newOid =
nullptr;
2239 for(
const auto& m : commitMap )
2241 if( memcmp( &m.orig, &tt.second,
sizeof( git_oid ) ) == 0 )
2251 git_object* obj =
nullptr;
2253 if( git_object_lookup( &obj, newRepo, newOid, GIT_OBJECT_COMMIT ) == 0 )
2255 git_oid tag_oid; git_tag_create_lightweight( &tag_oid, newRepo, tt.first.mb_str().data(), obj, 0 );
2256 git_object_free( obj );
2261 git_repository_free( newRepo );
2264 for(
const wxString&
name : { wxString( wxS(
".gitignore" ) ), wxString( wxS(
"README.txt" ) ) } )
2266 wxFileName src( aHistoryPath,
name );
2268 if( src.FileExists() && !wxCopyFile( src.GetFullPath(), wxFileName( trimPath,
name ).GetFullPath(),
true ) )
2275 wxString backupOld = aHistoryPath + wxS(
"_old_" ) +
KIID().
AsString();
2277 if( wxFileExists( backupOld ) || wxDirExists( backupOld ) )
2280 if( !wxRenameFile( aHistoryPath, backupOld,
false ) )
2283 if( !wxRenameFile( trimPath, aHistoryPath,
false ) )
2285 if( !wxRenameFile( backupOld, aHistoryPath,
false ) )
2286 wxLogError(
_(
"Could not restore local history '%s'. The previous history is preserved at '%s'." ),
2287 aHistoryPath, backupOld );
2292 if( !wxFileName::Rmdir( backupOld, wxPATH_RMDIR_RECURSIVE ) )
2293 wxLogTrace(
traceAutoSave, wxS(
"[history] Trimmed history installed; previous history retained at %s" ),
2306 wxDir gitDir( aGitDir );
2308 std::vector<wxString> stale;
2309 bool cont = gitDir.IsOpened()
2315 if( wxFileModificationTime( aGitDir +
name ) < expiry )
2316 stale.push_back( aGitDir +
name );
2318 cont = gitDir.GetNext( &
name );
2321 for(
const wxString& dir : stale )
2322 wxFileName::Rmdir( dir, wxPATH_RMDIR_RECURSIVE );
2329 std::vector<std::string> targets;
2330 git_reference_iterator* iter =
nullptr;
2331 git_reference* ref =
nullptr;
2334 if( git_reference_name_to_id( &oid, aRepo,
"HEAD" ) == 0 )
2335 targets.push_back( std::string(
"HEAD " ) + git_oid_tostr_s( &oid ) );
2337 if( git_reference_iterator_new( &iter, aRepo ) != 0 )
2340 while( git_reference_next( &ref, iter ) == 0 )
2342 git_reference* resolved =
nullptr;
2344 if( git_reference_resolve( &resolved, ref ) == 0 )
2346 targets.push_back( std::string( git_reference_name( ref ) ) +
" "
2347 + git_oid_tostr_s( git_reference_target( resolved ) ) );
2348 git_reference_free( resolved );
2351 git_reference_free( ref );
2354 git_reference_iterator_free( iter );
2355 std::sort( targets.begin(), targets.end() );
2363 wxDir objDir( aObjectsPath );
2365 bool cont = objDir.IsOpened() && objDir.GetFirst( &fanout, wxEmptyString, wxDIR_DIRS );
2369 if( fanout.length() == 2 )
2370 total +=
dirSizeRecursive( aObjectsPath + wxFileName::GetPathSeparator() + fanout );
2372 cont = objDir.GetNext( &fanout );
2389 [
this, projectPath = aProjectPath, hist =
historyPath( aProjectPath ), aLooseLimit]()
2393 if( git_repository_open( &repo, hist.mb_str().data() ) != 0 )
2396 wxString sep = wxFileName::GetPathSeparator();
2397 wxString gitDir = wxString::FromUTF8( git_repository_path( repo ) );
2398 wxString objPath = gitDir + wxS(
"objects" );
2399 wxString packDir = objPath + sep + wxS(
"pack" );
2400 wxString stageDir = gitDir +
STAGE_DIR_PREFIX + wxString::Format( wxS(
"%lu" ), wxGetProcessId() );
2404 git_repository_free( repo );
2410 std::vector<wxString> priorPacks =
listPackFiles( packDir );
2411 std::vector<std::string> priorRefs =
refTargets( repo );
2412 time_t walkStart = wxDateTime::Now().GetTicks();
2414 wxFileName::Rmdir( stageDir, wxPATH_RMDIR_RECURSIVE );
2415 wxMkdir( stageDir );
2418 git_repository_free( repo );
2431 wxString staged = stageDir + sep + stem;
2432 wxString target = packDir + sep + stem;
2436 bool packReady = wxFileExists( target + wxS(
".pack" ) )
2437 || wxRenameFile( staged + wxS(
".pack" ), target + wxS(
".pack" ),
false );
2438 bool installed = packReady
2439 && ( wxFileExists( target + wxS(
".idx" ) )
2440 || wxRenameFile( staged + wxS(
".idx" ), target + wxS(
".idx" ),
false ) );
2447 for(
const wxString& pack :
supersededBy( priorPacks, stem ) )
2449 if( !wxRemoveFile( pack ) )
2450 wxLogTrace(
traceAutoSave, wxS(
"[history] could not remove %s" ), pack );
2455 wxLogTrace(
traceAutoSave, wxS(
"[history] could not install %s" ), target );
2459 wxFileName::Rmdir( stageDir, wxPATH_RMDIR_RECURSIVE );
2468 [
this, previous = std::move(
m_compactFuture ), projectPath = aProjectPath,
2469 hist =
historyPath( aProjectPath ), aMaxBytes]()
mutable
2471 if( previous.valid() )
2484 &&
m_compactFuture.wait_for( std::chrono::seconds( 0 ) ) != std::future_status::ready;
2493 if( git_repository_open( &repo, hist.mb_str().data() ) != 0 )
2494 return wxEmptyString;
2497 if( git_reference_name_to_id( &head_oid, repo,
"HEAD" ) != 0 )
2499 git_repository_free( repo );
2500 return wxEmptyString;
2503 wxString hash = wxString::FromUTF8( git_oid_tostr_s( &head_oid ) );
2504 git_repository_free( repo );
2516bool checkForLockedFiles(
const wxString& aProjectPath, std::vector<wxString>& aLockedFiles )
2518 std::function<void(
const wxString& )> findLocks =
2519 [&](
const wxString& dirPath )
2521 wxDir dir( dirPath );
2522 if( !dir.IsOpened() )
2526 bool cont = dir.GetFirst( &filename );
2530 wxFileName fullPath( dirPath, filename );
2533 if( filename == wxS(
".history") || filename == wxS(
".git") )
2535 cont = dir.GetNext( &filename );
2539 if( fullPath.DirExists() )
2541 findLocks( fullPath.GetFullPath() );
2543 else if( fullPath.FileExists()
2550 baseName = baseName.BeforeLast(
'.' );
2551 wxFileName originalFile( dirPath, baseName );
2558 aLockedFiles.push_back( fullPath.GetFullPath() );
2562 cont = dir.GetNext( &filename );
2566 findLocks( aProjectPath );
2567 return aLockedFiles.empty();
2574bool extractCommitToTemp(
git_repository* aRepo, git_tree* aTree,
const wxString& aTempPath )
2576 bool extractSuccess =
true;
2578 std::function<void( git_tree*,
const wxString& )> extractTree =
2579 [&]( git_tree* t,
const wxString& prefix )
2581 if( !extractSuccess )
2584 size_t cnt = git_tree_entrycount( t );
2585 for(
size_t i = 0; i < cnt; ++i )
2587 const git_tree_entry* entry = git_tree_entry_byindex( t, i );
2588 wxString
name = wxString::FromUTF8( git_tree_entry_name( entry ) );
2589 wxString fullPath = prefix.IsEmpty() ?
name : prefix + wxS(
"/") +
name;
2591 if( git_tree_entry_type( entry ) == GIT_OBJECT_TREE )
2593 wxFileName dirPath( aTempPath + wxFileName::GetPathSeparator() + fullPath, wxEmptyString );
2595 if( !wxFileName::Mkdir( dirPath.GetPath(), 0777, wxPATH_MKDIR_FULL ) )
2598 wxS(
"[history] extractCommitToTemp: Failed to create directory '%s'" ),
2599 dirPath.GetPath() );
2600 extractSuccess =
false;
2604 git_tree* sub =
nullptr;
2606 if( git_tree_lookup( &sub, aRepo, git_tree_entry_id( entry ) ) == 0 )
2608 extractTree( sub, fullPath );
2609 git_tree_free( sub );
2612 else if( git_tree_entry_type( entry ) == GIT_OBJECT_BLOB )
2614 git_blob* blob =
nullptr;
2616 if( git_blob_lookup( &blob, aRepo, git_tree_entry_id( entry ) ) == 0 )
2618 wxFileName dst( aTempPath + wxFileName::GetPathSeparator() + fullPath );
2620 wxFileName dstDir( dst );
2621 dstDir.SetFullName( wxEmptyString );
2622 wxFileName::Mkdir( dstDir.GetPath(), 0777, wxPATH_MKDIR_FULL );
2624 wxFFile f( dst.GetFullPath(), wxT(
"wb" ) );
2628 f.Write( git_blob_rawcontent( blob ), git_blob_rawsize( blob ) );
2634 wxS(
"[history] extractCommitToTemp: Failed to write '%s'" ),
2635 dst.GetFullPath() );
2636 extractSuccess =
false;
2637 git_blob_free( blob );
2641 git_blob_free( blob );
2647 extractTree( aTree, wxEmptyString );
2648 return extractSuccess;
2658void collectRelativeFiles(
const wxString& aRoot,
const wxString& aDir, std::vector<wxString>& aOut )
2662 if( !dir.IsOpened() )
2667 for(
bool cont = dir.GetFirst( &
name ); cont; cont = dir.GetNext( &
name ) )
2669 wxString full = aDir + wxFILE_SEP_PATH +
name;
2671 if( wxDirExists( full ) )
2673 collectRelativeFiles( aRoot, full, aOut );
2675 else if( wxFileExists( full ) )
2677 wxString rel = full.Mid( aRoot.length() + 1 );
2678 rel.Replace( wxS(
"\\" ), wxS(
"/" ) );
2679 aOut.push_back( rel );
2693bool overlaySnapshotFiles(
const wxString& aTempRestorePath,
const wxString& aProjectPath,
const wxString& aBackupPath )
2695 std::vector<wxString> relPaths;
2696 collectRelativeFiles( aTempRestorePath, aTempRestorePath, relPaths );
2698 for(
const wxString& rel : relPaths )
2700 wxFileName src( aTempRestorePath + wxFILE_SEP_PATH + rel );
2701 wxFileName dst( aProjectPath + wxFILE_SEP_PATH + rel );
2703 if( dst.FileExists() )
2705 wxFileName bak( aBackupPath + wxFILE_SEP_PATH + rel );
2707 if( !wxFileName::Mkdir( bak.GetPath(), 0777, wxPATH_MKDIR_FULL )
2708 || !wxCopyFile( dst.GetFullPath(), bak.GetFullPath(),
true ) )
2714 if( !wxFileName::Mkdir( dst.GetPath(), 0777, wxPATH_MKDIR_FULL )
2715 || !wxCopyFile( src.GetFullPath(), dst.GetFullPath(),
true ) )
2732 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Checking for open files in %s" ),
2735 std::vector<wxString> lockedFiles;
2736 if( !checkForLockedFiles( aProjectPath, lockedFiles ) )
2739 for(
const auto& f : lockedFiles )
2740 lockList += wxS(
"\n - ") + f;
2743 wxS(
"[history] RestoreCommit: Cannot restore - files are open:%s" ),
2749 wxString msg =
_(
"Cannot restore - the following files are open by another user:" );
2751 wxMessageBox( msg,
_(
"Restore Failed" ), wxOK | wxICON_WARNING, aParent );
2762 wxS(
"[history] RestoreCommit: Failed to acquire lock for %s" ),
2773 if( git_oid_fromstr( &oid, aHash.mb_str().data() ) != 0 )
2775 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Invalid hash %s" ), aHash );
2779 git_commit* commit =
nullptr;
2780 if( git_commit_lookup( &commit, repo, &oid ) != 0 )
2782 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Commit not found %s" ), aHash );
2786 git_tree* tree =
nullptr;
2787 git_commit_tree( &tree, commit );
2791 if( aConfirm && aParent )
2793 wxDateTime when( (time_t) git_commit_time( commit ) );
2796 wxString::Format(
_(
"Restore the project to the version from %s?" ),
2797 when.Format( wxS(
"%Y-%m-%d %H:%M:%S" ) ) ),
2798 _(
"Restore Version" ), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION );
2800 dlg.SetYesNoLabels(
_(
"Restore" ),
_(
"Cancel" ) );
2801 dlg.SetExtendedMessage(
_(
"Your current files are backed up first so you can undo the "
2802 "restore. Files that are not part of this version are left "
2805 if( dlg.ShowModal() != wxID_YES )
2807 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: User cancelled at confirm" ) );
2808 git_tree_free( tree );
2809 git_commit_free( commit );
2815 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Creating pre-restore backup" ) );
2817 std::vector<wxString> backupFiles;
2820 if( !backupFiles.empty() )
2824 backupFiles, wxS(
"Pre-restore backup" ) );
2829 wxS(
"[history] RestoreCommit: Failed to create pre-restore backup" ) );
2830 git_tree_free( tree );
2831 git_commit_free( commit );
2837 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Current state already matches HEAD; "
2838 "continuing without a new backup commit" ) );
2843 wxString tempRestorePath = aProjectPath + wxS(
"_restore_temp");
2845 if( wxDirExists( tempRestorePath ) )
2846 wxFileName::Rmdir( tempRestorePath, wxPATH_RMDIR_RECURSIVE );
2848 if( !wxFileName::Mkdir( tempRestorePath, 0777, wxPATH_MKDIR_FULL ) )
2851 wxS(
"[history] RestoreCommit: Failed to create temp directory %s" ),
2853 git_tree_free( tree );
2854 git_commit_free( commit );
2858 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Extracting to temp location %s" ),
2861 if( !extractCommitToTemp( repo, tree, tempRestorePath ) )
2863 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Extraction failed, cleaning up" ) );
2864 wxFileName::Rmdir( tempRestorePath, wxPATH_RMDIR_RECURSIVE );
2865 git_tree_free( tree );
2866 git_commit_free( commit );
2875 wxString backupPath =
2876 aProjectPath + wxS(
"_restore_backup_" )
2877 + wxDateTime::UNow().Format( wxS(
"%Y-%m-%dT%H-%M-%S-%l" ) );
2879 if( !overlaySnapshotFiles( tempRestorePath, aProjectPath, backupPath ) )
2881 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Overlay failed, rolling back from backup" ) );
2884 if( wxDirExists( backupPath ) )
2886 wxString discard = aProjectPath + wxS(
"_restore_discard" );
2887 overlaySnapshotFiles( backupPath, aProjectPath, discard );
2888 wxFileName::Rmdir( discard, wxPATH_RMDIR_RECURSIVE );
2889 wxFileName::Rmdir( backupPath, wxPATH_RMDIR_RECURSIVE );
2892 wxFileName::Rmdir( tempRestorePath, wxPATH_RMDIR_RECURSIVE );
2893 git_tree_free( tree );
2894 git_commit_free( commit );
2900 wxS(
"[history] RestoreCommit: Restore successful, backup retained at %s" ),
2902 wxFileName::Rmdir( tempRestorePath, wxPATH_RMDIR_RECURSIVE );
2905 std::vector<wxString> resultFiles;
2908 wxString::Format( wxS(
"Restored from %s" ), aHash ) );
2913 git_tree_free( tree );
2914 git_commit_free( commit );
2916 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Complete" ) );
2925 std::vector<LOCAL_HISTORY_SNAPSHOT_INFO> snapshots =
LoadSnapshots( aProjectPath );
2927 if( snapshots.empty() )
2936 if( !selectedHash.IsEmpty() )
2943 std::vector<LOCAL_HISTORY_SNAPSHOT_INFO> snapshots;
2948 if( git_repository_open( &repo, hist.mb_str().data() ) != 0 )
2951 git_revwalk* walk =
nullptr;
2952 if( git_revwalk_new( &walk, repo ) != 0 )
2954 git_repository_free( repo );
2958 git_revwalk_sorting( walk, GIT_SORT_TIME );
2959 git_revwalk_push_head( walk );
2963 while( git_revwalk_next( &oid, walk ) == 0 )
2965 git_commit* commit =
nullptr;
2967 if( git_commit_lookup( &commit, repo, &oid ) != 0 )
2971 info.hash = wxString::FromUTF8( git_oid_tostr_s( &oid ) );
2972 info.date = wxDateTime(
static_cast<time_t
>( git_commit_time( commit ) ) );
2973 info.message = wxString::FromUTF8( git_commit_message( commit ) );
2975 wxString firstLine =
info.message.BeforeFirst(
'\n' );
2977 long parsedCount = 0;
2979 firstLine.BeforeFirst(
':', &remainder );
2980 remainder.Trim(
true ).Trim(
false );
2982 if( remainder.EndsWith( wxS(
"files changed" ) ) )
2984 wxString countText = remainder.BeforeFirst(
' ' );
2986 if( countText.ToLong( &parsedCount ) )
2987 info.filesChanged =
static_cast<int>( parsedCount );
2990 info.summary = firstLine.BeforeFirst(
':' );
2993 info.message.BeforeFirst(
'\n', &rest );
2994 wxArrayString lines = wxSplit( rest,
'\n',
'\0' );
2996 for(
const wxString& line : lines )
2998 if( !line.IsEmpty() )
2999 info.changedFiles.Add( line );
3002 snapshots.push_back( std::move(
info ) );
3003 git_commit_free( commit );
3006 git_revwalk_free( walk );
3007 git_repository_free( repo );
3019 const wxString& aExtension )
3024 if( git_repository_open( &repo, hist.mb_str().data() ) != 0 )
3025 return wxEmptyString;
3028 git_commit* commit =
nullptr;
3029 git_tree* tree =
nullptr;
3031 if( git_oid_fromstr( &oid, aHash.mb_str().data() ) != 0 || git_commit_lookup( &commit, repo, &oid ) != 0 )
3033 git_repository_free( repo );
3034 return wxEmptyString;
3037 if( git_commit_tree( &tree, commit ) != 0 )
3039 git_commit_free( commit );
3040 git_repository_free( repo );
3041 return wxEmptyString;
3047 std::vector<wxString> entries;
3048 } ctx{ aExtension, {} };
3050 auto collect = [](
const char* aRoot,
const git_tree_entry* aEntry,
void* aPayload ) ->
int
3052 WALK_CTX* c =
static_cast<WALK_CTX*
>( aPayload );
3054 if( git_tree_entry_type( aEntry ) != GIT_OBJECT_BLOB )
3057 wxString
name = wxString::FromUTF8( git_tree_entry_name( aEntry ) );
3059 if( !
name.EndsWith( c->ext ) )
3062 wxString
path = wxString::FromUTF8( aRoot ) +
name;
3063 c->entries.push_back(
path + wxS(
":" )
3064 + wxString::FromUTF8( git_oid_tostr_s( git_tree_entry_id( aEntry ) ) ) );
3068 git_tree_walk( tree, GIT_TREEWALK_PRE, collect, &ctx );
3070 git_tree_free( tree );
3071 git_commit_free( commit );
3072 git_repository_free( repo );
3074 std::sort( ctx.entries.begin(), ctx.entries.end() );
3076 wxString fingerprint;
3078 for(
const wxString& entry : ctx.entries )
3079 fingerprint << entry << wxS(
"|" );
3086 const wxString& aDestDir,
const std::vector<wxString>& aExtensions )
3091 if( git_repository_open( &repo, hist.mb_str().data() ) != 0 )
3095 git_commit* commit =
nullptr;
3096 git_tree* tree =
nullptr;
3098 if( git_oid_fromstr( &oid, aHash.mb_str().data() ) != 0 || git_commit_lookup( &commit, repo, &oid ) != 0 )
3100 git_repository_free( repo );
3104 if( git_commit_tree( &tree, commit ) != 0 )
3106 git_commit_free( commit );
3107 git_repository_free( repo );
3117 } ctx{ repo, aDestDir, &aExtensions,
true };
3120 auto writeEntry = [](
const char* aRoot,
const git_tree_entry* aEntry,
void* aPayload ) ->
int
3122 WALK_CTX* c =
static_cast<WALK_CTX*
>( aPayload );
3124 if( git_tree_entry_type( aEntry ) != GIT_OBJECT_BLOB )
3127 wxString
name = wxString::FromUTF8( git_tree_entry_name( aEntry ) );
3129 if( !c->extensions->empty() )
3133 for(
const wxString& ext : *c->extensions )
3135 if(
name.EndsWith( ext ) )
3146 wxString rel = wxString::FromUTF8( aRoot ) +
name;
3147 wxFileName outFn( c->destDir + wxS(
"/" ) + rel );
3149 if( !wxFileName::Mkdir( outFn.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
3155 git_blob* blob =
nullptr;
3157 if( git_blob_lookup( &blob, c->repo, git_tree_entry_id( aEntry ) ) == 0 )
3159 const void* data = git_blob_rawcontent( blob );
3160 const size_t size =
static_cast<size_t>( git_blob_rawsize( blob ) );
3161 wxFFile out( outFn.GetFullPath(), wxS(
"wb" ) );
3163 if( !( data && out.IsOpened() && out.Write( data, size ) == size ) )
3166 git_blob_free( blob );
3176 git_tree_walk( tree, GIT_TREEWALK_PRE, writeEntry, &ctx );
3178 git_tree_free( tree );
3179 git_commit_free( commit );
3180 git_repository_free( repo );
bool AutosaveUsesLocalHistory() const
The backup format is the single switch that selects the autosave mechanism: the incremental format re...
wxString GetSelectedHash() const
bool ShouldDescend(const wxString &aDir)
Hybrid locking mechanism for local history git repositories.
git_repository * GetRepository()
Get the git repository handle (only valid if IsLocked() returns true).
void ReleaseRepository()
Release git repository and index handles early, but keep the file lock.
wxString GetLockError() const
Get error message describing why lock could not be acquired.
git_index * GetIndex()
Get the git index handle (only valid if IsLocked() returns true).
git_repository * ReopenRepository()
Re-open the git repository and index after ReleaseRepository().
bool IsLocked() const
Check if locks were successfully acquired.
wxString AsString() const
std::vector< LOCAL_HISTORY_SNAPSHOT_INFO > LoadSnapshots(const wxString &aProjectPath)
void EnforceSizeLimitInBackground(const wxString &aProjectPath, size_t aMaxBytes)
Run EnforceSizeLimit without a reporter on a background thread, after any running compaction.
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.
static std::vector< std::pair< wxString, wxString > > CollectAutosaveFilePairs(const wxString &aAutosaveRoot, const wxString &aProjectPath, BACKUP_LOCATION aLocation)
Enumerate every (autosave, source) pair found under aAutosaveRoot for the project at aProjectPath,...
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.
std::future< void > m_compactFuture
SNAPSHOT_COMMIT_RESULT commitInBackground(const wxString &aProjectPath, const wxString &aTitle, const std::vector< HISTORY_FILE_DATA > &aFileData, bool aIsManualSave)
Execute file writes and git commit on a background thread.
void ShowRestoreDialog(const wxString &aProjectPath, wxWindow *aParent)
Show a dialog allowing the user to choose a snapshot to restore.
std::map< const void *, SAVER_ENTRY > m_savers
bool HeadNewerThanLastSave(const wxString &aProjectPath)
Return true if the autosave data is newer than the last manual save.
std::set< wxString > m_pendingFiles
bool CommitDuplicateOfLastSave(const wxString &aProjectPath, const wxString &aFileType, const wxString &aMessage)
Create a new commit duplicating the tree pointed to by Last_Save_<fileType> and move the Last_Save_<f...
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.
void scheduleCompaction(const wxString &aProjectPath, size_t aLooseLimit)
Start packing loose history objects on a background thread once they exceed aLooseLimit bytes,...
bool IsCompacting() const
True while a background compaction started by autosave is still running.
bool Init(const wxString &aProjectPath)
Initialize the local history repository for the given project path.
void ClearAllSavers()
Clear all registered savers.
bool CommitSnapshot(const std::vector< wxString > &aFiles, const wxString &aTitle)
Commit the given files to the local history repository.
std::atomic< bool > m_saveInProgress
void NoteFileChange(const wxString &aFile)
Record that a file has been modified and should be included in the next snapshot.
bool enforceSizeLimit(const wxString &aProjectPath, const wxString &aHistoryPath, size_t aMaxBytes, PROGRESS_REPORTER *aReporter)
EnforceSizeLimit with the history directory already resolved, so it can run off the UI thread.
bool CommitPending()
Commit any pending modified files to the history repository.
bool HistoryExists(const wxString &aProjectPath)
Return true if history exists for the project.
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...
std::vector< LOCAL_HISTORY_SNAPSHOT_INFO > GetSnapshots(const wxString &aProjectPath)
Snapshots (commits) for the project, newest first.
wxString TreeFingerprint(const wxString &aProjectPath, const wxString &aHash, const wxString &aExtension)
Fingerprint of all files ending in aExtension recorded by commit aHash (sorted path:blob pairs).
bool ExtractAllFilesAtCommit(const wxString &aProjectPath, const wxString &aHash, const wxString &aDestDir, const std::vector< wxString > &aExtensions={})
Write files recorded at aHash into aDestDir, recreating the project's relative folder structure.
std::atomic< bool > m_cancelCompaction
std::future< bool > m_pendingFuture
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.
void pruneExpiredSavers()
Drop tracked savers whose owning document has been freed, before any saver runs.
void RemoveAutosaveFiles(const wxString &aProjectPath) const
Remove every autosave file under the project at aProjectPath regardless of which source it shadowed.
Advisory lock over a file, taken by writing a sibling lock file and holding an exclusive lock on it f...
static LOCKFILE Inspect(const wxString &aFilename)
Look at a lock without taking it: nothing is created, nothing is claimed and nothing is removed on re...
static bool EnsurePathExists(const wxString &aPath, bool aPathToFile=false)
Attempts to create a given path if it does not exist.
virtual COMMON_SETTINGS * GetCommonSettings() const
virtual SETTINGS_MANAGER & GetSettingsManager() const
A progress reporter interface for use in multi-threaded environments.
virtual bool KeepRefreshing(bool aWait=false)=0
Update the UI (if any).
virtual void Report(const wxString &aMessage)=0
Display aMessage in the progress bar dialog.
virtual void AdvancePhase()=0
Use the next available virtual zone of the dialog progress bar.
virtual void SetCurrentProgress(double aProgress)=0
Set the progress value to aProgress (0..1).
COMMON_SETTINGS * GetCommonSettings() const
Retrieve the common settings shared by all applications.
wxString GetAutosaveRootForProject(const PROJECT *aProject=nullptr) const
Resolve the autosave-files root for a project.
PROJECT * GetProjectForPath(const wxString &aProjectPath) const
Return the active project iff its path matches aProjectPath, else nullptr.
wxString GetLocalHistoryDirForPath(const wxString &aProjectPath) const
Resolve the local-history directory for a project given by its on-disk path.
@ PROJECT_DIR
Inside the project directory (default)
This file is part of the common library.
#define KICAD_MESSAGE_DIALOG
static const std::string LockFileExtension
static const std::string ProjectFileExtension
static const std::string LockFilePrefix
const wxChar *const traceAutoSave
Flag to enable auto save feature debug tracing.
static wxString historyPath(const wxString &aProjectPath)
static bool readFileContent(const wxString &aPath, std::string &aContent)
static wxString historyPath(const wxString &aProjectPath)
static bool isRestoreProtectedEntry(const wxString &aName)
static const wxString STAGE_DIR_PREFIX
static std::vector< std::pair< wxString, wxString > > findAutosaveFilePairs(const wxString &aProjectPath)
static void removeStaleStageDirs(const wxString &aGitDir)
static const wxString AUTOSAVE_PREFIX
static bool commitSnapshotForProject(const wxString &aProjectPath, const std::vector< wxString > &aFiles, const wxString &aTitle)
static size_t dirSizeRecursive(const wxString &path)
static bool isKiCadProjectFile(const wxFileName &aFile)
static wxString buildPack(git_repository *aRepo, const wxString &aPackDir, PROGRESS_REPORTER *aReporter, const std::atomic< bool > *aCancel)
static wxString sourceForAutosaveFile(const wxString &aAutosavePath, const wxString &aProjectPath, const wxString &aAutosaveRoot, BACKUP_LOCATION aLocation)
static std::vector< wxString > supersededBy(const std::vector< wxString > &aPriorPacks, const wxString &aNewStem)
static bool tagSaveAtHead(git_repository *repo, const wxString &aFileType)
static bool writePack(git_packbuilder *aPb, const wxString &aPackDir, PROGRESS_REPORTER *aReporter, const std::atomic< bool > *aCancel=nullptr)
static wxString resolveAutosaveDestination(const wxString &aAutosaveRoot, const wxString &aRelativePath, BACKUP_LOCATION aLocation)
static SNAPSHOT_COMMIT_RESULT commitSnapshotWithLock(git_repository *repo, git_index *index, const wxString &aHistoryPath, const wxString &aProjectPath, const std::vector< wxString > &aFiles, const wxString &aTitle)
static bool localHistoryEnabled()
static bool filesContentEqual(const wxString &aPathA, const wxString &aPathB)
static void pruneLooseObjects(const wxString &aObjectsPath, const wxString &aPackIndex, time_t aWalkStart)
static const size_t UNLIMITED_HISTORY_LOOSE_BYTES
static void removeLegacyWorkingCopies(git_repository *aRepo, const wxString &aHistoryRoot)
static size_t looseObjectBytes(const wxString &aObjectsPath)
static const time_t UNREACHABLE_LOOSE_EXPIRY_SECONDS
static std::vector< wxString > listPackFiles(const wxString &aPackDir)
static bool compactRepository(git_repository *aRepo, PROGRESS_REPORTER *aReporter, std::vector< wxString > *aSupersededPacks)
static bool isProjectDirectory(const wxString &aProjectPath)
static void collectProjectFiles(const wxString &aProjectPath, std::vector< wxString > &aFiles)
static std::vector< std::string > refTargets(git_repository *aRepo)
static wxString joinHistoryDestination(const wxString &aHistoryRoot, const wxString &aRelativePath)
PGM_BASE & Pgm()
The global program "get" accessor.
static const std::vector< std::string > extensions
static std::vector< uint8_t > copyBytes(const void *aData, size_t aBytes)
#define PROJECT_BACKUPS_DIR_SUFFIX
Project settings path will be <projectname> + this.
unsigned long long limit_total_size
Maximum total size of backups (bytes), 0 for unlimited.
BACKUP_LOCATION location
Where backups, history, and autosave files live.
Data produced by a registered saver on the UI thread, consumed by either the background local-history...
std::function< void(const wxString &, std::vector< HISTORY_FILE_DATA > &)> saver
std::weak_ptr< void > lifetime
IbisParser parser & reporter
wxString result
Test unit parsing edge cases and error handling.
thread_pool & GetKiCadThreadPool()
Get a reference to the current thread pool.
wxLogTrace helper definitions.
Definition of file extensions used in Kicad.