2
2
mirror of https://github.com/octoleo/restic.git synced 2024-05-29 07:00:49 +00:00
restic/internal/restic/snapshot_find.go
Aneesh Agrawal 058dfc20da Avoid choosing parent snapshot newer than time of current snapshot
Currently, `restic backup` (if a `--parent` is not provided)
will choose the most recent matching snapshot as the parent snapshot.
This makes sense in the usual case,
where we tag the snapshot-being-created with the current time.

However, this doesn't make sense if the user has passed `--time`
and is currently creating a snapshot older than the latest snapshot.
Instead, choose the most recent snapshot
which is not newer than the snapshot-being-created's timestamp,
to avoid any time travel.

Impetus for this change:
I'm using restic for the first time!
I have a number of existing BTRFS snapshots
I am backing up via restic to serve as my initial set of backups.
I initially `restic backup`'d the most recent snapshot to test,
then started backing up each of the other snapshots.
I noticed in `restic cat snapshot <id>` output
that all the remaining snapshots have the most recent as the parent.
2022-01-23 23:55:00 -05:00

116 lines
2.5 KiB
Go

package restic
import (
"context"
"fmt"
"os"
"path/filepath"
"time"
"github.com/restic/restic/internal/errors"
)
// ErrNoSnapshotFound is returned when no snapshot for the given criteria could be found.
var ErrNoSnapshotFound = errors.New("no snapshot found")
// FindLatestSnapshot finds latest snapshot with optional target/directory, tags, hostname, and timestamp filters.
func FindLatestSnapshot(ctx context.Context, repo Repository, targets []string, tagLists []TagList, hostnames []string, timeStamp *time.Time) (ID, error) {
var err error
absTargets := make([]string, 0, len(targets))
for _, target := range targets {
if !filepath.IsAbs(target) {
target, err = filepath.Abs(target)
if err != nil {
return ID{}, errors.Wrap(err, "Abs")
}
}
absTargets = append(absTargets, filepath.Clean(target))
}
var (
latest time.Time
latestID ID
found bool
)
err = ForAllSnapshots(ctx, repo, nil, func(id ID, snapshot *Snapshot, err error) error {
if err != nil {
return errors.Errorf("Error loading snapshot %v: %v", id.Str(), err)
}
if timeStamp != nil && snapshot.Time.After(*timeStamp) {
return nil
}
if snapshot.Time.Before(latest) {
return nil
}
if !snapshot.HasHostname(hostnames) {
return nil
}
if !snapshot.HasTagList(tagLists) {
return nil
}
if !snapshot.HasPaths(absTargets) {
return nil
}
latest = snapshot.Time
latestID = id
found = true
return nil
})
if err != nil {
return ID{}, err
}
if !found {
return ID{}, ErrNoSnapshotFound
}
return latestID, nil
}
// FindSnapshot takes a string and tries to find a snapshot whose ID matches
// the string as closely as possible.
func FindSnapshot(ctx context.Context, repo Repository, s string) (ID, error) {
// find snapshot id with prefix
name, err := Find(ctx, repo.Backend(), SnapshotFile, s)
if err != nil {
return ID{}, err
}
return ParseID(name)
}
// FindFilteredSnapshots yields Snapshots filtered from the list of all
// snapshots.
func FindFilteredSnapshots(ctx context.Context, repo Repository, hosts []string, tags []TagList, paths []string) (Snapshots, error) {
results := make(Snapshots, 0, 20)
err := ForAllSnapshots(ctx, repo, nil, func(id ID, sn *Snapshot, err error) error {
if err != nil {
fmt.Fprintf(os.Stderr, "could not load snapshot %v: %v\n", id.Str(), err)
return nil
}
if !sn.HasHostname(hosts) || !sn.HasTagList(tags) || !sn.HasPaths(paths) {
return nil
}
results = append(results, sn)
return nil
})
if err != nil {
return nil, err
}
return results, nil
}