mirror of
https://github.com/restic/restic.git
synced 2025-08-12 11:17:42 +00:00
Moves files
This commit is contained in:
5
internal/filter/doc.go
Normal file
5
internal/filter/doc.go
Normal file
@@ -0,0 +1,5 @@
|
||||
// Package filter implements filters for files similar to filepath.Glob, but
|
||||
// in contrast to filepath.Glob a pattern may specify directories.
|
||||
//
|
||||
// For a list of valid patterns please see the documentation on filepath.Glob.
|
||||
package filter
|
122
internal/filter/filter.go
Normal file
122
internal/filter/filter.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package filter
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"restic/errors"
|
||||
)
|
||||
|
||||
// ErrBadString is returned when Match is called with the empty string as the
|
||||
// second argument.
|
||||
var ErrBadString = errors.New("filter.Match: string is empty")
|
||||
|
||||
// Match returns true if str matches the pattern. When the pattern is
|
||||
// malformed, filepath.ErrBadPattern is returned. The empty pattern matches
|
||||
// everything, when str is the empty string ErrBadString is returned.
|
||||
//
|
||||
// Pattern can be a combination of patterns suitable for filepath.Match, joined
|
||||
// by filepath.Separator.
|
||||
func Match(pattern, str string) (matched bool, err error) {
|
||||
if pattern == "" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
pattern = filepath.Clean(pattern)
|
||||
|
||||
if str == "" {
|
||||
return false, ErrBadString
|
||||
}
|
||||
|
||||
// convert file path separator to '/'
|
||||
if filepath.Separator != '/' {
|
||||
pattern = strings.Replace(pattern, string(filepath.Separator), "/", -1)
|
||||
str = strings.Replace(str, string(filepath.Separator), "/", -1)
|
||||
}
|
||||
|
||||
patterns := strings.Split(pattern, "/")
|
||||
strs := strings.Split(str, "/")
|
||||
|
||||
return match(patterns, strs)
|
||||
}
|
||||
|
||||
func hasDoubleWildcard(list []string) (ok bool, pos int) {
|
||||
for i, item := range list {
|
||||
if item == "**" {
|
||||
return true, i
|
||||
}
|
||||
}
|
||||
|
||||
return false, 0
|
||||
}
|
||||
|
||||
func match(patterns, strs []string) (matched bool, err error) {
|
||||
if ok, pos := hasDoubleWildcard(patterns); ok {
|
||||
// gradually expand '**' into separate wildcards
|
||||
for i := 0; i <= len(strs)-len(patterns)+1; i++ {
|
||||
newPat := make([]string, pos)
|
||||
copy(newPat, patterns[:pos])
|
||||
for k := 0; k < i; k++ {
|
||||
newPat = append(newPat, "*")
|
||||
}
|
||||
newPat = append(newPat, patterns[pos+1:]...)
|
||||
|
||||
matched, err := match(newPat, strs)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if matched {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
if len(patterns) == 0 && len(strs) == 0 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if len(patterns) <= len(strs) {
|
||||
outer:
|
||||
for offset := len(strs) - len(patterns); offset >= 0; offset-- {
|
||||
|
||||
for i := len(patterns) - 1; i >= 0; i-- {
|
||||
ok, err := filepath.Match(patterns[i], strs[offset+i])
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "Match")
|
||||
}
|
||||
|
||||
if !ok {
|
||||
continue outer
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// List returns true if str matches one of the patterns. Empty patterns are
|
||||
// ignored.
|
||||
func List(patterns []string, str string) (matched bool, err error) {
|
||||
for _, pat := range patterns {
|
||||
if pat == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
matched, err = Match(pat, str)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if matched {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
288
internal/filter/filter_test.go
Normal file
288
internal/filter/filter_test.go
Normal file
@@ -0,0 +1,288 @@
|
||||
package filter_test
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"compress/bzip2"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"restic/filter"
|
||||
)
|
||||
|
||||
var matchTests = []struct {
|
||||
pattern string
|
||||
path string
|
||||
match bool
|
||||
}{
|
||||
{"", "", true},
|
||||
{"", "foo", true},
|
||||
{"", "/x/y/z/foo", true},
|
||||
{"*.go", "/foo/bar/test.go", true},
|
||||
{"*.c", "/foo/bar/test.go", false},
|
||||
{"*", "/foo/bar/test.go", true},
|
||||
{"foo*", "/foo/bar/test.go", true},
|
||||
{"bar*", "/foo/bar/test.go", true},
|
||||
{"/bar*", "/foo/bar/test.go", false},
|
||||
{"bar/*", "/foo/bar/test.go", true},
|
||||
{"baz/*", "/foo/bar/test.go", false},
|
||||
{"bar/test.go", "/foo/bar/test.go", true},
|
||||
{"bar/*.go", "/foo/bar/test.go", true},
|
||||
{"ba*/*.go", "/foo/bar/test.go", true},
|
||||
{"bb*/*.go", "/foo/bar/test.go", false},
|
||||
{"test.*", "/foo/bar/test.go", true},
|
||||
{"tesT.*", "/foo/bar/test.go", false},
|
||||
{"bar/*", "/foo/bar/baz", true},
|
||||
{"bar", "/foo/bar", true},
|
||||
{"/foo/bar", "/foo/bar", true},
|
||||
{"/foo/bar/", "/foo/bar", true},
|
||||
{"/foo/bar", "/foo/baz", false},
|
||||
{"/foo/bar", "/foo/baz/", false},
|
||||
{"/foo///bar", "/foo/bar", true},
|
||||
{"/foo/../bar", "/foo/bar", false},
|
||||
{"/foo/../bar", "/bar", true},
|
||||
{"/foo", "/foo/baz", true},
|
||||
{"/foo/", "/foo/baz", true},
|
||||
{"/foo/*", "/foo", false},
|
||||
{"/foo/*", "/foo/baz", true},
|
||||
{"bar", "/foo/bar/baz", true},
|
||||
{"bar", "/foo/bar/test.go", true},
|
||||
{"/foo/*test.*", "/foo/bar/test.go", false},
|
||||
{"/foo/*/test.*", "/foo/bar/test.go", true},
|
||||
{"/foo/*/bar/test.*", "/foo/bar/test.go", false},
|
||||
{"/*/*/bar/test.*", "/foo/bar/test.go", false},
|
||||
{"/*/*/bar/test.*", "/foo/bar/baz/test.go", false},
|
||||
{"/*/*/baz/test.*", "/foo/bar/baz/test.go", true},
|
||||
{"/*/foo/bar/test.*", "/foo/bar/baz/test.go", false},
|
||||
{"/*/foo/bar/test.*", "/foo/bar/baz/test.go", false},
|
||||
{"/foo/bar/test.*", "bar/baz/test.go", false},
|
||||
{"/x/y/bar/baz/test.*", "bar/baz/test.go", false},
|
||||
{"/x/y/bar/baz/test.c", "bar/baz/test.go", false},
|
||||
{"baz/test.*", "bar/baz/test.go", true},
|
||||
{"baz/tesT.*", "bar/baz/test.go", false},
|
||||
{"test.go", "bar/baz/test.go", true},
|
||||
{"*.go", "bar/baz/test.go", true},
|
||||
{"*.c", "bar/baz/test.go", false},
|
||||
{"sdk", "/foo/bar/sdk", true},
|
||||
{"sdk", "/foo/bar/sdk/test/sdk_foo.go", true},
|
||||
{
|
||||
"sdk/*/cpp/*/*vars*.html",
|
||||
"/usr/share/doc/libreoffice/sdk/docs/cpp/ref/a00517.html",
|
||||
false,
|
||||
},
|
||||
{"foo/**/bar/*.go", "/home/user/foo/work/special/project/bar/test.go", true},
|
||||
{"foo/**/bar/*.go", "/home/user/foo/bar/test.go", true},
|
||||
{"foo/**/bar/*.go", "x/foo/bar/test.go", true},
|
||||
{"foo/**/bar/*.go", "foo/bar/test.go", true},
|
||||
{"foo/**/bar/*.go", "/home/user/foo/test.c", false},
|
||||
{"foo/**/bar/*.go", "bar/foo/main.go", false},
|
||||
{"foo/**/bar/*.go", "/foo/bar/main.go", true},
|
||||
{"foo/**/bar/*.go", "bar/main.go", false},
|
||||
{"foo/**/bar", "/home/user/foo/x/y/bar", true},
|
||||
{"foo/**/bar", "/home/user/foo/x/y/bar/main.go", true},
|
||||
{"user/**/important*", "/home/user/work/x/y/hidden/x", false},
|
||||
{"user/**/hidden*/**/c", "/home/user/work/x/y/hidden/z/a/b/c", true},
|
||||
{"c:/foo/*test.*", "c:/foo/bar/test.go", false},
|
||||
{"c:/foo", "c:/foo/bar", true},
|
||||
{"c:/foo/", "c:/foo/bar", true},
|
||||
{"c:/foo/*/test.*", "c:/foo/bar/test.go", true},
|
||||
{"c:/foo/*/bar/test.*", "c:/foo/bar/test.go", false},
|
||||
}
|
||||
|
||||
func testpattern(t *testing.T, pattern, path string, shouldMatch bool) {
|
||||
match, err := filter.Match(pattern, path)
|
||||
if err != nil {
|
||||
t.Errorf("test pattern %q failed: expected no error for path %q, but error returned: %v",
|
||||
pattern, path, err)
|
||||
}
|
||||
|
||||
if match != shouldMatch {
|
||||
t.Errorf("test: filter.Match(%q, %q): expected %v, got %v",
|
||||
pattern, path, shouldMatch, match)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
for _, test := range matchTests {
|
||||
testpattern(t, test.pattern, test.path, test.match)
|
||||
|
||||
// Test with native path separator
|
||||
if filepath.Separator != '/' {
|
||||
// Test with pattern as native
|
||||
pattern := strings.Replace(test.pattern, "/", string(filepath.Separator), -1)
|
||||
testpattern(t, pattern, test.path, test.match)
|
||||
|
||||
// Test with path as native
|
||||
path := strings.Replace(test.path, "/", string(filepath.Separator), -1)
|
||||
testpattern(t, test.pattern, path, test.match)
|
||||
|
||||
// Test with both pattern and path as native
|
||||
testpattern(t, pattern, path, test.match)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleMatch() {
|
||||
match, _ := filter.Match("*.go", "/home/user/file.go")
|
||||
fmt.Printf("match: %v\n", match)
|
||||
// Output:
|
||||
// match: true
|
||||
}
|
||||
|
||||
func ExampleMatch_wildcards() {
|
||||
match, _ := filter.Match("/home/[uU]ser/?.go", "/home/user/F.go")
|
||||
fmt.Printf("match: %v\n", match)
|
||||
// Output:
|
||||
// match: true
|
||||
}
|
||||
|
||||
var filterListTests = []struct {
|
||||
patterns []string
|
||||
path string
|
||||
match bool
|
||||
}{
|
||||
{[]string{"*.go"}, "/foo/bar/test.go", true},
|
||||
{[]string{"*.c"}, "/foo/bar/test.go", false},
|
||||
{[]string{"*.go", "*.c"}, "/foo/bar/test.go", true},
|
||||
{[]string{"*"}, "/foo/bar/test.go", true},
|
||||
{[]string{"x"}, "/foo/bar/test.go", false},
|
||||
{[]string{"?"}, "/foo/bar/test.go", false},
|
||||
{[]string{"?", "x"}, "/foo/bar/x", true},
|
||||
{[]string{"/*/*/bar/test.*"}, "/foo/bar/test.go", false},
|
||||
{[]string{"/*/*/bar/test.*", "*.go"}, "/foo/bar/test.go", true},
|
||||
{[]string{"", "*.c"}, "/foo/bar/test.go", false},
|
||||
}
|
||||
|
||||
func TestList(t *testing.T) {
|
||||
for i, test := range filterListTests {
|
||||
match, err := filter.List(test.patterns, test.path)
|
||||
if err != nil {
|
||||
t.Errorf("test %d failed: expected no error for patterns %q, but error returned: %v",
|
||||
i, test.patterns, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if match != test.match {
|
||||
t.Errorf("test %d: filter.MatchList(%q, %q): expected %v, got %v",
|
||||
i, test.patterns, test.path, test.match, match)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleList() {
|
||||
match, _ := filter.List([]string{"*.c", "*.go"}, "/home/user/file.go")
|
||||
fmt.Printf("match: %v\n", match)
|
||||
// Output:
|
||||
// match: true
|
||||
}
|
||||
|
||||
func extractTestLines(t testing.TB) (lines []string) {
|
||||
f, err := os.Open("testdata/libreoffice.txt.bz2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
sc := bufio.NewScanner(bzip2.NewReader(f))
|
||||
for sc.Scan() {
|
||||
lines = append(lines, sc.Text())
|
||||
}
|
||||
|
||||
return lines
|
||||
}
|
||||
|
||||
func TestFilterPatternsFile(t *testing.T) {
|
||||
lines := extractTestLines(t)
|
||||
|
||||
var testPatterns = []struct {
|
||||
pattern string
|
||||
hits uint
|
||||
}{
|
||||
{"*.html", 18249},
|
||||
{"sdk", 22186},
|
||||
{"sdk/*/cpp/*/*vars.html", 3},
|
||||
}
|
||||
|
||||
for _, test := range testPatterns {
|
||||
var c uint
|
||||
for _, line := range lines {
|
||||
match, err := filter.Match(test.pattern, line)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
if match {
|
||||
c++
|
||||
// fmt.Printf("pattern %q, line %q\n", test.pattern, line)
|
||||
}
|
||||
}
|
||||
|
||||
if c != test.hits {
|
||||
t.Errorf("wrong number of hits for pattern %q: want %d, got %d",
|
||||
test.pattern, test.hits, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFilterLines(b *testing.B) {
|
||||
pattern := "sdk/*/cpp/*/*vars.html"
|
||||
lines := extractTestLines(b)
|
||||
var c uint
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
c = 0
|
||||
for _, line := range lines {
|
||||
match, err := filter.Match(pattern, line)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if match {
|
||||
c++
|
||||
}
|
||||
}
|
||||
|
||||
if c != 3 {
|
||||
b.Fatalf("wrong number of matches: expected 3, got %d", c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkFilterPatterns(b *testing.B) {
|
||||
patterns := []string{
|
||||
"sdk/*",
|
||||
"*.html",
|
||||
}
|
||||
lines := extractTestLines(b)
|
||||
var c uint
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
c = 0
|
||||
for _, line := range lines {
|
||||
match, err := filter.List(patterns, line)
|
||||
if err != nil {
|
||||
b.Fatal(err)
|
||||
}
|
||||
|
||||
if match {
|
||||
c++
|
||||
}
|
||||
}
|
||||
|
||||
if c != 22185 {
|
||||
b.Fatalf("wrong number of matches: expected 22185, got %d", c)
|
||||
}
|
||||
}
|
||||
}
|
BIN
internal/filter/testdata/libreoffice.txt.bz2
vendored
Normal file
BIN
internal/filter/testdata/libreoffice.txt.bz2
vendored
Normal file
Binary file not shown.
Reference in New Issue
Block a user