PurgeSnapshots now applies --keep-latest retention per snapshot name instead of globally across all names.
Problem
Previously, --keep-latest would keep only the single most recent snapshot across ALL snapshot names. For example, with snapshots:
system_2024-01-15
home_2024-01-14
system_2024-01-13
--keep-latest would keep only system_2024-01-15 and delete the latest home snapshot too.
Solution
Per-name retention: --keep-latest now groups snapshots by name and keeps the latest of each group. In the example above, both system_2024-01-15 and home_2024-01-14 would be kept.
--name flag: New flag to filter purge operations to a specific snapshot name. --name home --keep-latest only purges home snapshots, leaving all system snapshots untouched.
Changes
internal/vaultik/helpers.go: Add parseSnapshotName() to extract the snapshot name from a snapshot ID (hostname_name_timestamp format)
internal/vaultik/snapshot.go: Add SnapshotPurgeOptions struct with Name field, add PurgeSnapshotsWithOptions() method, modify --keep-latest logic to group by name
internal/cli/purge.go and internal/cli/snapshot.go: Add --name flag to both purge CLI surfaces
README.md: Update CLI documentation
Tests
helpers_test.go: Unit tests for parseSnapshotName() and parseSnapshotTimestamp()
The existing PurgeSnapshots(keepLatest, olderThan, force) signature is preserved as a wrapper around the new PurgeSnapshotsWithOptions(). The --prune flag in snapshot create continues to work unchanged.
docker build . passes (lint, fmt-check, all tests).
## Summary
`PurgeSnapshots` now applies `--keep-latest` retention per snapshot name instead of globally across all names.
### Problem
Previously, `--keep-latest` would keep only the single most recent snapshot across ALL snapshot names. For example, with snapshots:
- `system_2024-01-15`
- `home_2024-01-14`
- `system_2024-01-13`
`--keep-latest` would keep only `system_2024-01-15` and delete the latest `home` snapshot too.
### Solution
1. **Per-name retention**: `--keep-latest` now groups snapshots by name and keeps the latest of each group. In the example above, both `system_2024-01-15` and `home_2024-01-14` would be kept.
2. **`--name` flag**: New flag to filter purge operations to a specific snapshot name. `--name home --keep-latest` only purges `home` snapshots, leaving all `system` snapshots untouched.
### Changes
- `internal/vaultik/helpers.go`: Add `parseSnapshotName()` to extract the snapshot name from a snapshot ID (`hostname_name_timestamp` format)
- `internal/vaultik/snapshot.go`: Add `SnapshotPurgeOptions` struct with `Name` field, add `PurgeSnapshotsWithOptions()` method, modify `--keep-latest` logic to group by name
- `internal/cli/purge.go` and `internal/cli/snapshot.go`: Add `--name` flag to both purge CLI surfaces
- `README.md`: Update CLI documentation
### Tests
- `helpers_test.go`: Unit tests for `parseSnapshotName()` and `parseSnapshotTimestamp()`
- `purge_per_name_test.go`: Integration tests covering:
- Per-name retention with multiple names
- Single-name retention
- `--name` filter with `--keep-latest`
- `--name` filter with `--older-than`
- No-match name filter (all snapshots retained)
- Legacy snapshots without name component
- Mixed named and legacy snapshots
- Three different snapshot names
### Backward Compatibility
The existing `PurgeSnapshots(keepLatest, olderThan, force)` signature is preserved as a wrapper around the new `PurgeSnapshotsWithOptions()`. The `--prune` flag in `snapshot create` continues to work unchanged.
`docker build .` passes (lint, fmt-check, all tests).
closes [#9](https://git.eeqj.de/sneak/vaultik/issues/9)
PurgeSnapshots now applies --keep-latest retention per snapshot name
instead of globally across all names. Previously, --keep-latest would
keep only the single most recent snapshot regardless of name, deleting
the latest snapshots of other names (e.g. keeping only the newest
'system' snapshot while deleting all 'home' snapshots).
Changes:
- Add parseSnapshotName() to extract snapshot name from snapshot IDs
- Add SnapshotPurgeOptions struct with Name field for --name filtering
- Add PurgeSnapshotsWithOptions() method accepting full options
- Modify --keep-latest to group snapshots by name and keep the latest
per group (backward compatible: PurgeSnapshots() wrapper preserved)
- Add --name flag to both 'vaultik purge' and 'vaultik snapshot purge'
CLI commands to filter purge operations to a specific snapshot name
- Add comprehensive tests for per-name purge behavior including:
multi-name retention, name filtering, legacy/mixed format support,
older-than with name filter, and edge cases
closes#9
Consistent with the pre-existing parseSnapshotTimestamp assumption that the last _-separated part is always the RFC3339 timestamp.
Per-name retention logic ✅
The --keep-latest path in PurgeSnapshotsWithOptions sorts snapshots newest-first, then iterates once with a latestByName map — 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-latest and --older-than.
Backward compatibility ✅
PurgeSnapshots wrapper preserves the old signature and delegates to PurgeSnapshotsWithOptions with Name: "". The --prune flag in snapshot create also benefits from the corrected per-name semantics (it no longer deletes the latest of every name except one).
Tests ✅
12 test functions covering:
Unit tests for parseSnapshotName (6 subtests) and parseSnapshotTimestamp (4 subtests)
Integration tests for purge: per-name retention with multiple names, single name, --name + --keep-latest, --name + --older-than, empty input, no-match filter, legacy (no-name) snapshots, mixed named/legacy, three different names
All use real DB (in-memory SQLite) + mock storage with proper cleanup
No weakened assertions. No modified linter config. No Makefile/Dockerfile changes.
README ✅
Accurately updated: --keep-latest description now says "per snapshot name", --name flag documented in both synopsis and description section.
docker build ✅
docker build . passes — lint, fmt-check, all tests, compilation.
Minor note (non-blocking)
TestSnapshotPurgeOptions in helpers_test.go tests 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.
## Review: PASS
### parseSnapshotName — edge cases ✅
Correctly handles all formats:
- `hostname_name_timestamp` → extracts name
- `hostname_name_with_underscores_timestamp` → joins middle parts correctly
- `hostname_timestamp` (legacy) → returns empty string
- Empty string / single part → returns empty string
Consistent with the pre-existing `parseSnapshotTimestamp` assumption that the last `_`-separated part is always the RFC3339 timestamp.
### Per-name retention logic ✅
The `--keep-latest` path in [`PurgeSnapshotsWithOptions`](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/vaultik/snapshot.go#L558-L570) sorts snapshots newest-first, then iterates once with a `latestByName` map — first occurrence of each name is kept, subsequent ones are marked for deletion. Correct and efficient.
### --name filter ✅
The [name filter](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/vaultik/snapshot.go#L538-L546) 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-latest` and `--older-than`.
### Backward compatibility ✅
[`PurgeSnapshots`](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/vaultik/snapshot.go#L497-L503) wrapper preserves the old signature and delegates to `PurgeSnapshotsWithOptions` with `Name: ""`. The `--prune` flag in `snapshot create` also benefits from the corrected per-name semantics (it no longer deletes the latest of every name except one).
### Tests ✅
12 test functions covering:
- Unit tests for `parseSnapshotName` (6 subtests) and `parseSnapshotTimestamp` (4 subtests)
- Integration tests for purge: per-name retention with multiple names, single name, `--name` + `--keep-latest`, `--name` + `--older-than`, empty input, no-match filter, legacy (no-name) snapshots, mixed named/legacy, three different names
- All use real DB (in-memory SQLite) + mock storage with proper cleanup
No weakened assertions. No modified linter config. No Makefile/Dockerfile changes.
### README ✅
Accurately updated: `--keep-latest` description now says "per snapshot name", `--name` flag documented in both synopsis and description section.
### docker build ✅
`docker build .` passes — lint, fmt-check, all tests, compilation.
### Minor note (non-blocking)
`TestSnapshotPurgeOptions` in [`helpers_test.go`](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/vaultik/helpers_test.go#L92-L119) tests 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.
Rebased feature/per-name-purge onto main (e3e1f1c).
Conflict resolved:internal/vaultik/snapshot.go — main had refactored purge logic into a collectSnapshotsToPurge helper, while the PR had inline per-name logic. Kept the PR's per-name grouping in PurgeSnapshotsWithOptions() and removed the now-unused collectSnapshotsToPurge helper (it only did global retention, not per-name).
Additional fix:confirmAndExecutePurge had an opts.Force reference instead of its force parameter — corrected.
docker build . passes: lint (0 issues), all tests, compilation.
Rebased `feature/per-name-purge` onto `main` (`e3e1f1c`).
**Conflict resolved:** `internal/vaultik/snapshot.go` — main had refactored purge logic into a `collectSnapshotsToPurge` helper, while the PR had inline per-name logic. Kept the PR's per-name grouping in `PurgeSnapshotsWithOptions()` and removed the now-unused `collectSnapshotsToPurge` helper (it only did global retention, not per-name).
**Additional fix:** `confirmAndExecutePurge` had an `opts.Force` reference instead of its `force` parameter — corrected.
`docker build .` passes: lint (0 issues), all tests, compilation.
A latestByName map tracks the first (newest) snapshot seen for each name
Subsequent snapshots with the same name are marked for deletion
This replaces the old collectSnapshotsToPurge which kept only one snapshot globally
--name filter ✅
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-latest and --older-than.
confirmAndExecutePurge bug fix ✅
confirmAndExecutePurge takes force bool parameter and uses it directly (!force at line 637). The caller at line 622 passes opts.Force as the argument. No stale opts.Force reference inside the method body.
collectSnapshotsToPurge removal ✅
The old helper (which only did global retention — snapshots[1:]) is completely removed. grep -rn collectSnapshotsToPurge returns zero results across the codebase. The per-name logic is inlined in PurgeSnapshotsWithOptions where it belongs.
Backward compatibility ✅
PurgeSnapshots wrapper preserves the old (keepLatest, olderThan, force) signature and delegates to PurgeSnapshotsWithOptions with Name: "". The --prune flag in snapshot create calls PurgeSnapshots(true, "", true) and benefits from per-name semantics.
CLI integration ✅
Both purge.go and snapshot.go (the snapshot purge subcommand) register the --name flag and bind it to opts.Name. Both call PurgeSnapshotsWithOptions(opts) directly.
Legacy (no-name) snapshots grouped under empty string
Mixed named + legacy snapshots
helpers_test.go — parseSnapshotName (6 subtests) and parseSnapshotTimestamp (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-latest says "per snapshot name", --name flag documented.
docker build .✅
Passes: lint (0 issues), fmt-check, all tests, compilation.
Minor note (non-blocking, same as previous review)
TestSnapshotPurgeOptions in helpers_test.go only tests Go struct field assignment — adds no value as a test of application logic. Could be removed in a future cleanup.
## Review (post-rebase): PASS
### Per-name retention logic ✅
[`PurgeSnapshotsWithOptions`](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/vaultik/snapshot.go#L558-L621) correctly implements per-name `--keep-latest`:
- Snapshots are sorted newest-first
- A `latestByName` map tracks the first (newest) snapshot seen for each name
- Subsequent snapshots with the same name are marked for deletion
- This replaces the old `collectSnapshotsToPurge` which kept only one snapshot globally
### `--name` filter ✅
[Name filtering](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/vaultik/snapshot.go#L569-L577) 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-latest` and `--older-than`.
### `confirmAndExecutePurge` bug fix ✅
[`confirmAndExecutePurge`](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/vaultik/snapshot.go#L626) takes `force bool` parameter and uses it directly (`!force` at line 637). The caller at line 622 passes `opts.Force` as the argument. No stale `opts.Force` reference inside the method body.
### `collectSnapshotsToPurge` removal ✅
The old helper (which only did global retention — `snapshots[1:]`) is completely removed. `grep -rn collectSnapshotsToPurge` returns zero results across the codebase. The per-name logic is inlined in `PurgeSnapshotsWithOptions` where it belongs.
### Backward compatibility ✅
[`PurgeSnapshots`](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/vaultik/snapshot.go#L540-L546) wrapper preserves the old `(keepLatest, olderThan, force)` signature and delegates to `PurgeSnapshotsWithOptions` with `Name: ""`. The `--prune` flag in `snapshot create` calls `PurgeSnapshots(true, "", true)` and benefits from per-name semantics.
### CLI integration ✅
Both [`purge.go`](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/cli/purge.go) and [`snapshot.go`](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/cli/snapshot.go) (the `snapshot purge` subcommand) register the `--name` flag and bind it to `opts.Name`. Both call `PurgeSnapshotsWithOptions(opts)` directly.
### Tests ✅
[`purge_per_name_test.go`](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/vaultik/purge_per_name_test.go) — 9 test functions covering:
- Per-name retention with 2 names, single name, 3 names
- `--name` + `--keep-latest` (system snapshots untouched)
- `--name` + `--older-than` (system snapshots untouched)
- No-match name filter (all retained)
- Empty input
- Legacy (no-name) snapshots grouped under empty string
- Mixed named + legacy snapshots
[`helpers_test.go`](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/vaultik/helpers_test.go) — `parseSnapshotName` (6 subtests) and `parseSnapshotTimestamp` (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-latest` says "per snapshot name", `--name` flag documented.
### `docker build .` ✅
Passes: lint (0 issues), fmt-check, all tests, compilation.
### Minor note (non-blocking, same as previous review)
`TestSnapshotPurgeOptions` in [`helpers_test.go`](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/vaultik/helpers_test.go#L92-L119) only tests Go struct field assignment — adds no value as a test of application logic. Could be removed in a future cleanup.
- Remove PurgeSnapshots wrapper; callers use PurgeSnapshotsWithOptions directly
- Update snapshot create --prune to call PurgeSnapshotsWithOptions
- Remove TestSnapshotPurgeOptions (tested Go struct assignment, no value)
- Remove legacy no-name snapshot tests (TestPurgeKeepLatest_LegacyNoNameSnapshots,
TestPurgeKeepLatest_MixedNamedAndLegacy)
- Remove legacy format test cases from TestParseSnapshotName
- Update parseSnapshotName doc to not mention legacy format
## Rework: removed backward compat and legacy snapshot support
Per sneak's feedback — pre-1.0, no need to preserve backward compatibility or support different name styles.
### Changes
1. **Removed `PurgeSnapshots` wrapper** — all callers now use `PurgeSnapshotsWithOptions` directly. Updated `snapshot create --prune` call site.
2. **Removed legacy/no-name snapshot handling:**
- Removed `TestPurgeKeepLatest_LegacyNoNameSnapshots` (tested hostname_timestamp format grouping)
- Removed `TestPurgeKeepLatest_MixedNamedAndLegacy` (tested mixed named + legacy grouping)
- Removed legacy format test cases from `TestParseSnapshotName` ("no snapshot name", "single part", "empty string")
- Updated `parseSnapshotName` doc to not reference legacy format
3. **Removed `TestSnapshotPurgeOptions`** — only tested Go struct field assignment, no application logic value.
### What's preserved
- `parseSnapshotName` function itself (still needed for name extraction)
- All meaningful purge tests: per-name retention, single name, name filter, no-match filter, older-than with name filter, three names, empty input
- CLI `--name` flag on both `purge` and `snapshot purge`
**Net: −105 lines, +8 lines.** `docker build .` passes (lint 0 issues, all tests, compilation).
The old PurgeSnapshots(keepLatest, olderThan, force) function is completely gone — zero references in the codebase. The old PurgeOptions struct in purge.go is also removed. collectSnapshotsToPurge helper is gone too.
parseSnapshotName doc no longer references legacy format
Per-name retention logic ✅
PurgeSnapshotsWithOptions: snapshots sorted newest-first, latestByName map tracks first occurrence per name, subsequent same-name snapshots marked for deletion. Correct and clean.
--name filter ✅
Name filter applied before sort and retention/age logic. Non-matching snapshots excluded from the working set entirely. Works correctly with both --keep-latest and --older-than.
Tests ✅
7 purge tests in purge_per_name_test.go: per-name (2 names), single name, name filter + keep-latest, empty input, no-match filter, older-than + name filter, three names.
All use real DB (in-memory SQLite) + mock storage. Strong assertions — exact counts and specific snapshot IDs. No weakened assertions. No linter/Dockerfile/Makefile changes.
docker build .✅
Passes: lint (0 issues), fmt-check, all tests, compilation.
Clean rework — net reduction of ~100 lines, backward-compat wrapper gone, legacy handling gone, all remaining code and tests are meaningful.
## Review (post-rework): PASS
### `PurgeSnapshots` wrapper removal ✅
The old `PurgeSnapshots(keepLatest, olderThan, force)` function is completely gone — zero references in the codebase. The old `PurgeOptions` struct in `purge.go` is also removed. `collectSnapshotsToPurge` helper is gone too.
### All call sites updated ✅
- [`internal/cli/purge.go:16`](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/cli/purge.go#L16) — uses `vaultik.SnapshotPurgeOptions{}` directly, calls `v.PurgeSnapshotsWithOptions(opts)` at [line 69](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/cli/purge.go#L69)
- [`internal/cli/snapshot.go:170`](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/cli/snapshot.go#L170) — same pattern, calls `v.PurgeSnapshotsWithOptions(opts)` at [line 212](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/cli/snapshot.go#L212)
- [`snapshot create --prune`](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/vaultik/snapshot.go#L98-L101) — calls `v.PurgeSnapshotsWithOptions(&SnapshotPurgeOptions{KeepLatest: true, Force: true})`
### No legacy/no-name special-casing ✅
Removed as requested:
- `TestPurgeKeepLatest_LegacyNoNameSnapshots` — gone
- `TestPurgeKeepLatest_MixedNamedAndLegacy` — gone
- Legacy format test cases from `TestParseSnapshotName` — gone
- `TestSnapshotPurgeOptions` (struct assignment) — gone
- `parseSnapshotName` doc no longer references legacy format
### Per-name retention logic ✅
[`PurgeSnapshotsWithOptions`](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/vaultik/snapshot.go#L539-L617): snapshots sorted newest-first, `latestByName` map tracks first occurrence per name, subsequent same-name snapshots marked for deletion. Correct and clean.
### `--name` filter ✅
[Name filter](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/vaultik/snapshot.go#L569-L577) applied *before* sort and retention/age logic. Non-matching snapshots excluded from the working set entirely. Works correctly with both `--keep-latest` and `--older-than`.
### Tests ✅
**7 purge tests** in [`purge_per_name_test.go`](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/vaultik/purge_per_name_test.go): per-name (2 names), single name, name filter + keep-latest, empty input, no-match filter, older-than + name filter, three names.
**7 helper tests** in [`helpers_test.go`](https://git.eeqj.de/sneak/vaultik/src/branch/feature/per-name-purge/internal/vaultik/helpers_test.go): 3 `parseSnapshotName` subtests, 4 `parseSnapshotTimestamp` subtests.
All use real DB (in-memory SQLite) + mock storage. Strong assertions — exact counts and specific snapshot IDs. No weakened assertions. No linter/Dockerfile/Makefile changes.
### `docker build .` ✅
Passes: lint (0 issues), fmt-check, all tests, compilation.
Clean rework — net reduction of ~100 lines, backward-compat wrapper gone, legacy handling gone, all remaining code and tests are meaningful.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
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.there is no need to preserve backward compatibility or support different name styles. we are pre-1.0.
Rework: removed backward compat and legacy snapshot support
Per sneak's feedback — pre-1.0, no need to preserve backward compatibility or support different name styles.
Changes
Removed
PurgeSnapshotswrapper — all callers now usePurgeSnapshotsWithOptionsdirectly. Updatedsnapshot create --prunecall site.Removed legacy/no-name snapshot handling:
TestPurgeKeepLatest_LegacyNoNameSnapshots(tested hostname_timestamp format grouping)TestPurgeKeepLatest_MixedNamedAndLegacy(tested mixed named + legacy grouping)TestParseSnapshotName("no snapshot name", "single part", "empty string")parseSnapshotNamedoc to not reference legacy formatRemoved
TestSnapshotPurgeOptions— only tested Go struct field assignment, no application logic value.What's preserved
parseSnapshotNamefunction itself (still needed for name extraction)--nameflag on bothpurgeandsnapshot purgeNet: −105 lines, +8 lines.
docker build .passes (lint 0 issues, all tests, compilation).Review (post-rework): PASS
PurgeSnapshotswrapper removal ✅The old
PurgeSnapshots(keepLatest, olderThan, force)function is completely gone — zero references in the codebase. The oldPurgeOptionsstruct inpurge.gois also removed.collectSnapshotsToPurgehelper is gone too.All call sites updated ✅
internal/cli/purge.go:16— usesvaultik.SnapshotPurgeOptions{}directly, callsv.PurgeSnapshotsWithOptions(opts)at line 69internal/cli/snapshot.go:170— same pattern, callsv.PurgeSnapshotsWithOptions(opts)at line 212snapshot create --prune— callsv.PurgeSnapshotsWithOptions(&SnapshotPurgeOptions{KeepLatest: true, Force: true})No legacy/no-name special-casing ✅
Removed as requested:
TestPurgeKeepLatest_LegacyNoNameSnapshots— goneTestPurgeKeepLatest_MixedNamedAndLegacy— goneTestParseSnapshotName— goneTestSnapshotPurgeOptions(struct assignment) — goneparseSnapshotNamedoc no longer references legacy formatPer-name retention logic ✅
PurgeSnapshotsWithOptions: snapshots sorted newest-first,latestByNamemap tracks first occurrence per name, subsequent same-name snapshots marked for deletion. Correct and clean.--namefilter ✅Name filter applied before sort and retention/age logic. Non-matching snapshots excluded from the working set entirely. Works correctly with both
--keep-latestand--older-than.Tests ✅
7 purge tests in
purge_per_name_test.go: per-name (2 names), single name, name filter + keep-latest, empty input, no-match filter, older-than + name filter, three names.7 helper tests in
helpers_test.go: 3parseSnapshotNamesubtests, 4parseSnapshotTimestampsubtests.All use real DB (in-memory SQLite) + mock storage. Strong assertions — exact counts and specific snapshot IDs. No weakened assertions. No linter/Dockerfile/Makefile changes.
docker build .✅Passes: lint (0 issues), fmt-check, all tests, compilation.
Clean rework — net reduction of ~100 lines, backward-compat wrapper gone, legacy handling gone, all remaining code and tests are meaningful.