mirror of
https://github.com/restic/restic.git
synced 2025-12-11 18:47:50 +00:00
data: split node and snapshot code from restic package
This commit is contained in:
@@ -19,6 +19,7 @@ import (
|
|||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/archiver"
|
"github.com/restic/restic/internal/archiver"
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/filter"
|
"github.com/restic/restic/internal/filter"
|
||||||
@@ -76,7 +77,7 @@ type BackupOptions struct {
|
|||||||
filter.ExcludePatternOptions
|
filter.ExcludePatternOptions
|
||||||
|
|
||||||
Parent string
|
Parent string
|
||||||
GroupBy restic.SnapshotGroupByOptions
|
GroupBy data.SnapshotGroupByOptions
|
||||||
Force bool
|
Force bool
|
||||||
ExcludeOtherFS bool
|
ExcludeOtherFS bool
|
||||||
ExcludeIfPresent []string
|
ExcludeIfPresent []string
|
||||||
@@ -86,7 +87,7 @@ type BackupOptions struct {
|
|||||||
Stdin bool
|
Stdin bool
|
||||||
StdinFilename string
|
StdinFilename string
|
||||||
StdinCommand bool
|
StdinCommand bool
|
||||||
Tags restic.TagLists
|
Tags data.TagLists
|
||||||
Host string
|
Host string
|
||||||
FilesFrom []string
|
FilesFrom []string
|
||||||
FilesFromVerbatim []string
|
FilesFromVerbatim []string
|
||||||
@@ -104,7 +105,7 @@ type BackupOptions struct {
|
|||||||
|
|
||||||
func (opts *BackupOptions) AddFlags(f *pflag.FlagSet) {
|
func (opts *BackupOptions) AddFlags(f *pflag.FlagSet) {
|
||||||
f.StringVar(&opts.Parent, "parent", "", "use this parent `snapshot` (default: latest snapshot in the group determined by --group-by and not newer than the timestamp determined by --time)")
|
f.StringVar(&opts.Parent, "parent", "", "use this parent `snapshot` (default: latest snapshot in the group determined by --group-by and not newer than the timestamp determined by --time)")
|
||||||
opts.GroupBy = restic.SnapshotGroupByOptions{Host: true, Path: true}
|
opts.GroupBy = data.SnapshotGroupByOptions{Host: true, Path: true}
|
||||||
f.VarP(&opts.GroupBy, "group-by", "g", "`group` snapshots by host, paths and/or tags, separated by comma (disable grouping with '')")
|
f.VarP(&opts.GroupBy, "group-by", "g", "`group` snapshots by host, paths and/or tags, separated by comma (disable grouping with '')")
|
||||||
f.BoolVarP(&opts.Force, "force", "f", false, `force re-reading the source files/directories (overrides the "parent" flag)`)
|
f.BoolVarP(&opts.Force, "force", "f", false, `force re-reading the source files/directories (overrides the "parent" flag)`)
|
||||||
|
|
||||||
@@ -446,7 +447,7 @@ func collectTargets(opts BackupOptions, args []string, warnf func(msg string, ar
|
|||||||
|
|
||||||
// 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.ListerLoaderUnpacked, opts BackupOptions, targets []string, timeStampLimit time.Time) (*restic.Snapshot, error) {
|
func findParentSnapshot(ctx context.Context, repo restic.ListerLoaderUnpacked, opts BackupOptions, targets []string, timeStampLimit time.Time) (*data.Snapshot, error) {
|
||||||
if opts.Force {
|
if opts.Force {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@@ -455,7 +456,7 @@ func findParentSnapshot(ctx context.Context, repo restic.ListerLoaderUnpacked, o
|
|||||||
if snName == "" {
|
if snName == "" {
|
||||||
snName = "latest"
|
snName = "latest"
|
||||||
}
|
}
|
||||||
f := restic.SnapshotFilter{TimestampLimit: timeStampLimit}
|
f := data.SnapshotFilter{TimestampLimit: timeStampLimit}
|
||||||
if opts.GroupBy.Host {
|
if opts.GroupBy.Host {
|
||||||
f.Hosts = []string{opts.Host}
|
f.Hosts = []string{opts.Host}
|
||||||
}
|
}
|
||||||
@@ -463,12 +464,12 @@ func findParentSnapshot(ctx context.Context, repo restic.ListerLoaderUnpacked, o
|
|||||||
f.Paths = targets
|
f.Paths = targets
|
||||||
}
|
}
|
||||||
if opts.GroupBy.Tag {
|
if opts.GroupBy.Tag {
|
||||||
f.Tags = []restic.TagList{opts.Tags.Flatten()}
|
f.Tags = []data.TagList{opts.Tags.Flatten()}
|
||||||
}
|
}
|
||||||
|
|
||||||
sn, _, err := f.FindLatest(ctx, repo, repo, snName)
|
sn, _, err := f.FindLatest(ctx, repo, repo, snName)
|
||||||
// Snapshot not found is ok if no explicit parent was set
|
// Snapshot not found is ok if no explicit parent was set
|
||||||
if opts.Parent == "" && errors.Is(err, restic.ErrNoSnapshotFound) {
|
if opts.Parent == "" && errors.Is(err, data.ErrNoSnapshotFound) {
|
||||||
err = nil
|
err = nil
|
||||||
}
|
}
|
||||||
return sn, err
|
return sn, err
|
||||||
@@ -529,7 +530,7 @@ func runBackup(ctx context.Context, opts BackupOptions, gopts GlobalOptions, ter
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var parentSnapshot *restic.Snapshot
|
var parentSnapshot *data.Snapshot
|
||||||
if !opts.Stdin {
|
if !opts.Stdin {
|
||||||
parentSnapshot, err = findParentSnapshot(ctx, repo, opts, targets, timeStamp)
|
parentSnapshot, err = findParentSnapshot(ctx, repo, opts, targets, timeStamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/fs"
|
"github.com/restic/restic/internal/fs"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
@@ -22,7 +23,7 @@ func testRunBackupAssumeFailure(t testing.TB, dir string, target []string, opts
|
|||||||
defer cleanup()
|
defer cleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
opts.GroupBy = restic.SnapshotGroupByOptions{Host: true, Path: true}
|
opts.GroupBy = data.SnapshotGroupByOptions{Host: true, Path: true}
|
||||||
return runBackup(ctx, opts, gopts, gopts.term, target)
|
return runBackup(ctx, opts, gopts, gopts.term, target)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -473,7 +474,7 @@ func TestBackupTags(t *testing.T) {
|
|||||||
"expected no tags, got %v", newest.Tags)
|
"expected no tags, got %v", newest.Tags)
|
||||||
parent := newest
|
parent := newest
|
||||||
|
|
||||||
opts.Tags = restic.TagLists{[]string{"NL"}}
|
opts.Tags = data.TagLists{[]string{"NL"}}
|
||||||
testRunBackup(t, "", []string{env.testdata}, opts, env.gopts)
|
testRunBackup(t, "", []string{env.testdata}, opts, env.gopts)
|
||||||
testRunCheck(t, env.gopts)
|
testRunCheck(t, env.gopts)
|
||||||
newest, _ = testRunSnapshots(t, env.gopts)
|
newest, _ = testRunSnapshots(t, env.gopts)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
@@ -105,7 +106,7 @@ func runCat(ctx context.Context, gopts GlobalOptions, args []string, term ui.Ter
|
|||||||
printer.S(string(buf))
|
printer.S(string(buf))
|
||||||
return nil
|
return nil
|
||||||
case "snapshot":
|
case "snapshot":
|
||||||
sn, _, err := restic.FindSnapshot(ctx, repo, repo, args[1])
|
sn, _, err := data.FindSnapshot(ctx, repo, repo, args[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Fatalf("could not find snapshot: %v", err)
|
return errors.Fatalf("could not find snapshot: %v", err)
|
||||||
}
|
}
|
||||||
@@ -190,7 +191,7 @@ func runCat(ctx context.Context, gopts GlobalOptions, args []string, term ui.Ter
|
|||||||
return errors.Fatal("blob not found")
|
return errors.Fatal("blob not found")
|
||||||
|
|
||||||
case "tree":
|
case "tree":
|
||||||
sn, subfolder, err := restic.FindSnapshot(ctx, repo, repo, args[1])
|
sn, subfolder, err := data.FindSnapshot(ctx, repo, repo, args[1])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Fatalf("could not find snapshot: %v", err)
|
return errors.Fatalf("could not find snapshot: %v", err)
|
||||||
}
|
}
|
||||||
@@ -200,7 +201,7 @@ func runCat(ctx context.Context, gopts GlobalOptions, args []string, term ui.Ter
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sn.Tree, err = restic.FindTreeDirectory(ctx, repo, sn.Tree, subfolder)
|
sn.Tree, err = data.FindTreeDirectory(ctx, repo, sn.Tree, subfolder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
@@ -59,7 +60,7 @@ Exit status is 12 if the password is incorrect.
|
|||||||
// CopyOptions bundles all options for the copy command.
|
// CopyOptions bundles all options for the copy command.
|
||||||
type CopyOptions struct {
|
type CopyOptions struct {
|
||||||
secondaryRepoOptions
|
secondaryRepoOptions
|
||||||
restic.SnapshotFilter
|
data.SnapshotFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts *CopyOptions) AddFlags(f *pflag.FlagSet) {
|
func (opts *CopyOptions) AddFlags(f *pflag.FlagSet) {
|
||||||
@@ -109,7 +110,7 @@ func runCopy(ctx context.Context, opts CopyOptions, gopts GlobalOptions, args []
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
dstSnapshotByOriginal := make(map[restic.ID][]*restic.Snapshot)
|
dstSnapshotByOriginal := make(map[restic.ID][]*data.Snapshot)
|
||||||
for sn := range FindFilteredSnapshots(ctx, dstSnapshotLister, dstRepo, &opts.SnapshotFilter, nil, printer) {
|
for sn := range FindFilteredSnapshots(ctx, dstSnapshotLister, dstRepo, &opts.SnapshotFilter, nil, printer) {
|
||||||
if sn.Original != nil && !sn.Original.IsNull() {
|
if sn.Original != nil && !sn.Original.IsNull() {
|
||||||
dstSnapshotByOriginal[*sn.Original] = append(dstSnapshotByOriginal[*sn.Original], sn)
|
dstSnapshotByOriginal[*sn.Original] = append(dstSnapshotByOriginal[*sn.Original], sn)
|
||||||
@@ -158,7 +159,7 @@ func runCopy(ctx context.Context, opts CopyOptions, gopts GlobalOptions, args []
|
|||||||
if sn.Original == nil {
|
if sn.Original == nil {
|
||||||
sn.Original = sn.ID()
|
sn.Original = sn.ID()
|
||||||
}
|
}
|
||||||
newID, err := restic.SaveSnapshot(ctx, dstRepo, sn)
|
newID, err := data.SaveSnapshot(ctx, dstRepo, sn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -167,7 +168,7 @@ func runCopy(ctx context.Context, opts CopyOptions, gopts GlobalOptions, args []
|
|||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func similarSnapshots(sna *restic.Snapshot, snb *restic.Snapshot) bool {
|
func similarSnapshots(sna *data.Snapshot, snb *data.Snapshot) bool {
|
||||||
// everything except Parent and Original must match
|
// everything except Parent and Original must match
|
||||||
if !sna.Time.Equal(snb.Time) || !sna.Tree.Equal(*snb.Tree) || sna.Hostname != snb.Hostname ||
|
if !sna.Time.Equal(snb.Time) || !sna.Tree.Equal(*snb.Tree) || sna.Hostname != snb.Hostname ||
|
||||||
sna.Username != snb.Username || sna.UID != snb.UID || sna.GID != snb.GID ||
|
sna.Username != snb.Username || sna.UID != snb.UID || sna.GID != snb.GID ||
|
||||||
@@ -191,7 +192,7 @@ func copyTree(ctx context.Context, srcRepo restic.Repository, dstRepo restic.Rep
|
|||||||
|
|
||||||
wg, wgCtx := errgroup.WithContext(ctx)
|
wg, wgCtx := errgroup.WithContext(ctx)
|
||||||
|
|
||||||
treeStream := restic.StreamTrees(wgCtx, wg, srcRepo, restic.IDs{rootTreeID}, func(treeID restic.ID) bool {
|
treeStream := data.StreamTrees(wgCtx, wg, srcRepo, restic.IDs{rootTreeID}, func(treeID restic.ID) bool {
|
||||||
visited := visitedTrees.Has(treeID)
|
visited := visitedTrees.Has(treeID)
|
||||||
visitedTrees.Insert(treeID)
|
visitedTrees.Insert(treeID)
|
||||||
return visited
|
return visited
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import (
|
|||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/crypto"
|
"github.com/restic/restic/internal/crypto"
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/repository/index"
|
"github.com/restic/restic/internal/repository/index"
|
||||||
@@ -115,7 +116,7 @@ func prettyPrintJSON(wr io.Writer, item interface{}) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func debugPrintSnapshots(ctx context.Context, repo *repository.Repository, wr io.Writer) error {
|
func debugPrintSnapshots(ctx context.Context, repo *repository.Repository, wr io.Writer) error {
|
||||||
return restic.ForAllSnapshots(ctx, repo, repo, nil, func(id restic.ID, snapshot *restic.Snapshot, err error) error {
|
return data.ForAllSnapshots(ctx, repo, repo, nil, func(id restic.ID, snapshot *data.Snapshot, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"reflect"
|
"reflect"
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
@@ -69,8 +70,8 @@ func (opts *DiffOptions) AddFlags(f *pflag.FlagSet) {
|
|||||||
f.BoolVar(&opts.ShowMetadata, "metadata", false, "print changes in metadata")
|
f.BoolVar(&opts.ShowMetadata, "metadata", false, "print changes in metadata")
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadSnapshot(ctx context.Context, be restic.Lister, repo restic.LoaderUnpacked, desc string) (*restic.Snapshot, string, error) {
|
func loadSnapshot(ctx context.Context, be restic.Lister, repo restic.LoaderUnpacked, desc string) (*data.Snapshot, string, error) {
|
||||||
sn, subfolder, err := restic.FindSnapshot(ctx, be, repo, desc)
|
sn, subfolder, err := data.FindSnapshot(ctx, be, repo, desc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", errors.Fatalf("%s", err)
|
return nil, "", errors.Fatalf("%s", err)
|
||||||
}
|
}
|
||||||
@@ -106,15 +107,15 @@ type DiffStat struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add adds stats information for node to s.
|
// Add adds stats information for node to s.
|
||||||
func (s *DiffStat) Add(node *restic.Node) {
|
func (s *DiffStat) Add(node *data.Node) {
|
||||||
if node == nil {
|
if node == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case restic.NodeTypeFile:
|
case data.NodeTypeFile:
|
||||||
s.Files++
|
s.Files++
|
||||||
case restic.NodeTypeDir:
|
case data.NodeTypeDir:
|
||||||
s.Dirs++
|
s.Dirs++
|
||||||
default:
|
default:
|
||||||
s.Others++
|
s.Others++
|
||||||
@@ -122,13 +123,13 @@ func (s *DiffStat) Add(node *restic.Node) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// addBlobs adds the blobs of node to s.
|
// addBlobs adds the blobs of node to s.
|
||||||
func addBlobs(bs restic.BlobSet, node *restic.Node) {
|
func addBlobs(bs restic.BlobSet, node *data.Node) {
|
||||||
if node == nil {
|
if node == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case restic.NodeTypeFile:
|
case data.NodeTypeFile:
|
||||||
for _, blob := range node.Content {
|
for _, blob := range node.Content {
|
||||||
h := restic.BlobHandle{
|
h := restic.BlobHandle{
|
||||||
ID: blob,
|
ID: blob,
|
||||||
@@ -136,7 +137,7 @@ func addBlobs(bs restic.BlobSet, node *restic.Node) {
|
|||||||
}
|
}
|
||||||
bs.Insert(h)
|
bs.Insert(h)
|
||||||
}
|
}
|
||||||
case restic.NodeTypeDir:
|
case data.NodeTypeDir:
|
||||||
h := restic.BlobHandle{
|
h := restic.BlobHandle{
|
||||||
ID: *node.Subtree,
|
ID: *node.Subtree,
|
||||||
Type: restic.TreeBlob,
|
Type: restic.TreeBlob,
|
||||||
@@ -177,7 +178,7 @@ func updateBlobs(repo restic.Loader, blobs restic.BlobSet, stats *DiffStat, prin
|
|||||||
|
|
||||||
func (c *Comparer) printDir(ctx context.Context, mode string, stats *DiffStat, blobs restic.BlobSet, prefix string, id restic.ID) error {
|
func (c *Comparer) printDir(ctx context.Context, mode string, stats *DiffStat, blobs restic.BlobSet, prefix string, id restic.ID) error {
|
||||||
debug.Log("print %v tree %v", mode, id)
|
debug.Log("print %v tree %v", mode, id)
|
||||||
tree, err := restic.LoadTree(ctx, c.repo, id)
|
tree, err := data.LoadTree(ctx, c.repo, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -188,14 +189,14 @@ func (c *Comparer) printDir(ctx context.Context, mode string, stats *DiffStat, b
|
|||||||
}
|
}
|
||||||
|
|
||||||
name := path.Join(prefix, node.Name)
|
name := path.Join(prefix, node.Name)
|
||||||
if node.Type == restic.NodeTypeDir {
|
if node.Type == data.NodeTypeDir {
|
||||||
name += "/"
|
name += "/"
|
||||||
}
|
}
|
||||||
c.printChange(NewChange(name, mode))
|
c.printChange(NewChange(name, mode))
|
||||||
stats.Add(node)
|
stats.Add(node)
|
||||||
addBlobs(blobs, node)
|
addBlobs(blobs, node)
|
||||||
|
|
||||||
if node.Type == restic.NodeTypeDir {
|
if node.Type == data.NodeTypeDir {
|
||||||
err := c.printDir(ctx, mode, stats, blobs, name, *node.Subtree)
|
err := c.printDir(ctx, mode, stats, blobs, name, *node.Subtree)
|
||||||
if err != nil && err != context.Canceled {
|
if err != nil && err != context.Canceled {
|
||||||
c.printError("error: %v", err)
|
c.printError("error: %v", err)
|
||||||
@@ -208,7 +209,7 @@ func (c *Comparer) printDir(ctx context.Context, mode string, stats *DiffStat, b
|
|||||||
|
|
||||||
func (c *Comparer) collectDir(ctx context.Context, blobs restic.BlobSet, id restic.ID) error {
|
func (c *Comparer) collectDir(ctx context.Context, blobs restic.BlobSet, id restic.ID) error {
|
||||||
debug.Log("print tree %v", id)
|
debug.Log("print tree %v", id)
|
||||||
tree, err := restic.LoadTree(ctx, c.repo, id)
|
tree, err := data.LoadTree(ctx, c.repo, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -220,7 +221,7 @@ func (c *Comparer) collectDir(ctx context.Context, blobs restic.BlobSet, id rest
|
|||||||
|
|
||||||
addBlobs(blobs, node)
|
addBlobs(blobs, node)
|
||||||
|
|
||||||
if node.Type == restic.NodeTypeDir {
|
if node.Type == data.NodeTypeDir {
|
||||||
err := c.collectDir(ctx, blobs, *node.Subtree)
|
err := c.collectDir(ctx, blobs, *node.Subtree)
|
||||||
if err != nil && err != context.Canceled {
|
if err != nil && err != context.Canceled {
|
||||||
c.printError("error: %v", err)
|
c.printError("error: %v", err)
|
||||||
@@ -231,15 +232,15 @@ func (c *Comparer) collectDir(ctx context.Context, blobs restic.BlobSet, id rest
|
|||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
func uniqueNodeNames(tree1, tree2 *restic.Tree) (tree1Nodes, tree2Nodes map[string]*restic.Node, uniqueNames []string) {
|
func uniqueNodeNames(tree1, tree2 *data.Tree) (tree1Nodes, tree2Nodes map[string]*data.Node, uniqueNames []string) {
|
||||||
names := make(map[string]struct{})
|
names := make(map[string]struct{})
|
||||||
tree1Nodes = make(map[string]*restic.Node)
|
tree1Nodes = make(map[string]*data.Node)
|
||||||
for _, node := range tree1.Nodes {
|
for _, node := range tree1.Nodes {
|
||||||
tree1Nodes[node.Name] = node
|
tree1Nodes[node.Name] = node
|
||||||
names[node.Name] = struct{}{}
|
names[node.Name] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
tree2Nodes = make(map[string]*restic.Node)
|
tree2Nodes = make(map[string]*data.Node)
|
||||||
for _, node := range tree2.Nodes {
|
for _, node := range tree2.Nodes {
|
||||||
tree2Nodes[node.Name] = node
|
tree2Nodes[node.Name] = node
|
||||||
names[node.Name] = struct{}{}
|
names[node.Name] = struct{}{}
|
||||||
@@ -256,12 +257,12 @@ func uniqueNodeNames(tree1, tree2 *restic.Tree) (tree1Nodes, tree2Nodes map[stri
|
|||||||
|
|
||||||
func (c *Comparer) diffTree(ctx context.Context, stats *DiffStatsContainer, prefix string, id1, id2 restic.ID) error {
|
func (c *Comparer) diffTree(ctx context.Context, stats *DiffStatsContainer, prefix string, id1, id2 restic.ID) error {
|
||||||
debug.Log("diffing %v to %v", id1, id2)
|
debug.Log("diffing %v to %v", id1, id2)
|
||||||
tree1, err := restic.LoadTree(ctx, c.repo, id1)
|
tree1, err := data.LoadTree(ctx, c.repo, id1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tree2, err := restic.LoadTree(ctx, c.repo, id2)
|
tree2, err := data.LoadTree(ctx, c.repo, id2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -288,12 +289,12 @@ func (c *Comparer) diffTree(ctx context.Context, stats *DiffStatsContainer, pref
|
|||||||
mod += "T"
|
mod += "T"
|
||||||
}
|
}
|
||||||
|
|
||||||
if node2.Type == restic.NodeTypeDir {
|
if node2.Type == data.NodeTypeDir {
|
||||||
name += "/"
|
name += "/"
|
||||||
}
|
}
|
||||||
|
|
||||||
if node1.Type == restic.NodeTypeFile &&
|
if node1.Type == data.NodeTypeFile &&
|
||||||
node2.Type == restic.NodeTypeFile &&
|
node2.Type == data.NodeTypeFile &&
|
||||||
!reflect.DeepEqual(node1.Content, node2.Content) {
|
!reflect.DeepEqual(node1.Content, node2.Content) {
|
||||||
mod += "M"
|
mod += "M"
|
||||||
stats.ChangedFiles++
|
stats.ChangedFiles++
|
||||||
@@ -315,7 +316,7 @@ func (c *Comparer) diffTree(ctx context.Context, stats *DiffStatsContainer, pref
|
|||||||
c.printChange(NewChange(name, mod))
|
c.printChange(NewChange(name, mod))
|
||||||
}
|
}
|
||||||
|
|
||||||
if node1.Type == restic.NodeTypeDir && node2.Type == restic.NodeTypeDir {
|
if node1.Type == data.NodeTypeDir && node2.Type == data.NodeTypeDir {
|
||||||
var err error
|
var err error
|
||||||
if (*node1.Subtree).Equal(*node2.Subtree) {
|
if (*node1.Subtree).Equal(*node2.Subtree) {
|
||||||
err = c.collectDir(ctx, stats.BlobsCommon, *node1.Subtree)
|
err = c.collectDir(ctx, stats.BlobsCommon, *node1.Subtree)
|
||||||
@@ -328,13 +329,13 @@ func (c *Comparer) diffTree(ctx context.Context, stats *DiffStatsContainer, pref
|
|||||||
}
|
}
|
||||||
case t1 && !t2:
|
case t1 && !t2:
|
||||||
prefix := path.Join(prefix, name)
|
prefix := path.Join(prefix, name)
|
||||||
if node1.Type == restic.NodeTypeDir {
|
if node1.Type == data.NodeTypeDir {
|
||||||
prefix += "/"
|
prefix += "/"
|
||||||
}
|
}
|
||||||
c.printChange(NewChange(prefix, "-"))
|
c.printChange(NewChange(prefix, "-"))
|
||||||
stats.Removed.Add(node1)
|
stats.Removed.Add(node1)
|
||||||
|
|
||||||
if node1.Type == restic.NodeTypeDir {
|
if node1.Type == data.NodeTypeDir {
|
||||||
err := c.printDir(ctx, "-", &stats.Removed, stats.BlobsBefore, prefix, *node1.Subtree)
|
err := c.printDir(ctx, "-", &stats.Removed, stats.BlobsBefore, prefix, *node1.Subtree)
|
||||||
if err != nil && err != context.Canceled {
|
if err != nil && err != context.Canceled {
|
||||||
c.printError("error: %v", err)
|
c.printError("error: %v", err)
|
||||||
@@ -342,13 +343,13 @@ func (c *Comparer) diffTree(ctx context.Context, stats *DiffStatsContainer, pref
|
|||||||
}
|
}
|
||||||
case !t1 && t2:
|
case !t1 && t2:
|
||||||
prefix := path.Join(prefix, name)
|
prefix := path.Join(prefix, name)
|
||||||
if node2.Type == restic.NodeTypeDir {
|
if node2.Type == data.NodeTypeDir {
|
||||||
prefix += "/"
|
prefix += "/"
|
||||||
}
|
}
|
||||||
c.printChange(NewChange(prefix, "+"))
|
c.printChange(NewChange(prefix, "+"))
|
||||||
stats.Added.Add(node2)
|
stats.Added.Add(node2)
|
||||||
|
|
||||||
if node2.Type == restic.NodeTypeDir {
|
if node2.Type == data.NodeTypeDir {
|
||||||
err := c.printDir(ctx, "+", &stats.Added, stats.BlobsAfter, prefix, *node2.Subtree)
|
err := c.printDir(ctx, "+", &stats.Added, stats.BlobsAfter, prefix, *node2.Subtree)
|
||||||
if err != nil && err != context.Canceled {
|
if err != nil && err != context.Canceled {
|
||||||
c.printError("error: %v", err)
|
c.printError("error: %v", err)
|
||||||
@@ -403,12 +404,12 @@ func runDiff(ctx context.Context, opts DiffOptions, gopts GlobalOptions, args []
|
|||||||
return errors.Errorf("snapshot %v has nil tree", sn2.ID().Str())
|
return errors.Errorf("snapshot %v has nil tree", sn2.ID().Str())
|
||||||
}
|
}
|
||||||
|
|
||||||
sn1.Tree, err = restic.FindTreeDirectory(ctx, repo, sn1.Tree, subfolder1)
|
sn1.Tree, err = data.FindTreeDirectory(ctx, repo, sn1.Tree, subfolder1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sn2.Tree, err = restic.FindTreeDirectory(ctx, repo, sn2.Tree, subfolder2)
|
sn2.Tree, err = data.FindTreeDirectory(ctx, repo, sn2.Tree, subfolder2)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/dump"
|
"github.com/restic/restic/internal/dump"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
@@ -57,7 +58,7 @@ Exit status is 12 if the password is incorrect.
|
|||||||
|
|
||||||
// DumpOptions collects all options for the dump command.
|
// DumpOptions collects all options for the dump command.
|
||||||
type DumpOptions struct {
|
type DumpOptions struct {
|
||||||
restic.SnapshotFilter
|
data.SnapshotFilter
|
||||||
Archive string
|
Archive string
|
||||||
Target string
|
Target string
|
||||||
}
|
}
|
||||||
@@ -77,7 +78,7 @@ func splitPath(p string) []string {
|
|||||||
return append(s, f)
|
return append(s, f)
|
||||||
}
|
}
|
||||||
|
|
||||||
func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.BlobLoader, prefix string, pathComponents []string, d *dump.Dumper, canWriteArchiveFunc func() error) error {
|
func printFromTree(ctx context.Context, tree *data.Tree, repo restic.BlobLoader, prefix string, pathComponents []string, d *dump.Dumper, canWriteArchiveFunc func() error) error {
|
||||||
// If we print / we need to assume that there are multiple nodes at that
|
// If we print / we need to assume that there are multiple nodes at that
|
||||||
// level in the tree.
|
// level in the tree.
|
||||||
if pathComponents[0] == "" {
|
if pathComponents[0] == "" {
|
||||||
@@ -98,26 +99,26 @@ func printFromTree(ctx context.Context, tree *restic.Tree, repo restic.BlobLoade
|
|||||||
// first item it finds and dump that according to the switch case below.
|
// first item it finds and dump that according to the switch case below.
|
||||||
if node.Name == pathComponents[0] {
|
if node.Name == pathComponents[0] {
|
||||||
switch {
|
switch {
|
||||||
case l == 1 && node.Type == restic.NodeTypeFile:
|
case l == 1 && node.Type == data.NodeTypeFile:
|
||||||
return d.WriteNode(ctx, node)
|
return d.WriteNode(ctx, node)
|
||||||
case l > 1 && node.Type == restic.NodeTypeDir:
|
case l > 1 && node.Type == data.NodeTypeDir:
|
||||||
subtree, err := restic.LoadTree(ctx, repo, *node.Subtree)
|
subtree, err := data.LoadTree(ctx, repo, *node.Subtree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrapf(err, "cannot load subtree for %q", item)
|
return errors.Wrapf(err, "cannot load subtree for %q", item)
|
||||||
}
|
}
|
||||||
return printFromTree(ctx, subtree, repo, item, pathComponents[1:], d, canWriteArchiveFunc)
|
return printFromTree(ctx, subtree, repo, item, pathComponents[1:], d, canWriteArchiveFunc)
|
||||||
case node.Type == restic.NodeTypeDir:
|
case node.Type == data.NodeTypeDir:
|
||||||
if err := canWriteArchiveFunc(); err != nil {
|
if err := canWriteArchiveFunc(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
subtree, err := restic.LoadTree(ctx, repo, *node.Subtree)
|
subtree, err := data.LoadTree(ctx, repo, *node.Subtree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return d.DumpTree(ctx, subtree, item)
|
return d.DumpTree(ctx, subtree, item)
|
||||||
case l > 1:
|
case l > 1:
|
||||||
return fmt.Errorf("%q should be a dir, but is a %q", item, node.Type)
|
return fmt.Errorf("%q should be a dir, but is a %q", item, node.Type)
|
||||||
case node.Type != restic.NodeTypeFile:
|
case node.Type != data.NodeTypeFile:
|
||||||
return fmt.Errorf("%q should be a file, but is a %q", item, node.Type)
|
return fmt.Errorf("%q should be a file, but is a %q", item, node.Type)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -151,7 +152,7 @@ func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []
|
|||||||
}
|
}
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
sn, subfolder, err := (&restic.SnapshotFilter{
|
sn, subfolder, err := (&data.SnapshotFilter{
|
||||||
Hosts: opts.Hosts,
|
Hosts: opts.Hosts,
|
||||||
Paths: opts.Paths,
|
Paths: opts.Paths,
|
||||||
Tags: opts.Tags,
|
Tags: opts.Tags,
|
||||||
@@ -165,12 +166,12 @@ func runDump(ctx context.Context, opts DumpOptions, gopts GlobalOptions, args []
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sn.Tree, err = restic.FindTreeDirectory(ctx, repo, sn.Tree, subfolder)
|
sn.Tree, err = data.FindTreeDirectory(ctx, repo, sn.Tree, subfolder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tree, err := restic.LoadTree(ctx, repo, *sn.Tree)
|
tree, err := data.LoadTree(ctx, repo, *sn.Tree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Fatalf("loading tree for snapshot %q failed: %v", snapshotIDString, err)
|
return errors.Fatalf("loading tree for snapshot %q failed: %v", snapshotIDString, err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/filter"
|
"github.com/restic/restic/internal/filter"
|
||||||
@@ -70,7 +71,7 @@ type FindOptions struct {
|
|||||||
ListLong bool
|
ListLong bool
|
||||||
HumanReadable bool
|
HumanReadable bool
|
||||||
Reverse bool
|
Reverse bool
|
||||||
restic.SnapshotFilter
|
data.SnapshotFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts *FindOptions) AddFlags(f *pflag.FlagSet) {
|
func (opts *FindOptions) AddFlags(f *pflag.FlagSet) {
|
||||||
@@ -124,8 +125,8 @@ type statefulOutput struct {
|
|||||||
HumanReadable bool
|
HumanReadable bool
|
||||||
JSON bool
|
JSON bool
|
||||||
inuse bool
|
inuse bool
|
||||||
newsn *restic.Snapshot
|
newsn *data.Snapshot
|
||||||
oldsn *restic.Snapshot
|
oldsn *data.Snapshot
|
||||||
hits int
|
hits int
|
||||||
printer interface {
|
printer interface {
|
||||||
S(string, ...interface{})
|
S(string, ...interface{})
|
||||||
@@ -135,8 +136,8 @@ type statefulOutput struct {
|
|||||||
stdout io.Writer
|
stdout io.Writer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statefulOutput) PrintPatternJSON(path string, node *restic.Node) {
|
func (s *statefulOutput) PrintPatternJSON(path string, node *data.Node) {
|
||||||
type findNode restic.Node
|
type findNode data.Node
|
||||||
b, err := json.Marshal(struct {
|
b, err := json.Marshal(struct {
|
||||||
// Add these attributes
|
// Add these attributes
|
||||||
Path string `json:"path,omitempty"`
|
Path string `json:"path,omitempty"`
|
||||||
@@ -179,7 +180,7 @@ func (s *statefulOutput) PrintPatternJSON(path string, node *restic.Node) {
|
|||||||
s.hits++
|
s.hits++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statefulOutput) PrintPatternNormal(path string, node *restic.Node) {
|
func (s *statefulOutput) PrintPatternNormal(path string, node *data.Node) {
|
||||||
if s.newsn != s.oldsn {
|
if s.newsn != s.oldsn {
|
||||||
if s.oldsn != nil {
|
if s.oldsn != nil {
|
||||||
s.printer.P("")
|
s.printer.P("")
|
||||||
@@ -190,7 +191,7 @@ func (s *statefulOutput) PrintPatternNormal(path string, node *restic.Node) {
|
|||||||
s.printer.S(formatNode(path, node, s.ListLong, s.HumanReadable))
|
s.printer.S(formatNode(path, node, s.ListLong, s.HumanReadable))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statefulOutput) PrintPattern(path string, node *restic.Node) {
|
func (s *statefulOutput) PrintPattern(path string, node *data.Node) {
|
||||||
if s.JSON {
|
if s.JSON {
|
||||||
s.PrintPatternJSON(path, node)
|
s.PrintPatternJSON(path, node)
|
||||||
} else {
|
} else {
|
||||||
@@ -198,7 +199,7 @@ func (s *statefulOutput) PrintPattern(path string, node *restic.Node) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statefulOutput) PrintObjectJSON(kind, id, nodepath, treeID string, sn *restic.Snapshot) {
|
func (s *statefulOutput) PrintObjectJSON(kind, id, nodepath, treeID string, sn *data.Snapshot) {
|
||||||
b, err := json.Marshal(struct {
|
b, err := json.Marshal(struct {
|
||||||
// Add these attributes
|
// Add these attributes
|
||||||
ObjectType string `json:"object_type"`
|
ObjectType string `json:"object_type"`
|
||||||
@@ -230,7 +231,7 @@ func (s *statefulOutput) PrintObjectJSON(kind, id, nodepath, treeID string, sn *
|
|||||||
s.hits++
|
s.hits++
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statefulOutput) PrintObjectNormal(kind, id, nodepath, treeID string, sn *restic.Snapshot) {
|
func (s *statefulOutput) PrintObjectNormal(kind, id, nodepath, treeID string, sn *data.Snapshot) {
|
||||||
s.printer.S("Found %s %s", kind, id)
|
s.printer.S("Found %s %s", kind, id)
|
||||||
if kind == "blob" {
|
if kind == "blob" {
|
||||||
s.printer.S(" ... in file %s", nodepath)
|
s.printer.S(" ... in file %s", nodepath)
|
||||||
@@ -241,7 +242,7 @@ func (s *statefulOutput) PrintObjectNormal(kind, id, nodepath, treeID string, sn
|
|||||||
s.printer.S(" ... in snapshot %s (%s)", sn.ID().Str(), sn.Time.Local().Format(TimeFormat))
|
s.printer.S(" ... in snapshot %s (%s)", sn.ID().Str(), sn.Time.Local().Format(TimeFormat))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *statefulOutput) PrintObject(kind, id, nodepath, treeID string, sn *restic.Snapshot) {
|
func (s *statefulOutput) PrintObject(kind, id, nodepath, treeID string, sn *data.Snapshot) {
|
||||||
if s.JSON {
|
if s.JSON {
|
||||||
s.PrintObjectJSON(kind, id, nodepath, treeID, sn)
|
s.PrintObjectJSON(kind, id, nodepath, treeID, sn)
|
||||||
} else {
|
} else {
|
||||||
@@ -279,7 +280,7 @@ type Finder struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Finder) findInSnapshot(ctx context.Context, sn *restic.Snapshot) error {
|
func (f *Finder) findInSnapshot(ctx context.Context, sn *data.Snapshot) error {
|
||||||
debug.Log("searching in snapshot %s\n for entries within [%s %s]", sn.ID(), f.pat.oldest, f.pat.newest)
|
debug.Log("searching in snapshot %s\n for entries within [%s %s]", sn.ID(), f.pat.oldest, f.pat.newest)
|
||||||
|
|
||||||
if sn.Tree == nil {
|
if sn.Tree == nil {
|
||||||
@@ -287,7 +288,7 @@ func (f *Finder) findInSnapshot(ctx context.Context, sn *restic.Snapshot) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
f.out.newsn = sn
|
f.out.newsn = sn
|
||||||
return walker.Walk(ctx, f.repo, *sn.Tree, walker.WalkVisitor{ProcessNode: func(parentTreeID restic.ID, nodepath string, node *restic.Node, err error) error {
|
return walker.Walk(ctx, f.repo, *sn.Tree, walker.WalkVisitor{ProcessNode: func(parentTreeID restic.ID, nodepath string, node *data.Node, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Log("Error loading tree %v: %v", parentTreeID, err)
|
debug.Log("Error loading tree %v: %v", parentTreeID, err)
|
||||||
|
|
||||||
@@ -320,7 +321,7 @@ func (f *Finder) findInSnapshot(ctx context.Context, sn *restic.Snapshot) error
|
|||||||
}
|
}
|
||||||
|
|
||||||
var errIfNoMatch error
|
var errIfNoMatch error
|
||||||
if node.Type == restic.NodeTypeDir {
|
if node.Type == data.NodeTypeDir {
|
||||||
var childMayMatch bool
|
var childMayMatch bool
|
||||||
for _, pat := range f.pat.pattern {
|
for _, pat := range f.pat.pattern {
|
||||||
mayMatch, err := filter.ChildMatch(pat, normalizedNodepath)
|
mayMatch, err := filter.ChildMatch(pat, normalizedNodepath)
|
||||||
@@ -378,7 +379,7 @@ func (f *Finder) findTree(treeID restic.ID, nodepath string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Finder) findIDs(ctx context.Context, sn *restic.Snapshot) error {
|
func (f *Finder) findIDs(ctx context.Context, sn *data.Snapshot) error {
|
||||||
debug.Log("searching IDs in snapshot %s", sn.ID())
|
debug.Log("searching IDs in snapshot %s", sn.ID())
|
||||||
|
|
||||||
if sn.Tree == nil {
|
if sn.Tree == nil {
|
||||||
@@ -386,7 +387,7 @@ func (f *Finder) findIDs(ctx context.Context, sn *restic.Snapshot) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
f.out.newsn = sn
|
f.out.newsn = sn
|
||||||
return walker.Walk(ctx, f.repo, *sn.Tree, walker.WalkVisitor{ProcessNode: func(parentTreeID restic.ID, nodepath string, node *restic.Node, err error) error {
|
return walker.Walk(ctx, f.repo, *sn.Tree, walker.WalkVisitor{ProcessNode: func(parentTreeID restic.ID, nodepath string, node *data.Node, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Log("Error loading tree %v: %v", parentTreeID, err)
|
debug.Log("Error loading tree %v: %v", parentTreeID, err)
|
||||||
|
|
||||||
@@ -411,7 +412,7 @@ func (f *Finder) findIDs(ctx context.Context, sn *restic.Snapshot) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.Type == restic.NodeTypeFile && f.blobIDs != nil {
|
if node.Type == data.NodeTypeFile && f.blobIDs != nil {
|
||||||
for _, id := range node.Content {
|
for _, id := range node.Content {
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
@@ -654,7 +655,7 @@ func runFind(ctx context.Context, opts FindOptions, gopts GlobalOptions, args []
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var filteredSnapshots []*restic.Snapshot
|
var filteredSnapshots []*data.Snapshot
|
||||||
for sn := range FindFilteredSnapshots(ctx, snapshotLister, repo, &opts.SnapshotFilter, opts.Snapshots, printer) {
|
for sn := range FindFilteredSnapshots(ctx, snapshotLister, repo, &opts.SnapshotFilter, opts.Snapshots, printer) {
|
||||||
filteredSnapshots = append(filteredSnapshots, sn)
|
filteredSnapshots = append(filteredSnapshots, sn)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
@@ -102,21 +103,21 @@ type ForgetOptions struct {
|
|||||||
Weekly ForgetPolicyCount
|
Weekly ForgetPolicyCount
|
||||||
Monthly ForgetPolicyCount
|
Monthly ForgetPolicyCount
|
||||||
Yearly ForgetPolicyCount
|
Yearly ForgetPolicyCount
|
||||||
Within restic.Duration
|
Within data.Duration
|
||||||
WithinHourly restic.Duration
|
WithinHourly data.Duration
|
||||||
WithinDaily restic.Duration
|
WithinDaily data.Duration
|
||||||
WithinWeekly restic.Duration
|
WithinWeekly data.Duration
|
||||||
WithinMonthly restic.Duration
|
WithinMonthly data.Duration
|
||||||
WithinYearly restic.Duration
|
WithinYearly data.Duration
|
||||||
KeepTags restic.TagLists
|
KeepTags data.TagLists
|
||||||
|
|
||||||
UnsafeAllowRemoveAll bool
|
UnsafeAllowRemoveAll bool
|
||||||
|
|
||||||
restic.SnapshotFilter
|
data.SnapshotFilter
|
||||||
Compact bool
|
Compact bool
|
||||||
|
|
||||||
// Grouping
|
// Grouping
|
||||||
GroupBy restic.SnapshotGroupByOptions
|
GroupBy data.SnapshotGroupByOptions
|
||||||
DryRun bool
|
DryRun bool
|
||||||
Prune bool
|
Prune bool
|
||||||
}
|
}
|
||||||
@@ -147,7 +148,7 @@ func (opts *ForgetOptions) AddFlags(f *pflag.FlagSet) {
|
|||||||
initMultiSnapshotFilter(f, &opts.SnapshotFilter, false)
|
initMultiSnapshotFilter(f, &opts.SnapshotFilter, false)
|
||||||
|
|
||||||
f.BoolVarP(&opts.Compact, "compact", "c", false, "use compact output format")
|
f.BoolVarP(&opts.Compact, "compact", "c", false, "use compact output format")
|
||||||
opts.GroupBy = restic.SnapshotGroupByOptions{Host: true, Path: true}
|
opts.GroupBy = data.SnapshotGroupByOptions{Host: true, Path: true}
|
||||||
f.VarP(&opts.GroupBy, "group-by", "g", "`group` snapshots by host, paths and/or tags, separated by comma (disable grouping with '')")
|
f.VarP(&opts.GroupBy, "group-by", "g", "`group` snapshots by host, paths and/or tags, separated by comma (disable grouping with '')")
|
||||||
f.BoolVarP(&opts.DryRun, "dry-run", "n", false, "do not delete anything, just print what would be done")
|
f.BoolVarP(&opts.DryRun, "dry-run", "n", false, "do not delete anything, just print what would be done")
|
||||||
f.BoolVar(&opts.Prune, "prune", false, "automatically run the 'prune' command if snapshots have been removed")
|
f.BoolVar(&opts.Prune, "prune", false, "automatically run the 'prune' command if snapshots have been removed")
|
||||||
@@ -161,7 +162,7 @@ func verifyForgetOptions(opts *ForgetOptions) error {
|
|||||||
return errors.Fatal("negative values other than -1 are not allowed for --keep-*")
|
return errors.Fatal("negative values other than -1 are not allowed for --keep-*")
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, d := range []restic.Duration{opts.Within, opts.WithinHourly, opts.WithinDaily,
|
for _, d := range []data.Duration{opts.Within, opts.WithinHourly, opts.WithinDaily,
|
||||||
opts.WithinMonthly, opts.WithinWeekly, opts.WithinYearly} {
|
opts.WithinMonthly, opts.WithinWeekly, opts.WithinYearly} {
|
||||||
if d.Hours < 0 || d.Days < 0 || d.Months < 0 || d.Years < 0 {
|
if d.Hours < 0 || d.Days < 0 || d.Months < 0 || d.Years < 0 {
|
||||||
return errors.Fatal("durations containing negative values are not allowed for --keep-within*")
|
return errors.Fatal("durations containing negative values are not allowed for --keep-within*")
|
||||||
@@ -193,7 +194,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption
|
|||||||
}
|
}
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
var snapshots restic.Snapshots
|
var snapshots data.Snapshots
|
||||||
removeSnIDs := restic.NewIDSet()
|
removeSnIDs := restic.NewIDSet()
|
||||||
|
|
||||||
for sn := range FindFilteredSnapshots(ctx, repo, repo, &opts.SnapshotFilter, args, printer) {
|
for sn := range FindFilteredSnapshots(ctx, repo, repo, &opts.SnapshotFilter, args, printer) {
|
||||||
@@ -211,12 +212,12 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption
|
|||||||
removeSnIDs.Insert(*sn.ID())
|
removeSnIDs.Insert(*sn.ID())
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
snapshotGroups, _, err := restic.GroupSnapshots(snapshots, opts.GroupBy)
|
snapshotGroups, _, err := data.GroupSnapshots(snapshots, opts.GroupBy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
policy := restic.ExpirePolicy{
|
policy := data.ExpirePolicy{
|
||||||
Last: int(opts.Last),
|
Last: int(opts.Last),
|
||||||
Hourly: int(opts.Hourly),
|
Hourly: int(opts.Hourly),
|
||||||
Daily: int(opts.Daily),
|
Daily: int(opts.Daily),
|
||||||
@@ -257,7 +258,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var key restic.SnapshotGroupKey
|
var key data.SnapshotGroupKey
|
||||||
if json.Unmarshal([]byte(k), &key) != nil {
|
if json.Unmarshal([]byte(k), &key) != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -267,7 +268,7 @@ func runForget(ctx context.Context, opts ForgetOptions, pruneOptions PruneOption
|
|||||||
fg.Host = key.Hostname
|
fg.Host = key.Hostname
|
||||||
fg.Paths = key.Paths
|
fg.Paths = key.Paths
|
||||||
|
|
||||||
keep, remove, reasons := restic.ApplyPolicy(snapshotGroup, policy)
|
keep, remove, reasons := data.ApplyPolicy(snapshotGroup, policy)
|
||||||
|
|
||||||
if !policy.Empty() && len(keep) == 0 {
|
if !policy.Empty() && len(keep) == 0 {
|
||||||
return fmt.Errorf("refusing to delete last snapshot of snapshot group \"%v\"", key.String())
|
return fmt.Errorf("refusing to delete last snapshot of snapshot group \"%v\"", key.String())
|
||||||
@@ -361,7 +362,7 @@ type ForgetGroup struct {
|
|||||||
Reasons []KeepReason `json:"reasons"`
|
Reasons []KeepReason `json:"reasons"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func asJSONSnapshots(list restic.Snapshots) []Snapshot {
|
func asJSONSnapshots(list data.Snapshots) []Snapshot {
|
||||||
var resultList []Snapshot
|
var resultList []Snapshot
|
||||||
for _, sn := range list {
|
for _, sn := range list {
|
||||||
k := Snapshot{
|
k := Snapshot{
|
||||||
@@ -380,7 +381,7 @@ type KeepReason struct {
|
|||||||
Matches []string `json:"matches"`
|
Matches []string `json:"matches"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func asJSONKeeps(list []restic.KeepReason) []KeepReason {
|
func asJSONKeeps(list []data.KeepReason) []KeepReason {
|
||||||
var resultList []KeepReason
|
var resultList []KeepReason
|
||||||
for _, keep := range list {
|
for _, keep := range list {
|
||||||
k := KeepReason{
|
k := KeepReason{
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/data"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -38,8 +38,8 @@ func TestRunForgetSafetyNet(t *testing.T) {
|
|||||||
|
|
||||||
// --keep-tags invalid
|
// --keep-tags invalid
|
||||||
err := testRunForgetMayFail(t, env.gopts, ForgetOptions{
|
err := testRunForgetMayFail(t, env.gopts, ForgetOptions{
|
||||||
KeepTags: restic.TagLists{restic.TagList{"invalid"}},
|
KeepTags: data.TagLists{data.TagList{"invalid"}},
|
||||||
GroupBy: restic.SnapshotGroupByOptions{Host: true, Path: true},
|
GroupBy: data.SnapshotGroupByOptions{Host: true, Path: true},
|
||||||
})
|
})
|
||||||
rtest.Assert(t, strings.Contains(err.Error(), `refusing to delete last snapshot of snapshot group "host example, path`), "wrong error message got %v", err)
|
rtest.Assert(t, strings.Contains(err.Error(), `refusing to delete last snapshot of snapshot group "host example, path`), "wrong error message got %v", err)
|
||||||
|
|
||||||
@@ -56,8 +56,8 @@ func TestRunForgetSafetyNet(t *testing.T) {
|
|||||||
// `forget --host example --unsafe-allow-remove-all` should work
|
// `forget --host example --unsafe-allow-remove-all` should work
|
||||||
testRunForget(t, env.gopts, ForgetOptions{
|
testRunForget(t, env.gopts, ForgetOptions{
|
||||||
UnsafeAllowRemoveAll: true,
|
UnsafeAllowRemoveAll: true,
|
||||||
GroupBy: restic.SnapshotGroupByOptions{Host: true, Path: true},
|
GroupBy: data.SnapshotGroupByOptions{Host: true, Path: true},
|
||||||
SnapshotFilter: restic.SnapshotFilter{
|
SnapshotFilter: data.SnapshotFilter{
|
||||||
Hosts: []string{opts.Host},
|
Hosts: []string{opts.Host},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/data"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
@@ -69,18 +69,18 @@ func TestForgetOptionValues(t *testing.T) {
|
|||||||
{ForgetOptions{Weekly: -2}, negValErrorMsg},
|
{ForgetOptions{Weekly: -2}, negValErrorMsg},
|
||||||
{ForgetOptions{Monthly: -2}, negValErrorMsg},
|
{ForgetOptions{Monthly: -2}, negValErrorMsg},
|
||||||
{ForgetOptions{Yearly: -2}, negValErrorMsg},
|
{ForgetOptions{Yearly: -2}, negValErrorMsg},
|
||||||
{ForgetOptions{Within: restic.ParseDurationOrPanic("1y2m3d3h")}, ""},
|
{ForgetOptions{Within: data.ParseDurationOrPanic("1y2m3d3h")}, ""},
|
||||||
{ForgetOptions{WithinHourly: restic.ParseDurationOrPanic("1y2m3d3h")}, ""},
|
{ForgetOptions{WithinHourly: data.ParseDurationOrPanic("1y2m3d3h")}, ""},
|
||||||
{ForgetOptions{WithinDaily: restic.ParseDurationOrPanic("1y2m3d3h")}, ""},
|
{ForgetOptions{WithinDaily: data.ParseDurationOrPanic("1y2m3d3h")}, ""},
|
||||||
{ForgetOptions{WithinWeekly: restic.ParseDurationOrPanic("1y2m3d3h")}, ""},
|
{ForgetOptions{WithinWeekly: data.ParseDurationOrPanic("1y2m3d3h")}, ""},
|
||||||
{ForgetOptions{WithinMonthly: restic.ParseDurationOrPanic("2y4m6d8h")}, ""},
|
{ForgetOptions{WithinMonthly: data.ParseDurationOrPanic("2y4m6d8h")}, ""},
|
||||||
{ForgetOptions{WithinYearly: restic.ParseDurationOrPanic("2y4m6d8h")}, ""},
|
{ForgetOptions{WithinYearly: data.ParseDurationOrPanic("2y4m6d8h")}, ""},
|
||||||
{ForgetOptions{Within: restic.ParseDurationOrPanic("-1y2m3d3h")}, negDurationValErrorMsg},
|
{ForgetOptions{Within: data.ParseDurationOrPanic("-1y2m3d3h")}, negDurationValErrorMsg},
|
||||||
{ForgetOptions{WithinHourly: restic.ParseDurationOrPanic("1y-2m3d3h")}, negDurationValErrorMsg},
|
{ForgetOptions{WithinHourly: data.ParseDurationOrPanic("1y-2m3d3h")}, negDurationValErrorMsg},
|
||||||
{ForgetOptions{WithinDaily: restic.ParseDurationOrPanic("1y2m-3d3h")}, negDurationValErrorMsg},
|
{ForgetOptions{WithinDaily: data.ParseDurationOrPanic("1y2m-3d3h")}, negDurationValErrorMsg},
|
||||||
{ForgetOptions{WithinWeekly: restic.ParseDurationOrPanic("1y2m3d-3h")}, negDurationValErrorMsg},
|
{ForgetOptions{WithinWeekly: data.ParseDurationOrPanic("1y2m3d-3h")}, negDurationValErrorMsg},
|
||||||
{ForgetOptions{WithinMonthly: restic.ParseDurationOrPanic("-2y4m6d8h")}, negDurationValErrorMsg},
|
{ForgetOptions{WithinMonthly: data.ParseDurationOrPanic("-2y4m6d8h")}, negDurationValErrorMsg},
|
||||||
{ForgetOptions{WithinYearly: restic.ParseDurationOrPanic("2y-4m6d8h")}, negDurationValErrorMsg},
|
{ForgetOptions{WithinYearly: data.ParseDurationOrPanic("2y-4m6d8h")}, negDurationValErrorMsg},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/fs"
|
"github.com/restic/restic/internal/fs"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
@@ -70,7 +71,7 @@ Exit status is 12 if the password is incorrect.
|
|||||||
// LsOptions collects all options for the ls command.
|
// LsOptions collects all options for the ls command.
|
||||||
type LsOptions struct {
|
type LsOptions struct {
|
||||||
ListLong bool
|
ListLong bool
|
||||||
restic.SnapshotFilter
|
data.SnapshotFilter
|
||||||
Recursive bool
|
Recursive bool
|
||||||
HumanReadable bool
|
HumanReadable bool
|
||||||
Ncdu bool
|
Ncdu bool
|
||||||
@@ -89,8 +90,8 @@ func (opts *LsOptions) AddFlags(f *pflag.FlagSet) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type lsPrinter interface {
|
type lsPrinter interface {
|
||||||
Snapshot(sn *restic.Snapshot) error
|
Snapshot(sn *data.Snapshot) error
|
||||||
Node(path string, node *restic.Node, isPrefixDirectory bool) error
|
Node(path string, node *data.Node, isPrefixDirectory bool) error
|
||||||
LeaveDir(path string) error
|
LeaveDir(path string) error
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
@@ -99,9 +100,9 @@ type jsonLsPrinter struct {
|
|||||||
enc *json.Encoder
|
enc *json.Encoder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *jsonLsPrinter) Snapshot(sn *restic.Snapshot) error {
|
func (p *jsonLsPrinter) Snapshot(sn *data.Snapshot) error {
|
||||||
type lsSnapshot struct {
|
type lsSnapshot struct {
|
||||||
*restic.Snapshot
|
*data.Snapshot
|
||||||
ID *restic.ID `json:"id"`
|
ID *restic.ID `json:"id"`
|
||||||
ShortID string `json:"short_id"` // deprecated
|
ShortID string `json:"short_id"` // deprecated
|
||||||
MessageType string `json:"message_type"` // "snapshot"
|
MessageType string `json:"message_type"` // "snapshot"
|
||||||
@@ -118,14 +119,14 @@ func (p *jsonLsPrinter) Snapshot(sn *restic.Snapshot) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Node formats node in our custom JSON format, followed by a newline.
|
// Node formats node in our custom JSON format, followed by a newline.
|
||||||
func (p *jsonLsPrinter) Node(path string, node *restic.Node, isPrefixDirectory bool) error {
|
func (p *jsonLsPrinter) Node(path string, node *data.Node, isPrefixDirectory bool) error {
|
||||||
if isPrefixDirectory {
|
if isPrefixDirectory {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return lsNodeJSON(p.enc, path, node)
|
return lsNodeJSON(p.enc, path, node)
|
||||||
}
|
}
|
||||||
|
|
||||||
func lsNodeJSON(enc *json.Encoder, path string, node *restic.Node) error {
|
func lsNodeJSON(enc *json.Encoder, path string, node *data.Node) error {
|
||||||
n := &struct {
|
n := &struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
@@ -161,7 +162,7 @@ func lsNodeJSON(enc *json.Encoder, path string, node *restic.Node) error {
|
|||||||
}
|
}
|
||||||
// Always print size for regular files, even when empty,
|
// Always print size for regular files, even when empty,
|
||||||
// but never for other types.
|
// but never for other types.
|
||||||
if node.Type == restic.NodeTypeFile {
|
if node.Type == data.NodeTypeFile {
|
||||||
n.Size = &n.size
|
n.Size = &n.size
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +180,7 @@ type ncduLsPrinter struct {
|
|||||||
// Snapshot prints a restic snapshot in Ncdu save format.
|
// Snapshot prints a restic snapshot in Ncdu save format.
|
||||||
// It opens the JSON list. Nodes are added with lsNodeNcdu and the list is closed by lsCloseNcdu.
|
// It opens the JSON list. Nodes are added with lsNodeNcdu and the list is closed by lsCloseNcdu.
|
||||||
// Format documentation: https://dev.yorhel.nl/ncdu/jsonfmt
|
// Format documentation: https://dev.yorhel.nl/ncdu/jsonfmt
|
||||||
func (p *ncduLsPrinter) Snapshot(sn *restic.Snapshot) error {
|
func (p *ncduLsPrinter) Snapshot(sn *data.Snapshot) error {
|
||||||
const NcduMajorVer = 1
|
const NcduMajorVer = 1
|
||||||
const NcduMinorVer = 2
|
const NcduMinorVer = 2
|
||||||
|
|
||||||
@@ -192,7 +193,7 @@ func (p *ncduLsPrinter) Snapshot(sn *restic.Snapshot) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func lsNcduNode(_ string, node *restic.Node) ([]byte, error) {
|
func lsNcduNode(_ string, node *data.Node) ([]byte, error) {
|
||||||
type NcduNode struct {
|
type NcduNode struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Asize uint64 `json:"asize"`
|
Asize uint64 `json:"asize"`
|
||||||
@@ -217,7 +218,7 @@ func lsNcduNode(_ string, node *restic.Node) ([]byte, error) {
|
|||||||
Dev: node.DeviceID,
|
Dev: node.DeviceID,
|
||||||
Ino: node.Inode,
|
Ino: node.Inode,
|
||||||
NLink: node.Links,
|
NLink: node.Links,
|
||||||
NotReg: node.Type != restic.NodeTypeDir && node.Type != restic.NodeTypeFile,
|
NotReg: node.Type != data.NodeTypeDir && node.Type != data.NodeTypeFile,
|
||||||
UID: node.UID,
|
UID: node.UID,
|
||||||
GID: node.GID,
|
GID: node.GID,
|
||||||
Mode: uint16(node.Mode & os.ModePerm),
|
Mode: uint16(node.Mode & os.ModePerm),
|
||||||
@@ -241,13 +242,13 @@ func lsNcduNode(_ string, node *restic.Node) ([]byte, error) {
|
|||||||
return json.Marshal(outNode)
|
return json.Marshal(outNode)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *ncduLsPrinter) Node(path string, node *restic.Node, _ bool) error {
|
func (p *ncduLsPrinter) Node(path string, node *data.Node, _ bool) error {
|
||||||
out, err := lsNcduNode(path, node)
|
out, err := lsNcduNode(path, node)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.Type == restic.NodeTypeDir {
|
if node.Type == data.NodeTypeDir {
|
||||||
_, err = fmt.Fprintf(p.out, ",\n%s[\n%s%s", strings.Repeat(" ", p.depth), strings.Repeat(" ", p.depth+1), string(out))
|
_, err = fmt.Fprintf(p.out, ",\n%s[\n%s%s", strings.Repeat(" ", p.depth), strings.Repeat(" ", p.depth+1), string(out))
|
||||||
p.depth++
|
p.depth++
|
||||||
} else {
|
} else {
|
||||||
@@ -277,11 +278,11 @@ type textLsPrinter struct {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *textLsPrinter) Snapshot(sn *restic.Snapshot) error {
|
func (p *textLsPrinter) Snapshot(sn *data.Snapshot) error {
|
||||||
p.termPrinter.P("%v filtered by %v:", sn, p.dirs)
|
p.termPrinter.P("%v filtered by %v:", sn, p.dirs)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
func (p *textLsPrinter) Node(path string, node *restic.Node, isPrefixDirectory bool) error {
|
func (p *textLsPrinter) Node(path string, node *data.Node, isPrefixDirectory bool) error {
|
||||||
if !isPrefixDirectory {
|
if !isPrefixDirectory {
|
||||||
p.termPrinter.S("%s", formatNode(path, node, p.ListLong, p.HumanReadable))
|
p.termPrinter.S("%s", formatNode(path, node, p.ListLong, p.HumanReadable))
|
||||||
}
|
}
|
||||||
@@ -298,7 +299,7 @@ func (p *textLsPrinter) Close() error {
|
|||||||
// for ls -l output sorting
|
// for ls -l output sorting
|
||||||
type toSortOutput struct {
|
type toSortOutput struct {
|
||||||
nodepath string
|
nodepath string
|
||||||
node *restic.Node
|
node *data.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []string, term ui.Terminal) error {
|
||||||
@@ -403,7 +404,7 @@ func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []stri
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sn, subfolder, err := (&restic.SnapshotFilter{
|
sn, subfolder, err := (&data.SnapshotFilter{
|
||||||
Hosts: opts.Hosts,
|
Hosts: opts.Hosts,
|
||||||
Paths: opts.Paths,
|
Paths: opts.Paths,
|
||||||
Tags: opts.Tags,
|
Tags: opts.Tags,
|
||||||
@@ -412,7 +413,7 @@ func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []stri
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sn.Tree, err = restic.FindTreeDirectory(ctx, repo, sn.Tree, subfolder)
|
sn.Tree, err = data.FindTreeDirectory(ctx, repo, sn.Tree, subfolder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -421,7 +422,7 @@ func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []stri
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
processNode := func(_ restic.ID, nodepath string, node *restic.Node, err error) error {
|
processNode := func(_ restic.ID, nodepath string, node *data.Node, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -456,7 +457,7 @@ func runLs(ctx context.Context, opts LsOptions, gopts GlobalOptions, args []stri
|
|||||||
|
|
||||||
// otherwise, signal the walker to not walk recursively into any
|
// otherwise, signal the walker to not walk recursively into any
|
||||||
// subdirs
|
// subdirs
|
||||||
if node.Type == restic.NodeTypeDir {
|
if node.Type == data.NodeTypeDir {
|
||||||
// immediately generate leaveDir if the directory is skipped
|
// immediately generate leaveDir if the directory is skipped
|
||||||
if printedDir {
|
if printedDir {
|
||||||
if err := printer.LeaveDir(nodepath); err != nil {
|
if err := printer.LeaveDir(nodepath); err != nil {
|
||||||
@@ -493,10 +494,10 @@ type sortedPrinter struct {
|
|||||||
reverse bool
|
reverse bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *sortedPrinter) Snapshot(sn *restic.Snapshot) error {
|
func (p *sortedPrinter) Snapshot(sn *data.Snapshot) error {
|
||||||
return p.printer.Snapshot(sn)
|
return p.printer.Snapshot(sn)
|
||||||
}
|
}
|
||||||
func (p *sortedPrinter) Node(path string, node *restic.Node, isPrefixDirectory bool) error {
|
func (p *sortedPrinter) Node(path string, node *data.Node, isPrefixDirectory bool) error {
|
||||||
if !isPrefixDirectory {
|
if !isPrefixDirectory {
|
||||||
p.collector = append(p.collector, toSortOutput{path, node})
|
p.collector = append(p.collector, toSortOutput{path, node})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
@@ -129,7 +130,7 @@ func TestRunLsJson(t *testing.T) {
|
|||||||
|
|
||||||
// partial copy of snapshot structure from cmd_ls
|
// partial copy of snapshot structure from cmd_ls
|
||||||
type lsSnapshot struct {
|
type lsSnapshot struct {
|
||||||
*restic.Snapshot
|
*data.Snapshot
|
||||||
ID *restic.ID `json:"id"`
|
ID *restic.ID `json:"id"`
|
||||||
ShortID string `json:"short_id"` // deprecated
|
ShortID string `json:"short_id"` // deprecated
|
||||||
MessageType string `json:"message_type"` // "snapshot"
|
MessageType string `json:"message_type"` // "snapshot"
|
||||||
|
|||||||
@@ -7,13 +7,13 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/data"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
type lsTestNode struct {
|
type lsTestNode struct {
|
||||||
path string
|
path string
|
||||||
restic.Node
|
data.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
var lsTestNodes = []lsTestNode{
|
var lsTestNodes = []lsTestNode{
|
||||||
@@ -21,9 +21,9 @@ var lsTestNodes = []lsTestNode{
|
|||||||
// Permissions, by convention is "-" per mode bit
|
// Permissions, by convention is "-" per mode bit
|
||||||
{
|
{
|
||||||
path: "/bar/baz",
|
path: "/bar/baz",
|
||||||
Node: restic.Node{
|
Node: data.Node{
|
||||||
Name: "baz",
|
Name: "baz",
|
||||||
Type: restic.NodeTypeFile,
|
Type: data.NodeTypeFile,
|
||||||
Size: 12345,
|
Size: 12345,
|
||||||
UID: 10000000,
|
UID: 10000000,
|
||||||
GID: 20000000,
|
GID: 20000000,
|
||||||
@@ -37,9 +37,9 @@ var lsTestNodes = []lsTestNode{
|
|||||||
// Even empty files get an explicit size.
|
// Even empty files get an explicit size.
|
||||||
{
|
{
|
||||||
path: "/foo/empty",
|
path: "/foo/empty",
|
||||||
Node: restic.Node{
|
Node: data.Node{
|
||||||
Name: "empty",
|
Name: "empty",
|
||||||
Type: restic.NodeTypeFile,
|
Type: data.NodeTypeFile,
|
||||||
Size: 0,
|
Size: 0,
|
||||||
UID: 1001,
|
UID: 1001,
|
||||||
GID: 1001,
|
GID: 1001,
|
||||||
@@ -54,9 +54,9 @@ var lsTestNodes = []lsTestNode{
|
|||||||
// Mode is printed in decimal, including the type bits.
|
// Mode is printed in decimal, including the type bits.
|
||||||
{
|
{
|
||||||
path: "/foo/link",
|
path: "/foo/link",
|
||||||
Node: restic.Node{
|
Node: data.Node{
|
||||||
Name: "link",
|
Name: "link",
|
||||||
Type: restic.NodeTypeSymlink,
|
Type: data.NodeTypeSymlink,
|
||||||
Mode: os.ModeSymlink | 0777,
|
Mode: os.ModeSymlink | 0777,
|
||||||
LinkTarget: "not printed",
|
LinkTarget: "not printed",
|
||||||
},
|
},
|
||||||
@@ -64,9 +64,9 @@ var lsTestNodes = []lsTestNode{
|
|||||||
|
|
||||||
{
|
{
|
||||||
path: "/some/directory",
|
path: "/some/directory",
|
||||||
Node: restic.Node{
|
Node: data.Node{
|
||||||
Name: "directory",
|
Name: "directory",
|
||||||
Type: restic.NodeTypeDir,
|
Type: data.NodeTypeDir,
|
||||||
Mode: os.ModeDir | 0755,
|
Mode: os.ModeDir | 0755,
|
||||||
ModTime: time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC),
|
ModTime: time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC),
|
||||||
AccessTime: time.Date(2021, 2, 3, 4, 5, 6, 7, time.UTC),
|
AccessTime: time.Date(2021, 2, 3, 4, 5, 6, 7, time.UTC),
|
||||||
@@ -77,9 +77,9 @@ var lsTestNodes = []lsTestNode{
|
|||||||
// Test encoding of setuid/setgid/sticky bit
|
// Test encoding of setuid/setgid/sticky bit
|
||||||
{
|
{
|
||||||
path: "/some/sticky",
|
path: "/some/sticky",
|
||||||
Node: restic.Node{
|
Node: data.Node{
|
||||||
Name: "sticky",
|
Name: "sticky",
|
||||||
Type: restic.NodeTypeDir,
|
Type: data.NodeTypeDir,
|
||||||
Mode: os.ModeDir | 0755 | os.ModeSetuid | os.ModeSetgid | os.ModeSticky,
|
Mode: os.ModeDir | 0755 | os.ModeSetuid | os.ModeSetgid | os.ModeSticky,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -134,24 +134,24 @@ func TestLsNcdu(t *testing.T) {
|
|||||||
}
|
}
|
||||||
modTime := time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC)
|
modTime := time.Date(2020, 1, 2, 3, 4, 5, 0, time.UTC)
|
||||||
|
|
||||||
rtest.OK(t, printer.Snapshot(&restic.Snapshot{
|
rtest.OK(t, printer.Snapshot(&data.Snapshot{
|
||||||
Hostname: "host",
|
Hostname: "host",
|
||||||
Paths: []string{"/example"},
|
Paths: []string{"/example"},
|
||||||
}))
|
}))
|
||||||
rtest.OK(t, printer.Node("/directory", &restic.Node{
|
rtest.OK(t, printer.Node("/directory", &data.Node{
|
||||||
Type: restic.NodeTypeDir,
|
Type: data.NodeTypeDir,
|
||||||
Name: "directory",
|
Name: "directory",
|
||||||
ModTime: modTime,
|
ModTime: modTime,
|
||||||
}, false))
|
}, false))
|
||||||
rtest.OK(t, printer.Node("/directory/data", &restic.Node{
|
rtest.OK(t, printer.Node("/directory/data", &data.Node{
|
||||||
Type: restic.NodeTypeFile,
|
Type: data.NodeTypeFile,
|
||||||
Name: "data",
|
Name: "data",
|
||||||
Size: 42,
|
Size: 42,
|
||||||
ModTime: modTime,
|
ModTime: modTime,
|
||||||
}, false))
|
}, false))
|
||||||
rtest.OK(t, printer.LeaveDir("/directory"))
|
rtest.OK(t, printer.LeaveDir("/directory"))
|
||||||
rtest.OK(t, printer.Node("/file", &restic.Node{
|
rtest.OK(t, printer.Node("/file", &data.Node{
|
||||||
Type: restic.NodeTypeFile,
|
Type: data.NodeTypeFile,
|
||||||
Name: "file",
|
Name: "file",
|
||||||
Size: 12345,
|
Size: 12345,
|
||||||
ModTime: modTime,
|
ModTime: modTime,
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/restic"
|
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/fuse"
|
"github.com/restic/restic/internal/fuse"
|
||||||
@@ -95,7 +95,7 @@ type MountOptions struct {
|
|||||||
OwnerRoot bool
|
OwnerRoot bool
|
||||||
AllowOther bool
|
AllowOther bool
|
||||||
NoDefaultPermissions bool
|
NoDefaultPermissions bool
|
||||||
restic.SnapshotFilter
|
data.SnapshotFilter
|
||||||
TimeTemplate string
|
TimeTemplate string
|
||||||
PathTemplates []string
|
PathTemplates []string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
systemFuse "github.com/anacrolix/fuse"
|
systemFuse "github.com/anacrolix/fuse"
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
@@ -137,7 +138,7 @@ func checkSnapshots(t testing.TB, gopts GlobalOptions, mountpoint string, snapsh
|
|||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
for _, id := range snapshotIDs {
|
for _, id := range snapshotIDs {
|
||||||
snapshot, err := restic.LoadSnapshot(ctx, repo, id)
|
snapshot, err := data.LoadSnapshot(ctx, repo, id)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
ts := snapshot.Time.Format(time.RFC3339)
|
ts := snapshot.Time.Format(time.RFC3339)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
@@ -278,8 +279,8 @@ func printPruneStats(printer progress.Printer, stats repository.PruneStats) erro
|
|||||||
func getUsedBlobs(ctx context.Context, repo restic.Repository, usedBlobs restic.FindBlobSet, ignoreSnapshots restic.IDSet, printer progress.Printer) error {
|
func getUsedBlobs(ctx context.Context, repo restic.Repository, usedBlobs restic.FindBlobSet, ignoreSnapshots restic.IDSet, printer progress.Printer) error {
|
||||||
var snapshotTrees restic.IDs
|
var snapshotTrees restic.IDs
|
||||||
printer.P("loading all snapshots...\n")
|
printer.P("loading all snapshots...\n")
|
||||||
err := restic.ForAllSnapshots(ctx, repo, repo, ignoreSnapshots,
|
err := data.ForAllSnapshots(ctx, repo, repo, ignoreSnapshots,
|
||||||
func(id restic.ID, sn *restic.Snapshot, err error) error {
|
func(id restic.ID, sn *data.Snapshot, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Log("failed to load snapshot %v (error %v)", id, err)
|
debug.Log("failed to load snapshot %v (error %v)", id, err)
|
||||||
return err
|
return err
|
||||||
@@ -298,5 +299,5 @@ func getUsedBlobs(ctx context.Context, repo restic.Repository, usedBlobs restic.
|
|||||||
bar.SetMax(uint64(len(snapshotTrees)))
|
bar.SetMax(uint64(len(snapshotTrees)))
|
||||||
defer bar.Done()
|
defer bar.Done()
|
||||||
|
|
||||||
return restic.FindUsedBlobs(ctx, repo, snapshotTrees, usedBlobs, bar)
|
return data.FindUsedBlobs(ctx, repo, snapshotTrees, usedBlobs, bar)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
@@ -87,7 +88,7 @@ func runRecover(ctx context.Context, gopts GlobalOptions, term ui.Terminal) erro
|
|||||||
bar := printer.NewCounter("trees loaded")
|
bar := printer.NewCounter("trees loaded")
|
||||||
bar.SetMax(uint64(len(trees)))
|
bar.SetMax(uint64(len(trees)))
|
||||||
for id := range trees {
|
for id := range trees {
|
||||||
tree, err := restic.LoadTree(ctx, repo, id)
|
tree, err := data.LoadTree(ctx, repo, id)
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
@@ -97,7 +98,7 @@ func runRecover(ctx context.Context, gopts GlobalOptions, term ui.Terminal) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, node := range tree.Nodes {
|
for _, node := range tree.Nodes {
|
||||||
if node.Type == restic.NodeTypeDir && node.Subtree != nil {
|
if node.Type == data.NodeTypeDir && node.Subtree != nil {
|
||||||
trees[*node.Subtree] = true
|
trees[*node.Subtree] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,7 +107,7 @@ func runRecover(ctx context.Context, gopts GlobalOptions, term ui.Terminal) erro
|
|||||||
bar.Done()
|
bar.Done()
|
||||||
|
|
||||||
printer.P("load snapshots\n")
|
printer.P("load snapshots\n")
|
||||||
err = restic.ForAllSnapshots(ctx, snapshotLister, repo, nil, func(_ restic.ID, sn *restic.Snapshot, _ error) error {
|
err = data.ForAllSnapshots(ctx, snapshotLister, repo, nil, func(_ restic.ID, sn *data.Snapshot, _ error) error {
|
||||||
trees[*sn.Tree] = true
|
trees[*sn.Tree] = true
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
@@ -133,11 +134,11 @@ func runRecover(ctx context.Context, gopts GlobalOptions, term ui.Terminal) erro
|
|||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
tree := restic.NewTree(len(roots))
|
tree := data.NewTree(len(roots))
|
||||||
for id := range roots {
|
for id := range roots {
|
||||||
var subtreeID = id
|
var subtreeID = id
|
||||||
node := restic.Node{
|
node := data.Node{
|
||||||
Type: restic.NodeTypeDir,
|
Type: data.NodeTypeDir,
|
||||||
Name: id.Str(),
|
Name: id.Str(),
|
||||||
Mode: 0755,
|
Mode: 0755,
|
||||||
Subtree: &subtreeID,
|
Subtree: &subtreeID,
|
||||||
@@ -157,7 +158,7 @@ func runRecover(ctx context.Context, gopts GlobalOptions, term ui.Terminal) erro
|
|||||||
var treeID restic.ID
|
var treeID restic.ID
|
||||||
wg.Go(func() error {
|
wg.Go(func() error {
|
||||||
var err error
|
var err error
|
||||||
treeID, err = restic.SaveTree(wgCtx, repo, tree)
|
treeID, err = data.SaveTree(wgCtx, repo, tree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Fatalf("unable to save new tree to the repository: %v", err)
|
return errors.Fatalf("unable to save new tree to the repository: %v", err)
|
||||||
}
|
}
|
||||||
@@ -178,14 +179,14 @@ func runRecover(ctx context.Context, gopts GlobalOptions, term ui.Terminal) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createSnapshot(ctx context.Context, printer progress.Printer, name, hostname string, tags []string, repo restic.SaverUnpacked[restic.WriteableFileType], tree *restic.ID) error {
|
func createSnapshot(ctx context.Context, printer progress.Printer, name, hostname string, tags []string, repo restic.SaverUnpacked[restic.WriteableFileType], tree *restic.ID) error {
|
||||||
sn, err := restic.NewSnapshot([]string{name}, tags, hostname, time.Now())
|
sn, err := data.NewSnapshot([]string{name}, tags, hostname, time.Now())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Fatalf("unable to save snapshot: %v", err)
|
return errors.Fatalf("unable to save snapshot: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sn.Tree = tree
|
sn.Tree = tree
|
||||||
|
|
||||||
id, err := restic.SaveSnapshot(ctx, repo, sn)
|
id, err := data.SaveSnapshot(ctx, repo, sn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Fatalf("unable to save snapshot: %v", err)
|
return errors.Fatalf("unable to save snapshot: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
@@ -63,7 +64,7 @@ type RepairOptions struct {
|
|||||||
DryRun bool
|
DryRun bool
|
||||||
Forget bool
|
Forget bool
|
||||||
|
|
||||||
restic.SnapshotFilter
|
data.SnapshotFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts *RepairOptions) AddFlags(f *pflag.FlagSet) {
|
func (opts *RepairOptions) AddFlags(f *pflag.FlagSet) {
|
||||||
@@ -96,12 +97,12 @@ func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOpt
|
|||||||
// - trees which cannot be loaded (-> the tree contents will be removed)
|
// - trees which cannot be loaded (-> the tree contents will be removed)
|
||||||
// - files whose contents are not fully available (-> file will be modified)
|
// - files whose contents are not fully available (-> file will be modified)
|
||||||
rewriter := walker.NewTreeRewriter(walker.RewriteOpts{
|
rewriter := walker.NewTreeRewriter(walker.RewriteOpts{
|
||||||
RewriteNode: func(node *restic.Node, path string) *restic.Node {
|
RewriteNode: func(node *data.Node, path string) *data.Node {
|
||||||
if node.Type == restic.NodeTypeIrregular || node.Type == restic.NodeTypeInvalid {
|
if node.Type == data.NodeTypeIrregular || node.Type == data.NodeTypeInvalid {
|
||||||
printer.P(" file %q: removed node with invalid type %q", path, node.Type)
|
printer.P(" file %q: removed node with invalid type %q", path, node.Type)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if node.Type != restic.NodeTypeFile {
|
if node.Type != data.NodeTypeFile {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -135,7 +136,7 @@ func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOpt
|
|||||||
}
|
}
|
||||||
// If a subtree fails to load, remove it
|
// If a subtree fails to load, remove it
|
||||||
printer.P(" dir %q: replaced with empty directory", path)
|
printer.P(" dir %q: replaced with empty directory", path)
|
||||||
emptyID, err := restic.SaveTree(ctx, repo, &restic.Tree{})
|
emptyID, err := data.SaveTree(ctx, repo, &data.Tree{})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return restic.ID{}, err
|
return restic.ID{}, err
|
||||||
}
|
}
|
||||||
@@ -148,7 +149,7 @@ func runRepairSnapshots(ctx context.Context, gopts GlobalOptions, opts RepairOpt
|
|||||||
for sn := range FindFilteredSnapshots(ctx, snapshotLister, repo, &opts.SnapshotFilter, args, printer) {
|
for sn := range FindFilteredSnapshots(ctx, snapshotLister, repo, &opts.SnapshotFilter, args, printer) {
|
||||||
printer.P("\n%v", sn)
|
printer.P("\n%v", sn)
|
||||||
changed, err := filterAndReplaceSnapshot(ctx, repo, sn,
|
changed, err := filterAndReplaceSnapshot(ctx, repo, sn,
|
||||||
func(ctx context.Context, sn *restic.Snapshot) (restic.ID, *restic.SnapshotSummary, error) {
|
func(ctx context.Context, sn *data.Snapshot) (restic.ID, *data.SnapshotSummary, error) {
|
||||||
id, err := rewriter.RewriteTree(ctx, repo, "/", *sn.Tree)
|
id, err := rewriter.RewriteTree(ctx, repo, "/", *sn.Tree)
|
||||||
return id, nil, err
|
return id, nil, err
|
||||||
}, opts.DryRun, opts.Forget, nil, "repaired", printer)
|
}, opts.DryRun, opts.Forget, nil, "repaired", printer)
|
||||||
|
|||||||
@@ -5,10 +5,10 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/filter"
|
"github.com/restic/restic/internal/filter"
|
||||||
"github.com/restic/restic/internal/restic"
|
|
||||||
"github.com/restic/restic/internal/restorer"
|
"github.com/restic/restic/internal/restorer"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/restic/restic/internal/ui/progress"
|
"github.com/restic/restic/internal/ui/progress"
|
||||||
@@ -59,7 +59,7 @@ type RestoreOptions struct {
|
|||||||
filter.ExcludePatternOptions
|
filter.ExcludePatternOptions
|
||||||
filter.IncludePatternOptions
|
filter.IncludePatternOptions
|
||||||
Target string
|
Target string
|
||||||
restic.SnapshotFilter
|
data.SnapshotFilter
|
||||||
DryRun bool
|
DryRun bool
|
||||||
Sparse bool
|
Sparse bool
|
||||||
Verify bool
|
Verify bool
|
||||||
@@ -142,7 +142,7 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
|
|||||||
}
|
}
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
sn, subfolder, err := (&restic.SnapshotFilter{
|
sn, subfolder, err := (&data.SnapshotFilter{
|
||||||
Hosts: opts.Hosts,
|
Hosts: opts.Hosts,
|
||||||
Paths: opts.Paths,
|
Paths: opts.Paths,
|
||||||
Tags: opts.Tags,
|
Tags: opts.Tags,
|
||||||
@@ -156,7 +156,7 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions,
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
sn.Tree, err = restic.FindTreeDirectory(ctx, repo, sn.Tree, subfolder)
|
sn.Tree, err = data.FindTreeDirectory(ctx, repo, sn.Tree, subfolder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
@@ -37,7 +38,7 @@ func testRunRestoreAssumeFailure(t testing.TB, snapshotID string, opts RestoreOp
|
|||||||
func testRunRestoreLatest(t testing.TB, gopts GlobalOptions, dir string, paths []string, hosts []string) {
|
func testRunRestoreLatest(t testing.TB, gopts GlobalOptions, dir string, paths []string, hosts []string) {
|
||||||
opts := RestoreOptions{
|
opts := RestoreOptions{
|
||||||
Target: dir,
|
Target: dir,
|
||||||
SnapshotFilter: restic.SnapshotFilter{
|
SnapshotFilter: data.SnapshotFilter{
|
||||||
Hosts: hosts,
|
Hosts: hosts,
|
||||||
Paths: paths,
|
Paths: paths,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/filter"
|
"github.com/restic/restic/internal/filter"
|
||||||
@@ -105,7 +106,7 @@ type RewriteOptions struct {
|
|||||||
SnapshotSummary bool
|
SnapshotSummary bool
|
||||||
|
|
||||||
Metadata snapshotMetadataArgs
|
Metadata snapshotMetadataArgs
|
||||||
restic.SnapshotFilter
|
data.SnapshotFilter
|
||||||
filter.ExcludePatternOptions
|
filter.ExcludePatternOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,9 +123,9 @@ func (opts *RewriteOptions) AddFlags(f *pflag.FlagSet) {
|
|||||||
|
|
||||||
// rewriteFilterFunc returns the filtered tree ID or an error. If a snapshot summary is returned, the snapshot will
|
// rewriteFilterFunc returns the filtered tree ID or an error. If a snapshot summary is returned, the snapshot will
|
||||||
// be updated accordingly.
|
// be updated accordingly.
|
||||||
type rewriteFilterFunc func(ctx context.Context, sn *restic.Snapshot) (restic.ID, *restic.SnapshotSummary, error)
|
type rewriteFilterFunc func(ctx context.Context, sn *data.Snapshot) (restic.ID, *data.SnapshotSummary, error)
|
||||||
|
|
||||||
func rewriteSnapshot(ctx context.Context, repo *repository.Repository, sn *restic.Snapshot, opts RewriteOptions, printer progress.Printer) (bool, error) {
|
func rewriteSnapshot(ctx context.Context, repo *repository.Repository, sn *data.Snapshot, opts RewriteOptions, printer progress.Printer) (bool, error) {
|
||||||
if sn.Tree == nil {
|
if sn.Tree == nil {
|
||||||
return false, errors.Errorf("snapshot %v has nil tree", sn.ID().Str())
|
return false, errors.Errorf("snapshot %v has nil tree", sn.ID().Str())
|
||||||
}
|
}
|
||||||
@@ -152,7 +153,7 @@ func rewriteSnapshot(ctx context.Context, repo *repository.Repository, sn *resti
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
rewriteNode := func(node *restic.Node, path string) *restic.Node {
|
rewriteNode := func(node *data.Node, path string) *data.Node {
|
||||||
if selectByName(path) {
|
if selectByName(path) {
|
||||||
return node
|
return node
|
||||||
}
|
}
|
||||||
@@ -162,13 +163,13 @@ func rewriteSnapshot(ctx context.Context, repo *repository.Repository, sn *resti
|
|||||||
|
|
||||||
rewriter, querySize := walker.NewSnapshotSizeRewriter(rewriteNode)
|
rewriter, querySize := walker.NewSnapshotSizeRewriter(rewriteNode)
|
||||||
|
|
||||||
filter = func(ctx context.Context, sn *restic.Snapshot) (restic.ID, *restic.SnapshotSummary, error) {
|
filter = func(ctx context.Context, sn *data.Snapshot) (restic.ID, *data.SnapshotSummary, error) {
|
||||||
id, err := rewriter.RewriteTree(ctx, repo, "/", *sn.Tree)
|
id, err := rewriter.RewriteTree(ctx, repo, "/", *sn.Tree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return restic.ID{}, nil, err
|
return restic.ID{}, nil, err
|
||||||
}
|
}
|
||||||
ss := querySize()
|
ss := querySize()
|
||||||
summary := &restic.SnapshotSummary{}
|
summary := &data.SnapshotSummary{}
|
||||||
if sn.Summary != nil {
|
if sn.Summary != nil {
|
||||||
*summary = *sn.Summary
|
*summary = *sn.Summary
|
||||||
}
|
}
|
||||||
@@ -178,7 +179,7 @@ func rewriteSnapshot(ctx context.Context, repo *repository.Repository, sn *resti
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
filter = func(_ context.Context, sn *restic.Snapshot) (restic.ID, *restic.SnapshotSummary, error) {
|
filter = func(_ context.Context, sn *data.Snapshot) (restic.ID, *data.SnapshotSummary, error) {
|
||||||
return *sn.Tree, nil, nil
|
return *sn.Tree, nil, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -187,14 +188,14 @@ func rewriteSnapshot(ctx context.Context, repo *repository.Repository, sn *resti
|
|||||||
filter, opts.DryRun, opts.Forget, metadata, "rewrite", printer)
|
filter, opts.DryRun, opts.Forget, metadata, "rewrite", printer)
|
||||||
}
|
}
|
||||||
|
|
||||||
func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *restic.Snapshot,
|
func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *data.Snapshot,
|
||||||
filter rewriteFilterFunc, dryRun bool, forget bool, newMetadata *snapshotMetadata, addTag string, printer progress.Printer) (bool, error) {
|
filter rewriteFilterFunc, dryRun bool, forget bool, newMetadata *snapshotMetadata, addTag string, printer progress.Printer) (bool, error) {
|
||||||
|
|
||||||
wg, wgCtx := errgroup.WithContext(ctx)
|
wg, wgCtx := errgroup.WithContext(ctx)
|
||||||
repo.StartPackUploader(wgCtx, wg)
|
repo.StartPackUploader(wgCtx, wg)
|
||||||
|
|
||||||
var filteredTree restic.ID
|
var filteredTree restic.ID
|
||||||
var summary *restic.SnapshotSummary
|
var summary *data.SnapshotSummary
|
||||||
wg.Go(func() error {
|
wg.Go(func() error {
|
||||||
var err error
|
var err error
|
||||||
filteredTree, summary, err = filter(ctx, sn)
|
filteredTree, summary, err = filter(ctx, sn)
|
||||||
@@ -273,7 +274,7 @@ func filterAndReplaceSnapshot(ctx context.Context, repo restic.Repository, sn *r
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save the new snapshot.
|
// Save the new snapshot.
|
||||||
id, err := restic.SaveSnapshot(ctx, repo, sn)
|
id, err := data.SaveSnapshot(ctx, repo, sn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/filter"
|
"github.com/restic/restic/internal/filter"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
@@ -37,17 +38,17 @@ func createBasicRewriteRepo(t testing.TB, env *testEnvironment) restic.ID {
|
|||||||
return snapshotIDs[0]
|
return snapshotIDs[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
func getSnapshot(t testing.TB, snapshotID restic.ID, env *testEnvironment) *restic.Snapshot {
|
func getSnapshot(t testing.TB, snapshotID restic.ID, env *testEnvironment) *data.Snapshot {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
var snapshots []*restic.Snapshot
|
var snapshots []*data.Snapshot
|
||||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
||||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
snapshots, err = restic.TestLoadAllSnapshots(ctx, repo, nil)
|
snapshots, err = data.TestLoadAllSnapshots(ctx, repo, nil)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
@@ -116,14 +117,14 @@ func testRewriteMetadata(t *testing.T, metadata snapshotMetadataArgs) {
|
|||||||
createBasicRewriteRepo(t, env)
|
createBasicRewriteRepo(t, env)
|
||||||
testRunRewriteExclude(t, env.gopts, []string{}, true, metadata)
|
testRunRewriteExclude(t, env.gopts, []string{}, true, metadata)
|
||||||
|
|
||||||
var snapshots []*restic.Snapshot
|
var snapshots []*data.Snapshot
|
||||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
||||||
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
ctx, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
snapshots, err = restic.TestLoadAllSnapshots(ctx, repo, nil)
|
snapshots, err = data.TestLoadAllSnapshots(ctx, repo, nil)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
@@ -164,19 +165,19 @@ func TestRewriteSnaphotSummary(t *testing.T) {
|
|||||||
snapshots := testListSnapshots(t, env.gopts, 1)
|
snapshots := testListSnapshots(t, env.gopts, 1)
|
||||||
|
|
||||||
// replace snapshot by one without a summary
|
// replace snapshot by one without a summary
|
||||||
var oldSummary *restic.SnapshotSummary
|
var oldSummary *data.SnapshotSummary
|
||||||
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, env.gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
||||||
_, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
_, repo, unlock, err := openWithExclusiveLock(ctx, gopts, false, printer)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
sn, err := restic.LoadSnapshot(ctx, repo, snapshots[0])
|
sn, err := data.LoadSnapshot(ctx, repo, snapshots[0])
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
oldSummary = sn.Summary
|
oldSummary = sn.Summary
|
||||||
sn.Summary = nil
|
sn.Summary = nil
|
||||||
rtest.OK(t, repo.RemoveUnpacked(ctx, restic.WriteableSnapshotFile, snapshots[0]))
|
rtest.OK(t, repo.RemoveUnpacked(ctx, restic.WriteableSnapshotFile, snapshots[0]))
|
||||||
snapshots[0], err = restic.SaveSnapshot(ctx, repo, sn)
|
snapshots[0], err = data.SaveSnapshot(ctx, repo, sn)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
"github.com/restic/restic/internal/ui/table"
|
"github.com/restic/restic/internal/ui/table"
|
||||||
@@ -46,11 +47,11 @@ Exit status is 12 if the password is incorrect.
|
|||||||
|
|
||||||
// SnapshotOptions bundles all options for the snapshots command.
|
// SnapshotOptions bundles all options for the snapshots command.
|
||||||
type SnapshotOptions struct {
|
type SnapshotOptions struct {
|
||||||
restic.SnapshotFilter
|
data.SnapshotFilter
|
||||||
Compact bool
|
Compact bool
|
||||||
Last bool // This option should be removed in favour of Latest.
|
Last bool // This option should be removed in favour of Latest.
|
||||||
Latest int
|
Latest int
|
||||||
GroupBy restic.SnapshotGroupByOptions
|
GroupBy data.SnapshotGroupByOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts *SnapshotOptions) AddFlags(f *pflag.FlagSet) {
|
func (opts *SnapshotOptions) AddFlags(f *pflag.FlagSet) {
|
||||||
@@ -74,14 +75,14 @@ func runSnapshots(ctx context.Context, opts SnapshotOptions, gopts GlobalOptions
|
|||||||
}
|
}
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
var snapshots restic.Snapshots
|
var snapshots data.Snapshots
|
||||||
for sn := range FindFilteredSnapshots(ctx, repo, repo, &opts.SnapshotFilter, args, printer) {
|
for sn := range FindFilteredSnapshots(ctx, repo, repo, &opts.SnapshotFilter, args, printer) {
|
||||||
snapshots = append(snapshots, sn)
|
snapshots = append(snapshots, sn)
|
||||||
}
|
}
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
snapshotGroups, grouped, err := restic.GroupSnapshots(snapshots, opts.GroupBy)
|
snapshotGroups, grouped, err := data.GroupSnapshots(snapshots, opts.GroupBy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -137,7 +138,7 @@ type filterLastSnapshotsKey struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// newFilterLastSnapshotsKey initializes a filterLastSnapshotsKey from a Snapshot
|
// newFilterLastSnapshotsKey initializes a filterLastSnapshotsKey from a Snapshot
|
||||||
func newFilterLastSnapshotsKey(sn *restic.Snapshot) filterLastSnapshotsKey {
|
func newFilterLastSnapshotsKey(sn *data.Snapshot) filterLastSnapshotsKey {
|
||||||
// Shallow slice copy
|
// Shallow slice copy
|
||||||
var paths = make([]string, len(sn.Paths))
|
var paths = make([]string, len(sn.Paths))
|
||||||
copy(paths, sn.Paths)
|
copy(paths, sn.Paths)
|
||||||
@@ -149,13 +150,13 @@ func newFilterLastSnapshotsKey(sn *restic.Snapshot) filterLastSnapshotsKey {
|
|||||||
// the limit last entries for each hostname and path. If the snapshot
|
// the limit last entries for each hostname and path. If the snapshot
|
||||||
// contains multiple paths, they will be joined and treated as one
|
// contains multiple paths, they will be joined and treated as one
|
||||||
// item.
|
// item.
|
||||||
func FilterLatestSnapshots(list restic.Snapshots, limit int) restic.Snapshots {
|
func FilterLatestSnapshots(list data.Snapshots, limit int) data.Snapshots {
|
||||||
// Sort the snapshots so that the newer ones are listed first
|
// Sort the snapshots so that the newer ones are listed first
|
||||||
sort.SliceStable(list, func(i, j int) bool {
|
sort.SliceStable(list, func(i, j int) bool {
|
||||||
return list[i].Time.After(list[j].Time)
|
return list[i].Time.After(list[j].Time)
|
||||||
})
|
})
|
||||||
|
|
||||||
var results restic.Snapshots
|
var results data.Snapshots
|
||||||
seen := make(map[filterLastSnapshotsKey]int)
|
seen := make(map[filterLastSnapshotsKey]int)
|
||||||
for _, sn := range list {
|
for _, sn := range list {
|
||||||
key := newFilterLastSnapshotsKey(sn)
|
key := newFilterLastSnapshotsKey(sn)
|
||||||
@@ -168,10 +169,10 @@ func FilterLatestSnapshots(list restic.Snapshots, limit int) restic.Snapshots {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// PrintSnapshots prints a text table of the snapshots in list to stdout.
|
// PrintSnapshots prints a text table of the snapshots in list to stdout.
|
||||||
func PrintSnapshots(stdout io.Writer, list restic.Snapshots, reasons []restic.KeepReason, compact bool) error {
|
func PrintSnapshots(stdout io.Writer, list data.Snapshots, reasons []data.KeepReason, compact bool) error {
|
||||||
// keep the reasons a snasphot is being kept in a map, so that it doesn't
|
// keep the reasons a snasphot is being kept in a map, so that it doesn't
|
||||||
// get lost when the list of snapshots is sorted
|
// get lost when the list of snapshots is sorted
|
||||||
keepReasons := make(map[restic.ID]restic.KeepReason, len(reasons))
|
keepReasons := make(map[restic.ID]data.KeepReason, len(reasons))
|
||||||
if len(reasons) > 0 {
|
if len(reasons) > 0 {
|
||||||
for i, sn := range list {
|
for i, sn := range list {
|
||||||
id := sn.ID()
|
id := sn.ID()
|
||||||
@@ -287,7 +288,7 @@ func PrintSnapshots(stdout io.Writer, list restic.Snapshots, reasons []restic.Ke
|
|||||||
// following snapshots belong to.
|
// following snapshots belong to.
|
||||||
// Prints nothing, if we did not group at all.
|
// Prints nothing, if we did not group at all.
|
||||||
func PrintSnapshotGroupHeader(stdout io.Writer, groupKeyJSON string) error {
|
func PrintSnapshotGroupHeader(stdout io.Writer, groupKeyJSON string) error {
|
||||||
var key restic.SnapshotGroupKey
|
var key data.SnapshotGroupKey
|
||||||
|
|
||||||
err := json.Unmarshal([]byte(groupKeyJSON), &key)
|
err := json.Unmarshal([]byte(groupKeyJSON), &key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -320,7 +321,7 @@ func PrintSnapshotGroupHeader(stdout io.Writer, groupKeyJSON string) error {
|
|||||||
|
|
||||||
// Snapshot helps to print Snapshots as JSON with their ID included.
|
// Snapshot helps to print Snapshots as JSON with their ID included.
|
||||||
type Snapshot struct {
|
type Snapshot struct {
|
||||||
*restic.Snapshot
|
*data.Snapshot
|
||||||
|
|
||||||
ID *restic.ID `json:"id"`
|
ID *restic.ID `json:"id"`
|
||||||
ShortID string `json:"short_id"` // deprecated
|
ShortID string `json:"short_id"` // deprecated
|
||||||
@@ -328,17 +329,17 @@ type Snapshot struct {
|
|||||||
|
|
||||||
// SnapshotGroup helps to print SnapshotGroups as JSON with their GroupReasons included.
|
// SnapshotGroup helps to print SnapshotGroups as JSON with their GroupReasons included.
|
||||||
type SnapshotGroup struct {
|
type SnapshotGroup struct {
|
||||||
GroupKey restic.SnapshotGroupKey `json:"group_key"`
|
GroupKey data.SnapshotGroupKey `json:"group_key"`
|
||||||
Snapshots []Snapshot `json:"snapshots"`
|
Snapshots []Snapshot `json:"snapshots"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// printSnapshotGroupJSON writes the JSON representation of list to stdout.
|
// printSnapshotGroupJSON writes the JSON representation of list to stdout.
|
||||||
func printSnapshotGroupJSON(stdout io.Writer, snGroups map[string]restic.Snapshots, grouped bool) error {
|
func printSnapshotGroupJSON(stdout io.Writer, snGroups map[string]data.Snapshots, grouped bool) error {
|
||||||
if grouped {
|
if grouped {
|
||||||
snapshotGroups := []SnapshotGroup{}
|
snapshotGroups := []SnapshotGroup{}
|
||||||
|
|
||||||
for k, list := range snGroups {
|
for k, list := range snGroups {
|
||||||
var key restic.SnapshotGroupKey
|
var key data.SnapshotGroupKey
|
||||||
var err error
|
var err error
|
||||||
var snapshots []Snapshot
|
var snapshots []Snapshot
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/restic/chunker"
|
"github.com/restic/chunker"
|
||||||
"github.com/restic/restic/internal/crypto"
|
"github.com/restic/restic/internal/crypto"
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/restorer"
|
"github.com/restic/restic/internal/restorer"
|
||||||
@@ -79,7 +80,7 @@ type StatsOptions struct {
|
|||||||
// the mode of counting to perform (see consts for available modes)
|
// the mode of counting to perform (see consts for available modes)
|
||||||
countMode string
|
countMode string
|
||||||
|
|
||||||
restic.SnapshotFilter
|
data.SnapshotFilter
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts *StatsOptions) AddFlags(f *pflag.FlagSet) {
|
func (opts *StatsOptions) AddFlags(f *pflag.FlagSet) {
|
||||||
@@ -200,7 +201,7 @@ func runStats(ctx context.Context, opts StatsOptions, gopts GlobalOptions, args
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func statsWalkSnapshot(ctx context.Context, snapshot *restic.Snapshot, repo restic.Loader, opts StatsOptions, stats *statsContainer) error {
|
func statsWalkSnapshot(ctx context.Context, snapshot *data.Snapshot, repo restic.Loader, opts StatsOptions, stats *statsContainer) error {
|
||||||
if snapshot.Tree == nil {
|
if snapshot.Tree == nil {
|
||||||
return fmt.Errorf("snapshot %s has nil tree", snapshot.ID().Str())
|
return fmt.Errorf("snapshot %s has nil tree", snapshot.ID().Str())
|
||||||
}
|
}
|
||||||
@@ -210,7 +211,7 @@ func statsWalkSnapshot(ctx context.Context, snapshot *restic.Snapshot, repo rest
|
|||||||
if opts.countMode == countModeRawData {
|
if opts.countMode == countModeRawData {
|
||||||
// count just the sizes of unique blobs; we don't need to walk the tree
|
// count just the sizes of unique blobs; we don't need to walk the tree
|
||||||
// ourselves in this case, since a nifty function does it for us
|
// ourselves in this case, since a nifty function does it for us
|
||||||
return restic.FindUsedBlobs(ctx, repo, restic.IDs{*snapshot.Tree}, stats.blobs, nil)
|
return data.FindUsedBlobs(ctx, repo, restic.IDs{*snapshot.Tree}, stats.blobs, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
hardLinkIndex := restorer.NewHardlinkIndex[struct{}]()
|
hardLinkIndex := restorer.NewHardlinkIndex[struct{}]()
|
||||||
@@ -225,7 +226,7 @@ func statsWalkSnapshot(ctx context.Context, snapshot *restic.Snapshot, repo rest
|
|||||||
}
|
}
|
||||||
|
|
||||||
func statsWalkTree(repo restic.Loader, opts StatsOptions, stats *statsContainer, hardLinkIndex *restorer.HardlinkIndex[struct{}]) walker.WalkFunc {
|
func statsWalkTree(repo restic.Loader, opts StatsOptions, stats *statsContainer, hardLinkIndex *restorer.HardlinkIndex[struct{}]) walker.WalkFunc {
|
||||||
return func(parentTreeID restic.ID, npath string, node *restic.Node, nodeErr error) error {
|
return func(parentTreeID restic.ID, npath string, node *data.Node, nodeErr error) error {
|
||||||
if nodeErr != nil {
|
if nodeErr != nil {
|
||||||
return nodeErr
|
return nodeErr
|
||||||
}
|
}
|
||||||
@@ -281,7 +282,7 @@ func statsWalkTree(repo restic.Loader, opts StatsOptions, stats *statsContainer,
|
|||||||
// will still be restored
|
// will still be restored
|
||||||
stats.TotalFileCount++
|
stats.TotalFileCount++
|
||||||
|
|
||||||
if node.Links == 1 || node.Type == restic.NodeTypeDir {
|
if node.Links == 1 || node.Type == data.NodeTypeDir {
|
||||||
stats.TotalSize += node.Size
|
stats.TotalSize += node.Size
|
||||||
} else {
|
} else {
|
||||||
// if hardlinks are present only count each deviceID+inode once
|
// if hardlinks are present only count each deviceID+inode once
|
||||||
@@ -298,7 +299,7 @@ func statsWalkTree(repo restic.Loader, opts StatsOptions, stats *statsContainer,
|
|||||||
|
|
||||||
// makeFileIDByContents returns a hash of the blob IDs of the
|
// makeFileIDByContents returns a hash of the blob IDs of the
|
||||||
// node's Content in sequence.
|
// node's Content in sequence.
|
||||||
func makeFileIDByContents(node *restic.Node) fileID {
|
func makeFileIDByContents(node *data.Node) fileID {
|
||||||
var bb []byte
|
var bb []byte
|
||||||
for _, c := range node.Content {
|
for _, c := range node.Content {
|
||||||
bb = append(bb, c[:]...)
|
bb = append(bb, c[:]...)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
@@ -49,10 +50,10 @@ Exit status is 12 if the password is incorrect.
|
|||||||
|
|
||||||
// TagOptions bundles all options for the 'tag' command.
|
// TagOptions bundles all options for the 'tag' command.
|
||||||
type TagOptions struct {
|
type TagOptions struct {
|
||||||
restic.SnapshotFilter
|
data.SnapshotFilter
|
||||||
SetTags restic.TagLists
|
SetTags data.TagLists
|
||||||
AddTags restic.TagLists
|
AddTags data.TagLists
|
||||||
RemoveTags restic.TagLists
|
RemoveTags data.TagLists
|
||||||
}
|
}
|
||||||
|
|
||||||
func (opts *TagOptions) AddFlags(f *pflag.FlagSet) {
|
func (opts *TagOptions) AddFlags(f *pflag.FlagSet) {
|
||||||
@@ -73,7 +74,7 @@ type changedSnapshotsSummary struct {
|
|||||||
ChangedSnapshots int `json:"changed_snapshots"`
|
ChangedSnapshots int `json:"changed_snapshots"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func changeTags(ctx context.Context, repo *repository.Repository, sn *restic.Snapshot, setTags, addTags, removeTags []string, printFunc func(changedSnapshot)) (bool, error) {
|
func changeTags(ctx context.Context, repo *repository.Repository, sn *data.Snapshot, setTags, addTags, removeTags []string, printFunc func(changedSnapshot)) (bool, error) {
|
||||||
var changed bool
|
var changed bool
|
||||||
|
|
||||||
if len(setTags) != 0 {
|
if len(setTags) != 0 {
|
||||||
@@ -97,7 +98,7 @@ func changeTags(ctx context.Context, repo *repository.Repository, sn *restic.Sna
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save the new snapshot.
|
// Save the new snapshot.
|
||||||
id, err := restic.SaveSnapshot(ctx, repo, sn)
|
id, err := data.SaveSnapshot(ctx, repo, sn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/data"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -32,7 +32,7 @@ func TestTag(t *testing.T) {
|
|||||||
"expected original ID to be nil, got %v", newest.Original)
|
"expected original ID to be nil, got %v", newest.Original)
|
||||||
originalID := *newest.ID
|
originalID := *newest.ID
|
||||||
|
|
||||||
testRunTag(t, TagOptions{SetTags: restic.TagLists{[]string{"NL"}}}, env.gopts)
|
testRunTag(t, TagOptions{SetTags: data.TagLists{[]string{"NL"}}}, env.gopts)
|
||||||
testRunCheck(t, env.gopts)
|
testRunCheck(t, env.gopts)
|
||||||
newest, _ = testRunSnapshots(t, env.gopts)
|
newest, _ = testRunSnapshots(t, env.gopts)
|
||||||
if newest == nil {
|
if newest == nil {
|
||||||
@@ -44,7 +44,7 @@ func TestTag(t *testing.T) {
|
|||||||
rtest.Assert(t, *newest.Original == originalID,
|
rtest.Assert(t, *newest.Original == originalID,
|
||||||
"expected original ID to be set to the first snapshot id")
|
"expected original ID to be set to the first snapshot id")
|
||||||
|
|
||||||
testRunTag(t, TagOptions{AddTags: restic.TagLists{[]string{"CH"}}}, env.gopts)
|
testRunTag(t, TagOptions{AddTags: data.TagLists{[]string{"CH"}}}, env.gopts)
|
||||||
testRunCheck(t, env.gopts)
|
testRunCheck(t, env.gopts)
|
||||||
newest, _ = testRunSnapshots(t, env.gopts)
|
newest, _ = testRunSnapshots(t, env.gopts)
|
||||||
if newest == nil {
|
if newest == nil {
|
||||||
@@ -56,7 +56,7 @@ func TestTag(t *testing.T) {
|
|||||||
rtest.Assert(t, *newest.Original == originalID,
|
rtest.Assert(t, *newest.Original == originalID,
|
||||||
"expected original ID to be set to the first snapshot id")
|
"expected original ID to be set to the first snapshot id")
|
||||||
|
|
||||||
testRunTag(t, TagOptions{RemoveTags: restic.TagLists{[]string{"NL"}}}, env.gopts)
|
testRunTag(t, TagOptions{RemoveTags: data.TagLists{[]string{"NL"}}}, env.gopts)
|
||||||
testRunCheck(t, env.gopts)
|
testRunCheck(t, env.gopts)
|
||||||
newest, _ = testRunSnapshots(t, env.gopts)
|
newest, _ = testRunSnapshots(t, env.gopts)
|
||||||
if newest == nil {
|
if newest == nil {
|
||||||
@@ -68,8 +68,8 @@ func TestTag(t *testing.T) {
|
|||||||
rtest.Assert(t, *newest.Original == originalID,
|
rtest.Assert(t, *newest.Original == originalID,
|
||||||
"expected original ID to be set to the first snapshot id")
|
"expected original ID to be set to the first snapshot id")
|
||||||
|
|
||||||
testRunTag(t, TagOptions{AddTags: restic.TagLists{[]string{"US", "RU"}}}, env.gopts)
|
testRunTag(t, TagOptions{AddTags: data.TagLists{[]string{"US", "RU"}}}, env.gopts)
|
||||||
testRunTag(t, TagOptions{RemoveTags: restic.TagLists{[]string{"CH", "US", "RU"}}}, env.gopts)
|
testRunTag(t, TagOptions{RemoveTags: data.TagLists{[]string{"CH", "US", "RU"}}}, env.gopts)
|
||||||
testRunCheck(t, env.gopts)
|
testRunCheck(t, env.gopts)
|
||||||
newest, _ = testRunSnapshots(t, env.gopts)
|
newest, _ = testRunSnapshots(t, env.gopts)
|
||||||
if newest == nil {
|
if newest == nil {
|
||||||
@@ -82,7 +82,7 @@ func TestTag(t *testing.T) {
|
|||||||
"expected original ID to be set to the first snapshot id")
|
"expected original ID to be set to the first snapshot id")
|
||||||
|
|
||||||
// Check special case of removing all tags.
|
// Check special case of removing all tags.
|
||||||
testRunTag(t, TagOptions{SetTags: restic.TagLists{[]string{""}}}, env.gopts)
|
testRunTag(t, TagOptions{SetTags: data.TagLists{[]string{""}}}, env.gopts)
|
||||||
testRunCheck(t, env.gopts)
|
testRunCheck(t, env.gopts)
|
||||||
newest, _ = testRunSnapshots(t, env.gopts)
|
newest, _ = testRunSnapshots(t, env.gopts)
|
||||||
if newest == nil {
|
if newest == nil {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui/progress"
|
"github.com/restic/restic/internal/ui/progress"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
@@ -11,7 +12,7 @@ import (
|
|||||||
|
|
||||||
// initMultiSnapshotFilter is used for commands that work on multiple snapshots
|
// initMultiSnapshotFilter is used for commands that work on multiple snapshots
|
||||||
// MUST be combined with restic.FindFilteredSnapshots or FindFilteredSnapshots
|
// MUST be combined with restic.FindFilteredSnapshots or FindFilteredSnapshots
|
||||||
func initMultiSnapshotFilter(flags *pflag.FlagSet, filt *restic.SnapshotFilter, addHostShorthand bool) {
|
func initMultiSnapshotFilter(flags *pflag.FlagSet, filt *data.SnapshotFilter, addHostShorthand bool) {
|
||||||
hostShorthand := "H"
|
hostShorthand := "H"
|
||||||
if !addHostShorthand {
|
if !addHostShorthand {
|
||||||
hostShorthand = ""
|
hostShorthand = ""
|
||||||
@@ -28,7 +29,7 @@ func initMultiSnapshotFilter(flags *pflag.FlagSet, filt *restic.SnapshotFilter,
|
|||||||
|
|
||||||
// initSingleSnapshotFilter is used for commands that work on a single snapshot
|
// initSingleSnapshotFilter is used for commands that work on a single snapshot
|
||||||
// MUST be combined with restic.FindFilteredSnapshot
|
// MUST be combined with restic.FindFilteredSnapshot
|
||||||
func initSingleSnapshotFilter(flags *pflag.FlagSet, filt *restic.SnapshotFilter) {
|
func initSingleSnapshotFilter(flags *pflag.FlagSet, filt *data.SnapshotFilter) {
|
||||||
flags.StringArrayVarP(&filt.Hosts, "host", "H", nil, "only consider snapshots for this `host`, when snapshot ID \"latest\" is given (can be specified multiple times) (default: $RESTIC_HOST)")
|
flags.StringArrayVarP(&filt.Hosts, "host", "H", nil, "only consider snapshots for this `host`, when snapshot ID \"latest\" is given (can be specified multiple times) (default: $RESTIC_HOST)")
|
||||||
flags.Var(&filt.Tags, "tag", "only consider snapshots including `tag[,tag,...]`, when snapshot ID \"latest\" is given (can be specified multiple times)")
|
flags.Var(&filt.Tags, "tag", "only consider snapshots including `tag[,tag,...]`, when snapshot ID \"latest\" is given (can be specified multiple times)")
|
||||||
flags.StringArrayVar(&filt.Paths, "path", nil, "only consider snapshots including this (absolute) `path`, when snapshot ID \"latest\" is given (can be specified multiple times, snapshots must include all specified paths)")
|
flags.StringArrayVar(&filt.Paths, "path", nil, "only consider snapshots including this (absolute) `path`, when snapshot ID \"latest\" is given (can be specified multiple times, snapshots must include all specified paths)")
|
||||||
@@ -40,8 +41,8 @@ func initSingleSnapshotFilter(flags *pflag.FlagSet, filt *restic.SnapshotFilter)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FindFilteredSnapshots yields Snapshots, either given explicitly by `snapshotIDs` or filtered from the list of all snapshots.
|
// FindFilteredSnapshots yields Snapshots, either given explicitly by `snapshotIDs` or filtered from the list of all snapshots.
|
||||||
func FindFilteredSnapshots(ctx context.Context, be restic.Lister, loader restic.LoaderUnpacked, f *restic.SnapshotFilter, snapshotIDs []string, printer progress.Printer) <-chan *restic.Snapshot {
|
func FindFilteredSnapshots(ctx context.Context, be restic.Lister, loader restic.LoaderUnpacked, f *data.SnapshotFilter, snapshotIDs []string, printer progress.Printer) <-chan *data.Snapshot {
|
||||||
out := make(chan *restic.Snapshot)
|
out := make(chan *data.Snapshot)
|
||||||
go func() {
|
go func() {
|
||||||
defer close(out)
|
defer close(out)
|
||||||
be, err := restic.MemorizeList(ctx, be, restic.SnapshotFile)
|
be, err := restic.MemorizeList(ctx, be, restic.SnapshotFile)
|
||||||
@@ -50,7 +51,7 @@ func FindFilteredSnapshots(ctx context.Context, be restic.Lister, loader restic.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = f.FindAll(ctx, be, loader, snapshotIDs, func(id string, sn *restic.Snapshot, err error) error {
|
err = f.FindAll(ctx, be, loader, snapshotIDs, func(id string, sn *data.Snapshot, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printer.E("Ignoring %q: %v", id, err)
|
printer.E("Ignoring %q: %v", id, err)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/data"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
)
|
)
|
||||||
@@ -45,7 +45,7 @@ func TestSnapshotFilter(t *testing.T) {
|
|||||||
|
|
||||||
for _, mode := range []bool{false, true} {
|
for _, mode := range []bool{false, true} {
|
||||||
set := pflag.NewFlagSet("test", pflag.PanicOnError)
|
set := pflag.NewFlagSet("test", pflag.PanicOnError)
|
||||||
flt := &restic.SnapshotFilter{}
|
flt := &data.SnapshotFilter{}
|
||||||
if mode {
|
if mode {
|
||||||
initMultiSnapshotFilter(set, flt, false)
|
initMultiSnapshotFilter(set, flt, false)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -4,11 +4,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/ui"
|
"github.com/restic/restic/internal/ui"
|
||||||
)
|
)
|
||||||
|
|
||||||
func formatNode(path string, n *restic.Node, long bool, human bool) string {
|
func formatNode(path string, n *data.Node, long bool, human bool) string {
|
||||||
if !long {
|
if !long {
|
||||||
return path
|
return path
|
||||||
}
|
}
|
||||||
@@ -24,20 +24,20 @@ func formatNode(path string, n *restic.Node, long bool, human bool) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch n.Type {
|
switch n.Type {
|
||||||
case restic.NodeTypeFile:
|
case data.NodeTypeFile:
|
||||||
mode = 0
|
mode = 0
|
||||||
case restic.NodeTypeDir:
|
case data.NodeTypeDir:
|
||||||
mode = os.ModeDir
|
mode = os.ModeDir
|
||||||
case restic.NodeTypeSymlink:
|
case data.NodeTypeSymlink:
|
||||||
mode = os.ModeSymlink
|
mode = os.ModeSymlink
|
||||||
target = fmt.Sprintf(" -> %v", n.LinkTarget)
|
target = fmt.Sprintf(" -> %v", n.LinkTarget)
|
||||||
case restic.NodeTypeDev:
|
case data.NodeTypeDev:
|
||||||
mode = os.ModeDevice
|
mode = os.ModeDevice
|
||||||
case restic.NodeTypeCharDev:
|
case data.NodeTypeCharDev:
|
||||||
mode = os.ModeDevice | os.ModeCharDevice
|
mode = os.ModeDevice | os.ModeCharDevice
|
||||||
case restic.NodeTypeFifo:
|
case data.NodeTypeFifo:
|
||||||
mode = os.ModeNamedPipe
|
mode = os.ModeNamedPipe
|
||||||
case restic.NodeTypeSocket:
|
case data.NodeTypeSocket:
|
||||||
mode = os.ModeSocket
|
mode = os.ModeSocket
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/data"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -17,9 +17,9 @@ func TestFormatNode(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
testPath := "/test/path"
|
testPath := "/test/path"
|
||||||
node := restic.Node{
|
node := data.Node{
|
||||||
Name: "baz",
|
Name: "baz",
|
||||||
Type: restic.NodeTypeFile,
|
Type: data.NodeTypeFile,
|
||||||
Size: 14680064,
|
Size: 14680064,
|
||||||
UID: 1000,
|
UID: 1000,
|
||||||
GID: 2000,
|
GID: 2000,
|
||||||
@@ -28,7 +28,7 @@ func TestFormatNode(t *testing.T) {
|
|||||||
|
|
||||||
for _, c := range []struct {
|
for _, c := range []struct {
|
||||||
path string
|
path string
|
||||||
restic.Node
|
data.Node
|
||||||
long bool
|
long bool
|
||||||
human bool
|
human bool
|
||||||
expect string
|
expect string
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import (
|
|||||||
|
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
"github.com/restic/restic/internal/backend/retry"
|
"github.com/restic/restic/internal/backend/retry"
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/options"
|
"github.com/restic/restic/internal/options"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
@@ -365,15 +366,15 @@ func lastSnapshot(old, new map[string]struct{}) (map[string]struct{}, string) {
|
|||||||
return old, ""
|
return old, ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func testLoadSnapshot(t testing.TB, gopts GlobalOptions, id restic.ID) *restic.Snapshot {
|
func testLoadSnapshot(t testing.TB, gopts GlobalOptions, id restic.ID) *data.Snapshot {
|
||||||
var snapshot *restic.Snapshot
|
var snapshot *data.Snapshot
|
||||||
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
err := withTermStatus(t, gopts, func(ctx context.Context, gopts GlobalOptions) error {
|
||||||
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
printer := ui.NewProgressPrinter(gopts.JSON, gopts.verbosity, gopts.term)
|
||||||
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
_, repo, unlock, err := openWithReadLock(ctx, gopts, false, printer)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
defer unlock()
|
defer unlock()
|
||||||
|
|
||||||
snapshot, err = restic.LoadSnapshot(ctx, repo, id)
|
snapshot, err = data.LoadSnapshot(ctx, repo, id)
|
||||||
return err
|
return err
|
||||||
})
|
})
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
@@ -170,7 +171,7 @@ func TestFindListOnce(t *testing.T) {
|
|||||||
|
|
||||||
snapshotIDs = restic.NewIDSet()
|
snapshotIDs = restic.NewIDSet()
|
||||||
// specify the two oldest snapshots explicitly and use "latest" to reference the newest one
|
// specify the two oldest snapshots explicitly and use "latest" to reference the newest one
|
||||||
for sn := range FindFilteredSnapshots(ctx, repo, repo, &restic.SnapshotFilter{}, []string{
|
for sn := range FindFilteredSnapshots(ctx, repo, repo, &data.SnapshotFilter{}, []string{
|
||||||
secondSnapshot[0].String(),
|
secondSnapshot[0].String(),
|
||||||
secondSnapshot[1].String()[:8],
|
secondSnapshot[1].String()[:8],
|
||||||
"latest",
|
"latest",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/feature"
|
"github.com/restic/restic/internal/feature"
|
||||||
@@ -66,9 +67,9 @@ func (s *ItemStats) Add(other ItemStats) {
|
|||||||
s.TreeSizeInRepo += other.TreeSizeInRepo
|
s.TreeSizeInRepo += other.TreeSizeInRepo
|
||||||
}
|
}
|
||||||
|
|
||||||
// ToNoder returns a restic.Node for a File.
|
// ToNoder returns a data.Node for a File.
|
||||||
type ToNoder interface {
|
type ToNoder interface {
|
||||||
ToNode(ignoreXattrListError bool, warnf func(format string, args ...any)) (*restic.Node, error)
|
ToNode(ignoreXattrListError bool, warnf func(format string, args ...any)) (*data.Node, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type archiverRepo interface {
|
type archiverRepo interface {
|
||||||
@@ -116,7 +117,7 @@ type Archiver struct {
|
|||||||
//
|
//
|
||||||
// CompleteItem may be called asynchronously from several different
|
// CompleteItem may be called asynchronously from several different
|
||||||
// goroutines!
|
// goroutines!
|
||||||
CompleteItem func(item string, previous, current *restic.Node, s ItemStats, d time.Duration)
|
CompleteItem func(item string, previous, current *data.Node, s ItemStats, d time.Duration)
|
||||||
|
|
||||||
// StartFile is called when a file is being processed by a worker.
|
// StartFile is called when a file is being processed by a worker.
|
||||||
StartFile func(filename string)
|
StartFile func(filename string)
|
||||||
@@ -193,7 +194,7 @@ func New(repo archiverRepo, filesystem fs.FS, opts Options) *Archiver {
|
|||||||
FS: filesystem,
|
FS: filesystem,
|
||||||
Options: opts.ApplyDefaults(),
|
Options: opts.ApplyDefaults(),
|
||||||
|
|
||||||
CompleteItem: func(string, *restic.Node, *restic.Node, ItemStats, time.Duration) {},
|
CompleteItem: func(string, *data.Node, *data.Node, ItemStats, time.Duration) {},
|
||||||
StartFile: func(string) {},
|
StartFile: func(string) {},
|
||||||
CompleteBlob: func(uint64) {},
|
CompleteBlob: func(uint64) {},
|
||||||
}
|
}
|
||||||
@@ -223,7 +224,7 @@ func (arch *Archiver) error(item string, err error) error {
|
|||||||
return errf
|
return errf
|
||||||
}
|
}
|
||||||
|
|
||||||
func (arch *Archiver) trackItem(item string, previous, current *restic.Node, s ItemStats, d time.Duration) {
|
func (arch *Archiver) trackItem(item string, previous, current *data.Node, s ItemStats, d time.Duration) {
|
||||||
arch.CompleteItem(item, previous, current, s, d)
|
arch.CompleteItem(item, previous, current, s, d)
|
||||||
|
|
||||||
arch.mu.Lock()
|
arch.mu.Lock()
|
||||||
@@ -239,7 +240,7 @@ func (arch *Archiver) trackItem(item string, previous, current *restic.Node, s I
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch current.Type {
|
switch current.Type {
|
||||||
case restic.NodeTypeDir:
|
case data.NodeTypeDir:
|
||||||
switch {
|
switch {
|
||||||
case previous == nil:
|
case previous == nil:
|
||||||
arch.summary.Dirs.New++
|
arch.summary.Dirs.New++
|
||||||
@@ -249,7 +250,7 @@ func (arch *Archiver) trackItem(item string, previous, current *restic.Node, s I
|
|||||||
arch.summary.Dirs.Changed++
|
arch.summary.Dirs.Changed++
|
||||||
}
|
}
|
||||||
|
|
||||||
case restic.NodeTypeFile:
|
case data.NodeTypeFile:
|
||||||
switch {
|
switch {
|
||||||
case previous == nil:
|
case previous == nil:
|
||||||
arch.summary.Files.New++
|
arch.summary.Files.New++
|
||||||
@@ -262,7 +263,7 @@ func (arch *Archiver) trackItem(item string, previous, current *restic.Node, s I
|
|||||||
}
|
}
|
||||||
|
|
||||||
// nodeFromFileInfo returns the restic node from an os.FileInfo.
|
// nodeFromFileInfo returns the restic node from an os.FileInfo.
|
||||||
func (arch *Archiver) nodeFromFileInfo(snPath, filename string, meta ToNoder, ignoreXattrListError bool) (*restic.Node, error) {
|
func (arch *Archiver) nodeFromFileInfo(snPath, filename string, meta ToNoder, ignoreXattrListError bool) (*data.Node, error) {
|
||||||
node, err := meta.ToNode(ignoreXattrListError, func(format string, args ...any) {
|
node, err := meta.ToNode(ignoreXattrListError, func(format string, args ...any) {
|
||||||
_ = arch.error(filename, fmt.Errorf(format, args...))
|
_ = arch.error(filename, fmt.Errorf(format, args...))
|
||||||
})
|
})
|
||||||
@@ -275,7 +276,7 @@ func (arch *Archiver) nodeFromFileInfo(snPath, filename string, meta ToNoder, ig
|
|||||||
node.AccessTime = node.ModTime
|
node.AccessTime = node.ModTime
|
||||||
}
|
}
|
||||||
if feature.Flag.Enabled(feature.DeviceIDForHardlinks) {
|
if feature.Flag.Enabled(feature.DeviceIDForHardlinks) {
|
||||||
if node.Links == 1 || node.Type == restic.NodeTypeDir {
|
if node.Links == 1 || node.Type == data.NodeTypeDir {
|
||||||
// the DeviceID is only necessary for hardlinked files
|
// the DeviceID is only necessary for hardlinked files
|
||||||
// when using subvolumes or snapshots their deviceIDs tend to change which causes
|
// when using subvolumes or snapshots their deviceIDs tend to change which causes
|
||||||
// restic to upload new tree blobs
|
// restic to upload new tree blobs
|
||||||
@@ -285,7 +286,7 @@ func (arch *Archiver) nodeFromFileInfo(snPath, filename string, meta ToNoder, ig
|
|||||||
// overwrite name to match that within the snapshot
|
// overwrite name to match that within the snapshot
|
||||||
node.Name = path.Base(snPath)
|
node.Name = path.Base(snPath)
|
||||||
// do not filter error for nodes of irregular or invalid type
|
// do not filter error for nodes of irregular or invalid type
|
||||||
if node.Type != restic.NodeTypeIrregular && node.Type != restic.NodeTypeInvalid && err != nil {
|
if node.Type != data.NodeTypeIrregular && node.Type != data.NodeTypeInvalid && err != nil {
|
||||||
err = fmt.Errorf("incomplete metadata for %v: %w", filename, err)
|
err = fmt.Errorf("incomplete metadata for %v: %w", filename, err)
|
||||||
return node, arch.error(filename, err)
|
return node, arch.error(filename, err)
|
||||||
}
|
}
|
||||||
@@ -294,12 +295,12 @@ func (arch *Archiver) nodeFromFileInfo(snPath, filename string, meta ToNoder, ig
|
|||||||
|
|
||||||
// loadSubtree tries to load the subtree referenced by node. In case of an error, nil is returned.
|
// loadSubtree tries to load the subtree referenced by node. In case of an error, nil is returned.
|
||||||
// If there is no node to load, then nil is returned without an error.
|
// If there is no node to load, then nil is returned without an error.
|
||||||
func (arch *Archiver) loadSubtree(ctx context.Context, node *restic.Node) (*restic.Tree, error) {
|
func (arch *Archiver) loadSubtree(ctx context.Context, node *data.Node) (*data.Tree, error) {
|
||||||
if node == nil || node.Type != restic.NodeTypeDir || node.Subtree == nil {
|
if node == nil || node.Type != data.NodeTypeDir || node.Subtree == nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tree, err := restic.LoadTree(ctx, arch.Repo, *node.Subtree)
|
tree, err := data.LoadTree(ctx, arch.Repo, *node.Subtree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Log("unable to load tree %v: %v", node.Subtree.Str(), err)
|
debug.Log("unable to load tree %v: %v", node.Subtree.Str(), err)
|
||||||
// a tree in the repository is not readable -> warn the user
|
// a tree in the repository is not readable -> warn the user
|
||||||
@@ -320,7 +321,7 @@ func (arch *Archiver) wrapLoadTreeError(id restic.ID, err error) error {
|
|||||||
|
|
||||||
// saveDir stores a directory in the repo and returns the node. snPath is the
|
// saveDir stores a directory in the repo and returns the node. snPath is the
|
||||||
// path within the current snapshot.
|
// path within the current snapshot.
|
||||||
func (arch *Archiver) saveDir(ctx context.Context, snPath string, dir string, meta fs.File, previous *restic.Tree, complete fileCompleteFunc) (d futureNode, err error) {
|
func (arch *Archiver) saveDir(ctx context.Context, snPath string, dir string, meta fs.File, previous *data.Tree, complete fileCompleteFunc) (d futureNode, err error) {
|
||||||
debug.Log("%v %v", snPath, dir)
|
debug.Log("%v %v", snPath, dir)
|
||||||
|
|
||||||
treeNode, names, err := arch.dirToNodeAndEntries(snPath, dir, meta)
|
treeNode, names, err := arch.dirToNodeAndEntries(snPath, dir, meta)
|
||||||
@@ -365,7 +366,7 @@ func (arch *Archiver) saveDir(ctx context.Context, snPath string, dir string, me
|
|||||||
return fn, nil
|
return fn, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (arch *Archiver) dirToNodeAndEntries(snPath, dir string, meta fs.File) (node *restic.Node, names []string, err error) {
|
func (arch *Archiver) dirToNodeAndEntries(snPath, dir string, meta fs.File) (node *data.Node, names []string, err error) {
|
||||||
err = meta.MakeReadable()
|
err = meta.MakeReadable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("openfile for readdirnames failed: %w", err)
|
return nil, nil, fmt.Errorf("openfile for readdirnames failed: %w", err)
|
||||||
@@ -375,7 +376,7 @@ func (arch *Archiver) dirToNodeAndEntries(snPath, dir string, meta fs.File) (nod
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if node.Type != restic.NodeTypeDir {
|
if node.Type != data.NodeTypeDir {
|
||||||
return nil, nil, fmt.Errorf("directory %q changed type, refusing to archive", snPath)
|
return nil, nil, fmt.Errorf("directory %q changed type, refusing to archive", snPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -400,7 +401,7 @@ type futureNode struct {
|
|||||||
type futureNodeResult struct {
|
type futureNodeResult struct {
|
||||||
snPath, target string
|
snPath, target string
|
||||||
|
|
||||||
node *restic.Node
|
node *data.Node
|
||||||
stats ItemStats
|
stats ItemStats
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
@@ -438,7 +439,7 @@ func (fn *futureNode) take(ctx context.Context) futureNodeResult {
|
|||||||
|
|
||||||
// allBlobsPresent checks if all blobs (contents) of the given node are
|
// allBlobsPresent checks if all blobs (contents) of the given node are
|
||||||
// present in the index.
|
// present in the index.
|
||||||
func (arch *Archiver) allBlobsPresent(previous *restic.Node) bool {
|
func (arch *Archiver) allBlobsPresent(previous *data.Node) bool {
|
||||||
// check if all blobs are contained in index
|
// check if all blobs are contained in index
|
||||||
for _, id := range previous.Content {
|
for _, id := range previous.Content {
|
||||||
if _, ok := arch.Repo.LookupBlobSize(restic.DataBlob, id); !ok {
|
if _, ok := arch.Repo.LookupBlobSize(restic.DataBlob, id); !ok {
|
||||||
@@ -455,7 +456,7 @@ func (arch *Archiver) allBlobsPresent(previous *restic.Node) bool {
|
|||||||
// Errors and completion needs to be handled by the caller.
|
// Errors and completion needs to be handled by the caller.
|
||||||
//
|
//
|
||||||
// snPath is the path within the current snapshot.
|
// snPath is the path within the current snapshot.
|
||||||
func (arch *Archiver) save(ctx context.Context, snPath, target string, previous *restic.Node) (fn futureNode, excluded bool, err error) {
|
func (arch *Archiver) save(ctx context.Context, snPath, target string, previous *data.Node) (fn futureNode, excluded bool, err error) {
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
|
|
||||||
debug.Log("%v target %q, previous %v", snPath, target, previous)
|
debug.Log("%v target %q, previous %v", snPath, target, previous)
|
||||||
@@ -574,7 +575,7 @@ func (arch *Archiver) save(ctx context.Context, snPath, target string, previous
|
|||||||
arch.StartFile(snPath)
|
arch.StartFile(snPath)
|
||||||
}, func() {
|
}, func() {
|
||||||
arch.trackItem(snPath, nil, nil, ItemStats{}, 0)
|
arch.trackItem(snPath, nil, nil, ItemStats{}, 0)
|
||||||
}, func(node *restic.Node, stats ItemStats) {
|
}, func(node *data.Node, stats ItemStats) {
|
||||||
arch.trackItem(snPath, previous, node, stats, time.Since(start))
|
arch.trackItem(snPath, previous, node, stats, time.Since(start))
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -591,7 +592,7 @@ func (arch *Archiver) save(ctx context.Context, snPath, target string, previous
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn, err = arch.saveDir(ctx, snPath, target, meta, oldSubtree,
|
fn, err = arch.saveDir(ctx, snPath, target, meta, oldSubtree,
|
||||||
func(node *restic.Node, stats ItemStats) {
|
func(node *data.Node, stats ItemStats) {
|
||||||
arch.trackItem(snItem, previous, node, stats, time.Since(start))
|
arch.trackItem(snItem, previous, node, stats, time.Since(start))
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -625,11 +626,11 @@ func (arch *Archiver) save(ctx context.Context, snPath, target string, previous
|
|||||||
// fileChanged tries to detect whether a file's content has changed compared
|
// fileChanged tries to detect whether a file's content has changed compared
|
||||||
// to the contents of node, which describes the same path in the parent backup.
|
// to the contents of node, which describes the same path in the parent backup.
|
||||||
// It should only be run for regular files.
|
// It should only be run for regular files.
|
||||||
func fileChanged(fi *fs.ExtendedFileInfo, node *restic.Node, ignoreFlags uint) bool {
|
func fileChanged(fi *fs.ExtendedFileInfo, node *data.Node, ignoreFlags uint) bool {
|
||||||
switch {
|
switch {
|
||||||
case node == nil:
|
case node == nil:
|
||||||
return true
|
return true
|
||||||
case node.Type != restic.NodeTypeFile:
|
case node.Type != data.NodeTypeFile:
|
||||||
// We're only called for regular files, so this is a type change.
|
// We're only called for regular files, so this is a type change.
|
||||||
return true
|
return true
|
||||||
case uint64(fi.Size) != node.Size:
|
case uint64(fi.Size) != node.Size:
|
||||||
@@ -658,9 +659,9 @@ func join(elem ...string) string {
|
|||||||
|
|
||||||
// saveTree stores a Tree in the repo, returned is the tree. snPath is the path
|
// saveTree stores a Tree in the repo, returned is the tree. snPath is the path
|
||||||
// within the current snapshot.
|
// within the current snapshot.
|
||||||
func (arch *Archiver) saveTree(ctx context.Context, snPath string, atree *tree, previous *restic.Tree, complete fileCompleteFunc) (futureNode, int, error) {
|
func (arch *Archiver) saveTree(ctx context.Context, snPath string, atree *tree, previous *data.Tree, complete fileCompleteFunc) (futureNode, int, error) {
|
||||||
|
|
||||||
var node *restic.Node
|
var node *data.Node
|
||||||
if snPath != "/" {
|
if snPath != "/" {
|
||||||
if atree.FileInfoPath == "" {
|
if atree.FileInfoPath == "" {
|
||||||
return futureNode{}, 0, errors.Errorf("FileInfoPath for %v is empty", snPath)
|
return futureNode{}, 0, errors.Errorf("FileInfoPath for %v is empty", snPath)
|
||||||
@@ -673,7 +674,7 @@ func (arch *Archiver) saveTree(ctx context.Context, snPath string, atree *tree,
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// fake root node
|
// fake root node
|
||||||
node = &restic.Node{}
|
node = &data.Node{}
|
||||||
}
|
}
|
||||||
|
|
||||||
debug.Log("%v (%v nodes), parent %v", snPath, len(atree.Nodes), previous)
|
debug.Log("%v (%v nodes), parent %v", snPath, len(atree.Nodes), previous)
|
||||||
@@ -721,7 +722,7 @@ func (arch *Archiver) saveTree(ctx context.Context, snPath string, atree *tree,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// not a leaf node, archive subtree
|
// not a leaf node, archive subtree
|
||||||
fn, _, err := arch.saveTree(ctx, join(snPath, name), &subatree, oldSubtree, func(n *restic.Node, is ItemStats) {
|
fn, _, err := arch.saveTree(ctx, join(snPath, name), &subatree, oldSubtree, func(n *data.Node, is ItemStats) {
|
||||||
arch.trackItem(snItem, oldNode, n, is, time.Since(start))
|
arch.trackItem(snItem, oldNode, n, is, time.Since(start))
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -740,7 +741,7 @@ func (arch *Archiver) saveTree(ctx context.Context, snPath string, atree *tree,
|
|||||||
return fn, len(nodes), nil
|
return fn, len(nodes), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (arch *Archiver) dirPathToNode(snPath, target string) (node *restic.Node, err error) {
|
func (arch *Archiver) dirPathToNode(snPath, target string) (node *data.Node, err error) {
|
||||||
meta, err := arch.FS.OpenFile(target, 0, true)
|
meta, err := arch.FS.OpenFile(target, 0, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -759,7 +760,7 @@ func (arch *Archiver) dirPathToNode(snPath, target string) (node *restic.Node, e
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if node.Type != restic.NodeTypeDir {
|
if node.Type != data.NodeTypeDir {
|
||||||
return nil, errors.Errorf("path is not a directory: %v", target)
|
return nil, errors.Errorf("path is not a directory: %v", target)
|
||||||
}
|
}
|
||||||
return node, err
|
return node, err
|
||||||
@@ -802,19 +803,19 @@ func resolveRelativeTargets(filesys fs.FS, targets []string) ([]string, error) {
|
|||||||
|
|
||||||
// SnapshotOptions collect attributes for a new snapshot.
|
// SnapshotOptions collect attributes for a new snapshot.
|
||||||
type SnapshotOptions struct {
|
type SnapshotOptions struct {
|
||||||
Tags restic.TagList
|
Tags data.TagList
|
||||||
Hostname string
|
Hostname string
|
||||||
Excludes []string
|
Excludes []string
|
||||||
BackupStart time.Time
|
BackupStart time.Time
|
||||||
Time time.Time
|
Time time.Time
|
||||||
ParentSnapshot *restic.Snapshot
|
ParentSnapshot *data.Snapshot
|
||||||
ProgramVersion string
|
ProgramVersion string
|
||||||
// SkipIfUnchanged omits the snapshot creation if it is identical to the parent snapshot.
|
// SkipIfUnchanged omits the snapshot creation if it is identical to the parent snapshot.
|
||||||
SkipIfUnchanged bool
|
SkipIfUnchanged bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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, sn *restic.Snapshot) *restic.Tree {
|
func (arch *Archiver) loadParentTree(ctx context.Context, sn *data.Snapshot) *data.Tree {
|
||||||
if sn == nil {
|
if sn == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -825,7 +826,7 @@ func (arch *Archiver) loadParentTree(ctx context.Context, sn *restic.Snapshot) *
|
|||||||
}
|
}
|
||||||
|
|
||||||
debug.Log("load parent tree %v", *sn.Tree)
|
debug.Log("load parent tree %v", *sn.Tree)
|
||||||
tree, err := restic.LoadTree(ctx, arch.Repo, *sn.Tree)
|
tree, err := data.LoadTree(ctx, arch.Repo, *sn.Tree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
debug.Log("unable to load tree %v: %v", *sn.Tree, err)
|
debug.Log("unable to load tree %v: %v", *sn.Tree, err)
|
||||||
_ = arch.error("/", arch.wrapLoadTreeError(*sn.Tree, err))
|
_ = arch.error("/", arch.wrapLoadTreeError(*sn.Tree, err))
|
||||||
@@ -858,7 +859,7 @@ func (arch *Archiver) stopWorkers() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot saves several targets and returns a snapshot.
|
// Snapshot saves several targets and returns a snapshot.
|
||||||
func (arch *Archiver) Snapshot(ctx context.Context, targets []string, opts SnapshotOptions) (*restic.Snapshot, restic.ID, *Summary, error) {
|
func (arch *Archiver) Snapshot(ctx context.Context, targets []string, opts SnapshotOptions) (*data.Snapshot, restic.ID, *Summary, error) {
|
||||||
arch.summary = &Summary{
|
arch.summary = &Summary{
|
||||||
BackupStart: opts.BackupStart,
|
BackupStart: opts.BackupStart,
|
||||||
}
|
}
|
||||||
@@ -886,7 +887,7 @@ func (arch *Archiver) Snapshot(ctx context.Context, targets []string, opts Snaps
|
|||||||
arch.runWorkers(wgCtx, wg)
|
arch.runWorkers(wgCtx, wg)
|
||||||
|
|
||||||
debug.Log("starting snapshot")
|
debug.Log("starting snapshot")
|
||||||
fn, nodeCount, err := arch.saveTree(wgCtx, "/", atree, arch.loadParentTree(wgCtx, opts.ParentSnapshot), func(_ *restic.Node, is ItemStats) {
|
fn, nodeCount, err := arch.saveTree(wgCtx, "/", atree, arch.loadParentTree(wgCtx, opts.ParentSnapshot), func(_ *data.Node, is ItemStats) {
|
||||||
arch.trackItem("/", nil, nil, is, time.Since(start))
|
arch.trackItem("/", nil, nil, is, time.Since(start))
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -934,7 +935,7 @@ func (arch *Archiver) Snapshot(ctx context.Context, targets []string, opts Snaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sn, err := restic.NewSnapshot(targets, opts.Tags, opts.Hostname, opts.Time)
|
sn, err := data.NewSnapshot(targets, opts.Tags, opts.Hostname, opts.Time)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, restic.ID{}, nil, err
|
return nil, restic.ID{}, nil, err
|
||||||
}
|
}
|
||||||
@@ -946,7 +947,7 @@ func (arch *Archiver) Snapshot(ctx context.Context, targets []string, opts Snaps
|
|||||||
}
|
}
|
||||||
sn.Tree = &rootTreeID
|
sn.Tree = &rootTreeID
|
||||||
arch.summary.BackupEnd = time.Now()
|
arch.summary.BackupEnd = time.Now()
|
||||||
sn.Summary = &restic.SnapshotSummary{
|
sn.Summary = &data.SnapshotSummary{
|
||||||
BackupStart: arch.summary.BackupStart,
|
BackupStart: arch.summary.BackupStart,
|
||||||
BackupEnd: arch.summary.BackupEnd,
|
BackupEnd: arch.summary.BackupEnd,
|
||||||
|
|
||||||
@@ -964,7 +965,7 @@ func (arch *Archiver) Snapshot(ctx context.Context, targets []string, opts Snaps
|
|||||||
TotalBytesProcessed: arch.summary.ProcessedBytes,
|
TotalBytesProcessed: arch.summary.ProcessedBytes,
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := restic.SaveSnapshot(ctx, arch.Repo, sn)
|
id, err := data.SaveSnapshot(ctx, arch.Repo, sn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, restic.ID{}, nil, err
|
return nil, restic.ID{}, nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
"github.com/restic/restic/internal/backend/mem"
|
"github.com/restic/restic/internal/backend/mem"
|
||||||
"github.com/restic/restic/internal/checker"
|
"github.com/restic/restic/internal/checker"
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/feature"
|
"github.com/restic/restic/internal/feature"
|
||||||
"github.com/restic/restic/internal/fs"
|
"github.com/restic/restic/internal/fs"
|
||||||
@@ -37,7 +38,7 @@ func prepareTempdirRepoSrc(t testing.TB, src TestDir) (string, restic.Repository
|
|||||||
return tempdir, repo
|
return tempdir, repo
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveFile(t testing.TB, repo archiverRepo, filename string, filesystem fs.FS) (*restic.Node, ItemStats) {
|
func saveFile(t testing.TB, repo archiverRepo, filename string, filesystem fs.FS) (*data.Node, ItemStats) {
|
||||||
wg, ctx := errgroup.WithContext(context.TODO())
|
wg, ctx := errgroup.WithContext(context.TODO())
|
||||||
repo.StartPackUploader(ctx, wg)
|
repo.StartPackUploader(ctx, wg)
|
||||||
|
|
||||||
@@ -52,7 +53,7 @@ func saveFile(t testing.TB, repo archiverRepo, filename string, filesystem fs.FS
|
|||||||
var (
|
var (
|
||||||
completeReadingCallback bool
|
completeReadingCallback bool
|
||||||
|
|
||||||
completeCallbackNode *restic.Node
|
completeCallbackNode *data.Node
|
||||||
completeCallbackStats ItemStats
|
completeCallbackStats ItemStats
|
||||||
completeCallback bool
|
completeCallback bool
|
||||||
|
|
||||||
@@ -66,7 +67,7 @@ func saveFile(t testing.TB, repo archiverRepo, filename string, filesystem fs.FS
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
complete := func(node *restic.Node, stats ItemStats) {
|
complete := func(node *data.Node, stats ItemStats) {
|
||||||
completeCallback = true
|
completeCallback = true
|
||||||
completeCallbackNode = node
|
completeCallbackNode = node
|
||||||
completeCallbackStats = stats
|
completeCallbackStats = stats
|
||||||
@@ -427,8 +428,8 @@ func (repo *blobCountingRepo) SaveBlob(ctx context.Context, t restic.BlobType, b
|
|||||||
return id, exists, size, err
|
return id, exists, size, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (repo *blobCountingRepo) SaveTree(ctx context.Context, t *restic.Tree) (restic.ID, error) {
|
func (repo *blobCountingRepo) SaveTree(ctx context.Context, t *data.Tree) (restic.ID, error) {
|
||||||
id, err := restic.SaveTree(ctx, repo.archiverRepo, t)
|
id, err := data.SaveTree(ctx, repo.archiverRepo, t)
|
||||||
h := restic.BlobHandle{ID: id, Type: restic.TreeBlob}
|
h := restic.BlobHandle{ID: id, Type: restic.TreeBlob}
|
||||||
repo.m.Lock()
|
repo.m.Lock()
|
||||||
repo.saved[h]++
|
repo.saved[h]++
|
||||||
@@ -548,7 +549,7 @@ func rename(t testing.TB, oldname, newname string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func nodeFromFile(t testing.TB, localFs fs.FS, filename string) *restic.Node {
|
func nodeFromFile(t testing.TB, localFs fs.FS, filename string) *data.Node {
|
||||||
meta, err := localFs.OpenFile(filename, fs.O_NOFOLLOW, true)
|
meta, err := localFs.OpenFile(filename, fs.O_NOFOLLOW, true)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
node, err := meta.ToNode(false, t.Logf)
|
node, err := meta.ToNode(false, t.Logf)
|
||||||
@@ -724,7 +725,7 @@ func TestFilChangedSpecialCases(t *testing.T) {
|
|||||||
t.Run("type-change", func(t *testing.T) {
|
t.Run("type-change", func(t *testing.T) {
|
||||||
fi := lstat(t, filename)
|
fi := lstat(t, filename)
|
||||||
node := nodeFromFile(t, &fs.Local{}, filename)
|
node := nodeFromFile(t, &fs.Local{}, filename)
|
||||||
node.Type = restic.NodeTypeSymlink
|
node.Type = data.NodeTypeSymlink
|
||||||
if !fileChanged(fi, node, 0) {
|
if !fileChanged(fi, node, 0) {
|
||||||
t.Fatal("node with changed type detected as unchanged")
|
t.Fatal("node with changed type detected as unchanged")
|
||||||
}
|
}
|
||||||
@@ -865,8 +866,8 @@ func TestArchiverSaveDir(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
node.Name = targetNodeName
|
node.Name = targetNodeName
|
||||||
tree := &restic.Tree{Nodes: []*restic.Node{node}}
|
tree := &data.Tree{Nodes: []*data.Node{node}}
|
||||||
treeID, err := restic.SaveTree(ctx, repo, tree)
|
treeID, err := data.SaveTree(ctx, repo, tree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -1679,7 +1680,7 @@ func (f MockFile) Read(p []byte) (int, error) {
|
|||||||
return n, err
|
return n, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkSnapshotStats(t *testing.T, sn *restic.Snapshot, stat Summary) {
|
func checkSnapshotStats(t *testing.T, sn *data.Snapshot, stat Summary) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
rtest.Equals(t, stat.BackupStart, sn.Summary.BackupStart, "BackupStart")
|
rtest.Equals(t, stat.BackupStart, sn.Summary.BackupStart, "BackupStart")
|
||||||
// BackupEnd is set to time.Now() and can't be compared to a fixed value
|
// BackupEnd is set to time.Now() and can't be compared to a fixed value
|
||||||
@@ -2219,7 +2220,7 @@ func TestArchiverAbortEarlyOnError(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func snapshot(t testing.TB, repo archiverRepo, fs fs.FS, parent *restic.Snapshot, filename string) (*restic.Snapshot, *restic.Node) {
|
func snapshot(t testing.TB, repo archiverRepo, fs fs.FS, parent *data.Snapshot, filename string) (*data.Snapshot, *data.Node) {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@@ -2234,7 +2235,7 @@ func snapshot(t testing.TB, repo archiverRepo, fs fs.FS, parent *restic.Snapshot
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tree, err := restic.LoadTree(ctx, repo, *snapshot.Tree)
|
tree, err := data.LoadTree(ctx, repo, *snapshot.Tree)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -2251,7 +2252,7 @@ type overrideFS struct {
|
|||||||
fs.FS
|
fs.FS
|
||||||
overrideFI *fs.ExtendedFileInfo
|
overrideFI *fs.ExtendedFileInfo
|
||||||
resetFIOnRead bool
|
resetFIOnRead bool
|
||||||
overrideNode *restic.Node
|
overrideNode *data.Node
|
||||||
overrideErr error
|
overrideErr error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2287,7 +2288,7 @@ func (f overrideFile) MakeReadable() error {
|
|||||||
return f.File.MakeReadable()
|
return f.File.MakeReadable()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f overrideFile) ToNode(ignoreXattrListError bool, warnf func(format string, args ...any)) (*restic.Node, error) {
|
func (f overrideFile) ToNode(ignoreXattrListError bool, warnf func(format string, args ...any)) (*data.Node, error) {
|
||||||
if f.ofs.overrideNode == nil {
|
if f.ofs.overrideNode == nil {
|
||||||
return f.File.ToNode(ignoreXattrListError, warnf)
|
return f.File.ToNode(ignoreXattrListError, warnf)
|
||||||
}
|
}
|
||||||
@@ -2328,7 +2329,7 @@ func TestMetadataChanged(t *testing.T) {
|
|||||||
fs := &overrideFS{
|
fs := &overrideFS{
|
||||||
FS: localFS,
|
FS: localFS,
|
||||||
overrideFI: fi,
|
overrideFI: fi,
|
||||||
overrideNode: &restic.Node{},
|
overrideNode: &data.Node{},
|
||||||
}
|
}
|
||||||
*fs.overrideNode = *want
|
*fs.overrideNode = *want
|
||||||
|
|
||||||
@@ -2451,11 +2452,11 @@ func TestRacyFileTypeSwap(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type mockToNoder struct {
|
type mockToNoder struct {
|
||||||
node *restic.Node
|
node *data.Node
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockToNoder) ToNode(_ bool, _ func(format string, args ...any)) (*restic.Node, error) {
|
func (m *mockToNoder) ToNode(_ bool, _ func(format string, args ...any)) (*data.Node, error) {
|
||||||
return m.node, m.err
|
return m.node, m.err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2474,7 +2475,7 @@ func TestMetadataBackupErrorFiltering(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nonExistNoder := &mockToNoder{
|
nonExistNoder := &mockToNoder{
|
||||||
node: &restic.Node{Type: restic.NodeTypeFile},
|
node: &data.Node{Type: data.NodeTypeFile},
|
||||||
err: fmt.Errorf("not found"),
|
err: fmt.Errorf("not found"),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2487,7 +2488,7 @@ func TestMetadataBackupErrorFiltering(t *testing.T) {
|
|||||||
// check that errors from reading irregular file are not filtered
|
// check that errors from reading irregular file are not filtered
|
||||||
filteredErr = nil
|
filteredErr = nil
|
||||||
nonExistNoder = &mockToNoder{
|
nonExistNoder = &mockToNoder{
|
||||||
node: &restic.Node{Type: restic.NodeTypeIrregular},
|
node: &data.Node{Type: data.NodeTypeIrregular},
|
||||||
err: fmt.Errorf(`unsupported file type "irregular"`),
|
err: fmt.Errorf(`unsupported file type "irregular"`),
|
||||||
}
|
}
|
||||||
node, err = arch.nodeFromFileInfo("file", filename, nonExistNoder, false)
|
node, err = arch.nodeFromFileInfo("file", filename, nonExistNoder, false)
|
||||||
@@ -2515,8 +2516,8 @@ func TestIrregularFile(t *testing.T) {
|
|||||||
override := &overrideFS{
|
override := &overrideFS{
|
||||||
FS: fs.Local{},
|
FS: fs.Local{},
|
||||||
overrideFI: fi,
|
overrideFI: fi,
|
||||||
overrideNode: &restic.Node{
|
overrideNode: &data.Node{
|
||||||
Type: restic.NodeTypeIrregular,
|
Type: data.NodeTypeIrregular,
|
||||||
},
|
},
|
||||||
overrideErr: fmt.Errorf(`unsupported file type "irregular"`),
|
overrideErr: fmt.Errorf(`unsupported file type "irregular"`),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,13 @@ package archiver
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/feature"
|
"github.com/restic/restic/internal/feature"
|
||||||
"github.com/restic/restic/internal/fs"
|
"github.com/restic/restic/internal/fs"
|
||||||
"github.com/restic/restic/internal/restic"
|
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func statAndSnapshot(t *testing.T, repo archiverRepo, name string) (*restic.Node, *restic.Node) {
|
func statAndSnapshot(t *testing.T, repo archiverRepo, name string) (*data.Node, *data.Node) {
|
||||||
want := nodeFromFile(t, &fs.Local{}, name)
|
want := nodeFromFile(t, &fs.Local{}, name)
|
||||||
_, node := snapshot(t, repo, &fs.Local{}, nil, name)
|
_, node := snapshot(t, repo, &fs.Local{}, nil, name)
|
||||||
return want, node
|
return want, node
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/restic/chunker"
|
"github.com/restic/chunker"
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/fs"
|
"github.com/restic/restic/internal/fs"
|
||||||
@@ -28,7 +29,7 @@ type fileSaver struct {
|
|||||||
|
|
||||||
CompleteBlob func(bytes uint64)
|
CompleteBlob func(bytes uint64)
|
||||||
|
|
||||||
NodeFromFileInfo func(snPath, filename string, meta ToNoder, ignoreXattrListError bool) (*restic.Node, error)
|
NodeFromFileInfo func(snPath, filename string, meta ToNoder, ignoreXattrListError bool) (*data.Node, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// newFileSaver returns a new file saver. A worker pool with fileWorkers is
|
// newFileSaver returns a new file saver. A worker pool with fileWorkers is
|
||||||
@@ -64,7 +65,7 @@ func (s *fileSaver) TriggerShutdown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// fileCompleteFunc is called when the file has been saved.
|
// fileCompleteFunc is called when the file has been saved.
|
||||||
type fileCompleteFunc func(*restic.Node, ItemStats)
|
type fileCompleteFunc func(*data.Node, ItemStats)
|
||||||
|
|
||||||
// Save stores the file f and returns the data once it has been completed. The
|
// Save stores the file f and returns the data once it has been completed. The
|
||||||
// file is closed by Save. completeReading is only called if the file was read
|
// file is closed by Save. completeReading is only called if the file was read
|
||||||
@@ -160,7 +161,7 @@ func (s *fileSaver) saveFile(ctx context.Context, chnker *chunker.Chunker, snPat
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if node.Type != restic.NodeTypeFile {
|
if node.Type != data.NodeTypeFile {
|
||||||
_ = f.Close()
|
_ = f.Close()
|
||||||
completeError(errors.Errorf("node type %q is wrong", node.Type))
|
completeError(errors.Errorf("node type %q is wrong", node.Type))
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/restic/chunker"
|
"github.com/restic/chunker"
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/fs"
|
"github.com/restic/restic/internal/fs"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/test"
|
"github.com/restic/restic/internal/test"
|
||||||
@@ -49,7 +50,7 @@ func startFileSaver(ctx context.Context, t testing.TB, _ fs.FS) (*fileSaver, con
|
|||||||
}
|
}
|
||||||
|
|
||||||
s := newFileSaver(ctx, wg, saveBlob, pol, workers, workers)
|
s := newFileSaver(ctx, wg, saveBlob, pol, workers, workers)
|
||||||
s.NodeFromFileInfo = func(snPath, filename string, meta ToNoder, ignoreXattrListError bool) (*restic.Node, error) {
|
s.NodeFromFileInfo = func(snPath, filename string, meta ToNoder, ignoreXattrListError bool) (*data.Node, error) {
|
||||||
return meta.ToNode(ignoreXattrListError, t.Logf)
|
return meta.ToNode(ignoreXattrListError, t.Logf)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,7 +65,7 @@ func TestFileSaver(t *testing.T) {
|
|||||||
|
|
||||||
startFn := func() {}
|
startFn := func() {}
|
||||||
completeReadingFn := func() {}
|
completeReadingFn := func() {}
|
||||||
completeFn := func(*restic.Node, ItemStats) {}
|
completeFn := func(*data.Node, ItemStats) {}
|
||||||
|
|
||||||
testFs := fs.Local{}
|
testFs := fs.Local{}
|
||||||
s, ctx, wg := startFileSaver(ctx, t, testFs)
|
s, ctx, wg := startFileSaver(ctx, t, testFs)
|
||||||
|
|||||||
@@ -11,13 +11,14 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/fs"
|
"github.com/restic/restic/internal/fs"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestSnapshot creates a new snapshot of path.
|
// TestSnapshot creates a new snapshot of path.
|
||||||
func TestSnapshot(t testing.TB, repo restic.Repository, path string, parent *restic.ID) *restic.Snapshot {
|
func TestSnapshot(t testing.TB, repo restic.Repository, path string, parent *restic.ID) *data.Snapshot {
|
||||||
arch := New(repo, fs.Local{}, Options{})
|
arch := New(repo, fs.Local{}, Options{})
|
||||||
opts := SnapshotOptions{
|
opts := SnapshotOptions{
|
||||||
Time: time.Now(),
|
Time: time.Now(),
|
||||||
@@ -25,7 +26,7 @@ func TestSnapshot(t testing.TB, repo restic.Repository, path string, parent *res
|
|||||||
Tags: []string{"test"},
|
Tags: []string{"test"},
|
||||||
}
|
}
|
||||||
if parent != nil {
|
if parent != nil {
|
||||||
sn, err := restic.LoadSnapshot(context.TODO(), repo, *parent)
|
sn, err := data.LoadSnapshot(context.TODO(), repo, *parent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -232,7 +233,7 @@ func TestEnsureFiles(t testing.TB, target string, dir TestDir) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// TestEnsureFileContent checks if the file in the repo is the same as file.
|
// TestEnsureFileContent checks if the file in the repo is the same as file.
|
||||||
func TestEnsureFileContent(ctx context.Context, t testing.TB, repo restic.BlobLoader, filename string, node *restic.Node, file TestFile) {
|
func TestEnsureFileContent(ctx context.Context, t testing.TB, repo restic.BlobLoader, filename string, node *data.Node, file TestFile) {
|
||||||
if int(node.Size) != len(file.Content) {
|
if int(node.Size) != len(file.Content) {
|
||||||
t.Fatalf("%v: wrong node size: want %d, got %d", filename, node.Size, len(file.Content))
|
t.Fatalf("%v: wrong node size: want %d, got %d", filename, node.Size, len(file.Content))
|
||||||
return
|
return
|
||||||
@@ -263,7 +264,7 @@ func TestEnsureFileContent(ctx context.Context, t testing.TB, repo restic.BlobLo
|
|||||||
func TestEnsureTree(ctx context.Context, t testing.TB, prefix string, repo restic.BlobLoader, treeID restic.ID, dir TestDir) {
|
func TestEnsureTree(ctx context.Context, t testing.TB, prefix string, repo restic.BlobLoader, treeID restic.ID, dir TestDir) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
tree, err := restic.LoadTree(ctx, repo, treeID)
|
tree, err := data.LoadTree(ctx, repo, treeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
return
|
return
|
||||||
@@ -289,7 +290,7 @@ func TestEnsureTree(ctx context.Context, t testing.TB, prefix string, repo resti
|
|||||||
|
|
||||||
switch e := entry.(type) {
|
switch e := entry.(type) {
|
||||||
case TestDir:
|
case TestDir:
|
||||||
if node.Type != restic.NodeTypeDir {
|
if node.Type != data.NodeTypeDir {
|
||||||
t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "dir")
|
t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "dir")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -301,12 +302,12 @@ func TestEnsureTree(ctx context.Context, t testing.TB, prefix string, repo resti
|
|||||||
|
|
||||||
TestEnsureTree(ctx, t, path.Join(prefix, node.Name), repo, *node.Subtree, e)
|
TestEnsureTree(ctx, t, path.Join(prefix, node.Name), repo, *node.Subtree, e)
|
||||||
case TestFile:
|
case TestFile:
|
||||||
if node.Type != restic.NodeTypeFile {
|
if node.Type != data.NodeTypeFile {
|
||||||
t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "file")
|
t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "file")
|
||||||
}
|
}
|
||||||
TestEnsureFileContent(ctx, t, repo, nodePrefix, node, e)
|
TestEnsureFileContent(ctx, t, repo, nodePrefix, node, e)
|
||||||
case TestSymlink:
|
case TestSymlink:
|
||||||
if node.Type != restic.NodeTypeSymlink {
|
if node.Type != data.NodeTypeSymlink {
|
||||||
t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "symlink")
|
t.Errorf("tree node %v has wrong type %q, want %q", nodePrefix, node.Type, "symlink")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -331,7 +332,7 @@ func TestEnsureSnapshot(t testing.TB, repo restic.Repository, snapshotID restic.
|
|||||||
ctx, cancel := context.WithCancel(context.Background())
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
sn, err := restic.LoadSnapshot(ctx, repo, snapshotID)
|
sn, err := data.LoadSnapshot(ctx, repo, snapshotID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
@@ -42,7 +43,7 @@ func (s *treeSaver) TriggerShutdown() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save stores the dir d and returns the data once it has been completed.
|
// Save stores the dir d and returns the data once it has been completed.
|
||||||
func (s *treeSaver) Save(ctx context.Context, snPath string, target string, node *restic.Node, nodes []futureNode, complete fileCompleteFunc) futureNode {
|
func (s *treeSaver) Save(ctx context.Context, snPath string, target string, node *data.Node, nodes []futureNode, complete fileCompleteFunc) futureNode {
|
||||||
fn, ch := newFutureNode()
|
fn, ch := newFutureNode()
|
||||||
job := saveTreeJob{
|
job := saveTreeJob{
|
||||||
snPath: snPath,
|
snPath: snPath,
|
||||||
@@ -65,22 +66,22 @@ func (s *treeSaver) Save(ctx context.Context, snPath string, target string, node
|
|||||||
type saveTreeJob struct {
|
type saveTreeJob struct {
|
||||||
snPath string
|
snPath string
|
||||||
target string
|
target string
|
||||||
node *restic.Node
|
node *data.Node
|
||||||
nodes []futureNode
|
nodes []futureNode
|
||||||
ch chan<- futureNodeResult
|
ch chan<- futureNodeResult
|
||||||
complete fileCompleteFunc
|
complete fileCompleteFunc
|
||||||
}
|
}
|
||||||
|
|
||||||
// save stores the nodes as a tree in the repo.
|
// save stores the nodes as a tree in the repo.
|
||||||
func (s *treeSaver) save(ctx context.Context, job *saveTreeJob) (*restic.Node, ItemStats, error) {
|
func (s *treeSaver) save(ctx context.Context, job *saveTreeJob) (*data.Node, ItemStats, error) {
|
||||||
var stats ItemStats
|
var stats ItemStats
|
||||||
node := job.node
|
node := job.node
|
||||||
nodes := job.nodes
|
nodes := job.nodes
|
||||||
// allow GC of nodes array once the loop is finished
|
// allow GC of nodes array once the loop is finished
|
||||||
job.nodes = nil
|
job.nodes = nil
|
||||||
|
|
||||||
builder := restic.NewTreeJSONBuilder()
|
builder := data.NewTreeJSONBuilder()
|
||||||
var lastNode *restic.Node
|
var lastNode *data.Node
|
||||||
|
|
||||||
for i, fn := range nodes {
|
for i, fn := range nodes {
|
||||||
// fn is a copy, so clear the original value explicitly
|
// fn is a copy, so clear the original value explicitly
|
||||||
@@ -110,7 +111,7 @@ func (s *treeSaver) save(ctx context.Context, job *saveTreeJob) (*restic.Node, I
|
|||||||
}
|
}
|
||||||
|
|
||||||
err := builder.AddNode(fnr.node)
|
err := builder.AddNode(fnr.node)
|
||||||
if err != nil && errors.Is(err, restic.ErrTreeNotOrdered) && lastNode != nil && fnr.node.Equals(*lastNode) {
|
if err != nil && errors.Is(err, data.ErrTreeNotOrdered) && lastNode != nil && fnr.node.Equals(*lastNode) {
|
||||||
debug.Log("insert %v failed: %v", fnr.node.Name, err)
|
debug.Log("insert %v failed: %v", fnr.node.Name, err)
|
||||||
// ignore error if an _identical_ node already exists, but nevertheless issue a warning
|
// ignore error if an _identical_ node already exists, but nevertheless issue a warning
|
||||||
_ = s.errFn(fnr.target, err)
|
_ = s.errFn(fnr.target, err)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"runtime"
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/test"
|
"github.com/restic/restic/internal/test"
|
||||||
@@ -46,7 +47,7 @@ func TestTreeSaver(t *testing.T) {
|
|||||||
var results []futureNode
|
var results []futureNode
|
||||||
|
|
||||||
for i := 0; i < 20; i++ {
|
for i := 0; i < 20; i++ {
|
||||||
node := &restic.Node{
|
node := &data.Node{
|
||||||
Name: fmt.Sprintf("file-%d", i),
|
Name: fmt.Sprintf("file-%d", i),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,11 +87,11 @@ func TestTreeSaverError(t *testing.T) {
|
|||||||
var results []futureNode
|
var results []futureNode
|
||||||
|
|
||||||
for i := 0; i < test.trees; i++ {
|
for i := 0; i < test.trees; i++ {
|
||||||
node := &restic.Node{
|
node := &data.Node{
|
||||||
Name: fmt.Sprintf("file-%d", i),
|
Name: fmt.Sprintf("file-%d", i),
|
||||||
}
|
}
|
||||||
nodes := []futureNode{
|
nodes := []futureNode{
|
||||||
newFutureNodeWithResult(futureNodeResult{node: &restic.Node{
|
newFutureNodeWithResult(futureNodeResult{node: &data.Node{
|
||||||
Name: fmt.Sprintf("child-%d", i),
|
Name: fmt.Sprintf("child-%d", i),
|
||||||
}}),
|
}}),
|
||||||
}
|
}
|
||||||
@@ -125,20 +126,20 @@ func TestTreeSaverDuplicates(t *testing.T) {
|
|||||||
ctx, cancel, b, shutdown := setupTreeSaver()
|
ctx, cancel, b, shutdown := setupTreeSaver()
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
node := &restic.Node{
|
node := &data.Node{
|
||||||
Name: "file",
|
Name: "file",
|
||||||
}
|
}
|
||||||
nodes := []futureNode{
|
nodes := []futureNode{
|
||||||
newFutureNodeWithResult(futureNodeResult{node: &restic.Node{
|
newFutureNodeWithResult(futureNodeResult{node: &data.Node{
|
||||||
Name: "child",
|
Name: "child",
|
||||||
}}),
|
}}),
|
||||||
}
|
}
|
||||||
if identicalNodes {
|
if identicalNodes {
|
||||||
nodes = append(nodes, newFutureNodeWithResult(futureNodeResult{node: &restic.Node{
|
nodes = append(nodes, newFutureNodeWithResult(futureNodeResult{node: &data.Node{
|
||||||
Name: "child",
|
Name: "child",
|
||||||
}}))
|
}}))
|
||||||
} else {
|
} else {
|
||||||
nodes = append(nodes, newFutureNodeWithResult(futureNodeResult{node: &restic.Node{
|
nodes = append(nodes, newFutureNodeWithResult(futureNodeResult{node: &data.Node{
|
||||||
Name: "child",
|
Name: "child",
|
||||||
Size: 42,
|
Size: 42,
|
||||||
}}))
|
}}))
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/klauspost/compress/zstd"
|
"github.com/klauspost/compress/zstd"
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
@@ -256,7 +257,7 @@ func (e *TreeError) Error() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkTreeWorker checks the trees received and sends out errors to errChan.
|
// checkTreeWorker checks the trees received and sends out errors to errChan.
|
||||||
func (c *Checker) checkTreeWorker(ctx context.Context, trees <-chan restic.TreeItem, out chan<- error) {
|
func (c *Checker) checkTreeWorker(ctx context.Context, trees <-chan data.TreeItem, out chan<- error) {
|
||||||
for job := range trees {
|
for job := range trees {
|
||||||
debug.Log("check tree %v (tree %v, err %v)", job.ID, job.Tree, job.Error)
|
debug.Log("check tree %v (tree %v, err %v)", job.ID, job.Tree, job.Error)
|
||||||
|
|
||||||
@@ -281,7 +282,7 @@ func (c *Checker) checkTreeWorker(ctx context.Context, trees <-chan restic.TreeI
|
|||||||
}
|
}
|
||||||
|
|
||||||
func loadSnapshotTreeIDs(ctx context.Context, lister restic.Lister, repo restic.LoaderUnpacked) (ids restic.IDs, errs []error) {
|
func loadSnapshotTreeIDs(ctx context.Context, lister restic.Lister, repo restic.LoaderUnpacked) (ids restic.IDs, errs []error) {
|
||||||
err := restic.ForAllSnapshots(ctx, lister, repo, nil, func(id restic.ID, sn *restic.Snapshot, err error) error {
|
err := data.ForAllSnapshots(ctx, lister, repo, nil, func(id restic.ID, sn *data.Snapshot, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errs = append(errs, err)
|
errs = append(errs, err)
|
||||||
return nil
|
return nil
|
||||||
@@ -315,7 +316,7 @@ func (c *Checker) Structure(ctx context.Context, p *progress.Counter, errChan ch
|
|||||||
}
|
}
|
||||||
|
|
||||||
wg, ctx := errgroup.WithContext(ctx)
|
wg, ctx := errgroup.WithContext(ctx)
|
||||||
treeStream := restic.StreamTrees(ctx, wg, c.repo, trees, func(treeID restic.ID) bool {
|
treeStream := data.StreamTrees(ctx, wg, c.repo, trees, func(treeID restic.ID) bool {
|
||||||
// blobRefs may be accessed in parallel by checkTree
|
// blobRefs may be accessed in parallel by checkTree
|
||||||
c.blobRefs.Lock()
|
c.blobRefs.Lock()
|
||||||
h := restic.BlobHandle{ID: treeID, Type: restic.TreeBlob}
|
h := restic.BlobHandle{ID: treeID, Type: restic.TreeBlob}
|
||||||
@@ -344,12 +345,12 @@ func (c *Checker) Structure(ctx context.Context, p *progress.Counter, errChan ch
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Checker) checkTree(id restic.ID, tree *restic.Tree) (errs []error) {
|
func (c *Checker) checkTree(id restic.ID, tree *data.Tree) (errs []error) {
|
||||||
debug.Log("checking tree %v", id)
|
debug.Log("checking tree %v", id)
|
||||||
|
|
||||||
for _, node := range tree.Nodes {
|
for _, node := range tree.Nodes {
|
||||||
switch node.Type {
|
switch node.Type {
|
||||||
case restic.NodeTypeFile:
|
case data.NodeTypeFile:
|
||||||
if node.Content == nil {
|
if node.Content == nil {
|
||||||
errs = append(errs, &Error{TreeID: id, Err: errors.Errorf("file %q has nil blob list", node.Name)})
|
errs = append(errs, &Error{TreeID: id, Err: errors.Errorf("file %q has nil blob list", node.Name)})
|
||||||
}
|
}
|
||||||
@@ -385,7 +386,7 @@ func (c *Checker) checkTree(id restic.ID, tree *restic.Tree) (errs []error) {
|
|||||||
c.blobRefs.Unlock()
|
c.blobRefs.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
case restic.NodeTypeDir:
|
case data.NodeTypeDir:
|
||||||
if node.Subtree == nil {
|
if node.Subtree == nil {
|
||||||
errs = append(errs, &Error{TreeID: id, Err: errors.Errorf("dir node %q has no subtree", node.Name)})
|
errs = append(errs, &Error{TreeID: id, Err: errors.Errorf("dir node %q has no subtree", node.Name)})
|
||||||
continue
|
continue
|
||||||
@@ -396,7 +397,7 @@ func (c *Checker) checkTree(id restic.ID, tree *restic.Tree) (errs []error) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
case restic.NodeTypeSymlink, restic.NodeTypeSocket, restic.NodeTypeCharDev, restic.NodeTypeDev, restic.NodeTypeFifo:
|
case data.NodeTypeSymlink, data.NodeTypeSocket, data.NodeTypeCharDev, data.NodeTypeDev, data.NodeTypeFifo:
|
||||||
// nothing to check
|
// nothing to check
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
"github.com/restic/restic/internal/archiver"
|
"github.com/restic/restic/internal/archiver"
|
||||||
"github.com/restic/restic/internal/backend"
|
"github.com/restic/restic/internal/backend"
|
||||||
"github.com/restic/restic/internal/checker"
|
"github.com/restic/restic/internal/checker"
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/repository/hashing"
|
"github.com/restic/restic/internal/repository/hashing"
|
||||||
@@ -440,7 +441,7 @@ type loadTreesOnceRepository struct {
|
|||||||
DuplicateTree bool
|
DuplicateTree bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *loadTreesOnceRepository) LoadTree(ctx context.Context, id restic.ID) (*restic.Tree, error) {
|
func (r *loadTreesOnceRepository) LoadTree(ctx context.Context, id restic.ID) (*data.Tree, error) {
|
||||||
r.mutex.Lock()
|
r.mutex.Lock()
|
||||||
defer r.mutex.Unlock()
|
defer r.mutex.Unlock()
|
||||||
|
|
||||||
@@ -450,7 +451,7 @@ func (r *loadTreesOnceRepository) LoadTree(ctx context.Context, id restic.ID) (*
|
|||||||
return nil, errors.Errorf("trying to load tree with id %v twice", id)
|
return nil, errors.Errorf("trying to load tree with id %v twice", id)
|
||||||
}
|
}
|
||||||
r.loadedTrees.Insert(id)
|
r.loadedTrees.Insert(id)
|
||||||
return restic.LoadTree(ctx, r.Repository, id)
|
return data.LoadTree(ctx, r.Repository, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCheckerNoDuplicateTreeDecodes(t *testing.T) {
|
func TestCheckerNoDuplicateTreeDecodes(t *testing.T) {
|
||||||
@@ -481,11 +482,11 @@ type delayRepository struct {
|
|||||||
Unblocker sync.Once
|
Unblocker sync.Once
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *delayRepository) LoadTree(ctx context.Context, id restic.ID) (*restic.Tree, error) {
|
func (r *delayRepository) LoadTree(ctx context.Context, id restic.ID) (*data.Tree, error) {
|
||||||
if id == r.DelayTree {
|
if id == r.DelayTree {
|
||||||
<-r.UnblockChannel
|
<-r.UnblockChannel
|
||||||
}
|
}
|
||||||
return restic.LoadTree(ctx, r.Repository, id)
|
return data.LoadTree(ctx, r.Repository, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *delayRepository) LookupBlobSize(t restic.BlobType, id restic.ID) (uint, bool) {
|
func (r *delayRepository) LookupBlobSize(t restic.BlobType, id restic.ID) (uint, bool) {
|
||||||
@@ -507,20 +508,20 @@ func TestCheckerBlobTypeConfusion(t *testing.T) {
|
|||||||
|
|
||||||
repo := repository.TestRepository(t)
|
repo := repository.TestRepository(t)
|
||||||
|
|
||||||
damagedNode := &restic.Node{
|
damagedNode := &data.Node{
|
||||||
Name: "damaged",
|
Name: "damaged",
|
||||||
Type: restic.NodeTypeFile,
|
Type: data.NodeTypeFile,
|
||||||
Mode: 0644,
|
Mode: 0644,
|
||||||
Size: 42,
|
Size: 42,
|
||||||
Content: restic.IDs{restic.TestParseID("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")},
|
Content: restic.IDs{restic.TestParseID("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef")},
|
||||||
}
|
}
|
||||||
damagedTree := &restic.Tree{
|
damagedTree := &data.Tree{
|
||||||
Nodes: []*restic.Node{damagedNode},
|
Nodes: []*data.Node{damagedNode},
|
||||||
}
|
}
|
||||||
|
|
||||||
wg, wgCtx := errgroup.WithContext(ctx)
|
wg, wgCtx := errgroup.WithContext(ctx)
|
||||||
repo.StartPackUploader(wgCtx, wg)
|
repo.StartPackUploader(wgCtx, wg)
|
||||||
id, err := restic.SaveTree(ctx, repo, damagedTree)
|
id, err := data.SaveTree(ctx, repo, damagedTree)
|
||||||
test.OK(t, repo.Flush(ctx))
|
test.OK(t, repo.Flush(ctx))
|
||||||
test.OK(t, err)
|
test.OK(t, err)
|
||||||
|
|
||||||
@@ -532,35 +533,35 @@ func TestCheckerBlobTypeConfusion(t *testing.T) {
|
|||||||
_, _, _, err = repo.SaveBlob(ctx, restic.DataBlob, buf, id, false)
|
_, _, _, err = repo.SaveBlob(ctx, restic.DataBlob, buf, id, false)
|
||||||
test.OK(t, err)
|
test.OK(t, err)
|
||||||
|
|
||||||
malNode := &restic.Node{
|
malNode := &data.Node{
|
||||||
Name: "aaaaa",
|
Name: "aaaaa",
|
||||||
Type: restic.NodeTypeFile,
|
Type: data.NodeTypeFile,
|
||||||
Mode: 0644,
|
Mode: 0644,
|
||||||
Size: uint64(len(buf)),
|
Size: uint64(len(buf)),
|
||||||
Content: restic.IDs{id},
|
Content: restic.IDs{id},
|
||||||
}
|
}
|
||||||
dirNode := &restic.Node{
|
dirNode := &data.Node{
|
||||||
Name: "bbbbb",
|
Name: "bbbbb",
|
||||||
Type: restic.NodeTypeDir,
|
Type: data.NodeTypeDir,
|
||||||
Mode: 0755,
|
Mode: 0755,
|
||||||
Subtree: &id,
|
Subtree: &id,
|
||||||
}
|
}
|
||||||
|
|
||||||
rootTree := &restic.Tree{
|
rootTree := &data.Tree{
|
||||||
Nodes: []*restic.Node{malNode, dirNode},
|
Nodes: []*data.Node{malNode, dirNode},
|
||||||
}
|
}
|
||||||
|
|
||||||
rootID, err := restic.SaveTree(ctx, repo, rootTree)
|
rootID, err := data.SaveTree(ctx, repo, rootTree)
|
||||||
test.OK(t, err)
|
test.OK(t, err)
|
||||||
|
|
||||||
test.OK(t, repo.Flush(ctx))
|
test.OK(t, repo.Flush(ctx))
|
||||||
|
|
||||||
snapshot, err := restic.NewSnapshot([]string{"/damaged"}, []string{"test"}, "foo", time.Now())
|
snapshot, err := data.NewSnapshot([]string{"/damaged"}, []string{"test"}, "foo", time.Now())
|
||||||
test.OK(t, err)
|
test.OK(t, err)
|
||||||
|
|
||||||
snapshot.Tree = &rootID
|
snapshot.Tree = &rootID
|
||||||
|
|
||||||
snapID, err := restic.SaveSnapshot(ctx, repo, snapshot)
|
snapID, err := data.SaveSnapshot(ctx, repo, snapshot)
|
||||||
test.OK(t, err)
|
test.OK(t, err)
|
||||||
|
|
||||||
t.Logf("saved snapshot %v", snapID.Str())
|
t.Logf("saved snapshot %v", snapID.Str())
|
||||||
@@ -637,7 +638,7 @@ func benchmarkSnapshotScaling(t *testing.B, newSnapshots int) {
|
|||||||
defer cleanup()
|
defer cleanup()
|
||||||
|
|
||||||
snID := restic.TestParseID("51d249d28815200d59e4be7b3f21a157b864dc343353df9d8e498220c2499b02")
|
snID := restic.TestParseID("51d249d28815200d59e4be7b3f21a157b864dc343353df9d8e498220c2499b02")
|
||||||
sn2, err := restic.LoadSnapshot(context.TODO(), repo, snID)
|
sn2, err := data.LoadSnapshot(context.TODO(), repo, snID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -645,13 +646,13 @@ func benchmarkSnapshotScaling(t *testing.B, newSnapshots int) {
|
|||||||
treeID := sn2.Tree
|
treeID := sn2.Tree
|
||||||
|
|
||||||
for i := 0; i < newSnapshots; i++ {
|
for i := 0; i < newSnapshots; i++ {
|
||||||
sn, err := restic.NewSnapshot([]string{"test" + strconv.Itoa(i)}, nil, "", time.Now())
|
sn, err := data.NewSnapshot([]string{"test" + strconv.Itoa(i)}, nil, "", time.Now())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
sn.Tree = treeID
|
sn.Tree = treeID
|
||||||
|
|
||||||
_, err = restic.SaveSnapshot(context.TODO(), repo, sn)
|
_, err = data.SaveSnapshot(context.TODO(), repo, sn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
package restic
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package restic
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
@@ -1,23 +1,24 @@
|
|||||||
package restic
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/restic"
|
||||||
"github.com/restic/restic/internal/ui/progress"
|
"github.com/restic/restic/internal/ui/progress"
|
||||||
"golang.org/x/sync/errgroup"
|
"golang.org/x/sync/errgroup"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FindUsedBlobs traverses the tree ID and adds all seen blobs (trees and data
|
// FindUsedBlobs traverses the tree ID and adds all seen blobs (trees and data
|
||||||
// blobs) to the set blobs. Already seen tree blobs will not be visited again.
|
// blobs) to the set blobs. Already seen tree blobs will not be visited again.
|
||||||
func FindUsedBlobs(ctx context.Context, repo Loader, treeIDs IDs, blobs FindBlobSet, p *progress.Counter) error {
|
func FindUsedBlobs(ctx context.Context, repo restic.Loader, treeIDs restic.IDs, blobs restic.FindBlobSet, p *progress.Counter) error {
|
||||||
var lock sync.Mutex
|
var lock sync.Mutex
|
||||||
|
|
||||||
wg, ctx := errgroup.WithContext(ctx)
|
wg, ctx := errgroup.WithContext(ctx)
|
||||||
treeStream := StreamTrees(ctx, wg, repo, treeIDs, func(treeID ID) bool {
|
treeStream := StreamTrees(ctx, wg, repo, treeIDs, func(treeID restic.ID) bool {
|
||||||
// locking is necessary the goroutine below concurrently adds data blobs
|
// locking is necessary the goroutine below concurrently adds data blobs
|
||||||
lock.Lock()
|
lock.Lock()
|
||||||
h := BlobHandle{ID: treeID, Type: TreeBlob}
|
h := restic.BlobHandle{ID: treeID, Type: restic.TreeBlob}
|
||||||
blobReferenced := blobs.Has(h)
|
blobReferenced := blobs.Has(h)
|
||||||
// noop if already referenced
|
// noop if already referenced
|
||||||
blobs.Insert(h)
|
blobs.Insert(h)
|
||||||
@@ -36,7 +37,7 @@ func FindUsedBlobs(ctx context.Context, repo Loader, treeIDs IDs, blobs FindBlob
|
|||||||
switch node.Type {
|
switch node.Type {
|
||||||
case NodeTypeFile:
|
case NodeTypeFile:
|
||||||
for _, blob := range node.Content {
|
for _, blob := range node.Content {
|
||||||
blobs.Insert(BlobHandle{ID: blob, Type: DataBlob})
|
blobs.Insert(restic.BlobHandle{ID: blob, Type: restic.DataBlob})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package restic_test
|
package data_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/restic"
|
||||||
@@ -86,9 +87,9 @@ var findTestTime = time.Unix(1469960361, 23)
|
|||||||
func TestFindUsedBlobs(t *testing.T) {
|
func TestFindUsedBlobs(t *testing.T) {
|
||||||
repo := repository.TestRepository(t)
|
repo := repository.TestRepository(t)
|
||||||
|
|
||||||
var snapshots []*restic.Snapshot
|
var snapshots []*data.Snapshot
|
||||||
for i := 0; i < findTestSnapshots; i++ {
|
for i := 0; i < findTestSnapshots; i++ {
|
||||||
sn := restic.TestCreateSnapshot(t, repo, findTestTime.Add(time.Duration(i)*time.Second), findTestDepth)
|
sn := data.TestCreateSnapshot(t, repo, findTestTime.Add(time.Duration(i)*time.Second), findTestDepth)
|
||||||
t.Logf("snapshot %v saved, tree %v", sn.ID().Str(), sn.Tree.Str())
|
t.Logf("snapshot %v saved, tree %v", sn.ID().Str(), sn.Tree.Str())
|
||||||
snapshots = append(snapshots, sn)
|
snapshots = append(snapshots, sn)
|
||||||
}
|
}
|
||||||
@@ -98,7 +99,7 @@ func TestFindUsedBlobs(t *testing.T) {
|
|||||||
|
|
||||||
for i, sn := range snapshots {
|
for i, sn := range snapshots {
|
||||||
usedBlobs := restic.NewBlobSet()
|
usedBlobs := restic.NewBlobSet()
|
||||||
err := restic.FindUsedBlobs(context.TODO(), repo, restic.IDs{*sn.Tree}, usedBlobs, p)
|
err := data.FindUsedBlobs(context.TODO(), repo, restic.IDs{*sn.Tree}, usedBlobs, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("FindUsedBlobs returned error: %v", err)
|
t.Errorf("FindUsedBlobs returned error: %v", err)
|
||||||
continue
|
continue
|
||||||
@@ -131,7 +132,7 @@ func TestMultiFindUsedBlobs(t *testing.T) {
|
|||||||
|
|
||||||
var snapshotTrees restic.IDs
|
var snapshotTrees restic.IDs
|
||||||
for i := 0; i < findTestSnapshots; i++ {
|
for i := 0; i < findTestSnapshots; i++ {
|
||||||
sn := restic.TestCreateSnapshot(t, repo, findTestTime.Add(time.Duration(i)*time.Second), findTestDepth)
|
sn := data.TestCreateSnapshot(t, repo, findTestTime.Add(time.Duration(i)*time.Second), findTestDepth)
|
||||||
t.Logf("snapshot %v saved, tree %v", sn.ID().Str(), sn.Tree.Str())
|
t.Logf("snapshot %v saved, tree %v", sn.ID().Str(), sn.Tree.Str())
|
||||||
snapshotTrees = append(snapshotTrees, *sn.Tree)
|
snapshotTrees = append(snapshotTrees, *sn.Tree)
|
||||||
}
|
}
|
||||||
@@ -148,7 +149,7 @@ func TestMultiFindUsedBlobs(t *testing.T) {
|
|||||||
// run twice to check progress bar handling of duplicate tree roots
|
// run twice to check progress bar handling of duplicate tree roots
|
||||||
usedBlobs := restic.NewBlobSet()
|
usedBlobs := restic.NewBlobSet()
|
||||||
for i := 1; i < 3; i++ {
|
for i := 1; i < 3; i++ {
|
||||||
err := restic.FindUsedBlobs(context.TODO(), repo, snapshotTrees, usedBlobs, p)
|
err := data.FindUsedBlobs(context.TODO(), repo, snapshotTrees, usedBlobs, p)
|
||||||
test.OK(t, err)
|
test.OK(t, err)
|
||||||
v, _ := p.Get()
|
v, _ := p.Get()
|
||||||
test.Equals(t, v, uint64(i*len(snapshotTrees)))
|
test.Equals(t, v, uint64(i*len(snapshotTrees)))
|
||||||
@@ -177,16 +178,16 @@ func (r ForbiddenRepo) Connections() uint {
|
|||||||
func TestFindUsedBlobsSkipsSeenBlobs(t *testing.T) {
|
func TestFindUsedBlobsSkipsSeenBlobs(t *testing.T) {
|
||||||
repo := repository.TestRepository(t)
|
repo := repository.TestRepository(t)
|
||||||
|
|
||||||
snapshot := restic.TestCreateSnapshot(t, repo, findTestTime, findTestDepth)
|
snapshot := data.TestCreateSnapshot(t, repo, findTestTime, findTestDepth)
|
||||||
t.Logf("snapshot %v saved, tree %v", snapshot.ID().Str(), snapshot.Tree.Str())
|
t.Logf("snapshot %v saved, tree %v", snapshot.ID().Str(), snapshot.Tree.Str())
|
||||||
|
|
||||||
usedBlobs := restic.NewBlobSet()
|
usedBlobs := restic.NewBlobSet()
|
||||||
err := restic.FindUsedBlobs(context.TODO(), repo, restic.IDs{*snapshot.Tree}, usedBlobs, nil)
|
err := data.FindUsedBlobs(context.TODO(), repo, restic.IDs{*snapshot.Tree}, usedBlobs, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("FindUsedBlobs returned error: %v", err)
|
t.Fatalf("FindUsedBlobs returned error: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = restic.FindUsedBlobs(context.TODO(), ForbiddenRepo{}, restic.IDs{*snapshot.Tree}, usedBlobs, nil)
|
err = data.FindUsedBlobs(context.TODO(), ForbiddenRepo{}, restic.IDs{*snapshot.Tree}, usedBlobs, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("FindUsedBlobs returned error: %v", err)
|
t.Fatalf("FindUsedBlobs returned error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -195,13 +196,13 @@ func TestFindUsedBlobsSkipsSeenBlobs(t *testing.T) {
|
|||||||
func BenchmarkFindUsedBlobs(b *testing.B) {
|
func BenchmarkFindUsedBlobs(b *testing.B) {
|
||||||
repo := repository.TestRepository(b)
|
repo := repository.TestRepository(b)
|
||||||
|
|
||||||
sn := restic.TestCreateSnapshot(b, repo, findTestTime, findTestDepth)
|
sn := data.TestCreateSnapshot(b, repo, findTestTime, findTestDepth)
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
blobs := restic.NewBlobSet()
|
blobs := restic.NewBlobSet()
|
||||||
err := restic.FindUsedBlobs(context.TODO(), repo, restic.IDs{*sn.Tree}, blobs, nil)
|
err := data.FindUsedBlobs(context.TODO(), repo, restic.IDs{*sn.Tree}, blobs, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Error(err)
|
b.Error(err)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package restic
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -13,6 +13,7 @@ import (
|
|||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/restic"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
)
|
)
|
||||||
@@ -105,8 +106,8 @@ type Node struct {
|
|||||||
ExtendedAttributes []ExtendedAttribute `json:"extended_attributes,omitempty"`
|
ExtendedAttributes []ExtendedAttribute `json:"extended_attributes,omitempty"`
|
||||||
GenericAttributes map[GenericAttributeType]json.RawMessage `json:"generic_attributes,omitempty"`
|
GenericAttributes map[GenericAttributeType]json.RawMessage `json:"generic_attributes,omitempty"`
|
||||||
Device uint64 `json:"device,omitempty"` // in case of Type == "dev", stat.st_rdev
|
Device uint64 `json:"device,omitempty"` // in case of Type == "dev", stat.st_rdev
|
||||||
Content IDs `json:"content"`
|
Content restic.IDs `json:"content"`
|
||||||
Subtree *ID `json:"subtree,omitempty"`
|
Subtree *restic.ID `json:"subtree,omitempty"`
|
||||||
|
|
||||||
Error string `json:"error,omitempty"`
|
Error string `json:"error,omitempty"`
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package restic
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package restic
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package restic
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -9,26 +9,27 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/debug"
|
"github.com/restic/restic/internal/debug"
|
||||||
|
"github.com/restic/restic/internal/restic"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Snapshot is the state of a resource at one point in time.
|
// Snapshot is the state of a resource at one point in time.
|
||||||
type Snapshot struct {
|
type Snapshot struct {
|
||||||
Time time.Time `json:"time"`
|
Time time.Time `json:"time"`
|
||||||
Parent *ID `json:"parent,omitempty"`
|
Parent *restic.ID `json:"parent,omitempty"`
|
||||||
Tree *ID `json:"tree"`
|
Tree *restic.ID `json:"tree"`
|
||||||
Paths []string `json:"paths"`
|
Paths []string `json:"paths"`
|
||||||
Hostname string `json:"hostname,omitempty"`
|
Hostname string `json:"hostname,omitempty"`
|
||||||
Username string `json:"username,omitempty"`
|
Username string `json:"username,omitempty"`
|
||||||
UID uint32 `json:"uid,omitempty"`
|
UID uint32 `json:"uid,omitempty"`
|
||||||
GID uint32 `json:"gid,omitempty"`
|
GID uint32 `json:"gid,omitempty"`
|
||||||
Excludes []string `json:"excludes,omitempty"`
|
Excludes []string `json:"excludes,omitempty"`
|
||||||
Tags []string `json:"tags,omitempty"`
|
Tags []string `json:"tags,omitempty"`
|
||||||
Original *ID `json:"original,omitempty"`
|
Original *restic.ID `json:"original,omitempty"`
|
||||||
|
|
||||||
ProgramVersion string `json:"program_version,omitempty"`
|
ProgramVersion string `json:"program_version,omitempty"`
|
||||||
Summary *SnapshotSummary `json:"summary,omitempty"`
|
Summary *SnapshotSummary `json:"summary,omitempty"`
|
||||||
|
|
||||||
id *ID // plaintext ID, used during restore
|
id *restic.ID // plaintext ID, used during restore
|
||||||
}
|
}
|
||||||
|
|
||||||
type SnapshotSummary struct {
|
type SnapshotSummary struct {
|
||||||
@@ -79,9 +80,9 @@ func NewSnapshot(paths []string, tags []string, hostname string, time time.Time)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LoadSnapshot loads the snapshot with the id and returns it.
|
// LoadSnapshot loads the snapshot with the id and returns it.
|
||||||
func LoadSnapshot(ctx context.Context, loader LoaderUnpacked, id ID) (*Snapshot, error) {
|
func LoadSnapshot(ctx context.Context, loader restic.LoaderUnpacked, id restic.ID) (*Snapshot, error) {
|
||||||
sn := &Snapshot{id: &id}
|
sn := &Snapshot{id: &id}
|
||||||
err := LoadJSONUnpacked(ctx, loader, SnapshotFile, id, sn)
|
err := restic.LoadJSONUnpacked(ctx, loader, restic.SnapshotFile, id, sn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to load snapshot %v: %w", id.Str(), err)
|
return nil, fmt.Errorf("failed to load snapshot %v: %w", id.Str(), err)
|
||||||
}
|
}
|
||||||
@@ -90,8 +91,8 @@ func LoadSnapshot(ctx context.Context, loader LoaderUnpacked, id ID) (*Snapshot,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SaveSnapshot saves the snapshot sn and returns its ID.
|
// SaveSnapshot saves the snapshot sn and returns its ID.
|
||||||
func SaveSnapshot(ctx context.Context, repo SaverUnpacked[WriteableFileType], sn *Snapshot) (ID, error) {
|
func SaveSnapshot(ctx context.Context, repo restic.SaverUnpacked[restic.WriteableFileType], sn *Snapshot) (restic.ID, error) {
|
||||||
return SaveJSONUnpacked(ctx, repo, WriteableSnapshotFile, sn)
|
return restic.SaveJSONUnpacked(ctx, repo, restic.WriteableSnapshotFile, sn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ForAllSnapshots reads all snapshots in parallel and calls the
|
// ForAllSnapshots reads all snapshots in parallel and calls the
|
||||||
@@ -99,11 +100,11 @@ func SaveSnapshot(ctx context.Context, repo SaverUnpacked[WriteableFileType], sn
|
|||||||
// If the called function returns an error, this function is cancelled and
|
// If the called function returns an error, this function is cancelled and
|
||||||
// also returns this error.
|
// also returns this error.
|
||||||
// If a snapshot ID is in excludeIDs, it will be ignored.
|
// If a snapshot ID is in excludeIDs, it will be ignored.
|
||||||
func ForAllSnapshots(ctx context.Context, be Lister, loader LoaderUnpacked, excludeIDs IDSet, fn func(ID, *Snapshot, error) error) error {
|
func ForAllSnapshots(ctx context.Context, be restic.Lister, loader restic.LoaderUnpacked, excludeIDs restic.IDSet, fn func(restic.ID, *Snapshot, error) error) error {
|
||||||
var m sync.Mutex
|
var m sync.Mutex
|
||||||
|
|
||||||
// For most snapshots decoding is nearly for free, thus just assume were only limited by IO
|
// For most snapshots decoding is nearly for free, thus just assume were only limited by IO
|
||||||
return ParallelList(ctx, be, SnapshotFile, loader.Connections(), func(ctx context.Context, id ID, _ int64) error {
|
return restic.ParallelList(ctx, be, restic.SnapshotFile, loader.Connections(), func(ctx context.Context, id restic.ID, _ int64) error {
|
||||||
if excludeIDs.Has(id) {
|
if excludeIDs.Has(id) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -121,7 +122,7 @@ func (sn Snapshot) String() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ID returns the snapshot's ID.
|
// ID returns the snapshot's ID.
|
||||||
func (sn Snapshot) ID() *ID {
|
func (sn Snapshot) ID() *restic.ID {
|
||||||
return sn.id
|
return sn.id
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +134,7 @@ func (sn *Snapshot) fillUserInfo() error {
|
|||||||
sn.Username = usr.Username
|
sn.Username = usr.Username
|
||||||
|
|
||||||
// set userid and groupid
|
// set userid and groupid
|
||||||
sn.UID, sn.GID, err = uidGidInt(usr)
|
sn.UID, sn.GID, err = restic.UidGidInt(usr)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package restic
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/restic/restic/internal/errors"
|
"github.com/restic/restic/internal/errors"
|
||||||
|
"github.com/restic/restic/internal/restic"
|
||||||
)
|
)
|
||||||
|
|
||||||
// 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.
|
||||||
@@ -34,7 +35,7 @@ func (f *SnapshotFilter) matches(sn *Snapshot) bool {
|
|||||||
|
|
||||||
// findLatest finds the latest snapshot with optional target/directory,
|
// findLatest finds the latest snapshot with optional target/directory,
|
||||||
// tags, hostname, and timestamp filters.
|
// tags, hostname, and timestamp filters.
|
||||||
func (f *SnapshotFilter) findLatest(ctx context.Context, be Lister, loader LoaderUnpacked) (*Snapshot, error) {
|
func (f *SnapshotFilter) findLatest(ctx context.Context, be restic.Lister, loader restic.LoaderUnpacked) (*Snapshot, error) {
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
absTargets := make([]string, 0, len(f.Paths))
|
absTargets := make([]string, 0, len(f.Paths))
|
||||||
@@ -51,7 +52,7 @@ func (f *SnapshotFilter) findLatest(ctx context.Context, be Lister, loader Loade
|
|||||||
|
|
||||||
var latest *Snapshot
|
var latest *Snapshot
|
||||||
|
|
||||||
err = ForAllSnapshots(ctx, be, loader, nil, func(id ID, snapshot *Snapshot, err error) error {
|
err = ForAllSnapshots(ctx, be, loader, nil, func(id restic.ID, snapshot *Snapshot, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Errorf("Error loading snapshot %v: %v", id.Str(), err)
|
return errors.Errorf("Error loading snapshot %v: %v", id.Str(), err)
|
||||||
}
|
}
|
||||||
@@ -90,14 +91,14 @@ func splitSnapshotID(s string) (id, subfolder string) {
|
|||||||
|
|
||||||
// FindSnapshot takes a string and tries to find a snapshot whose ID matches
|
// FindSnapshot takes a string and tries to find a snapshot whose ID matches
|
||||||
// the string as closely as possible.
|
// the string as closely as possible.
|
||||||
func FindSnapshot(ctx context.Context, be Lister, loader LoaderUnpacked, s string) (*Snapshot, string, error) {
|
func FindSnapshot(ctx context.Context, be restic.Lister, loader restic.LoaderUnpacked, s string) (*Snapshot, string, error) {
|
||||||
s, subfolder := splitSnapshotID(s)
|
s, subfolder := splitSnapshotID(s)
|
||||||
|
|
||||||
// no need to list snapshots if `s` is already a full id
|
// no need to list snapshots if `s` is already a full id
|
||||||
id, err := ParseID(s)
|
id, err := restic.ParseID(s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// find snapshot id with prefix
|
// find snapshot id with prefix
|
||||||
id, err = Find(ctx, be, SnapshotFile, s)
|
id, err = restic.Find(ctx, be, restic.SnapshotFile, s)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
@@ -108,7 +109,7 @@ func FindSnapshot(ctx context.Context, be Lister, loader LoaderUnpacked, s strin
|
|||||||
|
|
||||||
// FindLatest returns either the latest of a filtered list of all snapshots
|
// FindLatest returns either the latest of a filtered list of all snapshots
|
||||||
// or a snapshot specified by `snapshotID`.
|
// or a snapshot specified by `snapshotID`.
|
||||||
func (f *SnapshotFilter) FindLatest(ctx context.Context, be Lister, loader LoaderUnpacked, snapshotID string) (*Snapshot, string, error) {
|
func (f *SnapshotFilter) FindLatest(ctx context.Context, be restic.Lister, loader restic.LoaderUnpacked, snapshotID string) (*Snapshot, string, error) {
|
||||||
id, subfolder := splitSnapshotID(snapshotID)
|
id, subfolder := splitSnapshotID(snapshotID)
|
||||||
if id == "latest" {
|
if id == "latest" {
|
||||||
sn, err := f.findLatest(ctx, be, loader)
|
sn, err := f.findLatest(ctx, be, loader)
|
||||||
@@ -126,12 +127,12 @@ type SnapshotFindCb func(string, *Snapshot, error) error
|
|||||||
var ErrInvalidSnapshotSyntax = errors.New("<snapshot>:<subfolder> syntax not allowed")
|
var ErrInvalidSnapshotSyntax = errors.New("<snapshot>:<subfolder> syntax not allowed")
|
||||||
|
|
||||||
// FindAll yields Snapshots, either given explicitly by `snapshotIDs` or filtered from the list of all snapshots.
|
// FindAll yields Snapshots, either given explicitly by `snapshotIDs` or filtered from the list of all snapshots.
|
||||||
func (f *SnapshotFilter) FindAll(ctx context.Context, be Lister, loader LoaderUnpacked, snapshotIDs []string, fn SnapshotFindCb) error {
|
func (f *SnapshotFilter) FindAll(ctx context.Context, be restic.Lister, loader restic.LoaderUnpacked, snapshotIDs []string, fn SnapshotFindCb) error {
|
||||||
if len(snapshotIDs) != 0 {
|
if len(snapshotIDs) != 0 {
|
||||||
var err error
|
var err error
|
||||||
usedFilter := false
|
usedFilter := false
|
||||||
|
|
||||||
ids := NewIDSet()
|
ids := restic.NewIDSet()
|
||||||
// Process all snapshot IDs given as arguments.
|
// Process all snapshot IDs given as arguments.
|
||||||
for _, s := range snapshotIDs {
|
for _, s := range snapshotIDs {
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
@@ -183,7 +184,7 @@ func (f *SnapshotFilter) FindAll(ctx context.Context, be Lister, loader LoaderUn
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return ForAllSnapshots(ctx, be, loader, nil, func(id ID, sn *Snapshot, err error) error {
|
return ForAllSnapshots(ctx, be, loader, nil, func(id restic.ID, sn *Snapshot, err error) error {
|
||||||
if err == nil && !f.matches(sn) {
|
if err == nil && !f.matches(sn) {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1,21 +1,21 @@
|
|||||||
package restic_test
|
package data_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
|
||||||
"github.com/restic/restic/internal/test"
|
"github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFindLatestSnapshot(t *testing.T) {
|
func TestFindLatestSnapshot(t *testing.T) {
|
||||||
repo := repository.TestRepository(t)
|
repo := repository.TestRepository(t)
|
||||||
restic.TestCreateSnapshot(t, repo, parseTimeUTC("2015-05-05 05:05:05"), 1)
|
data.TestCreateSnapshot(t, repo, parseTimeUTC("2015-05-05 05:05:05"), 1)
|
||||||
restic.TestCreateSnapshot(t, repo, parseTimeUTC("2017-07-07 07:07:07"), 1)
|
data.TestCreateSnapshot(t, repo, parseTimeUTC("2017-07-07 07:07:07"), 1)
|
||||||
latestSnapshot := restic.TestCreateSnapshot(t, repo, parseTimeUTC("2019-09-09 09:09:09"), 1)
|
latestSnapshot := data.TestCreateSnapshot(t, repo, parseTimeUTC("2019-09-09 09:09:09"), 1)
|
||||||
|
|
||||||
f := restic.SnapshotFilter{Hosts: []string{"foo"}}
|
f := data.SnapshotFilter{Hosts: []string{"foo"}}
|
||||||
sn, _, err := f.FindLatest(context.TODO(), repo, repo, "latest")
|
sn, _, err := f.FindLatest(context.TODO(), repo, repo, "latest")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("FindLatest returned error: %v", err)
|
t.Fatalf("FindLatest returned error: %v", err)
|
||||||
@@ -28,11 +28,11 @@ func TestFindLatestSnapshot(t *testing.T) {
|
|||||||
|
|
||||||
func TestFindLatestSnapshotWithMaxTimestamp(t *testing.T) {
|
func TestFindLatestSnapshotWithMaxTimestamp(t *testing.T) {
|
||||||
repo := repository.TestRepository(t)
|
repo := repository.TestRepository(t)
|
||||||
restic.TestCreateSnapshot(t, repo, parseTimeUTC("2015-05-05 05:05:05"), 1)
|
data.TestCreateSnapshot(t, repo, parseTimeUTC("2015-05-05 05:05:05"), 1)
|
||||||
desiredSnapshot := restic.TestCreateSnapshot(t, repo, parseTimeUTC("2017-07-07 07:07:07"), 1)
|
desiredSnapshot := data.TestCreateSnapshot(t, repo, parseTimeUTC("2017-07-07 07:07:07"), 1)
|
||||||
restic.TestCreateSnapshot(t, repo, parseTimeUTC("2019-09-09 09:09:09"), 1)
|
data.TestCreateSnapshot(t, repo, parseTimeUTC("2019-09-09 09:09:09"), 1)
|
||||||
|
|
||||||
sn, _, err := (&restic.SnapshotFilter{
|
sn, _, err := (&data.SnapshotFilter{
|
||||||
Hosts: []string{"foo"},
|
Hosts: []string{"foo"},
|
||||||
TimestampLimit: parseTimeUTC("2018-08-08 08:08:08"),
|
TimestampLimit: parseTimeUTC("2018-08-08 08:08:08"),
|
||||||
}).FindLatest(context.TODO(), repo, repo, "latest")
|
}).FindLatest(context.TODO(), repo, repo, "latest")
|
||||||
@@ -47,8 +47,8 @@ func TestFindLatestSnapshotWithMaxTimestamp(t *testing.T) {
|
|||||||
|
|
||||||
func TestFindLatestWithSubpath(t *testing.T) {
|
func TestFindLatestWithSubpath(t *testing.T) {
|
||||||
repo := repository.TestRepository(t)
|
repo := repository.TestRepository(t)
|
||||||
restic.TestCreateSnapshot(t, repo, parseTimeUTC("2015-05-05 05:05:05"), 1)
|
data.TestCreateSnapshot(t, repo, parseTimeUTC("2015-05-05 05:05:05"), 1)
|
||||||
desiredSnapshot := restic.TestCreateSnapshot(t, repo, parseTimeUTC("2017-07-07 07:07:07"), 1)
|
desiredSnapshot := data.TestCreateSnapshot(t, repo, parseTimeUTC("2017-07-07 07:07:07"), 1)
|
||||||
|
|
||||||
for _, exp := range []struct {
|
for _, exp := range []struct {
|
||||||
query string
|
query string
|
||||||
@@ -62,7 +62,7 @@ func TestFindLatestWithSubpath(t *testing.T) {
|
|||||||
{desiredSnapshot.ID().String() + ":subfolder", "subfolder"},
|
{desiredSnapshot.ID().String() + ":subfolder", "subfolder"},
|
||||||
} {
|
} {
|
||||||
t.Run("", func(t *testing.T) {
|
t.Run("", func(t *testing.T) {
|
||||||
sn, subfolder, err := (&restic.SnapshotFilter{}).FindLatest(context.TODO(), repo, repo, exp.query)
|
sn, subfolder, err := (&data.SnapshotFilter{}).FindLatest(context.TODO(), repo, repo, exp.query)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("FindLatest returned error: %v", err)
|
t.Fatalf("FindLatest returned error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -75,13 +75,13 @@ func TestFindLatestWithSubpath(t *testing.T) {
|
|||||||
|
|
||||||
func TestFindAllSubpathError(t *testing.T) {
|
func TestFindAllSubpathError(t *testing.T) {
|
||||||
repo := repository.TestRepository(t)
|
repo := repository.TestRepository(t)
|
||||||
desiredSnapshot := restic.TestCreateSnapshot(t, repo, parseTimeUTC("2017-07-07 07:07:07"), 1)
|
desiredSnapshot := data.TestCreateSnapshot(t, repo, parseTimeUTC("2017-07-07 07:07:07"), 1)
|
||||||
|
|
||||||
count := 0
|
count := 0
|
||||||
test.OK(t, (&restic.SnapshotFilter{}).FindAll(context.TODO(), repo, repo,
|
test.OK(t, (&data.SnapshotFilter{}).FindAll(context.TODO(), repo, repo,
|
||||||
[]string{"latest:subfolder", desiredSnapshot.ID().Str() + ":subfolder"},
|
[]string{"latest:subfolder", desiredSnapshot.ID().Str() + ":subfolder"},
|
||||||
func(id string, sn *restic.Snapshot, err error) error {
|
func(id string, sn *data.Snapshot, err error) error {
|
||||||
if err == restic.ErrInvalidSnapshotSyntax {
|
if err == data.ErrInvalidSnapshotSyntax {
|
||||||
count++
|
count++
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package restic
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -1,41 +1,41 @@
|
|||||||
package restic_test
|
package data_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/test"
|
"github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGroupByOptions(t *testing.T) {
|
func TestGroupByOptions(t *testing.T) {
|
||||||
for _, exp := range []struct {
|
for _, exp := range []struct {
|
||||||
from string
|
from string
|
||||||
opts restic.SnapshotGroupByOptions
|
opts data.SnapshotGroupByOptions
|
||||||
normalized string
|
normalized string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
from: "",
|
from: "",
|
||||||
opts: restic.SnapshotGroupByOptions{},
|
opts: data.SnapshotGroupByOptions{},
|
||||||
normalized: "",
|
normalized: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: "host,paths",
|
from: "host,paths",
|
||||||
opts: restic.SnapshotGroupByOptions{Host: true, Path: true},
|
opts: data.SnapshotGroupByOptions{Host: true, Path: true},
|
||||||
normalized: "host,paths",
|
normalized: "host,paths",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: "host,path,tag",
|
from: "host,path,tag",
|
||||||
opts: restic.SnapshotGroupByOptions{Host: true, Path: true, Tag: true},
|
opts: data.SnapshotGroupByOptions{Host: true, Path: true, Tag: true},
|
||||||
normalized: "host,paths,tags",
|
normalized: "host,paths,tags",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
from: "hosts,paths,tags",
|
from: "hosts,paths,tags",
|
||||||
opts: restic.SnapshotGroupByOptions{Host: true, Path: true, Tag: true},
|
opts: data.SnapshotGroupByOptions{Host: true, Path: true, Tag: true},
|
||||||
normalized: "host,paths,tags",
|
normalized: "host,paths,tags",
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
var opts restic.SnapshotGroupByOptions
|
var opts data.SnapshotGroupByOptions
|
||||||
test.OK(t, opts.Set(exp.from))
|
test.OK(t, opts.Set(exp.from))
|
||||||
if !cmp.Equal(opts, exp.opts) {
|
if !cmp.Equal(opts, exp.opts) {
|
||||||
t.Errorf("unexpected opts %s", cmp.Diff(opts, exp.opts))
|
t.Errorf("unexpected opts %s", cmp.Diff(opts, exp.opts))
|
||||||
@@ -43,7 +43,7 @@ func TestGroupByOptions(t *testing.T) {
|
|||||||
test.Equals(t, opts.String(), exp.normalized)
|
test.Equals(t, opts.String(), exp.normalized)
|
||||||
}
|
}
|
||||||
|
|
||||||
var opts restic.SnapshotGroupByOptions
|
var opts data.SnapshotGroupByOptions
|
||||||
err := opts.Set("tags,invalid")
|
err := opts.Set("tags,invalid")
|
||||||
test.Assert(t, err != nil, "missing error on invalid tags")
|
test.Assert(t, err != nil, "missing error on invalid tags")
|
||||||
test.Assert(t, !opts.Host && !opts.Path && !opts.Tag, "unexpected opts %s %s %s", opts.Host, opts.Path, opts.Tag)
|
test.Assert(t, !opts.Host && !opts.Path && !opts.Tag, "unexpected opts %s %s %s", opts.Host, opts.Path, opts.Tag)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package restic
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package restic_test
|
package data_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"github.com/google/go-cmp/cmp/cmpopts"
|
"github.com/google/go-cmp/cmp/cmpopts"
|
||||||
"github.com/restic/restic/internal/restic"
|
"github.com/restic/restic/internal/data"
|
||||||
)
|
)
|
||||||
|
|
||||||
func parseTimeUTC(s string) time.Time {
|
func parseTimeUTC(s string) time.Time {
|
||||||
@@ -24,7 +24,7 @@ func parseTimeUTC(s string) time.Time {
|
|||||||
|
|
||||||
// Returns the maximum number of snapshots to be kept according to this policy.
|
// Returns the maximum number of snapshots to be kept according to this policy.
|
||||||
// If any of the counts is -1 it will return 0.
|
// If any of the counts is -1 it will return 0.
|
||||||
func policySum(e *restic.ExpirePolicy) int {
|
func policySum(e *data.ExpirePolicy) int {
|
||||||
if e.Last == -1 || e.Hourly == -1 || e.Daily == -1 || e.Weekly == -1 || e.Monthly == -1 || e.Yearly == -1 {
|
if e.Last == -1 || e.Hourly == -1 || e.Daily == -1 || e.Weekly == -1 || e.Monthly == -1 || e.Yearly == -1 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
@@ -36,11 +36,11 @@ func TestExpireSnapshotOps(t *testing.T) {
|
|||||||
data := []struct {
|
data := []struct {
|
||||||
expectEmpty bool
|
expectEmpty bool
|
||||||
expectSum int
|
expectSum int
|
||||||
p *restic.ExpirePolicy
|
p *data.ExpirePolicy
|
||||||
}{
|
}{
|
||||||
{true, 0, &restic.ExpirePolicy{}},
|
{true, 0, &data.ExpirePolicy{}},
|
||||||
{true, 0, &restic.ExpirePolicy{Tags: []restic.TagList{}}},
|
{true, 0, &data.ExpirePolicy{Tags: []data.TagList{}}},
|
||||||
{false, 22, &restic.ExpirePolicy{Daily: 7, Weekly: 2, Monthly: 3, Yearly: 10}},
|
{false, 22, &data.ExpirePolicy{Daily: 7, Weekly: 2, Monthly: 3, Yearly: 10}},
|
||||||
}
|
}
|
||||||
for i, d := range data {
|
for i, d := range data {
|
||||||
isEmpty := d.p.Empty()
|
isEmpty := d.p.Empty()
|
||||||
@@ -57,8 +57,8 @@ func TestExpireSnapshotOps(t *testing.T) {
|
|||||||
// ApplyPolicyResult is used to marshal/unmarshal the golden files for
|
// ApplyPolicyResult is used to marshal/unmarshal the golden files for
|
||||||
// TestApplyPolicy.
|
// TestApplyPolicy.
|
||||||
type ApplyPolicyResult struct {
|
type ApplyPolicyResult struct {
|
||||||
Keep restic.Snapshots `json:"keep"`
|
Keep data.Snapshots `json:"keep"`
|
||||||
Reasons []restic.KeepReason `json:"reasons,omitempty"`
|
Reasons []data.KeepReason `json:"reasons,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadGoldenFile(t testing.TB, filename string) (res ApplyPolicyResult) {
|
func loadGoldenFile(t testing.TB, filename string) (res ApplyPolicyResult) {
|
||||||
@@ -75,7 +75,7 @@ func loadGoldenFile(t testing.TB, filename string) (res ApplyPolicyResult) {
|
|||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
func saveGoldenFile(t testing.TB, filename string, keep restic.Snapshots, reasons []restic.KeepReason) {
|
func saveGoldenFile(t testing.TB, filename string, keep data.Snapshots, reasons []data.KeepReason) {
|
||||||
res := ApplyPolicyResult{
|
res := ApplyPolicyResult{
|
||||||
Keep: keep,
|
Keep: keep,
|
||||||
Reasons: reasons,
|
Reasons: reasons,
|
||||||
@@ -92,7 +92,7 @@ func saveGoldenFile(t testing.TB, filename string, keep restic.Snapshots, reason
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestApplyPolicy(t *testing.T) {
|
func TestApplyPolicy(t *testing.T) {
|
||||||
var testExpireSnapshots = restic.Snapshots{
|
var testExpireSnapshots = data.Snapshots{
|
||||||
{Time: parseTimeUTC("2014-09-01 10:20:30")},
|
{Time: parseTimeUTC("2014-09-01 10:20:30")},
|
||||||
{Time: parseTimeUTC("2014-09-02 10:20:30")},
|
{Time: parseTimeUTC("2014-09-02 10:20:30")},
|
||||||
{Time: parseTimeUTC("2014-09-05 10:20:30")},
|
{Time: parseTimeUTC("2014-09-05 10:20:30")},
|
||||||
@@ -198,7 +198,7 @@ func TestApplyPolicy(t *testing.T) {
|
|||||||
{Time: parseTimeUTC("2016-01-18 12:02:03")},
|
{Time: parseTimeUTC("2016-01-18 12:02:03")},
|
||||||
}
|
}
|
||||||
|
|
||||||
var tests = []restic.ExpirePolicy{
|
var tests = []data.ExpirePolicy{
|
||||||
{},
|
{},
|
||||||
{Last: 10},
|
{Last: 10},
|
||||||
{Last: 15},
|
{Last: 15},
|
||||||
@@ -217,29 +217,29 @@ func TestApplyPolicy(t *testing.T) {
|
|||||||
{Daily: 2, Weekly: 2, Monthly: 6},
|
{Daily: 2, Weekly: 2, Monthly: 6},
|
||||||
{Yearly: 10},
|
{Yearly: 10},
|
||||||
{Daily: 7, Weekly: 2, Monthly: 3, Yearly: 10},
|
{Daily: 7, Weekly: 2, Monthly: 3, Yearly: 10},
|
||||||
{Tags: []restic.TagList{{"foo"}}},
|
{Tags: []data.TagList{{"foo"}}},
|
||||||
{Tags: []restic.TagList{{"foo", "bar"}}},
|
{Tags: []data.TagList{{"foo", "bar"}}},
|
||||||
{Tags: []restic.TagList{{"foo"}, {"bar"}}},
|
{Tags: []data.TagList{{"foo"}, {"bar"}}},
|
||||||
{Within: restic.ParseDurationOrPanic("1d")},
|
{Within: data.ParseDurationOrPanic("1d")},
|
||||||
{Within: restic.ParseDurationOrPanic("2d")},
|
{Within: data.ParseDurationOrPanic("2d")},
|
||||||
{Within: restic.ParseDurationOrPanic("7d")},
|
{Within: data.ParseDurationOrPanic("7d")},
|
||||||
{Within: restic.ParseDurationOrPanic("1m")},
|
{Within: data.ParseDurationOrPanic("1m")},
|
||||||
{Within: restic.ParseDurationOrPanic("1m14d")},
|
{Within: data.ParseDurationOrPanic("1m14d")},
|
||||||
{Within: restic.ParseDurationOrPanic("1y1d1m")},
|
{Within: data.ParseDurationOrPanic("1y1d1m")},
|
||||||
{Within: restic.ParseDurationOrPanic("13d23h")},
|
{Within: data.ParseDurationOrPanic("13d23h")},
|
||||||
{Within: restic.ParseDurationOrPanic("2m2h")},
|
{Within: data.ParseDurationOrPanic("2m2h")},
|
||||||
{Within: restic.ParseDurationOrPanic("1y2m3d3h")},
|
{Within: data.ParseDurationOrPanic("1y2m3d3h")},
|
||||||
{WithinHourly: restic.ParseDurationOrPanic("1y2m3d3h")},
|
{WithinHourly: data.ParseDurationOrPanic("1y2m3d3h")},
|
||||||
{WithinDaily: restic.ParseDurationOrPanic("1y2m3d3h")},
|
{WithinDaily: data.ParseDurationOrPanic("1y2m3d3h")},
|
||||||
{WithinWeekly: restic.ParseDurationOrPanic("1y2m3d3h")},
|
{WithinWeekly: data.ParseDurationOrPanic("1y2m3d3h")},
|
||||||
{WithinMonthly: restic.ParseDurationOrPanic("1y2m3d3h")},
|
{WithinMonthly: data.ParseDurationOrPanic("1y2m3d3h")},
|
||||||
{WithinYearly: restic.ParseDurationOrPanic("1y2m3d3h")},
|
{WithinYearly: data.ParseDurationOrPanic("1y2m3d3h")},
|
||||||
{Within: restic.ParseDurationOrPanic("1h"),
|
{Within: data.ParseDurationOrPanic("1h"),
|
||||||
WithinHourly: restic.ParseDurationOrPanic("1d"),
|
WithinHourly: data.ParseDurationOrPanic("1d"),
|
||||||
WithinDaily: restic.ParseDurationOrPanic("7d"),
|
WithinDaily: data.ParseDurationOrPanic("7d"),
|
||||||
WithinWeekly: restic.ParseDurationOrPanic("1m"),
|
WithinWeekly: data.ParseDurationOrPanic("1m"),
|
||||||
WithinMonthly: restic.ParseDurationOrPanic("1y"),
|
WithinMonthly: data.ParseDurationOrPanic("1y"),
|
||||||
WithinYearly: restic.ParseDurationOrPanic("9999y")},
|
WithinYearly: data.ParseDurationOrPanic("9999y")},
|
||||||
{Last: -1}, // keep all
|
{Last: -1}, // keep all
|
||||||
{Last: -1, Hourly: -1}, // keep all (Last overrides Hourly)
|
{Last: -1, Hourly: -1}, // keep all (Last overrides Hourly)
|
||||||
{Hourly: -1}, // keep all hourlies
|
{Hourly: -1}, // keep all hourlies
|
||||||
@@ -249,7 +249,7 @@ func TestApplyPolicy(t *testing.T) {
|
|||||||
for i, p := range tests {
|
for i, p := range tests {
|
||||||
t.Run("", func(t *testing.T) {
|
t.Run("", func(t *testing.T) {
|
||||||
|
|
||||||
keep, remove, reasons := restic.ApplyPolicy(testExpireSnapshots, p)
|
keep, remove, reasons := data.ApplyPolicy(testExpireSnapshots, p)
|
||||||
|
|
||||||
if len(keep)+len(remove) != len(testExpireSnapshots) {
|
if len(keep)+len(remove) != len(testExpireSnapshots) {
|
||||||
t.Errorf("len(keep)+len(remove) = %d != len(testExpireSnapshots) = %d",
|
t.Errorf("len(keep)+len(remove) = %d != len(testExpireSnapshots) = %d",
|
||||||
@@ -273,7 +273,7 @@ func TestApplyPolicy(t *testing.T) {
|
|||||||
|
|
||||||
want := loadGoldenFile(t, goldenFilename)
|
want := loadGoldenFile(t, goldenFilename)
|
||||||
|
|
||||||
cmpOpts := cmpopts.IgnoreUnexported(restic.Snapshot{})
|
cmpOpts := cmpopts.IgnoreUnexported(data.Snapshot{})
|
||||||
|
|
||||||
if !cmp.Equal(want.Keep, keep, cmpOpts) {
|
if !cmp.Equal(want.Keep, keep, cmpOpts) {
|
||||||
t.Error(cmp.Diff(want.Keep, keep, cmpOpts))
|
t.Error(cmp.Diff(want.Keep, keep, cmpOpts))
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
package restic_test
|
package data_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/restic/restic/internal/data"
|
||||||
"github.com/restic/restic/internal/repository"
|
"github.com/restic/restic/internal/repository"
|
||||||
"github.com/restic/restic/internal/restic"
|
|
||||||
rtest "github.com/restic/restic/internal/test"
|
rtest "github.com/restic/restic/internal/test"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestNewSnapshot(t *testing.T) {
|
func TestNewSnapshot(t *testing.T) {
|
||||||
paths := []string{"/home/foobar"}
|
paths := []string{"/home/foobar"}
|
||||||
|
|
||||||
_, err := restic.NewSnapshot(paths, nil, "foo", time.Now())
|
_, err := data.NewSnapshot(paths, nil, "foo", time.Now())
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -21,7 +21,7 @@ func TestTagList(t *testing.T) {
|
|||||||
paths := []string{"/home/foobar"}
|
paths := []string{"/home/foobar"}
|
||||||
tags := []string{""}
|
tags := []string{""}
|
||||||
|
|
||||||
sn, _ := restic.NewSnapshot(paths, nil, "foo", time.Now())
|
sn, _ := data.NewSnapshot(paths, nil, "foo", time.Now())
|
||||||
|
|
||||||
r := sn.HasTags(tags)
|
r := sn.HasTags(tags)
|
||||||
rtest.Assert(t, r, "Failed to match untagged snapshot")
|
rtest.Assert(t, r, "Failed to match untagged snapshot")
|
||||||
@@ -35,15 +35,15 @@ func testLoadJSONUnpacked(t *testing.T, version uint) {
|
|||||||
repo, _, _ := repository.TestRepositoryWithVersion(t, version)
|
repo, _, _ := repository.TestRepositoryWithVersion(t, version)
|
||||||
|
|
||||||
// archive a snapshot
|
// archive a snapshot
|
||||||
sn := restic.Snapshot{}
|
sn := data.Snapshot{}
|
||||||
sn.Hostname = "foobar"
|
sn.Hostname = "foobar"
|
||||||
sn.Username = "test!"
|
sn.Username = "test!"
|
||||||
|
|
||||||
id, err := restic.SaveSnapshot(context.TODO(), repo, &sn)
|
id, err := data.SaveSnapshot(context.TODO(), repo, &sn)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
// restore
|
// restore
|
||||||
sn2, err := restic.LoadSnapshot(context.TODO(), repo, id)
|
sn2, err := data.LoadSnapshot(context.TODO(), repo, id)
|
||||||
rtest.OK(t, err)
|
rtest.OK(t, err)
|
||||||
|
|
||||||
rtest.Equals(t, sn.Hostname, sn2.Hostname)
|
rtest.Equals(t, sn.Hostname, sn2.Hostname)
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package restic
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
package restic
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user