diff options
author | Jakob Borg <jakob@nym.se> | 2014-09-04 22:29:53 +0200 |
---|---|---|
committer | Jakob Borg <jakob@nym.se> | 2014-09-04 22:30:42 +0200 |
commit | 92c44c8abecba900969c38ab17cbd786268828a6 (patch) | |
tree | d4253edc6b442ad25826196034e9925f28982c71 | |
parent | 8e4f7bbd3e624089256257b1d1ed0140e03bf245 (diff) |
Rework .stignore functionality (fixes #561) (...)
- Only one .stignore is supported, at the repo root
- Negative patterns (!) are supported
- Ignore patterns affect sent and received indexes, not only scanning
-rw-r--r-- | auto/gui.files.go | 2 | ||||
-rw-r--r-- | cmd/syncthing/gui.go | 2 | ||||
-rw-r--r-- | files/leveldb.go | 38 | ||||
-rw-r--r-- | files/set_test.go | 82 | ||||
-rw-r--r-- | gui/app.js | 2 | ||||
-rw-r--r-- | ignore/ignore.go | 146 | ||||
-rw-r--r-- | ignore/ignore_test.go | 104 | ||||
-rw-r--r-- | ignore/testdata/.stignore | 6 | ||||
-rw-r--r-- | ignore/testdata/dir3/cfile | 1 | ||||
-rw-r--r-- | ignore/testdata/dir3/dfile | 1 | ||||
-rw-r--r-- | ignore/testdata/excludes | 2 | ||||
-rw-r--r-- | ignore/testdata/further-excludes | 1 | ||||
-rwxr-xr-x | integration/all.sh | 2 | ||||
-rw-r--r-- | model/model.go | 106 | ||||
-rw-r--r-- | scanner/testdata/.stignore | 1 | ||||
-rw-r--r-- | scanner/testdata/excludes | 2 | ||||
-rw-r--r-- | scanner/testdata/loop-excludes | 1 | ||||
-rw-r--r-- | scanner/walk.go | 140 | ||||
-rw-r--r-- | scanner/walk_test.go | 100 |
19 files changed, 488 insertions, 251 deletions
diff --git a/auto/gui.files.go b/auto/gui.files.go index 3a294a082c..65c44f4a1b 100644 --- a/auto/gui.files.go +++ b/auto/gui.files.go @@ -27,7 +27,7 @@ func Assets() map[string][]byte { bs, _ = ioutil.ReadAll(gr) assets["angular/angular.min.js"] = bs - bs, _ = base64.StdEncoding.DecodeString("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") + bs, _ = base64.StdEncoding.DecodeString("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") gr, _ = gzip.NewReader(bytes.NewBuffer(bs)) bs, _ = ioutil.ReadAll(gr) assets["app.js"] = bs diff --git a/cmd/syncthing/gui.go b/cmd/syncthing/gui.go index 8624d120c5..682d370022 100644 --- a/cmd/syncthing/gui.go +++ b/cmd/syncthing/gui.go @@ -255,7 +255,7 @@ func restGetModel(m *model.Model, w http.ResponseWriter, r *http.Request) { func restPostOverride(m *model.Model, w http.ResponseWriter, r *http.Request) { var qs = r.URL.Query() var repo = qs.Get("repo") - m.Override(repo) + go m.Override(repo) } func restGetNeed(m *model.Model, w http.ResponseWriter, r *http.Request) { diff --git a/files/leveldb.go b/files/leveldb.go index 7691b75da9..7f970eb7f5 100644 --- a/files/leveldb.go +++ b/files/leveldb.go @@ -186,18 +186,28 @@ func ldbGenericReplace(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo if lv := ldbInsert(batch, repo, node, newName, fs[fsi]); lv > maxLocalVer { maxLocalVer = lv } - ldbUpdateGlobal(snap, batch, repo, node, newName, fs[fsi].Version) + if fs[fsi].IsInvalid() { + ldbRemoveFromGlobal(snap, batch, repo, node, newName) + } else { + ldbUpdateGlobal(snap, batch, repo, node, newName, fs[fsi].Version) + } fsi++ case moreFs && moreDb && cmp == 0: - // File exists on both sides - compare versions. + // File exists on both sides - compare versions. We might get an + // update with the same version and different flags if a node has + // marked a file as invalid, so handle that too. var ef protocol.FileInfoTruncated ef.UnmarshalXDR(dbi.Value()) - if fs[fsi].Version > ef.Version { + if fs[fsi].Version > ef.Version || fs[fsi].Version != ef.Version { if lv := ldbInsert(batch, repo, node, newName, fs[fsi]); lv > maxLocalVer { maxLocalVer = lv } - ldbUpdateGlobal(snap, batch, repo, node, newName, fs[fsi].Version) + if fs[fsi].IsInvalid() { + ldbRemoveFromGlobal(snap, batch, repo, node, newName) + } else { + ldbUpdateGlobal(snap, batch, repo, node, newName, fs[fsi].Version) + } } // Iterate both sides. fsi++ @@ -280,7 +290,11 @@ func ldbUpdate(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo) uint64 if lv := ldbInsert(batch, repo, node, name, f); lv > maxLocalVer { maxLocalVer = lv } - ldbUpdateGlobal(snap, batch, repo, node, name, f.Version) + if f.IsInvalid() { + ldbRemoveFromGlobal(snap, batch, repo, node, name) + } else { + ldbUpdateGlobal(snap, batch, repo, node, name, f.Version) + } continue } @@ -289,11 +303,17 @@ func ldbUpdate(db *leveldb.DB, repo, node []byte, fs []protocol.FileInfo) uint64 if err != nil { panic(err) } - if ef.Version != f.Version { + // Flags might change without the version being bumped when we set the + // invalid flag on an existing file. + if ef.Version != f.Version || ef.Flags != f.Flags { if lv := ldbInsert(batch, repo, node, name, f); lv > maxLocalVer { maxLocalVer = lv } - ldbUpdateGlobal(snap, batch, repo, node, name, f.Version) + if f.IsInvalid() { + ldbRemoveFromGlobal(snap, batch, repo, node, name) + } else { + ldbUpdateGlobal(snap, batch, repo, node, name, f.Version) + } } } @@ -385,7 +405,9 @@ func ldbRemoveFromGlobal(db dbReader, batch dbWriter, repo, node, file []byte) { gk := globalKey(repo, file) svl, err := db.Get(gk, nil) if err != nil { - panic(err) + // We might be called to "remove" a global version that doesn't exist + // if the first update for the file is already marked invalid. + return } var fl versionList diff --git a/files/set_test.go b/files/set_test.go index 8f3a9b3167..aa88ee65c4 100644 --- a/files/set_test.go +++ b/files/set_test.go @@ -86,7 +86,7 @@ func (l fileList) String() string { var b bytes.Buffer b.WriteString("[]protocol.FileList{\n") for _, f := range l { - fmt.Fprintf(&b, " %q: #%d, %d bytes, %d blocks\n", f.Name, f.Version, f.Size(), len(f.Blocks)) + fmt.Fprintf(&b, " %q: #%d, %d bytes, %d blocks, flags=%o\n", f.Name, f.Version, f.Size(), len(f.Blocks), f.Flags) } b.WriteString("}") return b.String() @@ -280,6 +280,86 @@ func TestNeedWithInvalid(t *testing.T) { } } +func TestUpdateToInvalid(t *testing.T) { + lamport.Default = lamport.Clock{} + + db, err := leveldb.Open(storage.NewMemStorage(), nil) + if err != nil { + t.Fatal(err) + } + + s := files.NewSet("test", db) + + localHave := fileList{ + protocol.FileInfo{Name: "a", Version: 1000, Blocks: genBlocks(1)}, + protocol.FileInfo{Name: "b", Version: 1001, Blocks: genBlocks(2)}, + protocol.FileInfo{Name: "c", Version: 1002, Blocks: genBlocks(5), Flags: protocol.FlagInvalid}, + protocol.FileInfo{Name: "d", Version: 1003, Blocks: genBlocks(7)}, + } + + s.ReplaceWithDelete(protocol.LocalNodeID, localHave) + + have := fileList(haveList(s, protocol.LocalNodeID)) + sort.Sort(have) + + if fmt.Sprint(have) != fmt.Sprint(localHave) { + t.Errorf("Have incorrect before invalidation;\n A: %v !=\n E: %v", have, localHave) + } + + localHave[1] = protocol.FileInfo{Name: "b", Version: 1001, Flags: protocol.FlagInvalid} + s.Update(protocol.LocalNodeID, localHave[1:2]) + + have = fileList(haveList(s, protocol.LocalNodeID)) + sort.Sort(have) + + if fmt.Sprint(have) != fmt.Sprint(localHave) { + t.Errorf("Have incorrect after invalidation;\n A: %v !=\n E: %v", have, localHave) + } +} + +func TestInvalidAvailability(t *testing.T) { + lamport.Default = lamport.Clock{} + + db, err := leveldb.Open(storage.NewMemStorage(), nil) + if err != nil { + t.Fatal(err) + } + + s := files.NewSet("test", db) + + remote0Have := fileList{ + protocol.FileInfo{Name: "both", Version: 1001, Blocks: genBlocks(2)}, + protocol.FileInfo{Name: "r1only", Version: 1002, Blocks: genBlocks(5), Flags: protocol.FlagInvalid}, + protocol.FileInfo{Name: "r0only", Version: 1003, Blocks: genBlocks(7)}, + protocol.FileInfo{Name: "none", Version: 1004, Blocks: genBlocks(5), Flags: protocol.FlagInvalid}, + } + remote1Have := fileList{ + protocol.FileInfo{Name: "both", Version: 1001, Blocks: genBlocks(2)}, + protocol.FileInfo{Name: "r1only", Version: 1002, Blocks: genBlocks(7)}, + protocol.FileInfo{Name: "r0only", Version: 1003, Blocks: genBlocks(5), Flags: protocol.FlagInvalid}, + protocol.FileInfo{Name: "none", Version: 1004, Blocks: genBlocks(5), Flags: protocol.FlagInvalid}, + } + + s.Replace(remoteNode0, remote0Have) + s.Replace(remoteNode1, remote1Have) + + if av := s.Availability("both"); len(av) != 2 { + t.Error("Incorrect availability for 'both':", av) + } + + if av := s.Availability("r0only"); len(av) != 1 || av[0] != remoteNode0 { + t.Error("Incorrect availability for 'r0only':", av) + } + + if av := s.Availability("r1only"); len(av) != 1 || av[0] != remoteNode1 { + t.Error("Incorrect availability for 'r1only':", av) + } + + if av := s.Availability("none"); len(av) != 0 { + t.Error("Incorrect availability for 'none':", av) + } +} + func TestLocalDeleted(t *testing.T) { db, err := leveldb.Open(storage.NewMemStorage(), nil) if err != nil { diff --git a/gui/app.js b/gui/app.js index 3617e8d08a..511bddb112 100644 --- a/gui/app.js +++ b/gui/app.js @@ -971,9 +971,9 @@ function debounce(func, wait) { } else { timeout = null; if (again) { + again = false; result = func.apply(context, args); context = args = null; - again = false; } } }; diff --git a/ignore/ignore.go b/ignore/ignore.go new file mode 100644 index 0000000000..f2809e8072 --- /dev/null +++ b/ignore/ignore.go @@ -0,0 +1,146 @@ +// Copyright (C) 2014 Jakob Borg and Contributors (see the CONTRIBUTORS file). +// All rights reserved. Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file. + +package ignore + +import ( + "bufio" + "fmt" + "io" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/syncthing/syncthing/fnmatch" +) + +type Pattern struct { + match *regexp.Regexp + include bool +} + +type Patterns []Pattern + +func Load(file string) (Patterns, error) { + seen := make(map[string]bool) + return loadIgnoreFile(file, seen) +} + +func Parse(r io.Reader, file string) (Patterns, error) { + seen := map[string]bool{ + file: true, + } + return parseIgnoreFile(r, file, seen) +} + +func (l Patterns) Match(file string) bool { + for _, pattern := range l { + if pattern.match.MatchString(file) { + return pattern.include + } + } + return false +} + +func loadIgnoreFile(file string, seen map[string]bool) (Patterns, error) { + if seen[file] { + return nil, fmt.Errorf("Multiple include of ignore file %q", file) + } + seen[file] = true + + fd, err := os.Open(file) + if err != nil { + return nil, err + } + defer fd.Close() + + return parseIgnoreFile(fd, file, seen) +} + +func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) (Patterns, error) { + var exps Patterns + + addPattern := func(line string) error { + include := true + if strings.HasPrefix(line, "!") { + line = line[1:] + include = false + } + + if strings.HasPrefix(line, "/") { + // Pattern is rooted in the current dir only + exp, err := fnmatch.Convert(line[1:], fnmatch.FNM_PATHNAME) + if err != nil { + return fmt.Errorf("Invalid pattern %q in ignore file", line) + } + exps = append(exps, Pattern{exp, include}) + } else if strings.HasPrefix(line, "**/") { + // Add the pattern as is, and without **/ so it matches in current dir + exp, err := fnmatch.Convert(line, fnmatch.FNM_PATHNAME) + if err != nil { + return fmt.Errorf("Invalid pattern %q in ignore file", line) + } + exps = append(exps, Pattern{exp, include}) + + exp, err = fnmatch.Convert(line[3:], fnmatch.FNM_PATHNAME) + if err != nil { + return fmt.Errorf("Invalid pattern %q in ignore file", line) + } + exps = append(exps, Pattern{exp, include}) + } else if strings.HasPrefix(line, "#include ") { + includeFile := filepath.Join(filepath.Dir(currentFile), line[len("#include "):]) + includes, err := loadIgnoreFile(includeFile, seen) + if err != nil { + return err + } else { + exps = append(exps, includes...) + } + } else { + // Path name or pattern, add it so it matches files both in + // current directory and subdirs. + exp, err := fnmatch.Convert(line, fnmatch.FNM_PATHNAME) + if err != nil { + return fmt.Errorf("Invalid pattern %q in ignore file", line) + } + exps = append(exps, Pattern{exp, include}) + + exp, err = fnmatch.Convert("**/"+line, fnmatch.FNM_PATHNAME) + if err != nil { + return fmt.Errorf("Invalid pattern %q in ignore file", line) + } + exps = append(exps, Pattern{exp, include}) + } + return nil + } + + scanner := bufio.NewScanner(fd) + var err error + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + switch { + case line == "": + continue + case strings.HasPrefix(line, "#"): + err = addPattern(line) + case strings.HasSuffix(line, "/**"): + err = addPattern(line) + case strings.HasSuffix(line, "/"): + err = addPattern(line) + if err == nil { + err = addPattern(line + "**") + } + default: + err = addPattern(line) + if err == nil { + err = addPattern(line + "/**") + } + } + if err != nil { + return nil, err + } + } + + return exps, nil +} diff --git a/ignore/ignore_test.go b/ignore/ignore_test.go new file mode 100644 index 0000000000..0d3d56ec42 --- /dev/null +++ b/ignore/ignore_test.go @@ -0,0 +1,104 @@ +package ignore_test + +import ( + "bytes" + "path/filepath" + "testing" + + "github.com/syncthing/syncthing/ignore" +) + +func TestIgnore(t *testing.T) { + pats, err := ignore.Load("testdata/.stignore") + if err != nil { + t.Fatal(err) + } + + var tests = []struct { + f string + r bool + }{ + {"afile", false}, + {"bfile", true}, + {"cfile", false}, + {"dfile", false}, + {"efile", true}, + {"ffile", true}, + + {"dir1", false}, + {filepath.Join("dir1", "cfile"), true}, + {filepath.Join("dir1", "dfile"), false}, + {filepath.Join("dir1", "efile"), true}, + {filepath.Join("dir1", "ffile"), false}, + + {"dir2", false}, + {filepath.Join("dir2", "cfile"), false}, + {filepath.Join("dir2", "dfile"), true}, + {filepath.Join("dir2", "efile"), true}, + {filepath.Join("dir2", "ffile"), false}, + + {filepath.Join("dir3"), true}, + {filepath.Join("dir3", "afile"), true}, + } + + for i, tc := range tests { + if r := pats.Match(tc.f); r != tc.r { + t.Errorf("Incorrect ignoreFile() #%d (%s); E: %v, A: %v", i, tc.f, tc.r, r) + } + } +} + +func TestExcludes(t *testing.T) { + stignore := ` + !iex2 + !ign1/ex + ign1 + i*2 + !ign2 + ` + pats, err := ignore.Parse(bytes.NewBufferString(stignore), ".stignore") + if err != nil { + t.Fatal(err) + } + + var tests = []struct { + f string + r bool + }{ + {"ign1", true}, + {"ign2", true}, + {"ibla2", true}, + {"iex2", false}, + {"ign1/ign", true}, + {"ign1/ex", false}, + {"ign1/iex2", false}, + {"iex2/ign", false}, + {"foo/bar/ign1", true}, + {"foo/bar/ign2", true}, + {"foo/bar/iex2", false}, + } + + for _, tc := range tests { + if r := pats.Match(tc.f); r != tc.r { + t.Errorf("Incorrect match for %s: %v != %v", tc.f, r, tc.r) + } + } +} + +func TestBadPatterns(t *testing.T) { + var badPatterns = []string{ + "[", + "/[", + "**/[", + "#include nonexistent", + "#include .stignore", + "!#include makesnosense", + } + + for _, pat := range badPatterns { + parsed, err := ignore.Parse(bytes.NewBufferString(pat), ".stignore") + if err == nil { + t.Errorf("No error for pattern %q: %v", pat, parsed) + } + } +} diff --git a/ignore/testdata/.stignore b/ignore/testdata/.stignore new file mode 100644 index 0000000000..89a11f4b85 --- /dev/null +++ b/ignore/testdata/.stignore @@ -0,0 +1,6 @@ +#include excludes + +bfile +dir1/cfile +**/efile +/ffile diff --git a/ignore/testdata/dir3/cfile b/ignore/testdata/dir3/cfile new file mode 100644 index 0000000000..76018072e0 --- /dev/null +++ b/ignore/testdata/dir3/cfile @@ -0,0 +1 @@ +baz diff --git a/ignore/testdata/dir3/dfile b/ignore/testdata/dir3/dfile new file mode 100644 index 0000000000..d90bda0ff3 --- /dev/null +++ b/ignore/testdata/dir3/dfile @@ -0,0 +1 @@ +quux diff --git a/ignore/testdata/excludes b/ignore/testdata/excludes new file mode 100644 index 0000000000..6794620480 --- /dev/null +++ b/igno |