diff --git a/CHANGELOG.md b/CHANGELOG.md index 31ab8fa..b07afcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -81,7 +81,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 family migrates, bounding peak migration disk overhead to the family in flight. Unindexed legacy packs are quarantined by content under `.grasp/migration/unindexed-packs/`, unverifiable backups are retained with - a warning, and completed installations whose backups were already removed + a warning, backup-directory removals are fsynced before their journals are + removed, and completed installations whose backups were already removed manually start unchanged. - Stop treating server-side shallow repositories as valid family storage. Migration preserves a legacy `shallow` marker on the installed thin view diff --git a/docs/explanation/git-family-object-storage.md b/docs/explanation/git-family-object-storage.md index dcf92c7..e0f7fb9 100644 --- a/docs/explanation/git-family-object-storage.md +++ b/docs/explanation/git-family-object-storage.md @@ -291,6 +291,11 @@ before the next family is migrated. Peak migration overhead is therefore bounded by the family currently in flight except for the small set of families still awaiting repair. +Backup deletion is itself durable: every directory entry removed while +deleting and pruning a backup is fsynced before its journal is removed. A +power loss therefore leaves either a discoverable `retired` journal that +finishes deletion or no backup entry to rediscover. + Packs without an index are Git-invisible, so the superset proof cannot vouch for them; they are moved into content-addressed directories beneath `.grasp/migration/unindexed-packs/` before their backup is deleted. Repeated diff --git a/src/git/migration.rs b/src/git/migration.rs index a6477f4..970de59 100644 --- a/src/git/migration.rs +++ b/src/git/migration.rs @@ -979,6 +979,7 @@ fn remove_retired_backup(storage: &LocalGitStorage, backup: &Path) -> Result {} Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(false), @@ -987,12 +988,38 @@ fn remove_retired_backup(storage: &LocalGitStorage, backup: &Path) -> Result { + fsync_directory(parent)?; + directory = parent; + } + Err(error) + if matches!( + error.kind(), + std::io::ErrorKind::DirectoryNotEmpty | std::io::ErrorKind::NotFound + ) => + { + break; + } + Err(error) => { + warn!( + directory = %directory.display(), + %error, + "Could not prune empty migration backup directory" + ); + break; + } } - parent = directory.parent(); } Ok(true) }