浏览代码

lib/ignore: Handle bare commas in ignore patterns (fixes #3042)

GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3048
Jakob Borg 9 年之前
父节点
当前提交
5d337bb24f
共有 2 个文件被更改,包括 82 次插入6 次删除
  1. 52 6
      lib/ignore/ignore.go
  2. 30 0
      lib/ignore/ignore_test.go

+ 52 - 6
lib/ignore/ignore.go

@@ -280,14 +280,14 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([]
 			// Pattern is rooted in the current dir only
 			pattern.match, err = glob.Compile(line[1:])
 			if err != nil {
-				return fmt.Errorf("invalid pattern %q in ignore file", line)
+				return fmt.Errorf("invalid pattern %q in ignore file (%v)", line, err)
 			}
 			patterns = append(patterns, pattern)
 		} else if strings.HasPrefix(line, "**/") {
 			// Add the pattern as is, and without **/ so it matches in current dir
 			pattern.match, err = glob.Compile(line)
 			if err != nil {
-				return fmt.Errorf("invalid pattern %q in ignore file", line)
+				return fmt.Errorf("invalid pattern %q in ignore file (%v)", line, err)
 			}
 			patterns = append(patterns, pattern)
 
@@ -295,7 +295,7 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([]
 			pattern.pattern = line
 			pattern.match, err = glob.Compile(line)
 			if err != nil {
-				return fmt.Errorf("invalid pattern %q in ignore file", line)
+				return fmt.Errorf("invalid pattern %q in ignore file (%v)", line, err)
 			}
 			patterns = append(patterns, pattern)
 		} else if strings.HasPrefix(line, "#include ") {
@@ -311,7 +311,7 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([]
 			// current directory and subdirs.
 			pattern.match, err = glob.Compile(line)
 			if err != nil {
-				return fmt.Errorf("invalid pattern %q in ignore file", line)
+				return fmt.Errorf("invalid pattern %q in ignore file (%v)", line, err)
 			}
 			patterns = append(patterns, pattern)
 
@@ -319,7 +319,7 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([]
 			pattern.pattern = line
 			pattern.match, err = glob.Compile(line)
 			if err != nil {
-				return fmt.Errorf("invalid pattern %q in ignore file", line)
+				return fmt.Errorf("invalid pattern %q in ignore file (%v)", line, err)
 			}
 			patterns = append(patterns, pattern)
 		}
@@ -337,7 +337,7 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([]
 			continue
 		}
 
-		line = filepath.ToSlash(line)
+		line = escapeCommas(filepath.ToSlash(line))
 		switch {
 		case strings.HasPrefix(line, "#"):
 			err = addPattern(line)
@@ -358,3 +358,49 @@ func parseIgnoreFile(fd io.Reader, currentFile string, seen map[string]bool) ([]
 
 	return patterns, nil
 }
+
+// escapes unescaped commas encountered outside of brackets
+func escapeCommas(s string) string {
+	buf := make([]rune, 0, len(s))
+	inEscape := false
+	inBrackets := 0
+	inSquareBrackets := 0
+	for _, r := range s {
+		// Escaped characters are passed on verbatim no matter what, and we
+		// clear the escape flag for the next character.
+		if inEscape {
+			buf = append(buf, r)
+			inEscape = false
+			continue
+		}
+
+		// Check for escapes and commas to escape. Also keep track of the
+		// brackets level by counting start and end brackets of the two
+		// types.
+
+		switch r {
+		case '\\':
+			inEscape = true
+
+		case '{':
+			inBrackets++
+		case '}':
+			inBrackets--
+		case '[':
+			inSquareBrackets++
+		case ']':
+			inSquareBrackets--
+
+		case ',':
+			// Commas should be escaped if we're not inside a brackets
+			// construction, and if they weren't already escaped (in which
+			// case we'll have taken the first branch way up top).
+			if inBrackets == 0 && inSquareBrackets == 0 {
+				buf = append(buf, '\\')
+			}
+		}
+
+		buf = append(buf, r)
+	}
+	return string(buf)
+}

+ 30 - 0
lib/ignore/ignore_test.go

@@ -635,3 +635,33 @@ func TestAutomaticCaseInsensitivity(t *testing.T) {
 		}
 	}
 }
+
+func TestCommas(t *testing.T) {
+	stignore := `
+	foo,bar.txt
+	{baz,quux}.txt
+	`
+	pats := New(true)
+	err := pats.Parse(bytes.NewBufferString(stignore), ".stignore")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	tests := []struct {
+		name  string
+		match bool
+	}{
+		{"foo.txt", false},
+		{"bar.txt", false},
+		{"foo,bar.txt", true},
+		{"baz.txt", true},
+		{"quux.txt", true},
+		{"baz,quux.txt", false},
+	}
+
+	for _, tc := range tests {
+		if pats.Match(tc.name).IsIgnored() != tc.match {
+			t.Errorf("Match of %s was %v, should be %v", tc.name, !tc.match, tc.match)
+		}
+	}
+}