This commit is contained in:
Srigovind Nayak 2026-05-22 23:19:02 +00:00 committed by GitHub
commit 12aff9d537
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 33 additions and 0 deletions

View file

@ -0,0 +1,7 @@
Enhancement: Added `--keep-unique` flag to `forget` command
Restic `forget` command can now remove duplicate snapshots with the
`--keep-unique` flag set.
https://github.com/restic/restic/issues/5157
https://github.com/restic/restic/pull/5364

View file

@ -112,6 +112,7 @@ type ForgetOptions struct {
WithinMonthly data.Duration
WithinYearly data.Duration
KeepTags data.TagLists
Unique bool
UnsafeAllowRemoveAll bool
@ -138,6 +139,7 @@ func (opts *ForgetOptions) AddFlags(f *pflag.FlagSet) {
f.VarP(&opts.WithinMonthly, "keep-within-monthly", "", "keep monthly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot")
f.VarP(&opts.WithinYearly, "keep-within-yearly", "", "keep yearly snapshots that are newer than `duration` (eg. 1y5m7d2h) relative to the latest snapshot")
f.Var(&opts.KeepTags, "keep-tag", "keep snapshots with this `taglist` (can be specified multiple times)")
f.BoolVar(&opts.Unique, "keep-unique", false, "keep the only one snapshot per tree")
f.BoolVar(&opts.UnsafeAllowRemoveAll, "unsafe-allow-remove-all", false, "allow deleting all snapshots of a snapshot group")
f.StringArrayVar(&opts.Hosts, "hostname", nil, "only consider snapshots with the given `hostname` (can be specified multiple times)")
@ -233,6 +235,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption
WithinMonthly: opts.WithinMonthly,
WithinYearly: opts.WithinYearly,
Tags: opts.KeepTags,
Unique: opts.Unique,
}
if policy.Empty() {

View file

@ -205,6 +205,7 @@ The ``forget`` command accepts the following policy options:
specified duration of the latest snapshot.
- ``--keep-within-yearly duration`` keep all yearly snapshots made within the
specified duration of the latest snapshot.
- ``--keep-unique`` keep only one snapshot per tree.
.. note:: All calendar related options (``--keep-{hourly,daily,...}``) work on
natural time boundaries and *not* relative to when you run ``forget``. Weeks

View file

@ -8,6 +8,7 @@ import (
"time"
"github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/restic"
)
// ExpirePolicy configures which snapshots should be automatically removed.
@ -25,6 +26,7 @@ type ExpirePolicy struct {
WithinMonthly Duration // keep monthly snapshots made within this duration
WithinYearly Duration // keep yearly snapshots made within this duration
Tags []TagList // keep all snapshots that include at least one of the tag lists.
Unique bool // keep the only one snapshot per tree
}
func (e ExpirePolicy) String() (s string) {
@ -164,6 +166,15 @@ func findLatestTimestamp(list Snapshots) time.Time {
return latest
}
func findParentSnapshot(list Snapshots, id restic.ID) *Snapshot {
for _, sn := range list {
if sn.ID().Equal(id) {
return sn
}
}
return nil
}
// KeepReason specifies why a particular snapshot was kept, and the counters at
// that point in the policy evaluation.
type KeepReason struct {
@ -288,6 +299,17 @@ func ApplyPolicy(list Snapshots, p ExpirePolicy) (keep, remove Snapshots, reason
}
}
if p.Unique {
if cur.Parent != nil {
parent := findParentSnapshot(keep, *cur.Parent)
if parent != nil {
if parent.Tree == cur.Tree {
keepSnap = false
}
}
}
}
if keepSnap {
keep = append(keep, cur)
kr := KeepReason{