feat: per-name purge filtering for snapshot purge #51
Reference in New Issue
Block a user
Delete Branch "feature/per-name-purge"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
PurgeSnapshotsnow applies--keep-latestretention per snapshot name instead of globally across all names.Problem
Previously,
--keep-latestwould keep only the single most recent snapshot across ALL snapshot names. For example, with snapshots:system_2024-01-15home_2024-01-14system_2024-01-13--keep-latestwould keep onlysystem_2024-01-15and delete the latesthomesnapshot too.Solution
Per-name retention:
--keep-latestnow groups snapshots by name and keeps the latest of each group. In the example above, bothsystem_2024-01-15andhome_2024-01-14would be kept.--nameflag: New flag to filter purge operations to a specific snapshot name.--name home --keep-latestonly purgeshomesnapshots, leaving allsystemsnapshots untouched.Changes
internal/vaultik/helpers.go: AddparseSnapshotName()to extract the snapshot name from a snapshot ID (hostname_name_timestampformat)internal/vaultik/snapshot.go: AddSnapshotPurgeOptionsstruct withNamefield, addPurgeSnapshotsWithOptions()method, modify--keep-latestlogic to group by nameinternal/cli/purge.goandinternal/cli/snapshot.go: Add--nameflag to both purge CLI surfacesREADME.md: Update CLI documentationTests
helpers_test.go: Unit tests forparseSnapshotName()andparseSnapshotTimestamp()purge_per_name_test.go: Integration tests covering:--namefilter with--keep-latest--namefilter with--older-thanBackward Compatibility
The existing
PurgeSnapshots(keepLatest, olderThan, force)signature is preserved as a wrapper around the newPurgeSnapshotsWithOptions(). The--pruneflag insnapshot createcontinues to work unchanged.docker build .passes (lint, fmt-check, all tests).closes #9
Review: PASS
parseSnapshotName — edge cases ✅
Correctly handles all formats:
hostname_name_timestamp→ extracts namehostname_name_with_underscores_timestamp→ joins middle parts correctlyhostname_timestamp(legacy) → returns empty stringConsistent with the pre-existing
parseSnapshotTimestampassumption that the last_-separated part is always the RFC3339 timestamp.Per-name retention logic ✅
The
--keep-latestpath inPurgeSnapshotsWithOptionssorts snapshots newest-first, then iterates once with alatestByNamemap — first occurrence of each name is kept, subsequent ones are marked for deletion. Correct and efficient.--name filter ✅
The name filter correctly narrows the working set before the sort and retention/age logic. Non-matching snapshots are never in the candidate list and are never touched. Works correctly with both
--keep-latestand--older-than.Backward compatibility ✅
PurgeSnapshotswrapper preserves the old signature and delegates toPurgeSnapshotsWithOptionswithName: "". The--pruneflag insnapshot createalso benefits from the corrected per-name semantics (it no longer deletes the latest of every name except one).Tests ✅
12 test functions covering:
parseSnapshotName(6 subtests) andparseSnapshotTimestamp(4 subtests)--name+--keep-latest,--name+--older-than, empty input, no-match filter, legacy (no-name) snapshots, mixed named/legacy, three different namesNo weakened assertions. No modified linter config. No Makefile/Dockerfile changes.
README ✅
Accurately updated:
--keep-latestdescription now says "per snapshot name",--nameflag documented in both synopsis and description section.docker build ✅
docker build .passes — lint, fmt-check, all tests, compilation.Minor note (non-blocking)
TestSnapshotPurgeOptionsinhelpers_test.gotests that Go struct field assignment works. It adds no value as a test of application logic. Not harmful, but could be removed in a future cleanup.c76a357570toe3e1f1c2e2Rebased
feature/per-name-purgeontomain(e3e1f1c).Conflict resolved:
internal/vaultik/snapshot.go— main had refactored purge logic into acollectSnapshotsToPurgehelper, while the PR had inline per-name logic. Kept the PR's per-name grouping inPurgeSnapshotsWithOptions()and removed the now-unusedcollectSnapshotsToPurgehelper (it only did global retention, not per-name).Additional fix:
confirmAndExecutePurgehad anopts.Forcereference instead of itsforceparameter — corrected.docker build .passes: lint (0 issues), all tests, compilation.Review (post-rebase): PASS
Per-name retention logic ✅
PurgeSnapshotsWithOptionscorrectly implements per-name--keep-latest:latestByNamemap tracks the first (newest) snapshot seen for each namecollectSnapshotsToPurgewhich kept only one snapshot globally--namefilter ✅Name filtering is applied before sort and retention logic. Non-matching snapshots are excluded from the working set entirely and are never touched. Works correctly with both
--keep-latestand--older-than.confirmAndExecutePurgebug fix ✅confirmAndExecutePurgetakesforce boolparameter and uses it directly (!forceat line 637). The caller at line 622 passesopts.Forceas the argument. No staleopts.Forcereference inside the method body.collectSnapshotsToPurgeremoval ✅The old helper (which only did global retention —
snapshots[1:]) is completely removed.grep -rn collectSnapshotsToPurgereturns zero results across the codebase. The per-name logic is inlined inPurgeSnapshotsWithOptionswhere it belongs.Backward compatibility ✅
PurgeSnapshotswrapper preserves the old(keepLatest, olderThan, force)signature and delegates toPurgeSnapshotsWithOptionswithName: "". The--pruneflag insnapshot createcallsPurgeSnapshots(true, "", true)and benefits from per-name semantics.CLI integration ✅
Both
purge.goandsnapshot.go(thesnapshot purgesubcommand) register the--nameflag and bind it toopts.Name. Both callPurgeSnapshotsWithOptions(opts)directly.Tests ✅
purge_per_name_test.go— 9 test functions covering:--name+--keep-latest(system snapshots untouched)--name+--older-than(system snapshots untouched)helpers_test.go—parseSnapshotName(6 subtests) andparseSnapshotTimestamp(4 subtests).All tests use real DB (in-memory SQLite) + mock storage with proper cleanup. No weakened assertions. No Makefile/Dockerfile/linter config changes.
README ✅
Synopsis and description updated:
--keep-latestsays "per snapshot name",--nameflag documented.docker build .✅Passes: lint (0 issues), fmt-check, all tests, compilation.
Minor note (non-blocking, same as previous review)
TestSnapshotPurgeOptionsinhelpers_test.goonly tests Go struct field assignment — adds no value as a test of application logic. Could be removed in a future cleanup.View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.