Skip to content

Commit 986b155

Browse files
committed
Ignore files matching patterns in .stignore (fixes #7)
1 parent 46d828e commit 986b155

5 files changed

Lines changed: 131 additions & 20 deletions

File tree

README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,15 @@ $ syncthing --gui 127.0.0.1:8080
186186

187187
You then point your browser to the given address.
188188

189+
Excluding Files
190+
---------------
191+
192+
syncthing looks for files named `.stignore` while walking the
193+
repository. The file is expected to contain glob patterns of file names
194+
to ignore. Patterns are matched on file name only and apply to files in
195+
the same directory as the `.stignore` file and in directories lower down
196+
in the hierarchy.
197+
189198
License
190199
=======
191200

main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -333,7 +333,7 @@ func connect(myID string, addr string, nodeAddrs map[string][]string, m *model.M
333333
}
334334

335335
func updateLocalModel(m *model.Model) {
336-
files := m.Walk(!opts.NoSymlinks)
336+
files := m.FilteredWalk(!opts.NoSymlinks)
337337
m.ReplaceLocal(files)
338338
saveIndex(m)
339339
}

model/model_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ func init() {
5858

5959
func TestUpdateLocal(t *testing.T) {
6060
m := NewModel("testdata")
61-
fs := m.Walk(false)
61+
fs, _ := m.Walk(false)
6262
m.ReplaceLocal(fs)
6363

6464
if len(m.need) > 0 {
@@ -100,7 +100,7 @@ func TestUpdateLocal(t *testing.T) {
100100

101101
func TestRemoteUpdateExisting(t *testing.T) {
102102
m := NewModel("testdata")
103-
fs := m.Walk(false)
103+
fs, _ := m.Walk(false)
104104
m.ReplaceLocal(fs)
105105

106106
newFile := protocol.FileInfo{
@@ -117,7 +117,7 @@ func TestRemoteUpdateExisting(t *testing.T) {
117117

118118
func TestRemoteAddNew(t *testing.T) {
119119
m := NewModel("testdata")
120-
fs := m.Walk(false)
120+
fs, _ := m.Walk(false)
121121
m.ReplaceLocal(fs)
122122

123123
newFile := protocol.FileInfo{
@@ -134,7 +134,7 @@ func TestRemoteAddNew(t *testing.T) {
134134

135135
func TestRemoteUpdateOld(t *testing.T) {
136136
m := NewModel("testdata")
137-
fs := m.Walk(false)
137+
fs, _ := m.Walk(false)
138138
m.ReplaceLocal(fs)
139139

140140
oldTimeStamp := int64(1234)
@@ -152,7 +152,7 @@ func TestRemoteUpdateOld(t *testing.T) {
152152

153153
func TestRemoteIndexUpdate(t *testing.T) {
154154
m := NewModel("testdata")
155-
fs := m.Walk(false)
155+
fs, _ := m.Walk(false)
156156
m.ReplaceLocal(fs)
157157

158158
foo := protocol.FileInfo{
@@ -185,7 +185,7 @@ func TestRemoteIndexUpdate(t *testing.T) {
185185

186186
func TestDelete(t *testing.T) {
187187
m := NewModel("testdata")
188-
fs := m.Walk(false)
188+
fs, _ := m.Walk(false)
189189
m.ReplaceLocal(fs)
190190

191191
if l1, l2 := len(m.local), len(fs); l1 != l2 {
@@ -275,7 +275,7 @@ func TestDelete(t *testing.T) {
275275

276276
func TestForgetNode(t *testing.T) {
277277
m := NewModel("testdata")
278-
fs := m.Walk(false)
278+
fs, _ := m.Walk(false)
279279
m.ReplaceLocal(fs)
280280

281281
if l1, l2 := len(m.local), len(fs); l1 != l2 {

model/walk.go

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package model
22

33
import (
4+
"bytes"
45
"fmt"
6+
"io/ioutil"
57
"log"
68
"os"
79
"path"
@@ -35,7 +37,7 @@ func tempName(name string, modified int64) string {
3537
return path.Join(tdir, tname)
3638
}
3739

38-
func (m *Model) genWalker(res *[]File) filepath.WalkFunc {
40+
func (m *Model) genWalker(res *[]File, ign map[string][]string) filepath.WalkFunc {
3941
return func(p string, info os.FileInfo, err error) error {
4042
if err != nil {
4143
return nil
@@ -45,12 +47,26 @@ func (m *Model) genWalker(res *[]File) filepath.WalkFunc {
4547
return nil
4648
}
4749

48-
if info.Mode()&os.ModeType == 0 {
49-
rn, err := filepath.Rel(m.dir, p)
50-
if err != nil {
51-
return nil
50+
rn, err := filepath.Rel(m.dir, p)
51+
if err != nil {
52+
return nil
53+
}
54+
55+
if pn, sn := path.Split(rn); sn == ".stignore" {
56+
pn := strings.Trim(pn, "/")
57+
bs, _ := ioutil.ReadFile(p)
58+
lines := bytes.Split(bs, []byte("\n"))
59+
var patterns []string
60+
for _, line := range lines {
61+
if len(line) > 0 {
62+
patterns = append(patterns, string(line))
63+
}
5264
}
65+
ign[pn] = patterns
66+
return nil
67+
}
5368

69+
if info.Mode()&os.ModeType == 0 {
5470
fi, err := os.Stat(p)
5571
if err != nil {
5672
return nil
@@ -94,21 +110,21 @@ func (m *Model) genWalker(res *[]File) filepath.WalkFunc {
94110

95111
// Walk returns the list of files found in the local repository by scanning the
96112
// file system. Files are blockwise hashed.
97-
func (m *Model) Walk(followSymlinks bool) []File {
98-
var files []File
99-
fn := m.genWalker(&files)
113+
func (m *Model) Walk(followSymlinks bool) (files []File, ignore map[string][]string) {
114+
ignore = make(map[string][]string)
115+
fn := m.genWalker(&files, ignore)
100116
filepath.Walk(m.dir, fn)
101117

102118
if followSymlinks {
103119
d, err := os.Open(m.dir)
104120
if err != nil {
105-
return files
121+
return
106122
}
107123
defer d.Close()
108124

109125
fis, err := d.Readdir(-1)
110126
if err != nil {
111-
return files
127+
return
112128
}
113129

114130
for _, fi := range fis {
@@ -118,7 +134,15 @@ func (m *Model) Walk(followSymlinks bool) []File {
118134
}
119135
}
120136

121-
return files
137+
return
138+
}
139+
140+
// Walk returns the list of files found in the local repository by scanning the
141+
// file system. Files are blockwise hashed. Patterns marked in .stignore files
142+
// are removed from the results.
143+
func (m *Model) FilteredWalk(followSymlinks bool) []File {
144+
var files, ignored = m.Walk(followSymlinks)
145+
return ignoreFilter(ignored, files)
122146
}
123147

124148
func (m *Model) cleanTempFile(path string, info os.FileInfo, err error) error {
@@ -137,3 +161,21 @@ func (m *Model) cleanTempFile(path string, info os.FileInfo, err error) error {
137161
func (m *Model) cleanTempFiles() {
138162
filepath.Walk(m.dir, m.cleanTempFile)
139163
}
164+
165+
func ignoreFilter(patterns map[string][]string, files []File) (filtered []File) {
166+
nextFile:
167+
for _, f := range files {
168+
first, last := path.Split(f.Name)
169+
for prefix, pats := range patterns {
170+
if len(prefix) == 0 || prefix == first || strings.HasPrefix(first, prefix+"/") {
171+
for _, pattern := range pats {
172+
if match, _ := path.Match(pattern, last); match {
173+
continue nextFile
174+
}
175+
}
176+
}
177+
}
178+
filtered = append(filtered, f)
179+
}
180+
return filtered
181+
}

model/walk_test.go

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package model
22

33
import (
44
"fmt"
5+
"reflect"
56
"testing"
67
"time"
78
)
@@ -16,9 +17,13 @@ var testdata = []struct {
1617
{"foo", 7, "aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f"},
1718
}
1819

20+
var correctIgnores = map[string][]string{
21+
"": {".*", "quux"},
22+
}
23+
1924
func TestWalk(t *testing.T) {
2025
m := NewModel("testdata")
21-
files := m.Walk(false)
26+
files, ignores := m.Walk(false)
2227

2328
if l1, l2 := len(files), len(testdata); l1 != l2 {
2429
t.Fatalf("Incorrect number of walked files %d != %d", l1, l2)
@@ -39,4 +44,59 @@ func TestWalk(t *testing.T) {
3944
t.Errorf("Unrealistic modtime %d for test %d", mt, i)
4045
}
4146
}
47+
48+
if !reflect.DeepEqual(ignores, correctIgnores) {
49+
t.Errorf("Incorrect ignores\n %v\n %v", correctIgnores, ignores)
50+
}
51+
}
52+
53+
func TestFilteredWalk(t *testing.T) {
54+
m := NewModel("testdata")
55+
files := m.FilteredWalk(false)
56+
57+
if len(files) != 2 {
58+
t.Fatalf("Incorrect number of walked filtered files %d != 2", len(files))
59+
}
60+
if files[0].Name != "bar" {
61+
t.Error("Incorrect first file", files[0])
62+
}
63+
if files[1].Name != "foo" {
64+
t.Error("Incorrect second file", files[1])
65+
}
66+
}
67+
68+
func TestIgnore(t *testing.T) {
69+
var patterns = map[string][]string{
70+
"": {"t2"},
71+
"foo": {"bar", "z*"},
72+
"foo/baz": {"quux", ".*"},
73+
}
74+
var files = []File{
75+
{Name: "foo/bar"},
76+
{Name: "foo/quux"},
77+
{Name: "foo/zuux"},
78+
{Name: "foo/qzuux"},
79+
{Name: "foo/baz/t1"},
80+
{Name: "foo/baz/t2"},
81+
{Name: "foo/baz/bar"},
82+
{Name: "foo/baz/quuxa"},
83+
{Name: "foo/baz/aquux"},
84+
{Name: "foo/baz/.quux"},
85+
{Name: "foo/baz/zquux"},
86+
{Name: "foo/baz/quux"},
87+
{Name: "foo/bazz/quux"},
88+
}
89+
var remaining = []File{
90+
{Name: "foo/quux"},
91+
{Name: "foo/qzuux"},
92+
{Name: "foo/baz/t1"},
93+
{Name: "foo/baz/quuxa"},
94+
{Name: "foo/baz/aquux"},
95+
{Name: "foo/bazz/quux"},
96+
}
97+
98+
var filtered = ignoreFilter(patterns, files)
99+
if !reflect.DeepEqual(filtered, remaining) {
100+
t.Errorf("Filtering mismatch\n %v\n %v", remaining, filtered)
101+
}
42102
}

0 commit comments

Comments
 (0)