Add support for snapshot:subpath syntax

This snapshot specification syntax is supported by the cat, diff, dump,
ls and restore command.
This commit is contained in:
Michael Eischer
2023-05-18 23:15:38 +02:00
parent 60d8066568
commit 85860e6e97
9 changed files with 90 additions and 21 deletions

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"path/filepath"
"strings"
"time"
"github.com/restic/restic/internal/errors"
@@ -82,31 +83,40 @@ func (f *SnapshotFilter) findLatest(ctx context.Context, be Lister, loader Loade
return latest, nil
}
func splitSnapshotID(s string) (id, subpath string) {
id, subpath, _ = strings.Cut(s, ":")
return
}
// 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) {
func FindSnapshot(ctx context.Context, be Lister, loader LoaderUnpacked, s string) (*Snapshot, string, error) {
s, subpath := splitSnapshotID(s)
// no need to list snapshots if `s` is already a full id
id, err := ParseID(s)
if err != nil {
// find snapshot id with prefix
id, err = Find(ctx, be, SnapshotFile, s)
if err != nil {
return nil, err
return nil, "", err
}
}
return LoadSnapshot(ctx, loader, id)
sn, err := LoadSnapshot(ctx, loader, id)
return sn, subpath, err
}
// FindLatest returns either the latest of a filtered list of all snapshots
// or a snapshot specified by `snapshotID`.
func (f *SnapshotFilter) FindLatest(ctx context.Context, be Lister, loader LoaderUnpacked, snapshotID string) (*Snapshot, error) {
if snapshotID == "latest" {
func (f *SnapshotFilter) FindLatest(ctx context.Context, be Lister, loader LoaderUnpacked, snapshotID string) (*Snapshot, string, error) {
id, subpath := splitSnapshotID(snapshotID)
if id == "latest" {
sn, err := f.findLatest(ctx, be, loader)
if err == ErrNoSnapshotFound {
err = fmt.Errorf("snapshot filter (Paths:%v Tags:%v Hosts:%v): %w",
f.Paths, f.Tags, f.Hosts, err)
}
return sn, err
return sn, subpath, err
}
return FindSnapshot(ctx, be, loader, snapshotID)
}
@@ -139,8 +149,11 @@ func (f *SnapshotFilter) FindAll(ctx context.Context, be Lister, loader LoaderUn
ids.Insert(*sn.ID())
}
} else {
sn, err = FindSnapshot(ctx, be, loader, s)
if err == nil {
var subpath string
sn, subpath, err = FindSnapshot(ctx, be, loader, s)
if err == nil && subpath != "" {
err = errors.New("snapshot:path syntax not allowed")
} else if err == nil {
if ids.Has(*sn.ID()) {
continue
}

View File

@@ -15,7 +15,7 @@ func TestFindLatestSnapshot(t *testing.T) {
latestSnapshot := restic.TestCreateSnapshot(t, repo, parseTimeUTC("2019-09-09 09:09:09"), 1, 0)
f := restic.SnapshotFilter{Hosts: []string{"foo"}}
sn, err := f.FindLatest(context.TODO(), repo.Backend(), repo, "latest")
sn, _, err := f.FindLatest(context.TODO(), repo.Backend(), repo, "latest")
if err != nil {
t.Fatalf("FindLatest returned error: %v", err)
}
@@ -31,7 +31,7 @@ func TestFindLatestSnapshotWithMaxTimestamp(t *testing.T) {
desiredSnapshot := restic.TestCreateSnapshot(t, repo, parseTimeUTC("2017-07-07 07:07:07"), 1, 0)
restic.TestCreateSnapshot(t, repo, parseTimeUTC("2019-09-09 09:09:09"), 1, 0)
sn, err := (&restic.SnapshotFilter{
sn, _, err := (&restic.SnapshotFilter{
Hosts: []string{"foo"},
TimestampLimit: parseTimeUTC("2018-08-08 08:08:08"),
}).FindLatest(context.TODO(), repo.Backend(), repo, "latest")

View File

@@ -5,7 +5,9 @@ import (
"context"
"encoding/json"
"fmt"
"path"
"sort"
"strings"
"github.com/restic/restic/internal/errors"
@@ -184,3 +186,32 @@ func (builder *TreeJSONBuilder) Finalize() ([]byte, error) {
builder.buf = bytes.Buffer{}
return buf, nil
}
func FindTreeDirectory(ctx context.Context, repo BlobLoader, id *ID, dir string) (*ID, error) {
if id == nil {
return nil, errors.New("tree id is null")
}
dirs := strings.Split(path.Clean(dir), "/")
subpath := ""
for _, name := range dirs {
if name == "" || name == "." {
continue
}
subpath = path.Join(subpath, name)
tree, err := LoadTree(ctx, repo, *id)
if err != nil {
return nil, fmt.Errorf("path %s: %w", subpath, err)
}
node := tree.Find(name)
if node == nil {
return nil, fmt.Errorf("path %s: not found", subpath)
}
if node.Type != "dir" || node.Subtree == nil {
return nil, fmt.Errorf("path %s: not a directory", subpath)
}
id = node.Subtree
}
return id, nil
}