diff --git a/lib/ignore/ignore.go b/lib/ignore/ignore.go index d0b7a29b5..0c269faea 100644 --- a/lib/ignore/ignore.go +++ b/lib/ignore/ignore.go @@ -246,8 +246,7 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([] addPattern := func(line string) error { pattern := Pattern{ - pattern: line, - result: defaultResult, + result: defaultResult, } // Allow prefixes to be specified in any order, but only once. @@ -275,6 +274,8 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([] line = strings.ToLower(line) } + pattern.pattern = line + var err error if strings.HasPrefix(line, "/") { // Pattern is rooted in the current dir only diff --git a/lib/ignore/ignore_test.go b/lib/ignore/ignore_test.go index a37415327..1696f019b 100644 --- a/lib/ignore/ignore_test.go +++ b/lib/ignore/ignore_test.go @@ -665,3 +665,41 @@ func TestCommas(t *testing.T) { } } } + +func TestIssue3164(t *testing.T) { + stignore := ` + (?d)(?i)*.part + (?d)(?i)/foo + (?d)(?i)**/bar + ` + pats := New(true) + err := pats.Parse(bytes.NewBufferString(stignore), ".stignore") + if err != nil { + t.Fatal(err) + } + + expanded := pats.Patterns() + t.Log(expanded) + expected := []string{ + "(?d)(?i)*.part", + "(?d)(?i)**/*.part", + "(?d)(?i)*.part/**", + "(?d)(?i)**/*.part/**", + "(?d)(?i)/foo", + "(?d)(?i)/foo/**", + "(?d)(?i)**/bar", + "(?d)(?i)bar", + "(?d)(?i)**/bar/**", + "(?d)(?i)bar/**", + } + + if len(expanded) != len(expected) { + t.Errorf("Unmatched count: %d != %d", len(expanded), len(expected)) + } + + for i := range expanded { + if expanded[i] != expected[i] { + t.Errorf("Pattern %d does not match: %s != %s", i, expanded[i], expected[i]) + } + } +} diff --git a/lib/model/rwfolder.go b/lib/model/rwfolder.go index 5c991e521..361337fe3 100644 --- a/lib/model/rwfolder.go +++ b/lib/model/rwfolder.go @@ -676,8 +676,9 @@ func (f *rwFolder) deleteDir(file protocol.FileInfo, matcher *ignore.Matcher) { if dir != nil { files, _ := dir.Readdirnames(-1) for _, dirFile := range files { - if defTempNamer.IsTemporary(dirFile) || (matcher != nil && matcher.Match(filepath.Join(file.Name, dirFile)).IsDeletable()) { - osutil.InWritableDir(osutil.Remove, filepath.Join(realName, dirFile)) + fullDirFile := filepath.Join(file.Name, dirFile) + if defTempNamer.IsTemporary(dirFile) || (matcher != nil && matcher.Match(fullDirFile).IsDeletable()) { + osutil.RemoveAll(fullDirFile) } } dir.Close() diff --git a/lib/osutil/osutil.go b/lib/osutil/osutil.go index 64802518e..f2ecd07d8 100644 --- a/lib/osutil/osutil.go +++ b/lib/osutil/osutil.go @@ -15,6 +15,7 @@ import ( "path/filepath" "runtime" "strings" + "syscall" "github.com/calmh/du" "github.com/syncthing/syncthing/lib/sync" @@ -107,6 +108,78 @@ func Remove(path string) error { return os.Remove(path) } +// RemoveAll is a copy of os.RemoveAll, but uses osutil.Remove. +// RemoveAll removes path and any children it contains. +// It removes everything it can but returns the first error +// it encounters. If the path does not exist, RemoveAll +// returns nil (no error). +func RemoveAll(path string) error { + // Simple case: if Remove works, we're done. + err := Remove(path) + if err == nil || os.IsNotExist(err) { + return nil + } + + // Otherwise, is this a directory we need to recurse into? + dir, serr := os.Lstat(path) + if serr != nil { + if serr, ok := serr.(*os.PathError); ok && (os.IsNotExist(serr.Err) || serr.Err == syscall.ENOTDIR) { + return nil + } + return serr + } + if !dir.IsDir() { + // Not a directory; return the error from Remove. + return err + } + + // Directory. + fd, err := os.Open(path) + if err != nil { + if os.IsNotExist(err) { + // Race. It was deleted between the Lstat and Open. + // Return nil per RemoveAll's docs. + return nil + } + return err + } + + // Remove contents & return first error. + err = nil + for { + names, err1 := fd.Readdirnames(100) + for _, name := range names { + err1 := RemoveAll(path + string(os.PathSeparator) + name) + if err == nil { + err = err1 + } + } + if err1 == io.EOF { + break + } + // If Readdirnames returned an error, use it. + if err == nil { + err = err1 + } + if len(names) == 0 { + break + } + } + + // Close directory, because windows won't remove opened directory. + fd.Close() + + // Remove directory. + err1 := Remove(path) + if err1 == nil || os.IsNotExist(err1) { + return nil + } + if err == nil { + err = err1 + } + return err +} + func ExpandTilde(path string) (string, error) { if path == "~" { return getHomeDir()