diff --git a/cmd/restic/cmd_restore.go b/cmd/restic/cmd_restore.go index 03a990cc6..12b698950 100644 --- a/cmd/restic/cmd_restore.go +++ b/cmd/restic/cmd_restore.go @@ -178,12 +178,12 @@ func runRestore(ctx context.Context, opts RestoreOptions, gopts GlobalOptions, matched, childMayMatch := includeFn(item) selectedForRestore = selectedForRestore || matched childMayBeSelected = childMayBeSelected || childMayMatch - childMayBeSelected = childMayBeSelected && node.Type == "dir" - if selectedForRestore || childMayBeSelected { + if selectedForRestore && childMayBeSelected { break } } + childMayBeSelected = childMayBeSelected && node.Type == "dir" return selectedForRestore, childMayBeSelected } diff --git a/cmd/restic/cmd_restore_integration_test.go b/cmd/restic/cmd_restore_integration_test.go index 0e1620f7e..b0543850b 100644 --- a/cmd/restic/cmd_restore_integration_test.go +++ b/cmd/restic/cmd_restore_integration_test.go @@ -13,7 +13,6 @@ import ( "time" "github.com/restic/restic/internal/feature" - "github.com/restic/restic/internal/filter" "github.com/restic/restic/internal/restic" rtest "github.com/restic/restic/internal/test" "github.com/restic/restic/internal/ui/termstatus" @@ -78,32 +77,11 @@ func testRunRestoreExcludesFromFile(t testing.TB, gopts GlobalOptions, dir strin } func TestRestoreMustFailWhenUsingBothIncludesAndExcludes(t *testing.T) { - testfiles := []struct { - path string - size uint - }{ - {"dir1/include_me.txt", 100}, - } - env, cleanup := withTestEnvironment(t) defer cleanup() testRunInit(t, env.gopts) - // Create test files and directories - for _, testFile := range testfiles { - fullPath := filepath.Join(env.testdata, testFile.path) - rtest.OK(t, os.MkdirAll(filepath.Dir(fullPath), 0755)) - rtest.OK(t, appendRandomData(fullPath, testFile.size)) - } - - opts := BackupOptions{} - // Perform backup - testRunBackup(t, filepath.Dir(env.testdata), []string{filepath.Base(env.testdata)}, opts, env.gopts) - testRunCheck(t, env.gopts) - - snapshotID := testListSnapshots(t, env.gopts, 1)[0] - // Add both include and exclude patterns includePatterns := []string{"dir1/*include_me.txt", "dir2/**", "dir4/**/*_me.txt"} excludePatterns := []string{"dir1/*include_me.txt", "dir2/**", "dir4/**/*_me.txt"} @@ -116,8 +94,9 @@ func TestRestoreMustFailWhenUsingBothIncludesAndExcludes(t *testing.T) { restoreOpts.Includes = includePatterns restoreOpts.Excludes = excludePatterns - err := testRunRestoreAssumeFailure(snapshotID.String(), restoreOpts, env.gopts) - rtest.Assert(t, err != nil, "restore must fail if include and exclude patterns are provided") + err := testRunRestoreAssumeFailure("latest", restoreOpts, env.gopts) + rtest.Assert(t, err != nil && strings.Contains(err.Error(), "exclude and include patterns are mutually exclusive"), + "expected: %s error, got %v", "exclude and include patterns are mutually exclusive", err) } func TestRestoreIncludes(t *testing.T) { @@ -159,7 +138,7 @@ func TestRestoreIncludes(t *testing.T) { restoredir := filepath.Join(env.base, "restore") testRunRestoreIncludes(t, env.gopts, restoredir, snapshotID, includePatterns) - testRestoreFileInclusions := func(t *testing.T, env *testEnvironment, includePatterns []string) { + testRestoreFileInclusions := func(t *testing.T) { // Check that only the included files are restored for _, testFile := range testfiles { restoredFilePath := filepath.Join(restoredir, "testdata", testFile.path) @@ -172,7 +151,7 @@ func TestRestoreIncludes(t *testing.T) { } } - testRestoreFileInclusions(t, env, includePatterns) + testRestoreFileInclusions(t) // Create an include file with some patterns patternsFile := env.base + "/patternsFile" @@ -185,18 +164,19 @@ func TestRestoreIncludes(t *testing.T) { testRunRestoreIncludesFromFile(t, env.gopts, restoredir, snapshotID, patternsFile) - testRestoreFileInclusions(t, env, includePatterns) + testRestoreFileInclusions(t) } func TestRestoreFilter(t *testing.T) { testfiles := []struct { - name string - size uint + name string + size uint + exclude bool }{ - {"testfile1.c", 100}, - {"testfile2.exe", 101}, - {"subdir1/subdir2/testfile3.docx", 102}, - {"subdir1/subdir2/testfile4.c", 102}, + {"testfile1.c", 100, true}, + {"testfile2.exe", 101, true}, + {"subdir1/subdir2/testfile3.docx", 102, true}, + {"subdir1/subdir2/testfile4.c", 102, false}, } env, cleanup := withTestEnvironment(t) @@ -223,38 +203,38 @@ func TestRestoreFilter(t *testing.T) { rtest.OK(t, testFileSize(filepath.Join(env.base, "restore0", "testdata", testFile.name), int64(testFile.size))) } - excludePatterns := []string{"*.c", "*.exe", "*", "*file3*"} + excludePatterns := []string{"testfile1.c", "*.exe", "*file3*"} - testRestoreFileExclusions := func(t *testing.T, env *testEnvironment, excludePatterns []string) { - for i, pat := range excludePatterns { - base := filepath.Join(env.base, fmt.Sprintf("restore%d", i+1)) - testRunRestoreExcludes(t, env.gopts, base, snapshotID, []string{pat}) - for _, testFile := range testfiles { - err := testFileSize(filepath.Join(base, "testdata", testFile.name), int64(testFile.size)) - if ok, _ := filter.Match(pat, filepath.Base(testFile.name)); !ok { - rtest.OK(t, err) - } else { - rtest.Assert(t, os.IsNotExist(err), - "expected %v to not exist in restore step %v, but it exists, err %v", testFile.name, i+1, err) - } + // checks if the files are excluded correctly + testRestoredFileExclusions := func(t *testing.T, restoredir string) { + for _, testFile := range testfiles { + restoredFilePath := filepath.Join(restoredir, "testdata", testFile.name) + _, err := os.Stat(restoredFilePath) + if testFile.exclude { + rtest.Assert(t, os.IsNotExist(err), "File %s should not have been restored", testFile.name) + } else { + rtest.OK(t, testFileSize(restoredFilePath, int64(testFile.size))) } } } - testRestoreFileExclusions(t, env, excludePatterns) + // restore with excludes + restoredir := filepath.Join(env.base, "restore-with-excludes") + testRunRestoreExcludes(t, env.gopts, restoredir, snapshotID, excludePatterns) + testRestoredFileExclusions(t, restoredir) - // Create an include file with some patterns + // Create an exclude file with some patterns patternsFile := env.base + "/patternsFile" fileErr := os.WriteFile(patternsFile, []byte(strings.Join(excludePatterns, "\n")), 0644) if fileErr != nil { t.Fatalf("Could not write include file: %v", fileErr) } - restoredir := filepath.Join(env.base, "restore-exclude-from-file") - + // restore with excludes from file + restoredir = filepath.Join(env.base, "restore-with-exclude-from-file") testRunRestoreExcludesFromFile(t, env.gopts, restoredir, snapshotID, patternsFile) - testRestoreFileExclusions(t, env, excludePatterns) + testRestoredFileExclusions(t, restoredir) } func TestRestore(t *testing.T) { diff --git a/cmd/restic/exclude.go b/cmd/restic/exclude.go index 9f5f40511..4657e4915 100644 --- a/cmd/restic/exclude.go +++ b/cmd/restic/exclude.go @@ -443,7 +443,7 @@ func initExcludePatternOptions(f *pflag.FlagSet, opts *excludePatternOptions) { f.StringArrayVarP(&opts.Excludes, "exclude", "e", nil, "exclude a `pattern` (can be specified multiple times)") f.StringArrayVar(&opts.InsensitiveExcludes, "iexclude", nil, "same as --exclude `pattern` but ignores the casing of filenames") f.StringArrayVar(&opts.ExcludeFiles, "exclude-file", nil, "read exclude patterns from a `file` (can be specified multiple times)") - f.StringArrayVar(&opts.InsensitiveExcludeFiles, "iexclude-file", nil, "same as --exclude-file but ignores casing of filenames in patterns") + f.StringArrayVar(&opts.InsensitiveExcludeFiles, "iexclude-file", nil, "same as --exclude-file but ignores casing of `file`names in patterns") } func (opts *excludePatternOptions) Empty() bool { diff --git a/cmd/restic/include.go b/cmd/restic/include.go index 64659d98f..dcc4c7f37 100644 --- a/cmd/restic/include.go +++ b/cmd/restic/include.go @@ -23,7 +23,7 @@ func initIncludePatternOptions(f *pflag.FlagSet, opts *includePatternOptions) { f.StringArrayVarP(&opts.Includes, "include", "i", nil, "include a `pattern` (can be specified multiple times)") f.StringArrayVar(&opts.InsensitiveIncludes, "iinclude", nil, "same as --include `pattern` but ignores the casing of filenames") f.StringArrayVar(&opts.IncludeFiles, "include-file", nil, "read include patterns from a `file` (can be specified multiple times)") - f.StringArrayVar(&opts.InsensitiveIncludeFiles, "iinclude-file", nil, "same as --include-file but ignores casing of filenames in patterns") + f.StringArrayVar(&opts.InsensitiveIncludeFiles, "iinclude-file", nil, "same as --include-file but ignores casing of `file`names in patterns") } func (opts includePatternOptions) CollectPatterns() ([]IncludeByNameFunc, error) {