diff --git a/cmd/restic/cmd_restore.go b/cmd/restic/cmd_restore.go index 4991423b9..cb74f4768 100644 --- a/cmd/restic/cmd_restore.go +++ b/cmd/restic/cmd_restore.go @@ -11,6 +11,7 @@ import ( type CmdRestore struct { Exclude []string `short:"e" long:"exclude" description:"Exclude a pattern (can be specified multiple times)"` + Include []string `short:"i" long:"include" description:"Include a pattern, exclude everything else (can be specified multiple times)"` Target string `short:"t" long:"target" description:"Directory to restore to"` global *GlobalOptions @@ -39,6 +40,10 @@ func (cmd CmdRestore) Execute(args []string) error { return errors.New("please specify a directory to restore to (--target)") } + if len(cmd.Exclude) > 0 && len(cmd.Include) > 0 { + return errors.New("exclude and include patterns are mutually exclusive") + } + snapshotIDString := args[0] debug.Log("restore", "restore %v to %v", snapshotIDString, cmd.Target) @@ -74,7 +79,7 @@ func (cmd CmdRestore) Execute(args []string) error { return err } - selectFilter := func(item string, dstpath string, node *restic.Node) bool { + selectExcludeFilter := func(item string, dstpath string, node *restic.Node) bool { matched, err := filter.List(cmd.Exclude, item) if err != nil { cmd.global.Warnf("error for exclude pattern: %v", err) @@ -83,8 +88,19 @@ func (cmd CmdRestore) Execute(args []string) error { return !matched } + selectIncludeFilter := func(item string, dstpath string, node *restic.Node) bool { + matched, err := filter.List(cmd.Include, item) + if err != nil { + cmd.global.Warnf("error for include pattern: %v", err) + } + + return matched + } + if len(cmd.Exclude) > 0 { - res.SelectFilter = selectFilter + res.SelectFilter = selectExcludeFilter + } else if len(cmd.Include) > 0 { + res.SelectFilter = selectIncludeFilter } cmd.global.Verbosef("restoring %s to %s\n", res.Snapshot(), cmd.Target) diff --git a/cmd/restic/integration_test.go b/cmd/restic/integration_test.go index 5803c38e8..93c98fa60 100644 --- a/cmd/restic/integration_test.go +++ b/cmd/restic/integration_test.go @@ -78,6 +78,11 @@ func cmdRestoreExcludes(t testing.TB, global GlobalOptions, dir string, snapshot OK(t, cmd.Execute([]string{snapshotID.String()})) } +func cmdRestoreIncludes(t testing.TB, global GlobalOptions, dir string, snapshotID backend.ID, includes []string) { + cmd := &CmdRestore{global: &global, Target: dir, Include: includes} + OK(t, cmd.Execute([]string{snapshotID.String()})) +} + func cmdCheck(t testing.TB, global GlobalOptions) { cmd := &CmdCheck{global: &global, ReadData: true} OK(t, cmd.Execute(nil)) @@ -563,7 +568,7 @@ func TestRestoreNoMetadataOnIgnoredIntermediateDirs(t *testing.T) { // restore with filter "*.ext", this should restore "file.ext", but // since the directories are ignored and only created because of // "file.ext", no meta data should be restored for them. - cmdRestoreExcludes(t, global, filepath.Join(env.base, "restore0"), snapshotID, []string{"*.ext"}) + cmdRestoreIncludes(t, global, filepath.Join(env.base, "restore0"), snapshotID, []string{"*.ext"}) f1 := filepath.Join(env.base, "restore0", "testdata", "subdir1", "subdir2") fi, err := os.Stat(f1) @@ -573,7 +578,7 @@ func TestRestoreNoMetadataOnIgnoredIntermediateDirs(t *testing.T) { "meta data of intermediate directory has been restore although it was ignored") // restore with filter "*", this should restore meta data on everything. - cmdRestoreExcludes(t, global, filepath.Join(env.base, "restore1"), snapshotID, []string{"*"}) + cmdRestoreIncludes(t, global, filepath.Join(env.base, "restore1"), snapshotID, []string{"*"}) f2 := filepath.Join(env.base, "restore1", "testdata", "subdir1", "subdir2") fi, err = os.Stat(f2)