mirror of
https://github.com/restic/restic.git
synced 2025-04-27 20:20:48 +00:00
Merge pull request #3951 from MichaelEischer/rework-snapshot-filter
Rework snapshot filtering
This commit is contained in:
commit
258b487d8f
6
changelog/unreleased/pull-3951
Normal file
6
changelog/unreleased/pull-3951
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
Bugfix: `ls` returns exit code 1 if snapshot cannot be loaded
|
||||||
|
|
||||||
|
If the `ls` command failed to load a snapshot, it only printed a warning and
|
||||||
|
returned exit code 0. This has been changed to return exit code 1 instead.
|
||||||
|
|
||||||
|
https://github.com/restic/restic/pull/3951
|
@ -504,28 +504,21 @@ func collectTargets(opts BackupOptions, args []string) (targets []string, err er
|
|||||||
|
|
||||||
// parent returns the ID of the parent snapshot. If there is none, nil is
|
// parent returns the ID of the parent snapshot. If there is none, nil is
|
||||||
// returned.
|
// returned.
|
||||||
func findParentSnapshot(ctx context.Context, repo restic.Repository, opts BackupOptions, targets []string, timeStampLimit time.Time) (parentID *restic.ID, err error) {
|
func findParentSnapshot(ctx context.Context, repo restic.Repository, opts BackupOptions, targets []string, timeStampLimit time.Time) (*restic.Snapshot, error) {
|
||||||
// Force using a parent
|
if opts.Force {
|
||||||
if !opts.Force && opts.Parent != "" {
|
return nil, nil
|
||||||
id, err := restic.FindSnapshot(ctx, repo.Backend(), opts.Parent)
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.Fatalf("invalid id %q: %v", opts.Parent, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
parentID = &id
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find last snapshot to set it as parent, if not already set
|
snName := opts.Parent
|
||||||
if !opts.Force && parentID == nil {
|
if snName == "" {
|
||||||
id, err := restic.FindLatestSnapshot(ctx, repo.Backend(), repo, targets, []restic.TagList{}, []string{opts.Host}, &timeStampLimit)
|
snName = "latest"
|
||||||
if err == nil {
|
|
||||||
parentID = &id
|
|
||||||
} else if err != restic.ErrNoSnapshotFound {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
sn, err := restic.FindFilteredSnapshot(ctx, repo.Backend(), repo, []string{opts.Host}, []restic.TagList{}, targets, &timeStampLimit, snName)
|
||||||
return parentID, nil
|
// Snapshot not found is ok if no explicit parent was set
|
||||||
|
if opts.Parent == "" && errors.Is(err, restic.ErrNoSnapshotFound) {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
return sn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, term *termstatus.Terminal, args []string) error {
|
func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, term *termstatus.Terminal, args []string) error {
|
||||||
@ -604,16 +597,16 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var parentSnapshotID *restic.ID
|
var parentSnapshot *restic.Snapshot
|
||||||
if !opts.Stdin {
|
if !opts.Stdin {
|
||||||
parentSnapshotID, err = findParentSnapshot(ctx, repo, opts, targets, timeStamp)
|
parentSnapshot, err = findParentSnapshot(ctx, repo, opts, targets, timeStamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !gopts.JSON {
|
if !gopts.JSON {
|
||||||
if parentSnapshotID != nil {
|
if parentSnapshot != nil {
|
||||||
progressPrinter.P("using parent snapshot %v\n", parentSnapshotID.Str())
|
progressPrinter.P("using parent snapshot %v\n", parentSnapshot.ID().Str())
|
||||||
} else {
|
} else {
|
||||||
progressPrinter.P("no parent snapshot found, will read all files\n")
|
progressPrinter.P("no parent snapshot found, will read all files\n")
|
||||||
}
|
}
|
||||||
@ -713,16 +706,12 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||||||
arch.ChangeIgnoreFlags |= archiver.ChangeIgnoreCtime
|
arch.ChangeIgnoreFlags |= archiver.ChangeIgnoreCtime
|
||||||
}
|
}
|
||||||
|
|
||||||
if parentSnapshotID == nil {
|
|
||||||
parentSnapshotID = &restic.ID{}
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshotOpts := archiver.SnapshotOptions{
|
snapshotOpts := archiver.SnapshotOptions{
|
||||||
Excludes: opts.Excludes,
|
Excludes: opts.Excludes,
|
||||||
Tags: opts.Tags.Flatten(),
|
Tags: opts.Tags.Flatten(),
|
||||||
Time: timeStamp,
|
Time: timeStamp,
|
||||||
Hostname: opts.Host,
|
Hostname: opts.Host,
|
||||||
ParentSnapshot: *parentSnapshotID,
|
ParentSnapshot: parentSnapshot,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !gopts.JSON {
|
if !gopts.JSON {
|
||||||
|
@ -55,18 +55,10 @@ func runCat(ctx context.Context, gopts GlobalOptions, args []string) error {
|
|||||||
tpe := args[0]
|
tpe := args[0]
|
||||||
|
|
||||||
var id restic.ID
|
var id restic.ID
|
||||||
if tpe != "masterkey" && tpe != "config" {
|
if tpe != "masterkey" && tpe != "config" && tpe != "snapshot" {
|
||||||
id, err = restic.ParseID(args[1])
|
id, err = restic.ParseID(args[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if tpe != "snapshot" {
|
return errors.Fatalf("unable to parse ID: %v\n", err)
|
||||||
return errors.Fatalf("unable to parse ID: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// find snapshot id with prefix
|
|
||||||
id, err = restic.FindSnapshot(ctx, repo.Backend(), args[1])
|
|
||||||
if err != nil {
|
|
||||||
return errors.Fatalf("could not find snapshot: %v\n", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,9 +80,9 @@ func runCat(ctx context.Context, gopts GlobalOptions, args []string) error {
|
|||||||
Println(string(buf))
|
Println(string(buf))
|
||||||
return nil
|
return nil
|
||||||
case "snapshot":
|
case "snapshot":
|
||||||
sn, err := restic.LoadSnapshot(ctx, repo, id)
|
sn, err := restic.FindSnapshot(ctx, repo.Backend(), repo, args[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return errors.Fatalf("could not find snapshot: %v\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
buf, err := json.MarshalIndent(sn, "", " ")
|
buf, err := json.MarshalIndent(sn, "", " ")
|
||||||
|
@ -54,11 +54,11 @@ func init() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadSnapshot(ctx context.Context, be restic.Lister, repo restic.Repository, desc string) (*restic.Snapshot, error) {
|
func loadSnapshot(ctx context.Context, be restic.Lister, repo restic.Repository, desc string) (*restic.Snapshot, error) {
|
||||||
id, err := restic.FindSnapshot(ctx, be, desc)
|
sn, err := restic.FindSnapshot(ctx, be, repo, desc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Fatal(err.Error())
|
return nil, errors.Fatal(err.Error())
|
||||||
}
|
}
|
||||||
return restic.LoadSnapshot(ctx, repo, id)
|
return sn, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Comparer collects all things needed to compare two snapshots.
|
// Comparer collects all things needed to compare two snapshots.
|
||||||
|
@ -50,7 +50,7 @@ func init() {
|
|||||||
cmdRoot.AddCommand(cmdDump)
|
cmdRoot.AddCommand(cmdDump)
|
||||||
|
|
||||||
flags := cmdDump.Flags()
|
flags := cmdDump.Flags()
|
||||||
initMultiSnapshotFilterOptions(flags, &dumpOptions.snapshotFilterOptions, true)
|
initSingleSnapshotFilterOptions(flags, &dumpOptions.snapshotFilterOptions)
|
||||||
flags.StringVarP(&dumpOptions.Archive, "archive", "a", "tar", "set archive `format` as \"tar\" or \"zip\"")
|
flags.StringVarP(&dumpOptions.Archive, "archive", "a", "tar", "set archive `format` as \"tar\" or \"zip\"")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,23 +139,9 @@ func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var id restic.ID
|
sn, err := restic.FindFilteredSnapshot(ctx, repo.Backend(), repo, opts.Paths, opts.Tags, opts.Hosts, nil, snapshotIDString)
|
||||||
|
|
||||||
if snapshotIDString == "latest" {
|
|
||||||
id, err = restic.FindLatestSnapshot(ctx, repo.Backend(), repo, opts.Paths, opts.Tags, opts.Hosts, nil)
|
|
||||||
if err != nil {
|
|
||||||
Exitf(1, "latest snapshot for criteria not found: %v Paths:%v Hosts:%v", err, opts.Paths, opts.Hosts)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
id, err = restic.FindSnapshot(ctx, repo.Backend(), snapshotIDString)
|
|
||||||
if err != nil {
|
|
||||||
Exitf(1, "invalid id %q: %v", snapshotIDString, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sn, err := restic.LoadSnapshot(ctx, repo, id)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Exitf(2, "loading snapshot %q failed: %v", snapshotIDString, err)
|
Exitf(1, "failed to find snapshot: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = repo.LoadIndex(ctx)
|
err = repo.LoadIndex(ctx)
|
||||||
|
@ -210,45 +210,48 @@ func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for sn := range FindFilteredSnapshots(ctx, snapshotLister, repo, opts.Hosts, opts.Tags, opts.Paths, args[:1]) {
|
sn, err := restic.FindFilteredSnapshot(ctx, snapshotLister, repo, opts.Hosts, opts.Tags, opts.Paths, nil, args[0])
|
||||||
printSnapshot(sn)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
err := walker.Walk(ctx, repo, *sn.Tree, nil, func(_ restic.ID, nodepath string, node *restic.Node, err error) (bool, error) {
|
printSnapshot(sn)
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
if node == nil {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if withinDir(nodepath) {
|
|
||||||
// if we're within a dir, print the node
|
|
||||||
printNode(nodepath, node)
|
|
||||||
|
|
||||||
// if recursive listing is requested, signal the walker that it
|
|
||||||
// should continue walking recursively
|
|
||||||
if opts.Recursive {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// if there's an upcoming match deeper in the tree (but we're not
|
|
||||||
// there yet), signal the walker to descend into any subdirs
|
|
||||||
if approachingMatchingTree(nodepath) {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// otherwise, signal the walker to not walk recursively into any
|
|
||||||
// subdirs
|
|
||||||
if node.Type == "dir" {
|
|
||||||
return false, walker.ErrSkipNode
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
})
|
|
||||||
|
|
||||||
|
err = walker.Walk(ctx, repo, *sn.Tree, nil, func(_ restic.ID, nodepath string, node *restic.Node, err error) (bool, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
if node == nil {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if withinDir(nodepath) {
|
||||||
|
// if we're within a dir, print the node
|
||||||
|
printNode(nodepath, node)
|
||||||
|
|
||||||
|
// if recursive listing is requested, signal the walker that it
|
||||||
|
// should continue walking recursively
|
||||||
|
if opts.Recursive {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if there's an upcoming match deeper in the tree (but we're not
|
||||||
|
// there yet), signal the walker to descend into any subdirs
|
||||||
|
if approachingMatchingTree(nodepath) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise, signal the walker to not walk recursively into any
|
||||||
|
// subdirs
|
||||||
|
if node.Type == "dir" {
|
||||||
|
return false, walker.ErrSkipNode
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -131,18 +131,9 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions, a
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var id restic.ID
|
sn, err := restic.FindFilteredSnapshot(ctx, repo.Backend(), repo, opts.Hosts, opts.Tags, opts.Paths, nil, snapshotIDString)
|
||||||
|
if err != nil {
|
||||||
if snapshotIDString == "latest" {
|
Exitf(1, "failed to find snapshot: %v", err)
|
||||||
id, err = restic.FindLatestSnapshot(ctx, repo.Backend(), repo, opts.Paths, opts.Tags, opts.Hosts, nil)
|
|
||||||
if err != nil {
|
|
||||||
Exitf(1, "latest snapshot for criteria not found: %v Paths:%v Hosts:%v", err, opts.Paths, opts.Hosts)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
id, err = restic.FindSnapshot(ctx, repo.Backend(), snapshotIDString)
|
|
||||||
if err != nil {
|
|
||||||
Exitf(1, "invalid id %q: %v", snapshotIDString, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = repo.LoadIndex(ctx)
|
err = repo.LoadIndex(ctx)
|
||||||
@ -150,10 +141,7 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions, a
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
res, err := restorer.NewRestorer(ctx, repo, id, opts.Sparse)
|
res := restorer.NewRestorer(ctx, repo, sn, opts.Sparse)
|
||||||
if err != nil {
|
|
||||||
Exitf(2, "creating restorer failed: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
totalErrors := 0
|
totalErrors := 0
|
||||||
res.Error = func(location string, err error) error {
|
res.Error = func(location string, err error) error {
|
||||||
|
@ -15,6 +15,7 @@ type snapshotFilterOptions struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// initMultiSnapshotFilterOptions is used for commands that work on multiple snapshots
|
// initMultiSnapshotFilterOptions is used for commands that work on multiple snapshots
|
||||||
|
// MUST be combined with restic.FindFilteredSnapshots or FindFilteredSnapshots
|
||||||
func initMultiSnapshotFilterOptions(flags *pflag.FlagSet, options *snapshotFilterOptions, addHostShorthand bool) {
|
func initMultiSnapshotFilterOptions(flags *pflag.FlagSet, options *snapshotFilterOptions, addHostShorthand bool) {
|
||||||
hostShorthand := "H"
|
hostShorthand := "H"
|
||||||
if !addHostShorthand {
|
if !addHostShorthand {
|
||||||
@ -26,6 +27,7 @@ func initMultiSnapshotFilterOptions(flags *pflag.FlagSet, options *snapshotFilte
|
|||||||
}
|
}
|
||||||
|
|
||||||
// initSingleSnapshotFilterOptions is used for commands that work on a single snapshot
|
// initSingleSnapshotFilterOptions is used for commands that work on a single snapshot
|
||||||
|
// MUST be combined with restic.FindFilteredSnapshot
|
||||||
func initSingleSnapshotFilterOptions(flags *pflag.FlagSet, options *snapshotFilterOptions) {
|
func initSingleSnapshotFilterOptions(flags *pflag.FlagSet, options *snapshotFilterOptions) {
|
||||||
flags.StringArrayVarP(&options.Hosts, "host", "H", nil, "only consider snapshots for this `host`, when snapshot ID \"latest\" is given (can be specified multiple times)")
|
flags.StringArrayVarP(&options.Hosts, "host", "H", nil, "only consider snapshots for this `host`, when snapshot ID \"latest\" is given (can be specified multiple times)")
|
||||||
flags.Var(&options.Tags, "tag", "only consider snapshots including `tag[,tag,...]`, when snapshot ID \"latest\" is given (can be specified multiple times)")
|
flags.Var(&options.Tags, "tag", "only consider snapshots including `tag[,tag,...]`, when snapshot ID \"latest\" is given (can be specified multiple times)")
|
||||||
@ -37,70 +39,26 @@ func FindFilteredSnapshots(ctx context.Context, be restic.Lister, loader restic.
|
|||||||
out := make(chan *restic.Snapshot)
|
out := make(chan *restic.Snapshot)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(out)
|
defer close(out)
|
||||||
if len(snapshotIDs) != 0 {
|
be, err := backend.MemorizeList(ctx, be, restic.SnapshotFile)
|
||||||
// memorize snapshots list to prevent repeated backend listings
|
|
||||||
be, err := backend.MemorizeList(ctx, be, restic.SnapshotFile)
|
|
||||||
if err != nil {
|
|
||||||
Warnf("could not load snapshots: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
id restic.ID
|
|
||||||
usedFilter bool
|
|
||||||
)
|
|
||||||
ids := make(restic.IDs, 0, len(snapshotIDs))
|
|
||||||
// Process all snapshot IDs given as arguments.
|
|
||||||
for _, s := range snapshotIDs {
|
|
||||||
if s == "latest" {
|
|
||||||
usedFilter = true
|
|
||||||
id, err = restic.FindLatestSnapshot(ctx, be, loader, paths, tags, hosts, nil)
|
|
||||||
if err != nil {
|
|
||||||
Warnf("Ignoring %q, no snapshot matched given filter (Paths:%v Tags:%v Hosts:%v)\n", s, paths, tags, hosts)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
id, err = restic.FindSnapshot(ctx, be, s)
|
|
||||||
if err != nil {
|
|
||||||
Warnf("Ignoring %q: %v\n", s, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ids = append(ids, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Give the user some indication their filters are not used.
|
|
||||||
if !usedFilter && (len(hosts) != 0 || len(tags) != 0 || len(paths) != 0) {
|
|
||||||
Warnf("Ignoring filters as there are explicit snapshot ids given\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, id := range ids.Uniq() {
|
|
||||||
sn, err := restic.LoadSnapshot(ctx, loader, id)
|
|
||||||
if err != nil {
|
|
||||||
Warnf("Ignoring %q, could not load snapshot: %v\n", id, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
select {
|
|
||||||
case <-ctx.Done():
|
|
||||||
return
|
|
||||||
case out <- sn:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
snapshots, err := restic.FindFilteredSnapshots(ctx, be, loader, hosts, tags, paths)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
Warnf("could not load snapshots: %v\n", err)
|
Warnf("could not load snapshots: %v\n", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, sn := range snapshots {
|
err = restic.FindFilteredSnapshots(ctx, be, loader, hosts, tags, paths, snapshotIDs, func(id string, sn *restic.Snapshot, err error) error {
|
||||||
select {
|
if err != nil {
|
||||||
case <-ctx.Done():
|
Warnf("Ignoring %q: %v\n", id, err)
|
||||||
return
|
} else {
|
||||||
case out <- sn:
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
case out <- sn:
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
Warnf("could not load snapshots: %v\n", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return out
|
return out
|
||||||
|
@ -676,24 +676,17 @@ type SnapshotOptions struct {
|
|||||||
Hostname string
|
Hostname string
|
||||||
Excludes []string
|
Excludes []string
|
||||||
Time time.Time
|
Time time.Time
|
||||||
ParentSnapshot restic.ID
|
ParentSnapshot *restic.Snapshot
|
||||||
}
|
}
|
||||||
|
|
||||||
// loadParentTree loads a tree referenced by snapshot id. If id is null, nil is returned.
|
// loadParentTree loads a tree referenced by snapshot id. If id is null, nil is returned.
|
||||||
func (arch *Archiver) loadParentTree(ctx context.Context, snapshotID restic.ID) *restic.Tree {
|
func (arch *Archiver) loadParentTree(ctx context.Context, sn *restic.Snapshot) *restic.Tree {
|
||||||
if snapshotID.IsNull() {
|
if sn == nil {
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
debug.Log("load parent snapshot %v", snapshotID)
|
|
||||||
sn, err := restic.LoadSnapshot(ctx, arch.Repo, snapshotID)
|
|
||||||
if err != nil {
|
|
||||||
debug.Log("unable to load snapshot %v: %v", snapshotID, err)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if sn.Tree == nil {
|
if sn.Tree == nil {
|
||||||
debug.Log("snapshot %v has empty tree %v", snapshotID)
|
debug.Log("snapshot %v has empty tree %v", *sn.ID())
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -801,9 +794,8 @@ func (arch *Archiver) Snapshot(ctx context.Context, targets []string, opts Snaps
|
|||||||
}
|
}
|
||||||
|
|
||||||
sn.Excludes = opts.Excludes
|
sn.Excludes = opts.Excludes
|
||||||
if !opts.ParentSnapshot.IsNull() {
|
if opts.ParentSnapshot != nil {
|
||||||
id := opts.ParentSnapshot
|
sn.Parent = opts.ParentSnapshot.ID()
|
||||||
sn.Parent = &id
|
|
||||||
}
|
}
|
||||||
sn.Tree = &rootTreeID
|
sn.Tree = &rootTreeID
|
||||||
|
|
||||||
|
@ -1662,7 +1662,7 @@ func TestArchiverParent(t *testing.T) {
|
|||||||
back := restictest.Chdir(t, tempdir)
|
back := restictest.Chdir(t, tempdir)
|
||||||
defer back()
|
defer back()
|
||||||
|
|
||||||
_, firstSnapshotID, err := arch.Snapshot(ctx, []string{"."}, SnapshotOptions{Time: time.Now()})
|
firstSnapshot, firstSnapshotID, err := arch.Snapshot(ctx, []string{"."}, SnapshotOptions{Time: time.Now()})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -1690,7 +1690,7 @@ func TestArchiverParent(t *testing.T) {
|
|||||||
|
|
||||||
opts := SnapshotOptions{
|
opts := SnapshotOptions{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
ParentSnapshot: firstSnapshotID,
|
ParentSnapshot: firstSnapshot,
|
||||||
}
|
}
|
||||||
_, secondSnapshotID, err := arch.Snapshot(ctx, []string{"."}, opts)
|
_, secondSnapshotID, err := arch.Snapshot(ctx, []string{"."}, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -2063,7 +2063,7 @@ func TestArchiverAbortEarlyOnError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshot(t testing.TB, repo restic.Repository, fs fs.FS, parent restic.ID, filename string) (restic.ID, *restic.Node) {
|
func snapshot(t testing.TB, repo restic.Repository, fs fs.FS, parent *restic.Snapshot, filename string) (*restic.Snapshot, *restic.Node) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@ -2073,7 +2073,7 @@ func snapshot(t testing.TB, repo restic.Repository, fs fs.FS, parent restic.ID,
|
|||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
ParentSnapshot: parent,
|
ParentSnapshot: parent,
|
||||||
}
|
}
|
||||||
snapshot, snapshotID, err := arch.Snapshot(ctx, []string{filename}, sopts)
|
snapshot, _, err := arch.Snapshot(ctx, []string{filename}, sopts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -2088,7 +2088,7 @@ func snapshot(t testing.TB, repo restic.Repository, fs fs.FS, parent restic.ID,
|
|||||||
t.Fatalf("unable to find node for testfile in snapshot")
|
t.Fatalf("unable to find node for testfile in snapshot")
|
||||||
}
|
}
|
||||||
|
|
||||||
return snapshotID, node
|
return snapshot, node
|
||||||
}
|
}
|
||||||
|
|
||||||
// StatFS allows overwriting what is returned by the Lstat function.
|
// StatFS allows overwriting what is returned by the Lstat function.
|
||||||
@ -2170,7 +2170,7 @@ func TestMetadataChanged(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshotID, node2 := snapshot(t, repo, fs, restic.ID{}, "testfile")
|
sn, node2 := snapshot(t, repo, fs, nil, "testfile")
|
||||||
|
|
||||||
// set some values so we can then compare the nodes
|
// set some values so we can then compare the nodes
|
||||||
want.Content = node2.Content
|
want.Content = node2.Content
|
||||||
@ -2201,7 +2201,7 @@ func TestMetadataChanged(t *testing.T) {
|
|||||||
want.Group = ""
|
want.Group = ""
|
||||||
|
|
||||||
// make another snapshot
|
// make another snapshot
|
||||||
_, node3 := snapshot(t, repo, fs, snapshotID, "testfile")
|
_, node3 := snapshot(t, repo, fs, sn, "testfile")
|
||||||
// Override username and group to empty string - in case underlying system has user with UID 51234
|
// Override username and group to empty string - in case underlying system has user with UID 51234
|
||||||
// See https://github.com/restic/restic/issues/2372
|
// See https://github.com/restic/restic/issues/2372
|
||||||
node3.User = ""
|
node3.User = ""
|
||||||
|
@ -26,7 +26,11 @@ func TestSnapshot(t testing.TB, repo restic.Repository, path string, parent *res
|
|||||||
Tags: []string{"test"},
|
Tags: []string{"test"},
|
||||||
}
|
}
|
||||||
if parent != nil {
|
if parent != nil {
|
||||||
opts.ParentSnapshot = *parent
|
sn, err := restic.LoadSnapshot(context.TODO(), arch.Repo, *parent)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
opts.ParentSnapshot = sn
|
||||||
}
|
}
|
||||||
sn, _, err := arch.Snapshot(context.TODO(), []string{path}, opts)
|
sn, _, err := arch.Snapshot(context.TODO(), []string{path}, opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -592,11 +592,7 @@ func benchmarkSnapshotScaling(t *testing.B, newSnapshots int) {
|
|||||||
chkr, repo, cleanup := loadBenchRepository(t)
|
chkr, repo, cleanup := loadBenchRepository(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
snID, err := restic.FindSnapshot(context.TODO(), repo.Backend(), "51d249d2")
|
snID := restic.TestParseID("51d249d28815200d59e4be7b3f21a157b864dc343353df9d8e498220c2499b02")
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
sn2, err := restic.LoadSnapshot(context.TODO(), repo, snID)
|
sn2, err := restic.LoadSnapshot(context.TODO(), repo, snID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
@ -292,7 +292,13 @@ func (d *SnapshotsDirStructure) updateSnapshots(ctx context.Context) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
snapshots, err := restic.FindFilteredSnapshots(ctx, d.root.repo.Backend(), d.root.repo, d.root.cfg.Hosts, d.root.cfg.Tags, d.root.cfg.Paths)
|
var snapshots restic.Snapshots
|
||||||
|
err := restic.FindFilteredSnapshots(ctx, d.root.repo.Backend(), d.root.repo, d.root.cfg.Hosts, d.root.cfg.Tags, d.root.cfg.Paths, nil, func(id string, sn *restic.Snapshot, err error) error {
|
||||||
|
if sn != nil {
|
||||||
|
snapshots = append(snapshots, sn)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,6 @@ package restic
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -13,27 +12,23 @@ import (
|
|||||||
// ErrNoSnapshotFound is returned when no snapshot for the given criteria could be found.
|
// ErrNoSnapshotFound is returned when no snapshot for the given criteria could be found.
|
||||||
var ErrNoSnapshotFound = errors.New("no snapshot found")
|
var ErrNoSnapshotFound = errors.New("no snapshot found")
|
||||||
|
|
||||||
// FindLatestSnapshot finds latest snapshot with optional target/directory, tags, hostname, and timestamp filters.
|
// findLatestSnapshot finds latest snapshot with optional target/directory, tags, hostname, and timestamp filters.
|
||||||
func FindLatestSnapshot(ctx context.Context, be Lister, loader LoaderUnpacked, targets []string,
|
func findLatestSnapshot(ctx context.Context, be Lister, loader LoaderUnpacked, hosts []string,
|
||||||
tagLists []TagList, hostnames []string, timeStampLimit *time.Time) (ID, error) {
|
tags []TagList, paths []string, timeStampLimit *time.Time) (*Snapshot, error) {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
absTargets := make([]string, 0, len(targets))
|
absTargets := make([]string, 0, len(paths))
|
||||||
for _, target := range targets {
|
for _, target := range paths {
|
||||||
if !filepath.IsAbs(target) {
|
if !filepath.IsAbs(target) {
|
||||||
target, err = filepath.Abs(target)
|
target, err = filepath.Abs(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return ID{}, errors.Wrap(err, "Abs")
|
return nil, errors.Wrap(err, "Abs")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
absTargets = append(absTargets, filepath.Clean(target))
|
absTargets = append(absTargets, filepath.Clean(target))
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var latest *Snapshot
|
||||||
latest time.Time
|
|
||||||
latestID ID
|
|
||||||
found bool
|
|
||||||
)
|
|
||||||
|
|
||||||
err = ForAllSnapshots(ctx, be, loader, nil, func(id ID, snapshot *Snapshot, err error) error {
|
err = ForAllSnapshots(ctx, be, loader, nil, func(id ID, snapshot *Snapshot, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -44,15 +39,15 @@ func FindLatestSnapshot(ctx context.Context, be Lister, loader LoaderUnpacked, t
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if snapshot.Time.Before(latest) {
|
if latest != nil && snapshot.Time.Before(latest.Time) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !snapshot.HasHostname(hostnames) {
|
if !snapshot.HasHostname(hosts) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !snapshot.HasTagList(tagLists) {
|
if !snapshot.HasTagList(tags) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -60,52 +55,7 @@ func FindLatestSnapshot(ctx context.Context, be Lister, loader LoaderUnpacked, t
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
latest = snapshot.Time
|
latest = snapshot
|
||||||
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, be Lister, s string) (ID, error) {
|
|
||||||
|
|
||||||
// find snapshot id with prefix
|
|
||||||
name, err := Find(ctx, be, 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, be Lister, loader LoaderUnpacked, hosts []string, tags []TagList, paths []string) (Snapshots, error) {
|
|
||||||
results := make(Snapshots, 0, 20)
|
|
||||||
|
|
||||||
err := ForAllSnapshots(ctx, be, loader, 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
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -113,5 +63,104 @@ func FindFilteredSnapshots(ctx context.Context, be Lister, loader LoaderUnpacked
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return results, nil
|
if latest == nil {
|
||||||
|
return nil, ErrNoSnapshotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
return latest, 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, be Lister, loader LoaderUnpacked, s string) (*Snapshot, error) {
|
||||||
|
// no need to list snapshots if `s` is already a full id
|
||||||
|
id, err := ParseID(s)
|
||||||
|
if err != nil {
|
||||||
|
// find snapshot id with prefix
|
||||||
|
name, err := Find(ctx, be, SnapshotFile, s)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
id, err = ParseID(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return LoadSnapshot(ctx, loader, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FindFilteredSnapshot returns either the latests from a filtered list of all snapshots or a snapshot specified by `snapshotID`.
|
||||||
|
func FindFilteredSnapshot(ctx context.Context, be Lister, loader LoaderUnpacked, hosts []string, tags []TagList, paths []string, timeStampLimit *time.Time, snapshotID string) (*Snapshot, error) {
|
||||||
|
if snapshotID == "latest" {
|
||||||
|
sn, err := findLatestSnapshot(ctx, be, loader, hosts, tags, paths, timeStampLimit)
|
||||||
|
if err == ErrNoSnapshotFound {
|
||||||
|
err = fmt.Errorf("snapshot filter (Paths:%v Tags:%v Hosts:%v): %w", paths, tags, hosts, err)
|
||||||
|
}
|
||||||
|
return sn, err
|
||||||
|
}
|
||||||
|
return FindSnapshot(ctx, be, loader, snapshotID)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SnapshotFindCb func(string, *Snapshot, error) error
|
||||||
|
|
||||||
|
// FindFilteredSnapshots yields Snapshots, either given explicitly by `snapshotIDs` or filtered from the list of all snapshots.
|
||||||
|
func FindFilteredSnapshots(ctx context.Context, be Lister, loader LoaderUnpacked, hosts []string, tags []TagList, paths []string, snapshotIDs []string, fn SnapshotFindCb) error {
|
||||||
|
if len(snapshotIDs) != 0 {
|
||||||
|
var err error
|
||||||
|
usedFilter := false
|
||||||
|
|
||||||
|
ids := NewIDSet()
|
||||||
|
// Process all snapshot IDs given as arguments.
|
||||||
|
for _, s := range snapshotIDs {
|
||||||
|
var sn *Snapshot
|
||||||
|
if s == "latest" {
|
||||||
|
if usedFilter {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
usedFilter = true
|
||||||
|
|
||||||
|
sn, err = findLatestSnapshot(ctx, be, loader, hosts, tags, paths, nil)
|
||||||
|
if err == ErrNoSnapshotFound {
|
||||||
|
err = errors.Errorf("no snapshot matched given filter (Paths:%v Tags:%v Hosts:%v)", paths, tags, hosts)
|
||||||
|
}
|
||||||
|
if sn != nil {
|
||||||
|
ids.Insert(*sn.ID())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
sn, err = FindSnapshot(ctx, be, loader, s)
|
||||||
|
if err == nil {
|
||||||
|
if ids.Has(*sn.ID()) {
|
||||||
|
continue
|
||||||
|
} else {
|
||||||
|
ids.Insert(*sn.ID())
|
||||||
|
s = sn.ID().String()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = fn(s, sn, err)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Give the user some indication their filters are not used.
|
||||||
|
if !usedFilter && (len(hosts) != 0 || len(tags) != 0 || len(paths) != 0) {
|
||||||
|
return fn("filters", nil, errors.Errorf("explicit snapshot ids are given"))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ForAllSnapshots(ctx, be, loader, nil, func(id ID, sn *Snapshot, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return fn(id.String(), sn, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !sn.HasHostname(hosts) || !sn.HasTagList(tags) || !sn.HasPaths(paths) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return fn(id.String(), sn, err)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -16,13 +16,13 @@ func TestFindLatestSnapshot(t *testing.T) {
|
|||||||
restic.TestCreateSnapshot(t, repo, parseTimeUTC("2017-07-07 07:07:07"), 1, 0)
|
restic.TestCreateSnapshot(t, repo, parseTimeUTC("2017-07-07 07:07:07"), 1, 0)
|
||||||
latestSnapshot := restic.TestCreateSnapshot(t, repo, parseTimeUTC("2019-09-09 09:09:09"), 1, 0)
|
latestSnapshot := restic.TestCreateSnapshot(t, repo, parseTimeUTC("2019-09-09 09:09:09"), 1, 0)
|
||||||
|
|
||||||
id, err := restic.FindLatestSnapshot(context.TODO(), repo.Backend(), repo, []string{}, []restic.TagList{}, []string{"foo"}, nil)
|
sn, err := restic.FindFilteredSnapshot(context.TODO(), repo.Backend(), repo, []string{"foo"}, []restic.TagList{}, []string{}, nil, "latest")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("FindLatestSnapshot returned error: %v", err)
|
t.Fatalf("FindLatestSnapshot returned error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if id != *latestSnapshot.ID() {
|
if *sn.ID() != *latestSnapshot.ID() {
|
||||||
t.Errorf("FindLatestSnapshot returned wrong snapshot ID: %v", id)
|
t.Errorf("FindLatestSnapshot returned wrong snapshot ID: %v", *sn.ID())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,12 +36,12 @@ func TestFindLatestSnapshotWithMaxTimestamp(t *testing.T) {
|
|||||||
|
|
||||||
maxTimestamp := parseTimeUTC("2018-08-08 08:08:08")
|
maxTimestamp := parseTimeUTC("2018-08-08 08:08:08")
|
||||||
|
|
||||||
id, err := restic.FindLatestSnapshot(context.TODO(), repo.Backend(), repo, []string{}, []restic.TagList{}, []string{"foo"}, &maxTimestamp)
|
sn, err := restic.FindFilteredSnapshot(context.TODO(), repo.Backend(), repo, []string{"foo"}, []restic.TagList{}, []string{}, &maxTimestamp, "latest")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("FindLatestSnapshot returned error: %v", err)
|
t.Fatalf("FindLatestSnapshot returned error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if id != *desiredSnapshot.ID() {
|
if *sn.ID() != *desiredSnapshot.ID() {
|
||||||
t.Errorf("FindLatestSnapshot returned wrong snapshot ID: %v", id)
|
t.Errorf("FindLatestSnapshot returned wrong snapshot ID: %v", *sn.ID())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,22 +27,16 @@ type Restorer struct {
|
|||||||
var restorerAbortOnAllErrors = func(location string, err error) error { return err }
|
var restorerAbortOnAllErrors = func(location string, err error) error { return err }
|
||||||
|
|
||||||
// NewRestorer creates a restorer preloaded with the content from the snapshot id.
|
// NewRestorer creates a restorer preloaded with the content from the snapshot id.
|
||||||
func NewRestorer(ctx context.Context, repo restic.Repository, id restic.ID, sparse bool) (*Restorer, error) {
|
func NewRestorer(ctx context.Context, repo restic.Repository, sn *restic.Snapshot, sparse bool) *Restorer {
|
||||||
r := &Restorer{
|
r := &Restorer{
|
||||||
repo: repo,
|
repo: repo,
|
||||||
sparse: sparse,
|
sparse: sparse,
|
||||||
Error: restorerAbortOnAllErrors,
|
Error: restorerAbortOnAllErrors,
|
||||||
SelectFilter: func(string, string, *restic.Node) (bool, bool) { return true, true },
|
SelectFilter: func(string, string, *restic.Node) (bool, bool) { return true, true },
|
||||||
|
sn: sn,
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
return r
|
||||||
|
|
||||||
r.sn, err = restic.LoadSnapshot(ctx, repo, id)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return r, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type treeVisitor struct {
|
type treeVisitor struct {
|
||||||
|
@ -323,13 +323,10 @@ func TestRestorer(t *testing.T) {
|
|||||||
t.Run("", func(t *testing.T) {
|
t.Run("", func(t *testing.T) {
|
||||||
repo, cleanup := repository.TestRepository(t)
|
repo, cleanup := repository.TestRepository(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
_, id := saveSnapshot(t, repo, test.Snapshot)
|
sn, id := saveSnapshot(t, repo, test.Snapshot)
|
||||||
t.Logf("snapshot saved as %v", id.Str())
|
t.Logf("snapshot saved as %v", id.Str())
|
||||||
|
|
||||||
res, err := NewRestorer(context.TODO(), repo, id, false)
|
res := NewRestorer(context.TODO(), repo, sn, false)
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tempdir, cleanup := rtest.TempDir(t)
|
tempdir, cleanup := rtest.TempDir(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
@ -366,7 +363,7 @@ func TestRestorer(t *testing.T) {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
err = res.RestoreTo(ctx, tempdir)
|
err := res.RestoreTo(ctx, tempdir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -446,13 +443,10 @@ func TestRestorerRelative(t *testing.T) {
|
|||||||
repo, cleanup := repository.TestRepository(t)
|
repo, cleanup := repository.TestRepository(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
_, id := saveSnapshot(t, repo, test.Snapshot)
|
sn, id := saveSnapshot(t, repo, test.Snapshot)
|
||||||
t.Logf("snapshot saved as %v", id.Str())
|
t.Logf("snapshot saved as %v", id.Str())
|
||||||
|
|
||||||
res, err := NewRestorer(context.TODO(), repo, id, false)
|
res := NewRestorer(context.TODO(), repo, sn, false)
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tempdir, cleanup := rtest.TempDir(t)
|
tempdir, cleanup := rtest.TempDir(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
@ -470,7 +464,7 @@ func TestRestorerRelative(t *testing.T) {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
err = res.RestoreTo(ctx, "restore")
|
err := res.RestoreTo(ctx, "restore")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -682,12 +676,9 @@ func TestRestorerTraverseTree(t *testing.T) {
|
|||||||
t.Run("", func(t *testing.T) {
|
t.Run("", func(t *testing.T) {
|
||||||
repo, cleanup := repository.TestRepository(t)
|
repo, cleanup := repository.TestRepository(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
sn, id := saveSnapshot(t, repo, test.Snapshot)
|
sn, _ := saveSnapshot(t, repo, test.Snapshot)
|
||||||
|
|
||||||
res, err := NewRestorer(context.TODO(), repo, id, false)
|
res := NewRestorer(context.TODO(), repo, sn, false)
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
res.SelectFilter = test.Select
|
res.SelectFilter = test.Select
|
||||||
|
|
||||||
@ -700,7 +691,7 @@ func TestRestorerTraverseTree(t *testing.T) {
|
|||||||
// make sure we're creating a new subdir of the tempdir
|
// make sure we're creating a new subdir of the tempdir
|
||||||
target := filepath.Join(tempdir, "target")
|
target := filepath.Join(tempdir, "target")
|
||||||
|
|
||||||
_, err = res.traverseTree(ctx, target, string(filepath.Separator), *sn.Tree, test.Visitor(t))
|
_, err := res.traverseTree(ctx, target, string(filepath.Separator), *sn.Tree, test.Visitor(t))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -735,7 +726,7 @@ func TestRestorerConsistentTimestampsAndPermissions(t *testing.T) {
|
|||||||
repo, cleanup := repository.TestRepository(t)
|
repo, cleanup := repository.TestRepository(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
_, id := saveSnapshot(t, repo, Snapshot{
|
sn, _ := saveSnapshot(t, repo, Snapshot{
|
||||||
Nodes: map[string]Node{
|
Nodes: map[string]Node{
|
||||||
"dir": Dir{
|
"dir": Dir{
|
||||||
Mode: normalizeFileMode(0750 | os.ModeDir),
|
Mode: normalizeFileMode(0750 | os.ModeDir),
|
||||||
@ -766,8 +757,7 @@ func TestRestorerConsistentTimestampsAndPermissions(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
res, err := NewRestorer(context.TODO(), repo, id, false)
|
res := NewRestorer(context.TODO(), repo, sn, false)
|
||||||
rtest.OK(t, err)
|
|
||||||
|
|
||||||
res.SelectFilter = func(item string, dstpath string, node *restic.Node) (selectedForRestore bool, childMayBeSelected bool) {
|
res.SelectFilter = func(item string, dstpath string, node *restic.Node) (selectedForRestore bool, childMayBeSelected bool) {
|
||||||
switch filepath.ToSlash(item) {
|
switch filepath.ToSlash(item) {
|
||||||
@ -792,7 +782,7 @@ func TestRestorerConsistentTimestampsAndPermissions(t *testing.T) {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
err = res.RestoreTo(ctx, tempdir)
|
err := res.RestoreTo(ctx, tempdir)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
var testPatterns = []struct {
|
var testPatterns = []struct {
|
||||||
@ -824,10 +814,9 @@ func TestVerifyCancel(t *testing.T) {
|
|||||||
repo, cleanup := repository.TestRepository(t)
|
repo, cleanup := repository.TestRepository(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
_, id := saveSnapshot(t, repo, snapshot)
|
sn, _ := saveSnapshot(t, repo, snapshot)
|
||||||
|
|
||||||
res, err := NewRestorer(context.TODO(), repo, id, false)
|
res := NewRestorer(context.TODO(), repo, sn, false)
|
||||||
rtest.OK(t, err)
|
|
||||||
|
|
||||||
tempdir, cleanup := rtest.TempDir(t)
|
tempdir, cleanup := rtest.TempDir(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
@ -836,7 +825,7 @@ func TestVerifyCancel(t *testing.T) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
rtest.OK(t, res.RestoreTo(ctx, tempdir))
|
rtest.OK(t, res.RestoreTo(ctx, tempdir))
|
||||||
err = ioutil.WriteFile(filepath.Join(tempdir, "foo"), []byte("bar"), 0644)
|
err := ioutil.WriteFile(filepath.Join(tempdir, "foo"), []byte("bar"), 0644)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
var errs []error
|
var errs []error
|
||||||
@ -868,12 +857,11 @@ func TestRestorerSparseFiles(t *testing.T) {
|
|||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
arch := archiver.New(repo, target, archiver.Options{})
|
arch := archiver.New(repo, target, archiver.Options{})
|
||||||
_, id, err := arch.Snapshot(context.Background(), []string{"/zeros"},
|
sn, _, err := arch.Snapshot(context.Background(), []string{"/zeros"},
|
||||||
archiver.SnapshotOptions{})
|
archiver.SnapshotOptions{})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
res, err := NewRestorer(context.TODO(), repo, id, true)
|
res := NewRestorer(context.TODO(), repo, sn, true)
|
||||||
rtest.OK(t, err)
|
|
||||||
|
|
||||||
tempdir, cleanup := rtest.TempDir(t)
|
tempdir, cleanup := rtest.TempDir(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
@ -19,7 +19,7 @@ func TestRestorerRestoreEmptyHardlinkedFileds(t *testing.T) {
|
|||||||
repo, cleanup := repository.TestRepository(t)
|
repo, cleanup := repository.TestRepository(t)
|
||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
_, id := saveSnapshot(t, repo, Snapshot{
|
sn, _ := saveSnapshot(t, repo, Snapshot{
|
||||||
Nodes: map[string]Node{
|
Nodes: map[string]Node{
|
||||||
"dirtest": Dir{
|
"dirtest": Dir{
|
||||||
Nodes: map[string]Node{
|
Nodes: map[string]Node{
|
||||||
@ -30,8 +30,7 @@ func TestRestorerRestoreEmptyHardlinkedFileds(t *testing.T) {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
res, err := NewRestorer(context.TODO(), repo, id, false)
|
res := NewRestorer(context.TODO(), repo, sn, false)
|
||||||
rtest.OK(t, err)
|
|
||||||
|
|
||||||
res.SelectFilter = func(item string, dstpath string, node *restic.Node) (selectedForRestore bool, childMayBeSelected bool) {
|
res.SelectFilter = func(item string, dstpath string, node *restic.Node) (selectedForRestore bool, childMayBeSelected bool) {
|
||||||
return true, true
|
return true, true
|
||||||
@ -43,7 +42,7 @@ func TestRestorerRestoreEmptyHardlinkedFileds(t *testing.T) {
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
err = res.RestoreTo(ctx, tempdir)
|
err := res.RestoreTo(ctx, tempdir)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
f1, err := os.Stat(filepath.Join(tempdir, "dirtest/file1"))
|
f1, err := os.Stat(filepath.Join(tempdir, "dirtest/file1"))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user