From fff3eeabb13bcdb11dd36b81e83c53fd4a1aeabe Mon Sep 17 00:00:00 2001 From: Srigovind Nayak <5201843+konidev20@users.noreply.github.com> Date: Sat, 19 Apr 2025 20:34:25 +0530 Subject: [PATCH 1/4] forget: add new option to policy to keep unique trees * snapshots which may have not been created if the --skip-if-unchanged flag was set * this will make sure we can remove unchanged snapshots --- cmd/restic/cmd_forget.go | 3 +++ internal/data/snapshot_policy.go | 21 +++++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/cmd/restic/cmd_forget.go b/cmd/restic/cmd_forget.go index 976db4d0d..387b3f90a 100644 --- a/cmd/restic/cmd_forget.go +++ b/cmd/restic/cmd_forget.go @@ -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() { diff --git a/internal/data/snapshot_policy.go b/internal/data/snapshot_policy.go index 1ee5af984..15df4f496 100644 --- a/internal/data/snapshot_policy.go +++ b/internal/data/snapshot_policy.go @@ -25,6 +25,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 +165,15 @@ func findLatestTimestamp(list Snapshots) time.Time { return latest } +func findParentSnapshot(list Snapshots, id 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 +298,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{ From be0f5c8b6455dfba410fb2d8a8e6fe25545ee61d Mon Sep 17 00:00:00 2001 From: Srigovind Nayak <5201843+konidev20@users.noreply.github.com> Date: Sat, 19 Apr 2025 20:39:21 +0530 Subject: [PATCH 2/4] changelog: add notes for issue-5157 --- changelog/unreleased/issue-5157 | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 changelog/unreleased/issue-5157 diff --git a/changelog/unreleased/issue-5157 b/changelog/unreleased/issue-5157 new file mode 100644 index 000000000..0b209a1cc --- /dev/null +++ b/changelog/unreleased/issue-5157 @@ -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 \ No newline at end of file From 9e831b56bdeecfab69824042d8e430a4a1fb3934 Mon Sep 17 00:00:00 2001 From: Srigovind Nayak <5201843+konidev20@users.noreply.github.com> Date: Sat, 19 Apr 2025 20:42:43 +0530 Subject: [PATCH 3/4] docs: add the `--keep-unique` option in the documentation --- doc/060_forget.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/060_forget.rst b/doc/060_forget.rst index 346c13f32..b1eeb0cfb 100644 --- a/doc/060_forget.rst +++ b/doc/060_forget.rst @@ -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 From 13cb18f8ad846a974542efce775618b2f039762b Mon Sep 17 00:00:00 2001 From: Srigovind Nayak <5201843+konidev20@users.noreply.github.com> Date: Mon, 20 Oct 2025 19:39:07 +0530 Subject: [PATCH 4/4] fix: restic.ID imports in snapshot_policy --- internal/data/snapshot_policy.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/data/snapshot_policy.go b/internal/data/snapshot_policy.go index 15df4f496..5708a342d 100644 --- a/internal/data/snapshot_policy.go +++ b/internal/data/snapshot_policy.go @@ -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. @@ -165,7 +166,7 @@ func findLatestTimestamp(list Snapshots) time.Time { return latest } -func findParentSnapshot(list Snapshots, id ID) *Snapshot { +func findParentSnapshot(list Snapshots, id restic.ID) *Snapshot { for _, sn := range list { if sn.ID().Equal(id) { return sn