78 if( !
Pgm().GetCommonSettings()->m_Backup.enabled )
80 wxLogTrace(
traceAutoSave, wxS(
"Autosave disabled, returning" ) );
84 wxLogTrace(
traceAutoSave, wxS(
"[history] RunRegisteredSaversAndCommit start project='%s' title='%s' savers=%zu"),
85 aProjectPath, aTitle,
s_savers.size() );
89 wxLogTrace(
traceAutoSave, wxS(
"[history] no savers registered; skipping") );
93 std::vector<wxString> files;
98 size_t before = files.size();
99 saver( aProjectPath, files );
100 wxLogTrace(
traceAutoSave, wxS(
"[history] saver #%zu added %zu files (total=%zu)"),
101 idx++, files.size() - before, files.size() );
105 wxString projectDir = aProjectPath;
106 if( !projectDir.EndsWith( wxFileName::GetPathSeparator() ) )
107 projectDir += wxFileName::GetPathSeparator();
109 auto it = std::remove_if( files.begin(), files.end(),
110 [&projectDir](
const wxString& file )
112 if( !file.StartsWith( projectDir ) )
114 wxLogTrace( traceAutoSave, wxS(
"[history] filtered out file outside project: %s"), file );
119 files.erase( it, files.end() );
123 wxLogTrace(
traceAutoSave, wxS(
"[history] saver set produced no files; skipping") );
130 if( !lock.IsLocked() )
132 wxLogTrace(
traceAutoSave, wxS(
"[history] failed to acquire lock: %s"), lock.GetLockError() );
136 git_repository* repo = lock.GetRepository();
137 git_index* index = lock.GetIndex();
141 for(
const wxString& file : files )
143 wxFileName src( file );
145 if( !src.FileExists() )
147 wxLogTrace(
traceAutoSave, wxS(
"[history] skip missing '%s'"), file );
152 if( src.GetFullPath().StartsWith( hist + wxFILE_SEP_PATH ) )
154 std::string relHist = src.GetFullPath().ToStdString().substr( hist.length() + 1 );
155 git_index_add_bypath( index, relHist.c_str() );
156 wxLogTrace(
traceAutoSave, wxS(
"[history] staged pre-mirrored '%s'"), file );
161 wxString proj = wxFileName( aProjectPath ).GetFullPath();
163 if( src.GetFullPath().StartsWith( proj + wxFILE_SEP_PATH ) )
164 relStr = src.GetFullPath().Mid( proj.length() + 1 );
166 relStr = src.GetFullName();
168 wxFileName dst( hist + wxFILE_SEP_PATH + relStr );
169 wxFileName dstDir( dst );
170 dstDir.SetFullName( wxEmptyString );
171 wxFileName::Mkdir( dstDir.GetPath(), 0777, wxPATH_MKDIR_FULL );
172 wxCopyFile( src.GetFullPath(), dst.GetFullPath(),
true );
173 std::string rel = dst.GetFullPath().ToStdString().substr( hist.length() + 1 );
174 git_index_add_bypath( index, rel.c_str() );
175 wxLogTrace(
traceAutoSave, wxS(
"[history] staged '%s' as '%s'"), file, wxString::FromUTF8( rel ) );
180 git_commit* head_commit =
nullptr;
181 git_tree* head_tree =
nullptr;
183 bool headExists = ( git_reference_name_to_id( &head_oid, repo,
"HEAD" ) == 0 )
184 && ( git_commit_lookup( &head_commit, repo, &head_oid ) == 0 )
185 && ( git_commit_tree( &head_tree, head_commit ) == 0 );
187 git_tree* rawIndexTree =
nullptr;
188 git_oid index_tree_oid;
190 if( git_index_write_tree( &index_tree_oid, index ) != 0 )
192 if( head_tree ) git_tree_free( head_tree );
193 if( head_commit ) git_commit_free( head_commit );
194 wxLogTrace(
traceAutoSave, wxS(
"[history] failed to write index tree" ) );
198 git_tree_lookup( &rawIndexTree, repo, &index_tree_oid );
199 std::unique_ptr<git_tree,
decltype( &git_tree_free )> indexTree( rawIndexTree, &git_tree_free );
201 bool hasChanges =
true;
205 git_diff* diff =
nullptr;
207 if( git_diff_tree_to_tree( &diff, repo, head_tree, indexTree.get(),
nullptr ) == 0 )
209 hasChanges = git_diff_num_deltas( diff ) > 0;
210 wxLogTrace(
traceAutoSave, wxS(
"[history] diff deltas=%u"), (
unsigned) git_diff_num_deltas( diff ) );
211 git_diff_free( diff );
215 if( head_tree ) git_tree_free( head_tree );
216 if( head_commit ) git_commit_free( head_commit );
220 wxLogTrace(
traceAutoSave, wxS(
"[history] no changes detected; no commit") );
224 git_signature* rawSig =
nullptr;
226 std::unique_ptr<git_signature,
decltype( &git_signature_free )> sig( rawSig, &git_signature_free );
228 git_commit* parent =
nullptr;
232 if( git_reference_name_to_id( &parent_id, repo,
"HEAD" ) == 0 )
234 if( git_commit_lookup( &parent, repo, &parent_id ) == 0 )
238 wxString msg = aTitle.IsEmpty() ? wxString(
"Autosave" ) : aTitle;
240 const git_commit* constParent = parent;
242 int rc = git_commit_create( &commit_id, repo,
"HEAD", sig.get(), sig.get(),
nullptr,
243 msg.mb_str().data(), indexTree.get(), parents,
244 parents ? &constParent :
nullptr );
247 wxLogTrace(
traceAutoSave, wxS(
"[history] commit created %s (%s files=%zu)"),
248 wxString::FromUTF8( git_oid_tostr_s( &commit_id ) ), msg, files.size() );
250 wxLogTrace(
traceAutoSave, wxS(
"[history] commit failed rc=%d"), rc );
252 if( parent ) git_commit_free( parent );
254 git_index_write( index );
303 if( aFiles.empty() || !
Pgm().GetCommonSettings()->m_Backup.enabled )
306 wxString proj = wxFileName( aFiles[0] ).GetPath();
314 wxLogTrace(
traceAutoSave, wxS(
"[history] CommitSnapshot failed to acquire lock: %s"),
322 for(
const wxString& file : aFiles )
324 wxFileName src( file );
327 if( src.GetFullPath().StartsWith( proj + wxFILE_SEP_PATH ) )
328 relStr = src.GetFullPath().Mid( proj.length() + 1 );
330 relStr = src.GetFullName();
332 wxFileName dst( hist + wxFILE_SEP_PATH + relStr );
335 wxFileName dstDir( dst );
336 dstDir.SetFullName( wxEmptyString );
337 wxFileName::Mkdir( dstDir.GetPath(), 0777, wxPATH_MKDIR_FULL );
340 wxCopyFile( src.GetFullPath(), dst.GetFullPath(),
true );
343 std::string rel = dst.GetFullPath().ToStdString().substr( hist.length() + 1 );
344 git_index_add_bypath( index, rel.c_str() );
348 if( git_index_write_tree( &tree_id, index ) != 0 )
351 git_tree* rawTree =
nullptr;
352 git_tree_lookup( &rawTree, repo, &tree_id );
353 std::unique_ptr<git_tree,
decltype( &git_tree_free )> tree( rawTree, &git_tree_free );
355 git_signature* rawSig =
nullptr;
357 std::unique_ptr<git_signature,
decltype( &git_signature_free )> sig( rawSig,
358 &git_signature_free );
360 git_commit* rawParent =
nullptr;
364 if( git_reference_name_to_id( &parent_id, repo,
"HEAD" ) == 0 )
366 git_commit_lookup( &rawParent, repo, &parent_id );
370 std::unique_ptr<git_commit,
decltype( &git_commit_free )> parent( rawParent,
373 git_tree* rawParentTree =
nullptr;
376 git_commit_tree( &rawParentTree, parent.get() );
378 std::unique_ptr<git_tree,
decltype( &git_tree_free )> parentTree( rawParentTree, &git_tree_free );
380 git_diff* rawDiff =
nullptr;
381 git_diff_tree_to_index( &rawDiff, repo, parentTree.get(), index,
nullptr );
382 std::unique_ptr<git_diff,
decltype( &git_diff_free )> diff( rawDiff, &git_diff_free );
386 if( !aTitle.IsEmpty() )
387 msg << aTitle << wxS(
": " );
389 msg << aFiles.size() << wxS(
" files changed" );
391 for(
size_t i = 0; i < git_diff_num_deltas( diff.get() ); ++i )
393 const git_diff_delta*
delta = git_diff_get_delta( diff.get(), i );
394 git_patch* rawPatch =
nullptr;
395 git_patch_from_diff( &rawPatch, diff.get(), i );
396 std::unique_ptr<git_patch,
decltype( &git_patch_free )> patch( rawPatch,
398 size_t context = 0, adds = 0, dels = 0;
399 git_patch_line_stats( &context, &adds, &dels, patch.get() );
400 size_t updated = std::min( adds, dels );
403 msg << wxS(
"\n" ) << wxString::FromUTF8(
delta->new_file.path )
404 << wxS(
" " ) << adds << wxS(
"/" ) << dels << wxS(
"/" ) << updated;
408 git_commit* parentPtr = parent.get();
409 const git_commit* constParentPtr = parentPtr;
410 git_commit_create( &commit_id, repo,
"HEAD", sig.get(), sig.get(),
nullptr,
411 msg.mb_str().data(), tree.get(), parents,
412 parentPtr ? &constParentPtr :
nullptr );
413 git_index_write( index );
580 const wxString& aMessage )
586 wxLogTrace(
traceAutoSave, wxS(
"[history] CommitDuplicateOfLastSave: Failed to acquire lock for %s" ), aProjectPath );
595 wxString lastName; lastName.Printf( wxS(
"Last_Save_%s"), aFileType );
596 git_reference* lastRef =
nullptr;
597 if( git_reference_lookup( &lastRef, repo, ( wxS(
"refs/tags/") + lastName ).mb_str().data() ) != 0 )
599 std::unique_ptr<git_reference,
decltype( &git_reference_free )> lastRefPtr( lastRef, &git_reference_free );
601 const git_oid* lastOid = git_reference_target( lastRef );
602 git_commit* lastCommit =
nullptr;
603 if( git_commit_lookup( &lastCommit, repo, lastOid ) != 0 )
605 std::unique_ptr<git_commit,
decltype( &git_commit_free )> lastCommitPtr( lastCommit, &git_commit_free );
607 git_tree* lastTree =
nullptr;
608 git_commit_tree( &lastTree, lastCommit );
609 std::unique_ptr<git_tree,
decltype( &git_tree_free )> lastTreePtr( lastTree, &git_tree_free );
613 git_commit* headCommit =
nullptr;
615 const git_commit* parentArray[1];
616 if( git_reference_name_to_id( &headOid, repo,
"HEAD" ) == 0 &&
617 git_commit_lookup( &headCommit, repo, &headOid ) == 0 )
619 parentArray[0] = headCommit;
623 git_signature* sigRaw =
nullptr;
625 std::unique_ptr<git_signature,
decltype( &git_signature_free )> sig( sigRaw, &git_signature_free );
627 wxString msg = aMessage.IsEmpty() ? wxS(
"Discard unsaved ") + aFileType : aMessage;
628 git_oid newCommitOid;
629 int rc = git_commit_create( &newCommitOid, repo,
"HEAD", sig.get(), sig.get(),
nullptr,
630 msg.mb_str().data(), lastTree, parents, parents ? parentArray :
nullptr );
631 if( headCommit ) git_commit_free( headCommit );
636 git_reference* existing =
nullptr;
637 if( git_reference_lookup( &existing, repo, ( wxS(
"refs/tags/") + lastName ).mb_str().data() ) == 0 )
639 git_reference_delete( existing );
640 git_reference_free( existing );
642 git_object* newCommitObj =
nullptr;
643 if( git_object_lookup( &newCommitObj, repo, &newCommitOid, GIT_OBJECT_COMMIT ) == 0 )
645 git_tag_create_lightweight( &newCommitOid, repo, lastName.mb_str().data(), newCommitObj, 0 );
646 git_object_free( newCommitObj );
678 if( !wxDirExists( hist ) )
683 if( current <= aMaxBytes )
690 wxLogTrace(
traceAutoSave, wxS(
"[history] EnforceSizeLimit: Failed to acquire lock for %s" ), aProjectPath );
700 git_revwalk* walk =
nullptr;
701 git_revwalk_new( &walk, repo );
702 git_revwalk_sorting( walk, GIT_SORT_TIME );
703 git_revwalk_push_head( walk );
704 std::vector<git_oid> commits;
707 while( git_revwalk_next( &oid, walk ) == 0 )
708 commits.push_back( oid );
710 git_revwalk_free( walk );
712 if( commits.empty() )
716 std::set<git_oid, bool ( * )(
const git_oid&,
const git_oid& )> seenBlobs(
717 [](
const git_oid& a,
const git_oid& b )
719 return memcmp( &a, &b,
sizeof( git_oid ) ) < 0;
722 size_t keptBytes = 0;
723 std::vector<git_oid> keep;
725 std::function<size_t( git_tree* )> accountTree = [&]( git_tree* tree )
728 size_t cnt = git_tree_entrycount( tree );
729 for(
size_t i = 0; i < cnt; ++i )
731 const git_tree_entry* entry = git_tree_entry_byindex( tree, i );
733 if( git_tree_entry_type( entry ) == GIT_OBJECT_BLOB )
735 const git_oid* bid = git_tree_entry_id( entry );
737 if( seenBlobs.find( *bid ) == seenBlobs.end() )
739 git_blob* blob =
nullptr;
741 if( git_blob_lookup( &blob, repo, bid ) == 0 )
743 added += git_blob_rawsize( blob );
744 git_blob_free( blob );
747 seenBlobs.insert( *bid );
750 else if( git_tree_entry_type( entry ) == GIT_OBJECT_TREE )
752 git_tree* sub =
nullptr;
754 if( git_tree_lookup( &sub, repo, git_tree_entry_id( entry ) ) == 0 )
756 added += accountTree( sub );
757 git_tree_free( sub );
764 for(
const git_oid& cOid : commits )
766 git_commit* c =
nullptr;
768 if( git_commit_lookup( &c, repo, &cOid ) != 0 )
771 git_tree* tree =
nullptr;
772 git_commit_tree( &tree, c );
773 size_t add = accountTree( tree );
774 git_tree_free( tree );
775 git_commit_free( c );
777 if( keep.empty() || keptBytes + add <= aMaxBytes )
779 keep.push_back( cOid );
787 keep.push_back( commits.front() );
791 std::vector<std::pair<wxString, git_oid>> tagTargets;
792 std::set<git_oid, bool ( * )(
const git_oid&,
const git_oid& )> taggedCommits(
793 [](
const git_oid& a,
const git_oid& b )
795 return memcmp( &a, &b,
sizeof( git_oid ) ) < 0;
797 git_strarray tagList;
799 if( git_tag_list( &tagList, repo ) == 0 )
801 for(
size_t i = 0; i < tagList.count; ++i )
803 wxString
name = wxString::FromUTF8( tagList.strings[i] );
804 if(
name.StartsWith( wxS(
"Save_") ) ||
name.StartsWith( wxS(
"Last_Save_") ) )
806 git_reference* tref =
nullptr;
808 if( git_reference_lookup( &tref, repo, ( wxS(
"refs/tags/" ) +
name ).mb_str().data() ) == 0 )
810 const git_oid* toid = git_reference_target( tref );
814 tagTargets.emplace_back(
name, *toid );
815 taggedCommits.insert( *toid );
819 for(
const auto& k : keep )
821 if( memcmp( &k, toid,
sizeof( git_oid ) ) == 0 )
831 keep.push_back( *toid );
832 wxLogTrace(
traceAutoSave, wxS(
"[history] EnforceSizeLimit: Preserving tagged commit %s" ),
837 git_reference_free( tref );
841 git_strarray_free( &tagList );
845 wxFileName trimFn( hist + wxS(
"_trim"), wxEmptyString );
846 wxString trimPath = trimFn.GetPath();
848 if( wxDirExists( trimPath ) )
849 wxFileName::Rmdir( trimPath, wxPATH_RMDIR_RECURSIVE );
852 git_repository* newRepo =
nullptr;
854 if( git_repository_init( &newRepo, trimPath.mb_str().data(), 0 ) != 0 )
858 std::reverse( keep.begin(), keep.end() );
859 git_commit* parent =
nullptr;
860 struct MAP_ENTRY { git_oid orig; git_oid neu; };
861 std::vector<MAP_ENTRY> commitMap;
863 for(
const git_oid& co : keep )
865 git_commit* orig =
nullptr;
867 if( git_commit_lookup( &orig, repo, &co ) != 0 )
870 git_tree* tree =
nullptr;
871 git_commit_tree( &tree, orig );
875 wxArrayString toDelete;
878 bool cont = d.GetFirst( &nm );
881 if( nm != wxS(
".git") )
883 cont = d.GetNext( &nm );
886 for(
auto& del : toDelete )
888 wxFileName f( trimPath, del );
890 wxFileName::Rmdir( f.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
891 else if( f.FileExists() )
892 wxRemoveFile( f.GetFullPath() );
896 std::function<void(git_tree*,
const wxString&)> writeTree = [&]( git_tree* t,
const wxString& base )
898 size_t ecnt = git_tree_entrycount( t );
899 for(
size_t i = 0; i < ecnt; ++i )
901 const git_tree_entry* e = git_tree_entry_byindex( t, i );
902 wxString
name = wxString::FromUTF8( git_tree_entry_name( e ) );
904 if( git_tree_entry_type( e ) == GIT_OBJECT_TREE )
906 wxFileName dir( base,
name );
907 wxMkdir( dir.GetFullPath() );
908 git_tree* sub =
nullptr;
910 if( git_tree_lookup( &sub, repo, git_tree_entry_id( e ) ) == 0 )
912 writeTree( sub, dir.GetFullPath() );
913 git_tree_free( sub );
916 else if( git_tree_entry_type( e ) == GIT_OBJECT_BLOB )
918 git_blob* blob =
nullptr;
920 if( git_blob_lookup( &blob, repo, git_tree_entry_id( e ) ) == 0 )
922 wxFileName file( base,
name );
923 wxFFile f( file.GetFullPath(), wxT(
"wb") );
927 f.Write( (
const char*) git_blob_rawcontent( blob ), git_blob_rawsize( blob ) );
930 git_blob_free( blob );
936 writeTree( tree, trimPath );
938 git_index* newIndex =
nullptr;
939 git_repository_index( &newIndex, newRepo );
940 git_index_add_all( newIndex,
nullptr, 0,
nullptr,
nullptr );
941 git_index_write( newIndex );
943 git_index_write_tree( &newTreeOid, newIndex );
944 git_tree* newTree =
nullptr;
945 git_tree_lookup( &newTree, newRepo, &newTreeOid );
948 const git_signature* origAuthor = git_commit_author( orig );
949 const git_signature* origCommitter = git_commit_committer( orig );
950 git_signature* sigAuthor =
nullptr;
951 git_signature* sigCommitter =
nullptr;
953 git_signature_new( &sigAuthor, origAuthor->name, origAuthor->email,
954 origAuthor->when.time, origAuthor->when.offset );
955 git_signature_new( &sigCommitter, origCommitter->name, origCommitter->email,
956 origCommitter->when.time, origCommitter->when.offset );
958 const git_commit* parents[1];
967 git_oid newCommitOid;
968 git_commit_create( &newCommitOid, newRepo,
"HEAD", sigAuthor, sigCommitter,
nullptr, git_commit_message( orig ),
969 newTree, parentCount, parentCount ? parents :
nullptr );
971 git_commit_free( parent );
973 git_commit_lookup( &parent, newRepo, &newCommitOid );
975 commitMap.emplace_back( co, newCommitOid );
977 git_signature_free( sigAuthor );
978 git_signature_free( sigCommitter );
979 git_tree_free( newTree );
980 git_index_free( newIndex );
981 git_tree_free( tree );
982 git_commit_free( orig );
986 git_commit_free( parent );
989 for(
const auto& tt : tagTargets )
992 const git_oid* newOid =
nullptr;
994 for(
const auto& m : commitMap )
996 if( memcmp( &m.orig, &tt.second,
sizeof( git_oid ) ) == 0 )
1006 git_object* obj =
nullptr;
1008 if( git_object_lookup( &obj, newRepo, newOid, GIT_OBJECT_COMMIT ) == 0 )
1010 git_oid tag_oid; git_tag_create_lightweight( &tag_oid, newRepo, tt.first.mb_str().data(), obj, 0 );
1011 git_object_free( obj );
1018 git_repository_free( newRepo );
1021 wxString backupOld = hist + wxS(
"_old");
1022 wxRenameFile( hist, backupOld );
1023 wxRenameFile( trimPath, hist );
1024 wxFileName::Rmdir( backupOld, wxPATH_RMDIR_RECURSIVE );
1051 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Checking for open files in %s" ), aProjectPath );
1053 std::vector<wxString> lockedFiles;
1054 std::function<void(
const wxString& )> findLocks = [&](
const wxString& dirPath )
1056 wxDir dir( dirPath );
1057 if( !dir.IsOpened() )
1061 bool cont = dir.GetFirst( &filename );
1065 wxFileName fullPath( dirPath, filename );
1068 if( filename == wxS(
".history") || filename == wxS(
".git") )
1070 cont = dir.GetNext( &filename );
1074 if( fullPath.DirExists() )
1076 findLocks( fullPath.GetFullPath() );
1078 else if( fullPath.FileExists() && filename.EndsWith( wxS(
".lock") ) )
1081 LOCKFILE testLock( fullPath.GetFullPath().BeforeLast(
'.' ) );
1084 lockedFiles.push_back( fullPath.GetFullPath() );
1088 cont = dir.GetNext( &filename );
1092 findLocks( aProjectPath );
1094 if( !lockedFiles.empty() )
1097 for(
const auto& f : lockedFiles )
1098 lockList += wxS(
"\n - ") + f;
1100 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Cannot restore - files are open:%s" ), lockList );
1109 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Failed to acquire lock for %s" ), aProjectPath );
1119 if( git_oid_fromstr( &oid, aHash.mb_str().data() ) != 0 )
1121 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Invalid hash %s" ), aHash );
1125 git_commit* commit =
nullptr;
1126 if( git_commit_lookup( &commit, repo, &oid ) != 0 )
1128 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Commit not found %s" ), aHash );
1132 git_tree* tree =
nullptr;
1133 git_commit_tree( &tree, commit );
1136 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Creating pre-restore backup" ) );
1137 wxString backupHash =
GetHeadHash( aProjectPath );
1141 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Failed to create pre-restore backup" ) );
1142 git_tree_free( tree );
1143 git_commit_free( commit );
1148 wxString tempRestorePath = aProjectPath + wxS(
"_restore_temp");
1150 if( wxDirExists( tempRestorePath ) )
1151 wxFileName::Rmdir( tempRestorePath, wxPATH_RMDIR_RECURSIVE );
1153 if( !wxFileName::Mkdir( tempRestorePath, 0777, wxPATH_MKDIR_FULL ) )
1155 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Failed to create temp directory %s" ), tempRestorePath );
1156 git_tree_free( tree );
1157 git_commit_free( commit );
1161 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Extracting to temp location %s" ), tempRestorePath );
1164 bool extractSuccess =
true;
1165 std::function<void( git_tree*,
const wxString& )> extractTree = [&]( git_tree* t,
const wxString& prefix )
1167 if( !extractSuccess )
1170 size_t cnt = git_tree_entrycount( t );
1171 for(
size_t i = 0; i < cnt; ++i )
1173 const git_tree_entry* entry = git_tree_entry_byindex( t, i );
1174 wxString
name = wxString::FromUTF8( git_tree_entry_name( entry ) );
1175 wxString fullPath = prefix.IsEmpty() ?
name : prefix + wxS(
"/") +
name;
1177 if( git_tree_entry_type( entry ) == GIT_OBJECT_TREE )
1179 wxFileName dirPath( tempRestorePath + wxFileName::GetPathSeparator() + fullPath, wxEmptyString );
1180 if( !wxFileName::Mkdir( dirPath.GetPath(), 0777, wxPATH_MKDIR_FULL ) )
1182 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Failed to create directory '%s'" ),
1183 dirPath.GetPath() );
1184 extractSuccess =
false;
1188 git_tree* sub =
nullptr;
1189 if( git_tree_lookup( &sub, repo, git_tree_entry_id( entry ) ) == 0 )
1191 extractTree( sub, fullPath );
1192 git_tree_free( sub );
1195 else if( git_tree_entry_type( entry ) == GIT_OBJECT_BLOB )
1197 git_blob* blob =
nullptr;
1198 if( git_blob_lookup( &blob, repo, git_tree_entry_id( entry ) ) == 0 )
1200 wxFileName dst( tempRestorePath + wxFileName::GetPathSeparator() + fullPath );
1202 wxFileName dstDir( dst );
1203 dstDir.SetFullName( wxEmptyString );
1204 wxFileName::Mkdir( dstDir.GetPath(), 0777, wxPATH_MKDIR_FULL );
1206 wxFFile f( dst.GetFullPath(), wxT(
"wb" ) );
1209 f.Write( git_blob_rawcontent( blob ), git_blob_rawsize( blob ) );
1214 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Failed to write '%s'" ),
1215 dst.GetFullPath() );
1216 extractSuccess =
false;
1217 git_blob_free( blob );
1221 git_blob_free( blob );
1227 extractTree( tree, wxEmptyString );
1229 if( !extractSuccess )
1231 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Extraction failed, cleaning up" ) );
1232 wxFileName::Rmdir( tempRestorePath, wxPATH_RMDIR_RECURSIVE );
1233 git_tree_free( tree );
1234 git_commit_free( commit );
1239 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Performing atomic swap" ) );
1241 wxString backupPath = aProjectPath + wxS(
"_restore_backup");
1244 if( wxDirExists( backupPath ) )
1246 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Removing old backup %s" ), backupPath );
1247 wxFileName::Rmdir( backupPath, wxPATH_RMDIR_RECURSIVE );
1251 std::set<wxString> backedUpFiles;
1254 bool moveSuccess =
true;
1255 wxDir currentDir( aProjectPath );
1257 if( currentDir.IsOpened() )
1260 bool cont = currentDir.GetFirst( &filename );
1264 if( filename != wxS(
".history") && filename != wxS(
".git") )
1266 wxFileName source( aProjectPath, filename );
1267 wxFileName dest( backupPath, filename );
1270 if( !wxDirExists( backupPath ) )
1272 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Creating backup directory %s" ), backupPath );
1273 wxFileName::Mkdir( backupPath, 0777, wxPATH_MKDIR_FULL );
1276 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Backing up '%s' to '%s'" ),
1277 source.GetFullPath(), dest.GetFullPath() );
1278 if( !wxRenameFile( source.GetFullPath(), dest.GetFullPath() ) )
1280 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Failed to backup '%s'" ),
1281 source.GetFullPath() );
1282 moveSuccess =
false;
1286 backedUpFiles.insert( filename );
1288 cont = currentDir.GetNext( &filename );
1293 std::set<wxString> restoredFiles;
1298 wxDir tempDir( tempRestorePath );
1300 if( tempDir.IsOpened() )
1303 bool cont = tempDir.GetFirst( &filename );
1307 wxFileName source( tempRestorePath, filename );
1308 wxFileName dest( aProjectPath, filename );
1310 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Restoring '%s' to '%s'" ),
1311 source.GetFullPath(), dest.GetFullPath() );
1313 if( !wxRenameFile( source.GetFullPath(), dest.GetFullPath() ) )
1315 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Failed to move '%s', rolling back" ),
1316 source.GetFullPath() );
1317 moveSuccess =
false;
1321 restoredFiles.insert( filename );
1322 cont = tempDir.GetNext( &filename );
1330 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: ROLLING BACK due to failure" ) );
1334 for(
const wxString& filename : restoredFiles )
1336 wxFileName toRemove( aProjectPath, filename );
1337 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Rollback removing '%s'" ),
1338 toRemove.GetFullPath() );
1340 if( toRemove.DirExists() )
1342 wxFileName::Rmdir( toRemove.GetFullPath(), wxPATH_RMDIR_RECURSIVE );
1343 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Rollback removed directory '%s'" ),
1344 toRemove.GetFullPath() );
1346 else if( toRemove.FileExists() )
1348 wxRemoveFile( toRemove.GetFullPath() );
1349 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Rollback removed file '%s'" ),
1350 toRemove.GetFullPath() );
1355 if( wxDirExists( backupPath ) )
1357 for(
const wxString& filename : backedUpFiles )
1359 wxFileName source( backupPath, filename );
1360 wxFileName dest( aProjectPath, filename );
1362 if( source.Exists() )
1364 wxRenameFile( source.GetFullPath(), dest.GetFullPath() );
1365 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Rollback restored '%s'" ),
1366 dest.GetFullPath() );
1372 wxFileName::Rmdir( tempRestorePath, wxPATH_RMDIR_RECURSIVE );
1373 wxFileName::Rmdir( backupPath, wxPATH_RMDIR_RECURSIVE );
1375 git_tree_free( tree );
1376 git_commit_free( commit );
1381 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Restore successful, cleaning up" ) );
1382 wxFileName::Rmdir( tempRestorePath, wxPATH_RMDIR_RECURSIVE );
1383 wxFileName::Rmdir( backupPath, wxPATH_RMDIR_RECURSIVE );
1386 git_time_t t = git_commit_time( commit );
1387 wxDateTime dt( (time_t) t );
1388 git_signature* sig =
nullptr;
1390 git_commit* parent =
nullptr;
1393 if( git_reference_name_to_id( &parent_id, repo,
"HEAD" ) == 0 )
1394 git_commit_lookup( &parent, repo, &parent_id );
1397 msg.Printf( wxS(
"Restored from %s %s" ), aHash, dt.FormatISOCombined().c_str() );
1400 const git_commit* constParent = parent;
1401 git_commit_create( &new_id, repo,
"HEAD", sig, sig,
nullptr,
1402 msg.mb_str().data(), tree, parent ? 1 : 0,
1403 parent ? &constParent :
nullptr );
1406 git_commit_free( parent );
1407 git_signature_free( sig );
1408 git_tree_free( tree );
1409 git_commit_free( commit );
1411 wxLogTrace(
traceAutoSave, wxS(
"[history] RestoreCommit: Complete" ) );
1421 git_repository* repo =
nullptr;
1423 if( git_repository_open( &repo, hist.mb_str().data() ) != 0 )
1426 git_revwalk* walk =
nullptr;
1427 git_revwalk_new( &walk, repo );
1428 git_revwalk_push_head( walk );
1430 std::vector<wxString> choices;
1431 std::vector<wxString> hashes;
1434 while( git_revwalk_next( &oid, walk ) == 0 )
1436 git_commit* commit =
nullptr;
1437 git_commit_lookup( &commit, repo, &oid );
1439 git_time_t t = git_commit_time( commit );
1440 wxDateTime dt( (time_t) t );
1443 line.Printf( wxS(
"%s %s" ), dt.FormatISOCombined().c_str(),
1444 wxString::FromUTF8( git_commit_summary( commit ) ) );
1445 choices.push_back( line );
1446 hashes.push_back( wxString::FromUTF8( git_oid_tostr_s( &oid ) ) );
1447 git_commit_free( commit );
1450 git_revwalk_free( walk );
1451 git_repository_free( repo );
1453 if( choices.empty() )
1456 int index = wxGetSingleChoiceIndex(
_(
"Select snapshot" ),
_(
"Restore" ),
1457 (
int) choices.size(), &choices[0], aParent );
1459 if( index != wxNOT_FOUND )