Merge pull request #1326 from fawick/rejectionCache

Cache evaluated directories in isExcludedByFile
This commit is contained in:
Alexander Neumann 2017-10-07 09:57:54 +02:00
commit b8866c1fe4
3 changed files with 70 additions and 7 deletions

View File

@ -367,8 +367,9 @@ func runBackup(opts BackupOptions, gopts GlobalOptions, args []string) error {
opts.ExcludeIfPresent = append(opts.ExcludeIfPresent, "CACHEDIR.TAG:Signature: 8a477f597d28d172789f06886806bc55") opts.ExcludeIfPresent = append(opts.ExcludeIfPresent, "CACHEDIR.TAG:Signature: 8a477f597d28d172789f06886806bc55")
} }
rc := &rejectionCache{}
for _, spec := range opts.ExcludeIfPresent { for _, spec := range opts.ExcludeIfPresent {
f, err := rejectIfPresent(spec) f, err := rejectIfPresent(spec, rc)
if err != nil { if err != nil {
return err return err
} }

View File

@ -7,6 +7,7 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"strings" "strings"
"sync"
"github.com/restic/restic/internal/debug" "github.com/restic/restic/internal/debug"
"github.com/restic/restic/internal/errors" "github.com/restic/restic/internal/errors"
@ -15,6 +16,50 @@ import (
"github.com/restic/restic/internal/repository" "github.com/restic/restic/internal/repository"
) )
type rejectionCache struct {
m map[string]bool
mtx sync.Mutex
}
// Lock locks the mutex in rc.
func (rc *rejectionCache) Lock() {
if rc != nil {
rc.mtx.Lock()
}
}
// Unlock unlocks the mutex in rc.
func (rc *rejectionCache) Unlock() {
if rc != nil {
rc.mtx.Unlock()
}
}
// Get returns the last stored value for dir and a second boolean that
// indicates whether that value was actually written to the cache. It is the
// callers responsibility to call rc.Lock and rc.Unlock before using this
// method, otherwise data races may occur.
func (rc *rejectionCache) Get(dir string) (bool, bool) {
if rc == nil || rc.m == nil {
return false, false
}
v, ok := rc.m[dir]
return v, ok
}
// Store stores a new value for dir. It is the callers responsibility to call
// rc.Lock and rc.Unlock before using this method, otherwise data races may
// occur.
func (rc *rejectionCache) Store(dir string, rejected bool) {
if rc == nil {
return
}
if rc.m == nil {
rc.m = make(map[string]bool)
}
rc.m[dir] = rejected
}
// RejectFunc is a function that takes a filename and os.FileInfo of a // RejectFunc is a function that takes a filename and os.FileInfo of a
// file that would be included in the backup. The function returns true if it // file that would be included in the backup. The function returns true if it
// should be excluded (rejected) from the backup. // should be excluded (rejected) from the backup.
@ -42,8 +87,10 @@ func rejectByPattern(patterns []string) RejectFunc {
// should be excluded. The RejectFunc considers a file to be excluded when // should be excluded. The RejectFunc considers a file to be excluded when
// it resides in a directory with an exclusion file, that is specified by // it resides in a directory with an exclusion file, that is specified by
// excludeFileSpec in the form "filename[:content]". The returned error is // excludeFileSpec in the form "filename[:content]". The returned error is
// non-nil if the filename component of excludeFileSpec is empty. // non-nil if the filename component of excludeFileSpec is empty. If rc is
func rejectIfPresent(excludeFileSpec string) (RejectFunc, error) { // non-nil, it is going to be used in the RejectFunc to expedite the evaluation
// of a directory based on previous visits.
func rejectIfPresent(excludeFileSpec string, rc *rejectionCache) (RejectFunc, error) {
if excludeFileSpec == "" { if excludeFileSpec == "" {
return nil, errors.New("name for exclusion tagfile is empty") return nil, errors.New("name for exclusion tagfile is empty")
} }
@ -60,15 +107,17 @@ func rejectIfPresent(excludeFileSpec string) (RejectFunc, error) {
} }
debug.Log("using %q as exclusion tagfile", tf) debug.Log("using %q as exclusion tagfile", tf)
fn := func(filename string, _ os.FileInfo) bool { fn := func(filename string, _ os.FileInfo) bool {
return isExcludedByFile(filename, tf, tc) return isExcludedByFile(filename, tf, tc, rc)
} }
return fn, nil return fn, nil
} }
// isExcludedByFile interprets filename as a path and returns true if that file // isExcludedByFile interprets filename as a path and returns true if that file
// is in a excluded directory. A directory is identified as excluded if it contains a // is in a excluded directory. A directory is identified as excluded if it contains a
// tagfile which bears the name specified in tagFilename and starts with header. // tagfile which bears the name specified in tagFilename and starts with
func isExcludedByFile(filename, tagFilename, header string) bool { // header. If rc is non-nil, it is used to expedite the evaluation of a
// directory based on previous visits.
func isExcludedByFile(filename, tagFilename, header string, rc *rejectionCache) bool {
if tagFilename == "" { if tagFilename == "" {
return false return false
} }
@ -76,6 +125,19 @@ func isExcludedByFile(filename, tagFilename, header string) bool {
if base == tagFilename { if base == tagFilename {
return false // do not exclude the tagfile itself return false // do not exclude the tagfile itself
} }
rc.Lock()
defer rc.Unlock()
rejected, visited := rc.Get(dir)
if visited {
return rejected
}
rejected = isDirExcludedByFile(dir, tagFilename, header)
rc.Store(dir, rejected)
return rejected
}
func isDirExcludedByFile(dir, tagFilename, header string) bool {
tf := filepath.Join(dir, tagFilename) tf := filepath.Join(dir, tagFilename)
_, err := fs.Lstat(tf) _, err := fs.Lstat(tf)
if os.IsNotExist(err) { if os.IsNotExist(err) {

View File

@ -76,7 +76,7 @@ func TestIsExcludedByFile(t *testing.T) {
if tc.content == "" { if tc.content == "" {
h = "" h = ""
} }
if got := isExcludedByFile(foo, tagFilename, h); tc.want != got { if got := isExcludedByFile(foo, tagFilename, h, nil); tc.want != got {
t.Fatalf("expected %v, got %v", tc.want, got) t.Fatalf("expected %v, got %v", tc.want, got)
} }
}) })