-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
backup: add --dry-run/-n flag to show what would happen.
This can be used to check how large a backup is or validate exclusions. It does not actually write any data to the underlying backend. This is implemented as a simple overlay backend that accepts writes without forwarding them, passes through reads, and generally does the minimal necessary to pretend that progress is actually happening. Fixes #1542 Example usage: $ restic -vv --dry-run . | grep added new /changelog/unreleased/issue-1542, saved in 0.000s (350 B added) modified /cmd/restic/cmd_backup.go, saved in 0.000s (16.543 KiB added) modified /cmd/restic/global.go, saved in 0.000s (0 B added) new /internal/backend/dry/dry_backend_test.go, saved in 0.000s (3.866 KiB added) new /internal/backend/dry/dry_backend.go, saved in 0.000s (3.744 KiB added) modified /internal/backend/test/tests.go, saved in 0.000s (0 B added) modified /internal/repository/repository.go, saved in 0.000s (20.707 KiB added) modified /internal/ui/backup.go, saved in 0.000s (9.110 KiB added) modified /internal/ui/jsonstatus/status.go, saved in 0.001s (11.055 KiB added) modified /restic, saved in 0.131s (25.542 MiB added) Would be added to the repo: 25.892 MiB
- Loading branch information
Showing
9 changed files
with
406 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
Enhancement: backup --dry-run/-n | ||
|
||
Added a new --dry-run/-n option to backup, which performs all the normal steps | ||
of a backup without actually writing data. Passing -vv will log information | ||
about files that would be added, allowing fast verification of backup options | ||
without any unnecessary write activity. | ||
|
||
https://github.com/restic/restic/issues/1542 | ||
https://github.com/restic/restic/pull/2308 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
package dry | ||
|
||
import ( | ||
"context" | ||
"io" | ||
"io/ioutil" | ||
"sync" | ||
|
||
"github.com/restic/restic/internal/errors" | ||
"github.com/restic/restic/internal/restic" | ||
|
||
"github.com/restic/restic/internal/debug" | ||
) | ||
|
||
type sizeMap map[restic.Handle]int | ||
|
||
var errNotFound = errors.New("not found") | ||
|
||
// Backend passes reads through to an underlying layer and only records | ||
// metadata about writes. This is used for `backup --dry-run`. | ||
// It is directly derivted from the mem backend. | ||
type Backend struct { | ||
be restic.Backend | ||
data sizeMap | ||
m sync.Mutex | ||
} | ||
|
||
// New returns a new backend that saves all data in a map in memory. | ||
func New(be restic.Backend) *Backend { | ||
b := &Backend{ | ||
be: be, | ||
data: make(sizeMap), | ||
} | ||
|
||
debug.Log("created new dry backend") | ||
|
||
return b | ||
} | ||
|
||
// Test returns whether a file exists. | ||
func (be *Backend) Test(ctx context.Context, h restic.Handle) (bool, error) { | ||
be.m.Lock() | ||
defer be.m.Unlock() | ||
|
||
debug.Log("Test %v", h) | ||
|
||
if _, ok := be.data[h]; ok { | ||
return true, nil | ||
} | ||
|
||
return be.be.Test(ctx, h) | ||
} | ||
|
||
// IsNotExist returns true if the file does not exist. | ||
func (be *Backend) IsNotExist(err error) bool { | ||
return errors.Cause(err) == errNotFound || be.be.IsNotExist(err) | ||
} | ||
|
||
// Save adds new Data to the backend. | ||
func (be *Backend) Save(ctx context.Context, h restic.Handle, rd restic.RewindReader) error { | ||
if err := h.Valid(); err != nil { | ||
return err | ||
} | ||
|
||
be.m.Lock() | ||
defer be.m.Unlock() | ||
|
||
if h.Type == restic.ConfigFile { | ||
h.Name = "" | ||
} | ||
|
||
if _, ok := be.data[h]; ok { | ||
return errors.New("file already exists") | ||
} | ||
|
||
buf, err := ioutil.ReadAll(rd) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
be.data[h] = len(buf) | ||
debug.Log("faked saving %v bytes at %v", len(buf), h) | ||
|
||
return nil | ||
} | ||
|
||
// Load runs fn with a reader that yields the contents of the file at h at the | ||
// given offset. | ||
func (be *Backend) Load(ctx context.Context, h restic.Handle, length int, offset int64, fn func(rd io.Reader) error) error { | ||
if _, ok := be.data[h]; ok { | ||
return errors.New("can't read file saved on dry backend") | ||
} | ||
return be.be.Load(ctx, h, length, offset, fn) | ||
} | ||
|
||
// Stat returns information about a file in the backend. | ||
func (be *Backend) Stat(ctx context.Context, h restic.Handle) (restic.FileInfo, error) { | ||
if err := h.Valid(); err != nil { | ||
return restic.FileInfo{}, err | ||
} | ||
|
||
be.m.Lock() | ||
defer be.m.Unlock() | ||
|
||
if h.Type == restic.ConfigFile { | ||
h.Name = "" | ||
} | ||
|
||
debug.Log("stat %v", h) | ||
|
||
s, ok := be.data[h] | ||
if !ok { | ||
return be.be.Stat(ctx, h) | ||
} | ||
|
||
return restic.FileInfo{Size: int64(s), Name: h.Name}, nil | ||
} | ||
|
||
// Remove deletes a file from the backend. | ||
func (be *Backend) Remove(ctx context.Context, h restic.Handle) error { | ||
be.m.Lock() | ||
defer be.m.Unlock() | ||
|
||
debug.Log("Remove %v", h) | ||
|
||
if _, ok := be.data[h]; !ok { | ||
return errNotFound | ||
} | ||
|
||
delete(be.data, h) | ||
|
||
return nil | ||
} | ||
|
||
// List returns a channel which yields entries from the backend. | ||
func (be *Backend) List(ctx context.Context, t restic.FileType, fn func(restic.FileInfo) error) error { | ||
entries := make(map[string]int64) | ||
|
||
be.m.Lock() | ||
for entry, size := range be.data { | ||
if entry.Type != t { | ||
continue | ||
} | ||
|
||
entries[entry.Name] = int64(size) | ||
} | ||
be.m.Unlock() | ||
|
||
for name, size := range entries { | ||
fi := restic.FileInfo{ | ||
Name: name, | ||
Size: size, | ||
} | ||
|
||
if ctx.Err() != nil { | ||
return ctx.Err() | ||
} | ||
|
||
err := fn(fi) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if ctx.Err() != nil { | ||
return ctx.Err() | ||
} | ||
} | ||
|
||
if ctx.Err() != nil { | ||
return ctx.Err() | ||
} | ||
|
||
return be.be.List(ctx, t, fn) | ||
} | ||
|
||
// Location returns the location of the backend (RAM). | ||
func (be *Backend) Location() string { | ||
return "DRY:" + be.be.Location() | ||
} | ||
|
||
// Delete removes all data in the backend. | ||
func (be *Backend) Delete(ctx context.Context) error { | ||
return errors.New("dry-run doesn't support Delete()") | ||
} | ||
|
||
// Close closes the backend. | ||
func (be *Backend) Close() error { | ||
return be.be.Close() | ||
} |
Oops, something went wrong.