39#include <git2/sys/odb_backend.h>
41#include <wx/filename.h>
45#include <wx/datetime.h>
71 const wxString& aRelativePath )
73 wxFileName fn( aRelativePath );
76 return fn.GetFullPath();
81 wxArrayString dirs = fn.GetDirs();
84 dst.AssignDir( aHistoryRoot );
86 for(
const wxString& d : dirs )
89 dst.SetFullName( fn.GetFullName() );
90 return dst.GetFullPath();
102 wxFFile file( aPath, wxS(
"rb" ) );
104 if( !file.IsOpened() )
107 wxFileOffset length = file.Length();
112 size_t len =
static_cast<size_t>( length );
113 aContent.assign( len,
'\0' );
115 return file.Read( aContent.data(), len ) == len;
123 wxDir histDir( aHistoryRoot );
125 bool hasCopies =
false;
126 bool cont = histDir.IsOpened()
127 && histDir.GetFirst( &
name, wxEmptyString, wxDIR_FILES | wxDIR_DIRS | wxDIR_HIDDEN );
130 while( cont && !hasCopies )
132 hasCopies =
name != wxS(
".git" ) &&
name != wxS(
".gitignore" ) &&
name != wxS(
"README.txt" );
133 cont = histDir.GetNext( &
name );
140 git_commit* head =
nullptr;
141 git_tree* tree =
nullptr;
143 if( git_reference_name_to_id( &headOid, aRepo,
"HEAD" ) != 0 || git_commit_lookup( &head, aRepo, &headOid ) != 0 )
146 if( git_commit_tree( &tree, head ) == 0 )
151 std::vector<wxString> dirs;
152 } state{ aHistoryRoot, {} };
155 tree, GIT_TREEWALK_POST,
156 [](
const char* aDir,
const git_tree_entry* aEntry,
void* aPayload ) ->
int
158 auto* walkState =
static_cast<WALK_STATE*
>( aPayload );
159 wxString rel = wxString::FromUTF8( aDir ) + wxString::FromUTF8( git_tree_entry_name( aEntry ) );
161 if( rel.Contains( wxS(
".." ) ) )
166 if( git_tree_entry_type( aEntry ) == GIT_OBJECT_TREE )
167 walkState->dirs.push_back(
path );
168 else if( wxFileExists(
path ) )
169 wxRemoveFile(
path );
177 wxLogNull suppressRmdirErrors;
179 for(
const wxString& dir : state.dirs )
181 if( wxDirExists( dir ) )
185 git_tree_free( tree );
188 git_commit_free( head );
195 wxFFile fileA( aPathA, wxS(
"rb" ) );
196 wxFFile fileB( aPathB, wxS(
"rb" ) );
198 if( !fileA.IsOpened() || !fileB.IsOpened() )
201 wxFileOffset lenA = fileA.Length();
202 wxFileOffset lenB = fileB.Length();
204 if( lenA < 0 || lenB < 0 || lenA != lenB )
207 constexpr size_t chunkSize = 64 * 1024;
208 std::vector<char> bufA( chunkSize );
209 std::vector<char> bufB( chunkSize );
211 while( !fileA.Eof() )
213 size_t readA = fileA.Read( bufA.data(), chunkSize );
214 size_t readB = fileB.Read( bufB.data(), chunkSize );
219 if( readA > 0 && std::memcmp( bufA.data(), bufB.data(), readA ) != 0 )
222 if( fileA.Error() || fileB.Error() )
236 const wxString& aRelativePath,
239 wxFileName rel( aRelativePath );
241 dst.AssignDir( aAutosaveRoot );
243 for(
const wxString& d : rel.GetDirs() )
249 dst.SetFullName( rel.GetFullName() );
251 return dst.GetFullPath();
259 const wxString& aProjectPath,
260 const wxString& aAutosaveRoot,
263 wxFileName autosave( aAutosavePath );
267 wxString
name = autosave.GetFullName();
270 return wxEmptyString;
273 return autosave.GetFullPath();
276 if( !aAutosavePath.StartsWith( aAutosaveRoot ) )
277 return wxEmptyString;
279 wxString rel = aAutosavePath.Mid( aAutosaveRoot.length() );
280 wxFileName projFn( aProjectPath, wxEmptyString );
282 return projFn.GetPathWithSep() + rel;
287 const wxString& aTitle );
308 if( aProjectPath.IsEmpty() || !wxDirExists( aProjectPath ) )
311 wxDir dir( aProjectPath );
314 return dir.IsOpened()
325 return aName == wxS(
".history" ) || aName == wxS(
".history_old" )
326 || aName.StartsWith( wxS(
".history_old_" ) )
327 || aName == wxS(
".git" ) || aName == wxS(
"_restore_backup" )
328 || aName.StartsWith( wxS(
"_restore_backup_" ) ) || aName == wxS(
"_restore_temp" )
350 wxFileName fn( aFile );
360 const void* aSaverObject,
361 const std::function<
void(
const wxString&, std::vector<HISTORY_FILE_DATA>& )>& aSaver,
362 const std::weak_ptr<void>& aLifetime )
366 wxLogTrace(
traceAutoSave, wxS(
"[history] Saver %p already registered, skipping" ), aSaverObject );
371 entry.
saver = aSaver;
376 entry.
tracked = aLifetime.lock() !=
nullptr;
378 m_savers[aSaverObject] = std::move( entry );
379 wxLogTrace(
traceAutoSave, wxS(
"[history] Registered saver %p (total=%zu)" ), aSaverObject,
m_savers.size() );
385 std::vector<const void*> expired;
389 for(
const auto& [saverObject, entry] :
m_savers )
391 if( entry.tracked && entry.lifetime.expired() )
392 expired.push_back( saverObject );
395 for(
const void* obj : expired )
404 auto it =
m_savers.find( aSaverObject );
409 wxLogTrace(
traceAutoSave, wxS(
"[history] Unregistered saver %p (total=%zu)" ),
419 wxLogTrace(
traceAutoSave, wxS(
"[history] Cleared all savers" ) );
424 const wxString& aTagFileType )
428 wxLogTrace(
traceAutoSave, wxS(
"Local history disabled, returning" ) );
435 Init( aProjectPath );
438 wxS(
"[history] RunRegisteredSaversAndCommit start project='%s' title='%s' savers=%zu tag='%s'" ),
439 aProjectPath, aTitle,
m_savers.size(), aTagFileType );
443 wxLogTrace(
traceAutoSave, wxS(
"[history] no savers registered; skipping") );
448 if( !aTagFileType.IsEmpty() )
454 wxLogTrace(
traceAutoSave, wxS(
"[history] previous save still in progress; skipping cycle" ) );
461 std::vector<HISTORY_FILE_DATA> fileData;
463 for(
const auto& [saverObject, entry] :
m_savers )
465 size_t before = fileData.size();
466 entry.saver( aProjectPath, fileData );
467 wxLogTrace(
traceAutoSave, wxS(
"[history] saver %p produced %zu entries (total=%zu)" ),
468 saverObject, fileData.size() - before, fileData.size() );
474 fileData.erase( std::remove_if( fileData.begin(), fileData.end(),
477 if( entry.relativePath.IsEmpty() || wxFileName( entry.relativePath ).IsAbsolute() )
479 wxLogTrace( traceAutoSave, wxS(
"[history] filtered out entry with invalid path: '%s'" ),
480 entry.relativePath );
487 if( fileData.empty() )
489 wxLogTrace(
traceAutoSave, wxS(
"[history] saver set produced no entries; skipping" ) );
494 m_saveInProgress.store(
true, std::memory_order_release );
497 [
this, projectPath = aProjectPath, title = aTitle, tagFileType = aTagFileType,
498 data = std::move( fileData )]()
mutable ->
bool
501 !tagFileType.IsEmpty() );
505 TagSave( projectPath, tagFileType );
507 m_saveInProgress.store(
false, std::memory_order_release );
512 if( !aTagFileType.IsEmpty() )
514 WaitForPendingSave();
529 if( !
Pgm().GetCommonSettings()->m_Backup.enabled )
534 wxLogTrace(
traceAutoSave, wxS(
"[autosave] no savers registered; skipping" ) );
544 wxLogTrace(
traceAutoSave, wxS(
"[autosave] cannot create autosave root '%s'" ), autosaveRoot );
550 std::vector<HISTORY_FILE_DATA> fileData;
552 for(
const auto& [saverObject, entry] :
m_savers )
553 entry.saver( aProjectPath, fileData );
555 bool anyWritten =
false;
559 if( entry.relativePath.IsEmpty() || wxFileName( entry.relativePath ).IsAbsolute() )
563 wxFileName dstFn( dst );
567 wxLogTrace(
traceAutoSave, wxS(
"[autosave] cannot create dir '%s'" ), dstFn.GetPath() );
573 if( !entry.content.empty() )
575 buf = std::move( entry.content );
580 else if( !entry.sourcePath.IsEmpty() )
582 wxFFile src( entry.sourcePath, wxS(
"rb" ) );
584 if( !src.IsOpened() )
587 wxFileOffset len = src.Length();
592 buf.resize(
static_cast<size_t>( len ) );
594 if( len > 0 && src.Read( buf.data(), buf.size() ) != buf.size() )
610 wxLogTrace(
traceAutoSave, wxS(
"[autosave] wrote %zu bytes to '%s'" ), buf.size(), dst );
614 wxLogTrace(
traceAutoSave, wxS(
"[autosave] write failed for '%s': %s" ), dst, err );
627std::vector<std::pair<wxString, wxString>>
631 std::vector<std::pair<wxString, wxString>> results;
633 if( !wxDirExists( aAutosaveRoot ) )
641 std::function<void(
const wxString& )> walk =
642 [&](
const wxString& aDir )
650 bool cont = d.GetFirst( &
name );
654 wxFileName fn( aDir,
name );
655 wxString fullPath = fn.GetFullPath();
657 if( wxDirExists( fullPath ) )
660 && (
name == wxS(
".history" ) ||
name.EndsWith( wxS(
"-backups" ) ) ) )
662 cont = d.GetNext( &
name );
676 results.emplace_back( fullPath, src );
679 cont = d.GetNext( &
name );
683 walk( aAutosaveRoot );
688static std::vector<std::pair<wxString, wxString>>
699std::vector<std::pair<wxString, wxString>>
702 std::vector<std::pair<wxString, wxString>> results;
704 if( aExtensions.empty() )
709 wxFileName srcFn( pair.second );
712 for(
const wxString& ext : aExtensions )
714 if( srcFn.GetExt().IsSameAs( ext,
false ) )
726 if( srcFn.FileExists() )
727 srcTime = srcFn.GetModificationTime();
729 wxDateTime autosaveTime = wxFileName( pair.first ).GetModificationTime();
733 bool stale = !srcTime.IsValid()
734 || ( autosaveTime.IsLaterThan( srcTime )
738 results.emplace_back( std::move( pair ) );
752 if( wxFileExists( autosavePath ) )
753 wxRemoveFile( autosavePath );
759 const std::vector<wxString>& aSourcePaths )
const
761 if( aSourcePaths.empty() )
764 std::vector<wxFileName> targets;
765 targets.reserve( aSourcePaths.size() );
767 for(
const wxString& src : aSourcePaths )
770 targets.emplace_back( src );
773 if( targets.empty() )
778 wxFileName srcFn( srcPath );
781 for(
const wxFileName& target : targets )
783 if( srcFn.SameAs( target ) )
790 if( match && wxFileExists( autosavePath ) )
791 wxRemoveFile( autosavePath );
797 const std::vector<HISTORY_FILE_DATA>& aFileData,
800 wxLogTrace(
traceAutoSave, wxS(
"[history] background: writing %zu entries for '%s'" ),
801 aFileData.size(), aProjectPath );
807 wxLogTrace(
traceAutoSave, wxS(
"[history] background: cannot create history root '%s'" ), hist );
813 wxString relativePath;
817 std::vector<STAGED_FILE> staged;
821 STAGED_FILE file{ entry.relativePath, std::string() };
823 if( !entry.content.empty() )
825 file.content = entry.content;
830 else if( entry.sourcePath.IsEmpty() )
837 wxLogTrace(
traceAutoSave, wxS(
"[history] background: cannot read '%s'" ), entry.sourcePath );
841 staged.push_back( std::move( file ) );
859 for(
const STAGED_FILE& file : staged )
861 wxString rel = file.relativePath;
862 rel.Replace( wxS(
"\\" ), wxS(
"/" ) );
864 std::string
path = rel.utf8_string();
865 git_index_entry indexEntry = {};
866 indexEntry.mode = GIT_FILEMODE_BLOB;
867 indexEntry.path =
path.c_str();
869 if( git_index_add_from_buffer(
index, &indexEntry, file.content.data(), file.content.size() ) != 0 )
872 wxLogTrace(
traceAutoSave, wxS(
"[history] background: failed to stage '%s'" ), rel );
873 git_index_read(
index,
true );
880 git_commit* head_commit =
nullptr;
881 git_tree* head_tree =
nullptr;
883 bool headExists = ( git_reference_name_to_id( &head_oid, repo,
"HEAD" ) == 0 )
884 && ( git_commit_lookup( &head_commit, repo, &head_oid ) == 0 )
885 && ( git_commit_tree( &head_tree, head_commit ) == 0 );
887 git_tree* rawIndexTree =
nullptr;
888 git_oid index_tree_oid;
890 if( git_index_write_tree( &index_tree_oid,
index ) != 0 )
893 git_tree_free( head_tree );
896 git_commit_free( head_commit );
898 wxLogTrace(
traceAutoSave, wxS(
"[history] background: failed to write index tree" ) );
902 git_tree_lookup( &rawIndexTree, repo, &index_tree_oid );
903 std::unique_ptr<git_tree,
decltype( &git_tree_free )> indexTree( rawIndexTree, &git_tree_free );
905 bool hasChanges =
true;
909 git_diff* diff =
nullptr;
911 if( git_diff_tree_to_tree( &diff, repo, head_tree, indexTree.get(),
nullptr ) == 0 )
913 hasChanges = git_diff_num_deltas( diff ) > 0;
914 wxLogTrace(
traceAutoSave, wxS(
"[history] background: diff deltas=%u" ),
915 (
unsigned) git_diff_num_deltas( diff ) );
916 git_diff_free( diff );
923 bool stagedMatchesDisk =
true;
925 for(
const STAGED_FILE& file : staged )
927 wxString diskPath = aProjectPath + wxFileName::GetPathSeparator() + file.relativePath;
928 std::string diskContent;
930 if( !
readFileContent( diskPath, diskContent ) || diskContent != file.content )
932 stagedMatchesDisk =
false;
937 if( stagedMatchesDisk && !aIsManualSave )
939 wxLogTrace(
traceAutoSave, wxS(
"[history] background: first commit; staged matches disk -- skipping" ) );
945 git_tree_free( head_tree );
948 git_commit_free( head_commit );
952 wxLogTrace(
traceAutoSave, wxS(
"[history] background: no changes detected; no commit") );
956 if( !aTitle.IsEmpty() && aTitle != wxS(
"Autosave" ) )
958 git_oid head_oid_amend;
960 if( git_reference_name_to_id( &head_oid_amend, repo,
"HEAD" ) == 0 )
962 git_commit* head_commit_amend =
nullptr;
964 if( git_commit_lookup( &head_commit_amend, repo, &head_oid_amend ) == 0 )
966 wxString existingMsg = wxString::FromUTF8( git_commit_message( head_commit_amend ) );
967 existingMsg.Trim(
true ).Trim(
false );
969 if( existingMsg != aTitle )
972 int amend_rc = git_commit_amend( &amended_oid, head_commit_amend,
"HEAD",
nullptr,
nullptr,
973 nullptr, aTitle.mb_str().data(),
nullptr );
976 wxLogTrace(
traceAutoSave, wxS(
"[history] background: amended HEAD message '%s' -> '%s'" ),
977 existingMsg, aTitle );
979 wxLogTrace(
traceAutoSave, wxS(
"[history] background: amend failed rc=%d" ), amend_rc );
982 git_commit_free( head_commit_amend );
990 git_signature* rawSig =
nullptr;
992 std::unique_ptr<git_signature,
decltype( &git_signature_free )> sig( rawSig, &git_signature_free );
994 git_commit* parent =
nullptr;
998 if( git_reference_name_to_id( &parent_id, repo,
"HEAD" ) == 0 )
1000 if( git_commit_lookup( &parent, repo, &parent_id ) == 0 )
1004 wxString msg = aTitle.IsEmpty() ? wxString(
"Autosave" ) : aTitle;
1006 const git_commit* constParent = parent;
1008 int rc = git_commit_create( &commit_id, repo,
"HEAD", sig.get(), sig.get(),
nullptr,
1009 msg.mb_str().data(), indexTree.get(), parents,
1010 parents ? &constParent :
nullptr );
1014 wxLogTrace(
traceAutoSave, wxS(
"[history] background: commit created %s (%s entries=%zu)" ),
1015 wxString::FromUTF8( git_oid_tostr_s( &commit_id ) ), msg, aFileData.size() );
1019 wxLogTrace(
traceAutoSave, wxS(
"[history] background: commit failed rc=%d" ), rc );
1023 git_commit_free( parent );
1025 git_index_write(
index );
1034 wxLogTrace(
traceAutoSave, wxS(
"[history] waiting for pending background save" ) );
1058 if( !wxDirExists( hist ) )
1060 wxLogNull suppressSysErrorPopups;
1069 git_repository* rawRepo =
nullptr;
1071 if( git_repository_open( &rawRepo, hist.mb_str().data() ) != 0 )
1073 if( git_repository_init( &rawRepo, hist.mb_str().data(), 0 ) != 0 )
1076 wxFileName ignoreFile( hist, wxS(
".gitignore" ) );
1077 if( !ignoreFile.FileExists() )
1079 wxFFile f( ignoreFile.GetFullPath(), wxT(
"w" ) );
1082 f.Write( wxS(
"# KiCad local history exclusions. Edit to add your own rules.\n"
1089 wxFileName readmeFile( hist, wxS(
"README.txt" ) );
1091 if( !readmeFile.FileExists() )
1093 wxFFile f( readmeFile.GetFullPath(), wxT(
"w" ) );
1097 f.Write( wxS(
"KiCad Local History Directory\n"
1098 "=============================\n\n"
1099 "This directory contains automatic snapshots of your project files.\n"
1100 "KiCad periodically saves copies of your work here, allowing you to\n"
1101 "recover from accidental changes or data loss.\n\n"
1102 "You can browse and restore previous versions through KiCad's\n"
1103 "File > Local History menu.\n\n"
1104 "To disable this feature:\n"
1105 " Preferences > Common > Project Backup > Enable automatic backups\n\n"
1106 "This directory can be safely deleted if you no longer need the\n"
1107 "history, but doing so will permanently remove all saved snapshots.\n" ) );
1113 git_repository_free( rawRepo );
1123 const wxString& aHistoryPath,
const wxString& aProjectPath,
1124 const std::vector<wxString>& aFiles,
const wxString& aTitle )
1126 std::vector<std::string> filesArrStr;
1128 for(
const wxString& file : aFiles )
1130 wxFileName src( file );
1133 if( src.GetFullPath().StartsWith( aProjectPath + wxFILE_SEP_PATH ) )
1134 relPath = src.GetFullPath().Mid( aProjectPath.length() + 1 );
1136 relPath = src.GetFullName();
1138 relPath.Replace(
"\\",
"/" );
1139 std::string relPathStr = relPath.ToStdString();
1141 unsigned int status = 0;
1142 int rc = git_status_file( &status, repo, relPathStr.data() );
1144 if( rc == 0 && status != 0 )
1146 wxLogTrace(
traceAutoSave, wxS(
"File %s status %d " ), relPath, status );
1147 filesArrStr.emplace_back( relPathStr );
1151 wxLogTrace(
traceAutoSave, wxS(
"File %s status error %d " ), relPath, rc );
1152 filesArrStr.emplace_back( relPathStr );
1156 std::vector<char*> cStrings( filesArrStr.size() );
1158 for(
size_t i = 0; i < filesArrStr.size(); i++ )
1159 cStrings[i] = filesArrStr[i].data();
1161 git_strarray filesArrGit;
1162 filesArrGit.count = filesArrStr.size();
1163 filesArrGit.strings = cStrings.data();
1165 if( filesArrStr.size() == 0 )
1171 int rc = git_index_add_all(
index, &filesArrGit, GIT_INDEX_ADD_DISABLE_PATHSPEC_MATCH | GIT_INDEX_ADD_FORCE, NULL,
1173 wxLogTrace(
traceAutoSave, wxS(
"Adding %zu files, rc %d" ), filesArrStr.size(), rc );
1179 if( git_index_write_tree( &tree_id,
index ) != 0 )
1182 git_tree* rawTree =
nullptr;
1183 git_tree_lookup( &rawTree, repo, &tree_id );
1184 std::unique_ptr<git_tree,
decltype( &git_tree_free )> tree( rawTree, &git_tree_free );
1186 git_signature* rawSig =
nullptr;
1188 std::unique_ptr<git_signature,
decltype( &git_signature_free )> sig( rawSig,
1189 &git_signature_free );
1191 git_commit* rawParent =
nullptr;
1195 if( git_reference_name_to_id( &parent_id, repo,
"HEAD" ) == 0 )
1197 git_commit_lookup( &rawParent, repo, &parent_id );
1201 std::unique_ptr<git_commit,
decltype( &git_commit_free )> parent( rawParent,
1204 git_tree* rawParentTree =
nullptr;
1207 git_commit_tree( &rawParentTree, parent.get() );
1209 std::unique_ptr<git_tree,
decltype( &git_tree_free )> parentTree( rawParentTree, &git_tree_free );
1211 git_diff* rawDiff =
nullptr;
1212 git_diff_tree_to_index( &rawDiff, repo, parentTree.get(),
index,
nullptr );
1213 std::unique_ptr<git_diff,
decltype( &git_diff_free )> diff( rawDiff, &git_diff_free );
1215 size_t numChangedFiles = git_diff_num_deltas( diff.get() );
1217 if( numChangedFiles == 0 )
1219 wxLogTrace(
traceAutoSave, wxS(
"No actual changes in tree, skipping commit" ) );
1225 if( !aTitle.IsEmpty() )
1226 msg << aTitle << wxS(
": " );
1228 msg << numChangedFiles << wxS(
" files changed" );
1230 for(
size_t i = 0; i < numChangedFiles; ++i )
1232 const git_diff_delta*
delta = git_diff_get_delta( diff.get(), i );
1233 git_patch* rawPatch =
nullptr;
1234 git_patch_from_diff( &rawPatch, diff.get(), i );
1235 std::unique_ptr<git_patch,
decltype( &git_patch_free )> patch( rawPatch,
1237 size_t context = 0, adds = 0, dels = 0;
1238 git_patch_line_stats( &context, &adds, &dels, patch.get() );
1239 size_t updated = std::min( adds, dels );
1242 msg << wxS(
"\n" ) << wxString::FromUTF8(
delta->new_file.path )
1243 << wxS(
" " ) << adds << wxS(
"/" ) << dels << wxS(
"/" ) << updated;
1247 git_commit* parentPtr = parent.get();
1248 const git_commit* constParentPtr = parentPtr;
1249 if( git_commit_create( &commit_id, repo,
"HEAD", sig.get(), sig.get(),
nullptr, msg.mb_str().data(), tree.get(),
1250 parents, parentPtr ? &constParentPtr :
nullptr )
1256 git_index_write(
index );
1265 const wxString& aTitle )
1273 wxLogTrace(
traceAutoSave, wxS(
"[history] commitSnapshotForProject failed to acquire lock: %s" ),
1290 wxString proj = wxFileName( aFiles[0] ).GetPath();
1304 wxString
name = aFile.GetFullName();
1306 if(
name == wxS(
"sym-lib-table" ) ||
name == wxS(
"fp-lib-table" ) )
1309 return aFile.GetExt().StartsWith( wxS(
"kicad_" ) );
1318 wxDir dir( aProjectPath );
1320 if( !dir.IsOpened() )
1331 std::function<void(
const wxString&,
bool )> collect =
1332 [&](
const wxString&
path,
bool topLevel )
1337 wxS(
"[history] collectProjectFiles: Skipping nested project at %s" ),
1348 bool cont = d.GetFirst( &
name );
1354 cont = d.GetNext( &
name );
1359 wxString fullPath = fn.GetFullPath();
1361 if( wxFileName::DirExists( fullPath ) )
1364 collect( fullPath,
false );
1366 else if( fn.FileExists() && fn.GetFullName() != wxS(
"fp-info-cache" ) &&
isKiCadProjectFile( fn ) )
1368 aFiles.push_back( fn.GetFullPath() );
1371 cont = d.GetNext( &
name );
1375 collect( aProjectPath,
true );
1384 std::vector<wxString> files;
1390 Init( aProjectPath );
1396 return wxDirExists(
historyPath( aProjectPath ) );
1408 if( git_reference_name_to_id( &head, repo,
"HEAD" ) != 0 )
1413 git_reference* ref =
nullptr;
1416 tagName.Printf( wxS(
"Save_%s_%d" ), aFileType, i++ );
1417 }
while( git_reference_lookup( &ref, repo, ( wxS(
"refs/tags/" ) + tagName ).mb_str().data() ) == 0 );
1420 git_object* head_obj =
nullptr;
1421 git_object_lookup( &head_obj, repo, &head, GIT_OBJECT_COMMIT );
1422 git_tag_create_lightweight( &tag_oid, repo, tagName.mb_str().data(), head_obj, 0 );
1423 git_object_free( head_obj );
1426 lastName.Printf( wxS(
"Last_Save_%s" ), aFileType );
1427 if( git_reference_lookup( &ref, repo, ( wxS(
"refs/tags/" ) + lastName ).mb_str().data() ) == 0 )
1429 git_reference_delete( ref );
1430 git_reference_free( ref );
1433 git_oid last_tag_oid;
1434 git_object* head_obj2 =
nullptr;
1435 git_object_lookup( &head_obj2, repo, &head, GIT_OBJECT_COMMIT );
1436 git_tag_create_lightweight( &last_tag_oid, repo, lastName.mb_str().data(), head_obj2, 0 );
1437 git_object_free( head_obj2 );
1455 wxLogTrace(
traceAutoSave, wxS(
"[history] TagSave: Failed to acquire lock for %s" ), aProjectPath );
1465 git_repository* repo =
nullptr;
1467 if( git_repository_open( &repo, hist.mb_str().data() ) != 0 )
1471 if( git_reference_name_to_id( &head_oid, repo,
"HEAD" ) != 0 )
1473 git_repository_free( repo );
1477 git_commit* head_commit =
nullptr;
1478 git_commit_lookup( &head_commit, repo, &head_oid );
1479 git_time_t head_time = git_commit_time( head_commit );
1482 git_tag_list_match( &tags,
"Last_Save_*", repo );
1483 git_time_t save_time = 0;
1485 for(
size_t i = 0; i < tags.count; ++i )
1487 git_reference* ref =
nullptr;
1488 if( git_reference_lookup( &ref, repo,
1489 ( wxS(
"refs/tags/" ) +
1490 wxString::FromUTF8( tags.strings[i] ) ).mb_str().data() ) == 0 )
1492 const git_oid* oid = git_reference_target( ref );
1493 git_commit* c =
nullptr;
1494 if( git_commit_lookup( &c, repo, oid ) == 0 )
1496 git_time_t t = git_commit_time( c );
1499 git_commit_free( c );
1501 git_reference_free( ref );
1505 git_strarray_free( &tags );
1506 git_commit_free( head_commit );
1507 git_repository_free( repo );
1511 if( save_time == 0 )
1514 return head_time > save_time;
1518 const wxString& aMessage )
1530 wxLogTrace(
traceAutoSave, wxS(
"[history] CommitDuplicateOfLastSave: Failed to acquire lock for %s" ), aProjectPath );
1539 wxString lastName; lastName.Printf( wxS(
"Last_Save_%s"), aFileType );
1540 git_reference* lastRef =
nullptr;
1541 if( git_reference_lookup( &lastRef, repo, ( wxS(
"refs/tags/") + lastName ).mb_str().data() ) != 0 )
1543 std::unique_ptr<git_reference,
decltype( &git_reference_free )> lastRefPtr( lastRef, &git_reference_free );
1545 const git_oid* lastOid = git_reference_target( lastRef );
1546 git_commit* lastCommit =
nullptr;
1547 if( git_commit_lookup( &lastCommit, repo, lastOid ) != 0 )
1549 std::unique_ptr<git_commit,
decltype( &git_commit_free )> lastCommitPtr( lastCommit, &git_commit_free );
1551 git_tree* lastTree =
nullptr;
1552 git_commit_tree( &lastTree, lastCommit );
1553 std::unique_ptr<git_tree,
decltype( &git_tree_free )> lastTreePtr( lastTree, &git_tree_free );
1557 git_commit* headCommit =
nullptr;
1559 const git_commit* parentArray[1];
1560 if( git_reference_name_to_id( &headOid, repo,
"HEAD" ) == 0 &&
1561 git_commit_lookup( &headCommit, repo, &headOid ) == 0 )
1563 parentArray[0] = headCommit;
1567 git_signature* sigRaw =
nullptr;
1569 std::unique_ptr<git_signature,
decltype( &git_signature_free )> sig( sigRaw, &git_signature_free );
1571 wxString msg = aMessage.IsEmpty() ? wxS(
"Discard unsaved ") + aFileType : aMessage;
1572 git_oid newCommitOid;
1573 int rc = git_commit_create( &newCommitOid, repo,
"HEAD", sig.get(), sig.get(),
nullptr,
1574 msg.mb_str().data(), lastTree, parents, parents ? parentArray :
nullptr );
1575 if( headCommit ) git_commit_free( headCommit );
1580 git_reference* existing =
nullptr;
1581 if( git_reference_lookup( &existing, repo, ( wxS(
"refs/tags/") + lastName ).mb_str().data() ) == 0 )
1583 git_reference_delete( existing );
1584 git_reference_free( existing );
1586 git_object* newCommitObj =
nullptr;
1587 if( git_object_lookup( &newCommitObj, repo, &newCommitOid, GIT_OBJECT_COMMIT ) == 0 )
1589 git_tag_create_lightweight( &newCommitOid, repo, lastName.mb_str().data(), newCommitObj, 0 );
1590 git_object_free( newCommitObj );
1599 if( !dir.IsOpened() )
1602 bool cont = dir.GetFirst( &
name );
1606 wxString fullPath = fn.GetFullPath();
1608 if( wxFileName::DirExists( fullPath ) )
1610 else if( fn.FileExists() )
1611 total += (size_t) fn.GetSize().GetValue();
1612 cont = dir.GetNext( &
name );
1619 std::vector<wxString> packs;
1620 wxDir packDir( aPackDir );
1622 if( !packDir.IsOpened() )
1626 bool cont = packDir.GetFirst( &
name, wxEmptyString, wxDIR_FILES );
1630 if(
name.EndsWith( wxS(
".pack" ) ) ||
name.EndsWith( wxS(
".idx" ) ) )
1631 packs.push_back( aPackDir + wxFileName::GetPathSeparator() +
name );
1633 cont = packDir.GetNext( &
name );
1636 std::sort( packs.begin(), packs.end() );
1642 const std::atomic<bool>* aCancel =
nullptr )
1644 struct PROGRESS_STATE
1647 const std::atomic<bool>*
cancel;
1648 } state{ aReporter, aCancel };
1652 if( aReporter || aCancel )
1654 git_packbuilder_set_callbacks(
1656 [](
int aStage, uint32_t aCurrent, uint32_t aTotal,
void* aPayload )
1658 auto* progress =
static_cast<PROGRESS_STATE*
>( aPayload );
1660 if( progress->reporter )
1663 progress->reporter->SetCurrentProgress( (
double) aCurrent / aTotal );
1665 progress->reporter->KeepRefreshing();
1668 return progress->cancel && progress->cancel->load() ? -1 : 0;
1673 std::string packDir = aPackDir.utf8_string();
1675 return git_packbuilder_write( aPb, aPackDir.IsEmpty() ?
nullptr : packDir.c_str(), 0,
nullptr,
nullptr ) == 0;
1686static void pruneLooseObjects(
const wxString& aObjectsPath,
const wxString& aPackIndex, time_t aWalkStart )
1688 git_odb* packOdb =
nullptr;
1689 git_odb_backend* backend =
nullptr;
1691 if( git_odb_new( &packOdb ) != 0 )
1694 if( git_odb_backend_one_pack( &backend, aPackIndex.utf8_string().c_str() ) != 0 )
1696 git_odb_free( packOdb );
1700 if( git_odb_add_backend( packOdb, backend, 1 ) != 0 )
1702 backend->free( backend );
1703 git_odb_free( packOdb );
1707 wxLogNull suppressRmdirErrors;
1708 wxDir objDir( aObjectsPath );
1710 bool contDir = objDir.IsOpened() && objDir.GetFirst( &fanout, wxEmptyString, wxDIR_DIRS );
1714 wxString fanoutPath = aObjectsPath + wxFileName::GetPathSeparator() + fanout;
1715 wxDir looseDir( fanoutPath );
1717 bool contFile = fanout.length() == 2 && looseDir.IsOpened()
1718 && looseDir.GetFirst( &rest, wxEmptyString, wxDIR_FILES );
1723 wxString loosePath = fanoutPath + wxFileName::GetPathSeparator() + rest;
1725 if( git_oid_fromstr( &oid, ( fanout + rest ).utf8_string().c_str() ) == 0
1726 && ( git_odb_exists( packOdb, &oid )
1729 wxRemoveFile( loosePath );
1732 contFile = looseDir.GetNext( &rest );
1735 if( fanout.length() == 2 && !wxDir( fanoutPath ).HasFiles() )
1736 wxRmdir( fanoutPath );
1738 contDir = objDir.GetNext( &fanout );
1741 git_odb_free( packOdb );
1748 const std::atomic<bool>* aCancel )
1750 git_packbuilder* pb =
nullptr;
1751 git_revwalk* walk =
nullptr;
1753 if( git_packbuilder_new( &pb, aRepo ) != 0 )
1754 return wxEmptyString;
1756 if( git_revwalk_new( &walk, aRepo ) != 0 )
1758 git_packbuilder_free( pb );
1759 return wxEmptyString;
1763 bool ok = git_revwalk_push_glob( walk,
"refs/*" ) == 0 && git_revwalk_push_head( walk ) == 0;
1767 while( ok && ( rc = git_revwalk_next( &oid, walk ) ) == 0 )
1768 ok = git_packbuilder_insert_commit( pb, &oid ) == 0;
1770 git_revwalk_free( walk );
1775 if( ok && rc == GIT_ITEROVER &&
writePack( pb, aPackDir, aReporter, aCancel ) && git_packbuilder_name( pb ) )
1776 stem = wxS(
"pack-" ) + wxString::FromUTF8( git_packbuilder_name( pb ) );
1778 git_packbuilder_free( pb );
1784static std::vector<wxString>
supersededBy(
const std::vector<wxString>& aPriorPacks,
const wxString& aNewStem )
1786 std::vector<wxString> superseded;
1788 for(
const wxString& pack : aPriorPacks )
1790 if( wxFileName( pack ).GetName() != aNewStem )
1791 superseded.push_back( pack );
1801 std::vector<wxString>* aSupersededPacks )
1803 wxString sep = wxFileName::GetPathSeparator();
1804 wxString objPath = wxString::FromUTF8( git_repository_path( aRepo ) ) + wxS(
"objects" );
1805 wxString packDir = objPath + sep + wxS(
"pack" );
1807 std::vector<wxString> priorPacks =
listPackFiles( packDir );
1808 time_t walkStart = wxDateTime::Now().GetTicks();
1809 wxString stem =
buildPack( aRepo, wxEmptyString, aReporter,
nullptr );
1811 if( stem.IsEmpty() )
1829 if( aMaxBytes == 0 )
1832 if( !wxDirExists( aHistoryPath ) )
1837 if( current <= aMaxBytes )
1844 wxLogTrace(
traceAutoSave, wxS(
"[history] EnforceSizeLimit: Failed to acquire lock for %s" ), aProjectPath );
1856 if( current <= aMaxBytes )
1860 aReporter->
Report(
_(
"Compacting local history..." ) );
1863 std::vector<wxString> supersededPacks;
1869 for(
const wxString& pack : supersededPacks )
1871 if( !wxRemoveFile( pack ) )
1872 wxLogTrace(
traceAutoSave, wxS(
"[history] could not remove %s" ), pack );
1878 const size_t target = aMaxBytes / 4 * 3;
1880 if( current <= target )
1889 git_revwalk* walk =
nullptr;
1890 git_revwalk_new( &walk, repo );
1891 git_revwalk_sorting( walk, GIT_SORT_TIME );
1892 git_revwalk_push_head( walk );
1893 std::vector<git_oid> commits;
1896 while( git_revwalk_next( &oid, walk ) == 0 )
1897 commits.push_back( oid );
1899 git_revwalk_free( walk );
1901 if( commits.empty() )
1905 std::set<git_oid, bool ( * )(
const git_oid&,
const git_oid& )> seenBlobs(
1906 [](
const git_oid& a,
const git_oid& b )
1908 return memcmp( &a, &b,
sizeof( git_oid ) ) < 0;
1912 wxString gitDir = wxString::FromUTF8( git_repository_path( repo ) );
1914 size_t copyBytes = current > gitBytes ? current - gitBytes : 0;
1915 size_t budget = target > copyBytes ? target - copyBytes : 0;
1917 size_t keptBytes = 0;
1918 std::vector<git_oid> keep;
1920 git_odb* odb =
nullptr;
1921 git_repository_odb( &odb, repo );
1923 std::function<size_t( git_tree* )> accountTree =
1924 [&]( git_tree* tree )
1927 size_t cnt = git_tree_entrycount( tree );
1929 for(
size_t i = 0; i < cnt; ++i )
1931 const git_tree_entry* entry = git_tree_entry_byindex( tree, i );
1933 if( git_tree_entry_type( entry ) == GIT_OBJECT_BLOB )
1935 const git_oid* bid = git_tree_entry_id( entry );
1937 if( seenBlobs.find( *bid ) == seenBlobs.end() )
1940 git_object_t type = GIT_OBJECT_ANY;
1942 if( odb && git_odb_read_header( &len, &type, odb, bid ) == 0 )
1945 seenBlobs.insert( *bid );
1948 else if( git_tree_entry_type( entry ) == GIT_OBJECT_TREE )
1950 git_tree* sub =
nullptr;
1952 if( git_tree_lookup( &sub, repo, git_tree_entry_id( entry ) ) == 0 )
1954 added += accountTree( sub );
1955 git_tree_free( sub );
1963 for(
const git_oid& cOid : commits )
1965 git_commit* c =
nullptr;
1967 if( git_commit_lookup( &c, repo, &cOid ) != 0 )
1970 git_tree* tree =
nullptr;
1971 git_commit_tree( &tree, c );
1972 size_t add = accountTree( tree );
1973 git_tree_free( tree );
1974 git_commit_free( c );
1976 if( keep.empty() || keptBytes + add <= budget )
1978 keep.push_back( cOid );
1986 keep.push_back( commits.front() );
1989 git_odb_free( odb );
1993 std::vector<std::pair<wxString, git_oid>> tagTargets;
1994 std::set<git_oid, bool ( * )(
const git_oid&,
const git_oid& )> taggedCommits(
1995 [](
const git_oid& a,
const git_oid& b )
1997 return memcmp( &a, &b,
sizeof( git_oid ) ) < 0;
1999 git_strarray tagList;
2001 if( git_tag_list( &tagList, repo ) == 0 )
2003 for(
size_t i = 0; i < tagList.count; ++i )
2005 wxString
name = wxString::FromUTF8( tagList.strings[i] );
2006 if(
name.StartsWith( wxS(
"Save_") ) ||
name.StartsWith( wxS(
"Last_Save_") ) )
2008 git_reference* tref =
nullptr;
2010 if( git_reference_lookup( &tref, repo, ( wxS(
"refs/tags/" ) +
name ).mb_str().data() ) == 0 )
2012 const git_oid* toid = git_reference_target( tref );
2016 tagTargets.emplace_back(
name, *toid );
2017 taggedCommits.insert( *toid );
2021 for(
const auto& k : keep )
2023 if( memcmp( &k, toid,
sizeof( git_oid ) ) == 0 )
2033 keep.push_back( *toid );
2034 wxLogTrace(
traceAutoSave, wxS(
"[history] EnforceSizeLimit: Preserving tagged commit %s" ),
2039 git_reference_free( tref );
2043 git_strarray_free( &tagList );
2047 wxFileName trimFn( aHistoryPath + wxS(
"_trim"), wxEmptyString );
2048 wxString trimPath = trimFn.GetPath();
2050 if( wxDirExists( trimPath ) )
2051 wxFileName::Rmdir( trimPath, wxPATH_RMDIR_RECURSIVE );
2053 wxMkdir( trimPath );
2054 git_repository* newRepo =
nullptr;
2056 if( git_repository_init( &newRepo, trimPath.mb_str().data(), 0 ) != 0 )
2059 wxString newPackDir = wxString::FromUTF8( git_repository_path( newRepo ) ) + wxS(
"objects" )
2060 + wxFileName::GetPathSeparator() + wxS(
"pack" );
2061 git_repository_free( newRepo );
2066 git_packbuilder* pb =
nullptr;
2068 if( git_packbuilder_new( &pb, repo ) != 0 )
2072 std::reverse( keep.begin(), keep.end() );
2073 git_commit* parent =
nullptr;
2074 struct MAP_ENTRY { git_oid orig; git_oid neu; };
2075 std::vector<MAP_ENTRY> commitMap;
2076 bool rewriteOk =
true;
2080 aReporter->
AdvancePhase(
_(
"Trimming local history..." ) );
2084 for(
size_t idx = 0; idx < keep.size() && rewriteOk; ++idx )
2092 const git_oid& co = keep[idx];
2093 git_commit* orig =
nullptr;
2095 if( git_commit_lookup( &orig, repo, &co ) != 0 )
2098 git_tree* tree =
nullptr;
2100 if( git_commit_tree( &tree, orig ) != 0 )
2102 git_commit_free( orig );
2107 const git_signature* origAuthor = git_commit_author( orig );
2108 const git_signature* origCommitter = git_commit_committer( orig );
2109 git_signature* sigAuthor =
nullptr;
2110 git_signature* sigCommitter =
nullptr;
2112 git_signature_new( &sigAuthor, origAuthor->name, origAuthor->email,
2113 origAuthor->when.time, origAuthor->when.offset );
2114 git_signature_new( &sigCommitter, origCommitter->name, origCommitter->email,
2115 origCommitter->when.time, origCommitter->when.offset );
2117 const git_commit* parents[1];
2118 int parentCount = 0;
2122 parents[0] = parent;
2126 git_oid newCommitOid;
2128 if( git_commit_create( &newCommitOid, repo,
nullptr, sigAuthor, sigCommitter,
nullptr,
2129 git_commit_message( orig ), tree, parentCount, parentCount ? parents :
nullptr )
2137 git_commit_free( parent );
2140 git_commit_lookup( &parent, repo, &newCommitOid );
2141 commitMap.emplace_back( co, newCommitOid );
2144 git_signature_free( sigAuthor );
2145 git_signature_free( sigCommitter );
2146 git_tree_free( tree );
2147 git_commit_free( orig );
2151 git_commit_free( parent );
2154 for(
auto it = commitMap.rbegin(); it != commitMap.rend() && rewriteOk; ++it )
2156 if( git_packbuilder_insert_commit( pb, &it->neu ) != 0 )
2160 if( !rewriteOk || commitMap.empty() )
2162 git_packbuilder_free( pb );
2167 aReporter->
AdvancePhase(
_(
"Compacting trimmed history..." ) );
2169 bool packed =
writePack( pb, newPackDir, aReporter );
2170 git_packbuilder_free( pb );
2172 if( !packed || git_repository_open( &newRepo, trimPath.mb_str().data() ) != 0 )
2175 const git_oid& newHeadOid = commitMap.back().neu;
2176 git_reference* headRef =
nullptr;
2177 bool headOk =
false;
2179 if( git_reference_lookup( &headRef, newRepo,
"HEAD" ) == 0 )
2181 const char* branch = git_reference_symbolic_target( headRef );
2182 git_reference* branchRef =
nullptr;
2184 if( branch && git_reference_create( &branchRef, newRepo, branch, &newHeadOid, 1,
nullptr ) == 0 )
2187 git_reference_free( branchRef );
2190 git_reference_free( headRef );
2195 git_repository_free( newRepo );
2199 git_commit* newHead =
nullptr;
2201 if( git_commit_lookup( &newHead, newRepo, &newHeadOid ) == 0 )
2203 git_tree* newHeadTree =
nullptr;
2204 git_index* newIndex =
nullptr;
2206 if( git_commit_tree( &newHeadTree, newHead ) == 0 && git_repository_index( &newIndex, newRepo ) == 0 )
2208 git_index_read_tree( newIndex, newHeadTree );
2209 git_index_write( newIndex );
2213 git_index_free( newIndex );
2216 git_tree_free( newHeadTree );
2218 git_commit_free( newHead );
2222 for(
const auto& tt : tagTargets )
2225 const git_oid* newOid =
nullptr;
2227 for(
const auto& m : commitMap )
2229 if( memcmp( &m.orig, &tt.second,
sizeof( git_oid ) ) == 0 )
2239 git_object* obj =
nullptr;
2241 if( git_object_lookup( &obj, newRepo, newOid, GIT_OBJECT_COMMIT ) == 0 )
2243 git_oid tag_oid; git_tag_create_lightweight( &tag_oid, newRepo, tt.first.mb_str().data(), obj, 0 );
2244 git_object_free( obj );
2249 git_repository_free( newRepo );
2252 for(
const wxString&
name : { wxString( wxS(
".gitignore" ) ), wxString( wxS(
"README.txt" ) ) } )
2254 wxFileName src( aHistoryPath,
name );
2256 if( src.FileExists() && !wxCopyFile( src.GetFullPath(), wxFileName( trimPath,
name ).GetFullPath(),
true ) )
2263 wxString backupOld = aHistoryPath + wxS(
"_old_" ) +
KIID().
AsString();
2265 if( wxFileExists( backupOld ) || wxDirExists( backupOld ) )
2268 if( !wxRenameFile( aHistoryPath, backupOld,
false ) )
2271 if( !wxRenameFile( trimPath, aHistoryPath,
false ) )
2273 if( !wxRenameFile( backupOld, aHistoryPath,
false ) )
2274 wxLogError(
_(
"Could not restore local history '%s'. The previous history is preserved at '%s'." ),
2275 aHistoryPath, backupOld );
2280 if( !wxFileName::Rmdir( backupOld, wxPATH_RMDIR_RECURSIVE ) )
2281 wxLogTrace(
traceAutoSave, wxS(
"[history] Trimmed history installed; previous history retained at %s" ),
2294 wxDir gitDir( aGitDir );
2296 std::vector<wxString> stale;
2297 bool cont = gitDir.IsOpened()
2303 if( wxFileModificationTime( aGitDir +
name ) < expiry )
2304 stale.push_back( aGitDir +
name );
2306 cont = gitDir.GetNext( &
name );
2309 for(
const wxString& dir : stale )
2310 wxFileName::Rmdir( dir, wxPATH_RMDIR_RECURSIVE );
2315static std::vector<std::string>
refTargets( git_repository* aRepo )
2317 std::vector<std::string> targets;
2318 git_reference_iterator* iter =
nullptr;
2319 git_reference* ref =
nullptr;
2322 if( git_reference_name_to_id( &oid, aRepo,
"HEAD" ) == 0 )
2323 targets.push_back( std::string(
"HEAD " ) + git_oid_tostr_s( &oid ) );
2325 if( git_reference_iterator_new( &iter, aRepo ) != 0 )
2328 while( git_reference_next( &ref, iter ) == 0 )
2330 git_reference* resolved =
nullptr;
2332 if( git_reference_resolve( &resolved, ref ) == 0 )
2334 targets.push_back( std::string( git_reference_name( ref ) ) +
" "
2335 + git_oid_tostr_s( git_reference_target( resolved ) ) );
2336 git_reference_free( resolved );
2339 git_reference_free( ref );
2342 git_reference_iterator_free( iter );
2343 std::sort( targets.begin(), targets.end() );
2351 wxDir objDir( aObjectsPath );
2353 bool cont = objDir.IsOpened() && objDir.GetFirst( &fanout, wxEmptyString, wxDIR_DIRS );
2357 if( fanout.length() == 2 )
2358 total +=
dirSizeRecursive( aObjectsPath + wxFileName::GetPathSeparator() + fanout );
2360 cont = objDir.GetNext( &fanout );
2377 [
this, projectPath = aProjectPath, hist =
historyPath( aProjectPath ), aLooseLimit]()
2379 git_repository* repo =
nullptr;
2381 if( git_repository_open( &repo, hist.mb_str().data() ) != 0 )
2384 wxString sep = wxFileName::GetPathSeparator();
2385 wxString gitDir = wxString::FromUTF8( git_repository_path( repo ) );
2386 wxString objPath = gitDir + wxS(
"objects" );
2387 wxString packDir = objPath + sep + wxS(
"pack" );
2388 wxString stageDir = gitDir +
STAGE_DIR_PREFIX + wxString::Format( wxS(
"%lu" ), wxGetProcessId() );
2392 git_repository_free( repo );
2398 std::vector<wxString> priorPacks =
listPackFiles( packDir );
2399 std::vector<std::string> priorRefs =
refTargets( repo );
2400 time_t walkStart = wxDateTime::Now().GetTicks();
2402 wxFileName::Rmdir( stageDir, wxPATH_RMDIR_RECURSIVE );
2403 wxMkdir( stageDir );
2406 git_repository_free( repo );
2419 wxString staged = stageDir + sep + stem;
2420 wxString target = packDir + sep + stem;
2424 bool packReady = wxFileExists( target + wxS(
".pack" ) )
2425 || wxRenameFile( staged + wxS(
".pack" ), target + wxS(
".pack" ),
false );
2426 bool installed = packReady
2427 && ( wxFileExists( target + wxS(
".idx" ) )
2428 || wxRenameFile( staged + wxS(
".idx" ), target + wxS(
".idx" ),
false ) );
2435 for(
const wxString& pack :
supersededBy( priorPacks, stem ) )
2437 if( !wxRemoveFile( pack ) )
2438 wxLogTrace(
traceAutoSave, wxS(
"[history] could not remove %s" ), pack );
2443 wxLogTrace(
traceAutoSave, wxS(
"[history] could not install %s" ), target );
2447 wxFileName::Rmdir( stageDir, wxPATH_RMDIR_RECURSIVE );
2456 [
this, previous = std::move(
m_compactFuture ), projectPath = aProjectPath,
2457 hist =
historyPath( aProjectPath ), aMaxBytes]()
mutable
2459 if( previous.valid() )
2472 &&
m_compactFuture.wait_for( std::chrono::seconds( 0 ) ) != std::future_status::ready;
2479 git_repository* repo =
nullptr;
2481 if( git_repository_open( &repo, hist.mb_str().data() ) != 0 )
2482 return wxEmptyString;
2485 if( git_reference_name_to_id( &head_oid, repo,
"HEAD" ) != 0 )
2487 git_repository_free( repo );
2488 return wxEmptyString;
2491 wxString hash = wxString::FromUTF8( git_oid_tostr_s( &head_oid ) );
2492 git_repository_free( repo );
2504bool checkForLockedFiles(
const wxString& aProjectPath, std::vector<wxString>& aLockedFiles )
2506 std::function<void(
const wxString& )> findLocks =
2507 [&](
const wxString& dirPath )
2509 wxDir dir( dirPath );
2510 if( !dir.IsOpened() )
2514 bool cont = dir.GetFirst( &filename );
2518 wxFileName fullPath( dirPath, filename );
2521 if( filename == wxS(
".history") || filename == wxS(
".git") )
2523 cont = dir.GetNext( &filename );
2527 if( fullPath.DirExists() )
2529 findLocks( fullPath.GetFullPath() );
2531 else if( fullPath.FileExists()
2538 baseName = baseName.BeforeLast(
'.' );
2539 wxFileName originalFile( dirPath, baseName );
2546 aLockedFiles.push_back( fullPath.GetFullPath() );
2550 cont = dir.GetNext( &filename );
2554 findLocks( aProjectPath );
2555 return aLockedFiles.empty();
2562bool extractCommitToTemp( git_repository* aRepo, git_tree* aTree,
const wxString& aTempPath )
2564 bool extractSuccess =
true;
2566 std::function<void( git_tree*,
const wxString& )> extractTree =
2567 [&]( git_tree* t,
const wxString& prefix )
2569 if( !extractSuccess )
2572 size_t cnt = git_tree_entrycount( t );
2573 for(
size_t i = 0; i < cnt; ++i )
2575 const git_tree_entry* entry = git_tree_entry_byindex( t, i );
2576 wxString
name = wxString::FromUTF8( git_tree_entry_name( entry ) );
2577 wxString fullPath = prefix.IsEmpty() ?
name : prefix + wxS(
"/") +
name;
2579 if( git_tree_entry_type( entry ) == GIT_OBJECT_TREE )
2581 wxFileName dirPath( aTempPath + wxFileName::GetPathSeparator() + fullPath, wxEmptyString );
2583 if( !wxFileName::Mkdir( dirPath.GetPath(), 0777, wxPATH_MKDIR_FULL ) )
2586 wxS(
"[history] extractCommitToTemp: Failed to create directory '%s'" ),
2587 dirPath.GetPath() );
2588 extractSuccess =
false;
2592 git_tree* sub =
nullptr;
2594 if( git_tree_lookup( &sub, aRepo, git_tree_entry_id( entry ) ) == 0 )
2596 extractTree( sub, fullPath );
2597 git_tree_free( sub );
2600 else if( git_tree_entry_type( entry ) == GIT_OBJECT_BLOB )
2602 git_blob* blob =
nullptr;
2604 if( git_blob_lookup( &blob, aRepo, git_tree_entry_id( entry ) ) == 0 )
2606 wxFileName dst( aTempPath + wxFileName::GetPathSeparator() + fullPath );
2608 wxFileName dstDir( dst );
2609 dstDir.SetFullName( wxEmptyString );
2610 wxFileName::Mkdir( dstDir.GetPath(), 0777, wxPATH_MKDIR_FULL );
2612 wxFFile f( dst.GetFullPath(), wxT(
"wb" ) );
2616 f.Write( git_blob_rawcontent( blob ), git_blob_rawsize( blob ) );
2622 wxS(
"[history] extractCommitToTemp: Failed to write '%s'" ),
2623 dst.GetFullPath() );
2624 extractSuccess =
false;
2625 git_blob_free( blob );
2629 git_blob_free( blob );
2635 extractTree( aTree, wxEmptyString );
2636 return extractSuccess;
2646void collectRelativeFiles(
const wxString& aRoot,
const wxString& aDir, std::vector<wxString>& aOut )
2650 if( !dir.IsOpened() )
2655 for(
bool cont = dir.GetFirst( &
name ); cont; cont = dir.GetNext( &
name ) )
2657 wxString full = aDir + wxFILE_SEP_PATH +
name;
2659 if( wxDirExists( full ) )
2661 collectRelativeFiles( aRoot, full, aOut );
2663 else if( wxFileExists( full ) )
2665 wxString rel = full.Mid( aRoot.length() + 1 );
2666 rel.Replace( wxS(
"\\" ), wxS(
"/" ) );
2667 aOut.push_back( rel );
2681bool overlaySnapshotFiles(
const wxString& aTempRestorePath,
const wxString& aProjectPath,
const wxString& aBackupPath )
2683 std::vector<wxString> relPaths;
2684 collectRelativeFiles( aTempRestorePath, aTempRestorePath, relPaths );
2686 for(
const wxString& rel : relPaths )
2688 wxFileName src( aTempRestorePath + wxFILE_SEP_PATH + rel );
2689 wxFileName dst( aProjectPath + wxFILE_SEP_PATH + rel );
2691 if( dst.FileExists() )
2693 wxFileName bak( aBackupPath + wxFILE_SEP_PATH + rel );
2695 if( !wxFileName::Mkdir( bak.GetPath(), 0777, wxPATH_MKDIR_FULL )
2696 || !wxCopyFile( dst.GetFullPath(), bak.GetFullPath(),
true ) )
2702 if( !wxFileName::Mkdir( dst.GetPath(), 0777, wxPATH_MKDIR_FULL )
2703 || !wxCopyFile( src.GetFullPath(), dst.GetFullPath(),
true ) )
2720 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Checking for open files in %s" ),
2723 std::vector<wxString> lockedFiles;
2724 if( !checkForLockedFiles( aProjectPath, lockedFiles ) )
2727 for(
const auto& f : lockedFiles )
2728 lockList += wxS(
"\n - ") + f;
2731 wxS(
"[history] RestoreCommit: Cannot restore - files are open:%s" ),
2737 wxString msg =
_(
"Cannot restore - the following files are open by another user:" );
2739 wxMessageBox( msg,
_(
"Restore Failed" ), wxOK | wxICON_WARNING, aParent );
2750 wxS(
"[history] RestoreCommit: Failed to acquire lock for %s" ),
2761 if( git_oid_fromstr( &oid, aHash.mb_str().data() ) != 0 )
2763 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Invalid hash %s" ), aHash );
2767 git_commit* commit =
nullptr;
2768 if( git_commit_lookup( &commit, repo, &oid ) != 0 )
2770 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Commit not found %s" ), aHash );
2774 git_tree* tree =
nullptr;
2775 git_commit_tree( &tree, commit );
2779 if( aConfirm && aParent )
2781 wxDateTime when( (time_t) git_commit_time( commit ) );
2784 wxString::Format(
_(
"Restore the project to the version from %s?" ),
2785 when.Format( wxS(
"%Y-%m-%d %H:%M:%S" ) ) ),
2786 _(
"Restore Version" ), wxYES_NO | wxNO_DEFAULT | wxICON_QUESTION );
2788 dlg.SetYesNoLabels(
_(
"Restore" ),
_(
"Cancel" ) );
2789 dlg.SetExtendedMessage(
_(
"Your current files are backed up first so you can undo the "
2790 "restore. Files that are not part of this version are left "
2793 if( dlg.ShowModal() != wxID_YES )
2795 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: User cancelled at confirm" ) );
2796 git_tree_free( tree );
2797 git_commit_free( commit );
2803 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Creating pre-restore backup" ) );
2805 std::vector<wxString> backupFiles;
2808 if( !backupFiles.empty() )
2812 backupFiles, wxS(
"Pre-restore backup" ) );
2817 wxS(
"[history] RestoreCommit: Failed to create pre-restore backup" ) );
2818 git_tree_free( tree );
2819 git_commit_free( commit );
2825 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Current state already matches HEAD; "
2826 "continuing without a new backup commit" ) );
2831 wxString tempRestorePath = aProjectPath + wxS(
"_restore_temp");
2833 if( wxDirExists( tempRestorePath ) )
2834 wxFileName::Rmdir( tempRestorePath, wxPATH_RMDIR_RECURSIVE );
2836 if( !wxFileName::Mkdir( tempRestorePath, 0777, wxPATH_MKDIR_FULL ) )
2839 wxS(
"[history] RestoreCommit: Failed to create temp directory %s" ),
2841 git_tree_free( tree );
2842 git_commit_free( commit );
2846 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Extracting to temp location %s" ),
2849 if( !extractCommitToTemp( repo, tree, tempRestorePath ) )
2851 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Extraction failed, cleaning up" ) );
2852 wxFileName::Rmdir( tempRestorePath, wxPATH_RMDIR_RECURSIVE );
2853 git_tree_free( tree );
2854 git_commit_free( commit );
2863 wxString backupPath =
2864 aProjectPath + wxS(
"_restore_backup_" )
2865 + wxDateTime::UNow().Format( wxS(
"%Y-%m-%dT%H-%M-%S-%l" ) );
2867 if( !overlaySnapshotFiles( tempRestorePath, aProjectPath, backupPath ) )
2869 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Overlay failed, rolling back from backup" ) );
2872 if( wxDirExists( backupPath ) )
2874 wxString discard = aProjectPath + wxS(
"_restore_discard" );
2875 overlaySnapshotFiles( backupPath, aProjectPath, discard );
2876 wxFileName::Rmdir( discard, wxPATH_RMDIR_RECURSIVE );
2877 wxFileName::Rmdir( backupPath, wxPATH_RMDIR_RECURSIVE );
2880 wxFileName::Rmdir( tempRestorePath, wxPATH_RMDIR_RECURSIVE );
2881 git_tree_free( tree );
2882 git_commit_free( commit );
2888 wxS(
"[history] RestoreCommit: Restore successful, backup retained at %s" ),
2890 wxFileName::Rmdir( tempRestorePath, wxPATH_RMDIR_RECURSIVE );
2893 std::vector<wxString> resultFiles;
2896 wxString::Format( wxS(
"Restored from %s" ), aHash ) );
2901 git_tree_free( tree );
2902 git_commit_free( commit );
2904 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Complete" ) );
2913 std::vector<LOCAL_HISTORY_SNAPSHOT_INFO> snapshots =
LoadSnapshots( aProjectPath );
2915 if( snapshots.empty() )
2924 if( !selectedHash.IsEmpty() )
2931 std::vector<LOCAL_HISTORY_SNAPSHOT_INFO> snapshots;
2934 git_repository* repo =
nullptr;
2936 if( git_repository_open( &repo, hist.mb_str().data() ) != 0 )
2939 git_revwalk* walk =
nullptr;
2940 if( git_revwalk_new( &walk, repo ) != 0 )
2942 git_repository_free( repo );
2946 git_revwalk_sorting( walk, GIT_SORT_TIME );
2947 git_revwalk_push_head( walk );
2951 while( git_revwalk_next( &oid, walk ) == 0 )
2953 git_commit* commit =
nullptr;
2955 if( git_commit_lookup( &commit, repo, &oid ) != 0 )
2959 info.hash = wxString::FromUTF8( git_oid_tostr_s( &oid ) );
2960 info.date = wxDateTime(
static_cast<time_t
>( git_commit_time( commit ) ) );
2961 info.message = wxString::FromUTF8( git_commit_message( commit ) );
2963 wxString firstLine =
info.message.BeforeFirst(
'\n' );
2965 long parsedCount = 0;
2967 firstLine.BeforeFirst(
':', &remainder );
2968 remainder.Trim(
true ).Trim(
false );
2970 if( remainder.EndsWith( wxS(
"files changed" ) ) )
2972 wxString countText = remainder.BeforeFirst(
' ' );
2974 if( countText.ToLong( &parsedCount ) )
2975 info.filesChanged =
static_cast<int>( parsedCount );
2978 info.summary = firstLine.BeforeFirst(
':' );
2981 info.message.BeforeFirst(
'\n', &rest );
2982 wxArrayString lines = wxSplit( rest,
'\n',
'\0' );
2984 for(
const wxString& line : lines )
2986 if( !line.IsEmpty() )
2987 info.changedFiles.Add( line );
2990 snapshots.push_back( std::move(
info ) );
2991 git_commit_free( commit );
2994 git_revwalk_free( walk );
2995 git_repository_free( repo );
3007 const wxString& aExtension )
3010 git_repository* repo =
nullptr;
3012 if( git_repository_open( &repo, hist.mb_str().data() ) != 0 )
3013 return wxEmptyString;
3016 git_commit* commit =
nullptr;
3017 git_tree* tree =
nullptr;
3019 if( git_oid_fromstr( &oid, aHash.mb_str().data() ) != 0 || git_commit_lookup( &commit, repo, &oid ) != 0 )
3021 git_repository_free( repo );
3022 return wxEmptyString;
3025 if( git_commit_tree( &tree, commit ) != 0 )
3027 git_commit_free( commit );
3028 git_repository_free( repo );
3029 return wxEmptyString;
3035 std::vector<wxString> entries;
3036 } ctx{ aExtension, {} };
3038 auto collect = [](
const char* aRoot,
const git_tree_entry* aEntry,
void* aPayload ) ->
int
3040 WALK_CTX* c =
static_cast<WALK_CTX*
>( aPayload );
3042 if( git_tree_entry_type( aEntry ) != GIT_OBJECT_BLOB )
3045 wxString
name = wxString::FromUTF8( git_tree_entry_name( aEntry ) );
3047 if( !
name.EndsWith( c->ext ) )
3050 wxString
path = wxString::FromUTF8( aRoot ) +
name;
3051 c->entries.push_back(
path + wxS(
":" )
3052 + wxString::FromUTF8( git_oid_tostr_s( git_tree_entry_id( aEntry ) ) ) );
3056 git_tree_walk( tree, GIT_TREEWALK_PRE, collect, &ctx );
3058 git_tree_free( tree );
3059 git_commit_free( commit );
3060 git_repository_free( repo );
3062 std::sort( ctx.entries.begin(), ctx.entries.end() );
3064 wxString fingerprint;
3066 for(
const wxString& entry : ctx.entries )
3067 fingerprint << entry << wxS(
"|" );
3074 const wxString& aDestDir,
const std::vector<wxString>& aExtensions )
3077 git_repository* repo =
nullptr;
3079 if( git_repository_open( &repo, hist.mb_str().data() ) != 0 )
3083 git_commit* commit =
nullptr;
3084 git_tree* tree =
nullptr;
3086 if( git_oid_fromstr( &oid, aHash.mb_str().data() ) != 0 || git_commit_lookup( &commit, repo, &oid ) != 0 )
3088 git_repository_free( repo );
3092 if( git_commit_tree( &tree, commit ) != 0 )
3094 git_commit_free( commit );
3095 git_repository_free( repo );
3101 git_repository* repo;
3105 } ctx{ repo, aDestDir, &aExtensions,
true };
3108 auto writeEntry = [](
const char* aRoot,
const git_tree_entry* aEntry,
void* aPayload ) ->
int
3110 WALK_CTX* c =
static_cast<WALK_CTX*
>( aPayload );
3112 if( git_tree_entry_type( aEntry ) != GIT_OBJECT_BLOB )
3115 wxString
name = wxString::FromUTF8( git_tree_entry_name( aEntry ) );
3117 if( !c->extensions->empty() )
3121 for(
const wxString& ext : *c->extensions )
3123 if(
name.EndsWith( ext ) )
3134 wxString rel = wxString::FromUTF8( aRoot ) +
name;
3135 wxFileName outFn( c->destDir + wxS(
"/" ) + rel );
3137 if( !wxFileName::Mkdir( outFn.GetPath(), wxS_DIR_DEFAULT, wxPATH_MKDIR_FULL ) )
3143 git_blob* blob =
nullptr;
3145 if( git_blob_lookup( &blob, c->repo, git_tree_entry_id( aEntry ) ) == 0 )
3147 const void* data = git_blob_rawcontent( blob );
3148 const size_t size =
static_cast<size_t>( git_blob_rawsize( blob ) );
3149 wxFFile out( outFn.GetFullPath(), wxS(
"wb" ) );
3151 if( !( data && out.IsOpened() && out.Write( data, size ) == size ) )
3154 git_blob_free( blob );
3164 git_tree_walk( tree, GIT_TREEWALK_PRE, writeEntry, &ctx );
3166 git_tree_free( tree );
3167 git_commit_free( commit );
3168 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
#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.