mirror of
https://github.com/restic/restic.git
synced 2025-08-23 04:27:24 +00:00
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:
@@ -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
|
||||
}
|
||||
|
@@ -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")
|
||||
|
@@ -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
|
||||
}
|
||||
|
Reference in New Issue
Block a user