support exclude file in --fast-list mode

This commit is contained in:
Iakov Davydov 2017-11-09 10:28:36 +01:00 committed by Nick Craig-Wood
parent 557dd8f031
commit 538246f6c3
5 changed files with 83 additions and 23 deletions

View file

@ -406,22 +406,51 @@ func (f *Filter) ListContainsExcludeFile(entries DirEntries) bool {
return false return false
} }
// IncludeDirectory returns whether this directory should be included // IncludeDirectory returns a function which checks whether this
// in the sync or not. // directory should be included in the sync or not.
func (f *Filter) IncludeDirectory(remote string) bool { func (f *Filter) IncludeDirectory(fs Fs) func(string) (bool, error) {
remote = strings.Trim(remote, "/") return func(remote string) (bool, error) {
// filesFrom takes precedence remote = strings.Trim(remote, "/")
if f.files != nil { // first check if we need to remove directory based on
_, include := f.dirs[remote] // the exclude file
return include excl, err := f.DirContainsExcludeFile(fs, remote)
if err != nil {
return false, err
}
if excl {
return false, nil
}
// filesFrom takes precedence
if f.files != nil {
_, include := f.dirs[remote]
return include, nil
}
remote += "/"
for _, rule := range f.dirRules.rules {
if rule.Match(remote) {
return rule.Include, nil
}
}
return true, nil
} }
remote += "/" }
for _, rule := range f.dirRules.rules {
if rule.Match(remote) { // DirContainsExcludeFile checks if exclude file is present in a
return rule.Include // directroy. If fs is nil, it works properly if ExcludeFile is an
// empty string (for testing).
func (f *Filter) DirContainsExcludeFile(fs Fs, remote string) (bool, error) {
if len(Config.Filter.ExcludeFile) > 0 {
exists, err := FileExists(fs, path.Join(remote, Config.Filter.ExcludeFile))
if err != nil {
return false, err
}
if exists {
return true, nil
} }
} }
return true return false, nil
} }
// Include returns whether this object should be included into the // Include returns whether this object should be included into the

View file

@ -164,7 +164,8 @@ type includeDirTest struct {
func testDirInclude(t *testing.T, f *Filter, tests []includeDirTest) { func testDirInclude(t *testing.T, f *Filter, tests []includeDirTest) {
for _, test := range tests { for _, test := range tests {
got := f.IncludeDirectory(test.in) got, err := f.IncludeDirectory(nil)(test.in)
require.NoError(t, err)
assert.Equal(t, test.want, got, test.in) assert.Equal(t, test.want, got, test.in)
} }
} }

View file

@ -622,17 +622,20 @@ func ListDirSorted(fs Fs, includeAll bool, dir string) (entries DirEntries, err
if err != nil { if err != nil {
return nil, err return nil, err
} }
// This should happen only if exclude files lives in the
// starting directory, otherwise ListDirSorted should not be
// called.
if !includeAll && Config.Filter.ListContainsExcludeFile(entries) { if !includeAll && Config.Filter.ListContainsExcludeFile(entries) {
Debugf(dir, "Excluded from sync (and deletion) based on exclude file") Debugf(dir, "Excluded from sync (and deletion)")
return nil, nil return nil, nil
} }
return filterAndSortDir(entries, includeAll, dir, Config.Filter.IncludeObject, Config.Filter.IncludeDirectory) return filterAndSortDir(entries, includeAll, dir, Config.Filter.IncludeObject, Config.Filter.IncludeDirectory(fs))
} }
// filter (if required) and check the entries, then sort them // filter (if required) and check the entries, then sort them
func filterAndSortDir(entries DirEntries, includeAll bool, dir string, func filterAndSortDir(entries DirEntries, includeAll bool, dir string,
IncludeObject func(o Object) bool, IncludeObject func(o Object) bool,
IncludeDirectory func(remote string) bool) (newEntries DirEntries, err error) { IncludeDirectory func(remote string) (bool, error)) (newEntries DirEntries, err error) {
newEntries = entries[:0] // in place filter newEntries = entries[:0] // in place filter
prefix := "" prefix := ""
if dir != "" { if dir != "" {
@ -649,9 +652,15 @@ func filterAndSortDir(entries DirEntries, includeAll bool, dir string,
Debugf(x, "Excluded from sync (and deletion)") Debugf(x, "Excluded from sync (and deletion)")
} }
case Directory: case Directory:
if !includeAll && !IncludeDirectory(x.Remote()) { if !includeAll {
ok = false include, err := IncludeDirectory(x.Remote())
Debugf(x, "Excluded from sync (and deletion)") if err != nil {
return nil, err
}
if !include {
ok = false
Debugf(x, "Excluded from sync (and deletion)")
}
} }
default: default:
return nil, errors.Errorf("unknown object type %T", entry) return nil, errors.Errorf("unknown object type %T", entry)

View file

@ -22,8 +22,8 @@ func TestFilterAndSortIncludeAll(t *testing.T) {
includeObject := func(o Object) bool { includeObject := func(o Object) bool {
return o != oB return o != oB
} }
includeDirectory := func(remote string) bool { includeDirectory := func(remote string) (bool, error) {
return remote != "c" return remote != "c", nil
} }
// no filter // no filter
newEntries, err := filterAndSortDir(entries, true, "", includeObject, includeDirectory) newEntries, err := filterAndSortDir(entries, true, "", includeObject, includeDirectory)

View file

@ -349,6 +349,10 @@ func (dt DirTree) String() string {
func walkRDirTree(f Fs, startPath string, includeAll bool, maxLevel int, listR ListRFn) (DirTree, error) { func walkRDirTree(f Fs, startPath string, includeAll bool, maxLevel int, listR ListRFn) (DirTree, error) {
dirs := make(DirTree) dirs := make(DirTree)
// Entries can come in arbitrary order. We use toPrune to keep
// all directories to exclude later.
toPrune := make(map[string]bool)
includeDirectory := Config.Filter.IncludeDirectory(f)
var mu sync.Mutex var mu sync.Mutex
err := listR(startPath, func(entries DirEntries) error { err := listR(startPath, func(entries DirEntries) error {
mu.Lock() mu.Lock()
@ -372,8 +376,21 @@ func walkRDirTree(f Fs, startPath string, includeAll bool, maxLevel int, listR L
} else { } else {
Debugf(x, "Excluded from sync (and deletion)") Debugf(x, "Excluded from sync (and deletion)")
} }
// Check if we need to prune a directory later.
if !includeAll && len(Config.Filter.ExcludeFile) > 0 {
basename := path.Base(x.Remote())
if basename == Config.Filter.ExcludeFile {
excludeDir := parentDir(x.Remote())
toPrune[excludeDir] = true
Debugf(basename, "Excluded from sync (and deletion) based on exclude file")
}
}
case Directory: case Directory:
if includeAll || Config.Filter.IncludeDirectory(x.Remote()) { inc, err := includeDirectory(x.Remote())
if err != nil {
return err
}
if includeAll || inc {
if maxLevel < 0 || slashes <= maxLevel-1 { if maxLevel < 0 || slashes <= maxLevel-1 {
if slashes == maxLevel-1 { if slashes == maxLevel-1 {
// Just add the object if at maxLevel // Just add the object if at maxLevel
@ -398,6 +415,10 @@ func walkRDirTree(f Fs, startPath string, includeAll bool, maxLevel int, listR L
if len(dirs) == 0 { if len(dirs) == 0 {
dirs[startPath] = nil dirs[startPath] = nil
} }
err = dirs.Prune(toPrune)
if err != nil {
return nil, err
}
dirs.Sort() dirs.Sort()
return dirs, nil return dirs, nil
} }