From 5d337bb24fdefd9c61f4cacca329a436a6152ae1 Mon Sep 17 00:00:00 2001 From: Jakob Borg Date: Fri, 6 May 2016 15:45:11 +0000 Subject: [PATCH] lib/ignore: Handle bare commas in ignore patterns (fixes #3042) GitHub-Pull-Request: https://github.com/syncthing/syncthing/pull/3048 --- lib/ignore/ignore.go | 58 +++++++++++++++++++++++++++++++++++---- lib/ignore/ignore_test.go | 30 ++++++++++++++++++++ 2 files changed, 82 insertions(+), 6 deletions(-) diff --git a/lib/ignore/ignore.go b/lib/ignore/ignore.go index 493e0046..0a0406b7 100644 --- a/lib/ignore/ignore.go +++ b/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) +} diff --git a/lib/ignore/ignore_test.go b/lib/ignore/ignore_test.go index a014371c..f74e9bfe 100644 --- a/lib/ignore/ignore_test.go +++ b/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) + } + } +}