diff --git a/changelog/unreleased/issue-5233 b/changelog/unreleased/issue-5233 new file mode 100644 index 000000000..14ff2b249 --- /dev/null +++ b/changelog/unreleased/issue-5233 @@ -0,0 +1,8 @@ +Bugfix: forget command returns exit code 3 on partial removal of snapshots + +The `forget` command now returns exit code 3 when it fails to remove one or +more snapshots. Previously, it returned exit code 0, which could lead to +confusion if the command was used in a script. + +https://github.com/restic/restic/issues/5233 +https://github.com/restic/restic/pull/5322 diff --git a/cmd/restic/cmd_forget.go b/cmd/restic/cmd_forget.go index 3a410e223..bef00ffa1 100644 --- a/cmd/restic/cmd_forget.go +++ b/cmd/restic/cmd_forget.go @@ -41,6 +41,7 @@ EXIT STATUS Exit status is 0 if the command was successful. Exit status is 1 if there was any error. +Exit status is 3 if there was an error removing one or more snapshots. Exit status is 10 if the repository does not exist. Exit status is 11 if the repository is already locked. Exit status is 12 if the password is incorrect. @@ -62,6 +63,7 @@ Exit status is 12 if the password is incorrect. type ForgetPolicyCount int var ErrNegativePolicyCount = errors.New("negative values not allowed, use 'unlimited' instead") +var ErrFailedToRemoveOneOrMoreSnapshots = errors.New("failed to remove one or more snapshots") func (c *ForgetPolicyCount) Set(s string) error { switch s { @@ -305,12 +307,15 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption return ctx.Err() } + // these are the snapshots that failed to be removed + failedSnIDs := restic.NewIDSet() if len(removeSnIDs) > 0 { if !opts.DryRun { bar := printer.NewCounter("files deleted") err := restic.ParallelRemove(ctx, repo, removeSnIDs, restic.WriteableSnapshotFile, func(id restic.ID, err error) error { if err != nil { printer.E("unable to remove %v/%v from the repository\n", restic.SnapshotFile, id) + failedSnIDs.Insert(id) } else { printer.VV("removed %v/%v\n", restic.SnapshotFile, id) } @@ -332,6 +337,10 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption } } + if len(failedSnIDs) > 0 { + return ErrFailedToRemoveOneOrMoreSnapshots + } + if len(removeSnIDs) > 0 && opts.Prune { if opts.DryRun { printer.P("%d snapshots would be removed, running prune dry run\n", len(removeSnIDs)) diff --git a/cmd/restic/main.go b/cmd/restic/main.go index b04b3b9ab..f4b17aa92 100644 --- a/cmd/restic/main.go +++ b/cmd/restic/main.go @@ -206,6 +206,8 @@ func main() { exitCode = 0 case err == ErrInvalidSourceData: exitCode = 3 + case errors.Is(err, ErrFailedToRemoveOneOrMoreSnapshots): + exitCode = 3 case errors.Is(err, ErrNoRepository): exitCode = 10 case restic.IsAlreadyLocked(err):