aboutsummaryrefslogtreecommitdiffstats
path: root/plumbing
diff options
context:
space:
mode:
Diffstat (limited to 'plumbing')
-rw-r--r--plumbing/format/gitignore/dir.go57
-rw-r--r--plumbing/format/gitignore/dir_test.go51
-rw-r--r--plumbing/format/gitignore/doc.go70
-rw-r--r--plumbing/format/gitignore/matcher.go30
-rw-r--r--plumbing/format/gitignore/matcher_test.go16
-rw-r--r--plumbing/format/gitignore/pattern.go150
-rw-r--r--plumbing/format/gitignore/pattern_test.go283
7 files changed, 657 insertions, 0 deletions
diff --git a/plumbing/format/gitignore/dir.go b/plumbing/format/gitignore/dir.go
new file mode 100644
index 0000000..c3bfc53
--- /dev/null
+++ b/plumbing/format/gitignore/dir.go
@@ -0,0 +1,57 @@
+package gitignore
+
+import (
+ "io/ioutil"
+ "os"
+ "strings"
+
+ "gopkg.in/src-d/go-billy.v3"
+)
+
+const (
+ commentPrefix = "#"
+ eol = "\n"
+ gitDir = ".git"
+ gitignoreFile = ".gitignore"
+)
+
+// ReadPatterns reads gitignore patterns recursively traversing through the directory
+// structure. The result is in the ascending order of priority (last higher).
+func ReadPatterns(fs billy.Filesystem, path []string) (ps []Pattern, err error) {
+ f, err := fs.Open(fs.Join(append(path, gitignoreFile)...))
+ if err == nil {
+ defer f.Close()
+
+ if data, err := ioutil.ReadAll(f); err == nil {
+ for _, s := range strings.Split(string(data), eol) {
+ if !strings.HasPrefix(s, commentPrefix) && len(strings.TrimSpace(s)) > 0 {
+ ps = append(ps, ParsePattern(s, path))
+ }
+ }
+ }
+ } else if !os.IsNotExist(err) {
+ return nil, err
+ }
+
+ var fis []os.FileInfo
+ fis, err = fs.ReadDir(fs.Join(path...))
+ if err != nil {
+ return
+ }
+
+ for _, fi := range fis {
+ if fi.IsDir() && fi.Name() != gitDir {
+ var subps []Pattern
+ subps, err = ReadPatterns(fs, append(path, fi.Name()))
+ if err != nil {
+ return
+ }
+
+ if len(subps) > 0 {
+ ps = append(ps, subps...)
+ }
+ }
+ }
+
+ return
+}
diff --git a/plumbing/format/gitignore/dir_test.go b/plumbing/format/gitignore/dir_test.go
new file mode 100644
index 0000000..d28a714
--- /dev/null
+++ b/plumbing/format/gitignore/dir_test.go
@@ -0,0 +1,51 @@
+package gitignore
+
+import (
+ "os"
+
+ "gopkg.in/src-d/go-billy.v3"
+ "gopkg.in/src-d/go-billy.v3/memfs"
+
+ . "gopkg.in/check.v1"
+)
+
+type MatcherSuite struct {
+ FS billy.Filesystem
+}
+
+var _ = Suite(&MatcherSuite{})
+
+func (s *MatcherSuite) SetUpTest(c *C) {
+ fs := memfs.New()
+ f, err := fs.Create(".gitignore")
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte("vendor/g*/\n"))
+ c.Assert(err, IsNil)
+ err = f.Close()
+ c.Assert(err, IsNil)
+
+ err = fs.MkdirAll("vendor", os.ModePerm)
+ c.Assert(err, IsNil)
+ f, err = fs.Create("vendor/.gitignore")
+ c.Assert(err, IsNil)
+ _, err = f.Write([]byte("!github.com/\n"))
+ c.Assert(err, IsNil)
+ err = f.Close()
+ c.Assert(err, IsNil)
+
+ fs.MkdirAll("another", os.ModePerm)
+ fs.MkdirAll("vendor/github.com", os.ModePerm)
+ fs.MkdirAll("vendor/gopkg.in", os.ModePerm)
+
+ s.FS = fs
+}
+
+func (s *MatcherSuite) TestDir_ReadPatterns(c *C) {
+ ps, err := ReadPatterns(s.FS, nil)
+ c.Assert(err, IsNil)
+ c.Assert(ps, HasLen, 2)
+
+ m := NewMatcher(ps)
+ c.Assert(m.Match([]string{"vendor", "gopkg.in"}, true), Equals, true)
+ c.Assert(m.Match([]string{"vendor", "github.com"}, true), Equals, false)
+}
diff --git a/plumbing/format/gitignore/doc.go b/plumbing/format/gitignore/doc.go
new file mode 100644
index 0000000..eecd4ba
--- /dev/null
+++ b/plumbing/format/gitignore/doc.go
@@ -0,0 +1,70 @@
+// Package gitignore implements matching file system paths to gitignore patterns that
+// can be automatically read from a git repository tree in the order of definition
+// priorities. It support all pattern formats as specified in the original gitignore
+// documentation, copied below:
+//
+// Pattern format
+// ==============
+//
+// - A blank line matches no files, so it can serve as a separator for readability.
+//
+// - A line starting with # serves as a comment. Put a backslash ("\") in front of
+// the first hash for patterns that begin with a hash.
+//
+// - Trailing spaces are ignored unless they are quoted with backslash ("\").
+//
+// - An optional prefix "!" which negates the pattern; any matching file excluded
+// by a previous pattern will become included again. It is not possible to
+// re-include a file if a parent directory of that file is excluded.
+// Git doesn’t list excluded directories for performance reasons, so
+// any patterns on contained files have no effect, no matter where they are
+// defined. Put a backslash ("\") in front of the first "!" for patterns
+// that begin with a literal "!", for example, "\!important!.txt".
+//
+// - If the pattern ends with a slash, it is removed for the purpose of the
+// following description, but it would only find a match with a directory.
+// In other words, foo/ will match a directory foo and paths underneath it,
+// but will not match a regular file or a symbolic link foo (this is consistent
+// with the way how pathspec works in general in Git).
+//
+// - If the pattern does not contain a slash /, Git treats it as a shell glob
+// pattern and checks for a match against the pathname relative to the location
+// of the .gitignore file (relative to the toplevel of the work tree if not
+// from a .gitignore file).
+//
+// - Otherwise, Git treats the pattern as a shell glob suitable for consumption
+// by fnmatch(3) with the FNM_PATHNAME flag: wildcards in the pattern will
+// not match a / in the pathname. For example, "Documentation/*.html" matches
+// "Documentation/git.html" but not "Documentation/ppc/ppc.html" or
+// "tools/perf/Documentation/perf.html".
+//
+// - A leading slash matches the beginning of the pathname. For example,
+// "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c".
+//
+// Two consecutive asterisks ("**") in patterns matched against full pathname
+// may have special meaning:
+//
+// - A leading "**" followed by a slash means match in all directories.
+// For example, "**/foo" matches file or directory "foo" anywhere, the same as
+// pattern "foo". "**/foo/bar" matches file or directory "bar"
+// anywhere that is directly under directory "foo".
+//
+// - A trailing "/**" matches everything inside. For example, "abc/**" matches
+// all files inside directory "abc", relative to the location of the
+// .gitignore file, with infinite depth.
+//
+// - A slash followed by two consecutive asterisks then a slash matches
+// zero or more directories. For example, "a/**/b" matches "a/b", "a/x/b",
+// "a/x/y/b" and so on.
+//
+// - Other consecutive asterisks are considered invalid.
+//
+// Copyright and license
+// =====================
+//
+// Copyright (c) Oleg Sklyar, Silvertern and source{d}
+//
+// The package code was donated to source{d} to include, modify and develop
+// further as a part of the `go-git` project, release it on the license of
+// the whole project or delete it from the project.
+package gitignore
diff --git a/plumbing/format/gitignore/matcher.go b/plumbing/format/gitignore/matcher.go
new file mode 100644
index 0000000..bd1e9e2
--- /dev/null
+++ b/plumbing/format/gitignore/matcher.go
@@ -0,0 +1,30 @@
+package gitignore
+
+// Matcher defines a global multi-pattern matcher for gitignore patterns
+type Matcher interface {
+ // Match matches patterns in the order of priorities. As soon as an inclusion or
+ // exclusion is found, not further matching is performed.
+ Match(path []string, isDir bool) bool
+}
+
+// NewMatcher constructs a new global matcher. Patterns must be given in the order of
+// increasing priority. That is most generic settings files first, then the content of
+// the repo .gitignore, then content of .gitignore down the path or the repo and then
+// the content command line arguments.
+func NewMatcher(ps []Pattern) Matcher {
+ return &matcher{ps}
+}
+
+type matcher struct {
+ patterns []Pattern
+}
+
+func (m *matcher) Match(path []string, isDir bool) bool {
+ n := len(m.patterns)
+ for i := n - 1; i >= 0; i-- {
+ if match := m.patterns[i].Match(path, isDir); match > NoMatch {
+ return match == Exclude
+ }
+ }
+ return false
+}
diff --git a/plumbing/format/gitignore/matcher_test.go b/plumbing/format/gitignore/matcher_test.go
new file mode 100644
index 0000000..7311042
--- /dev/null
+++ b/plumbing/format/gitignore/matcher_test.go
@@ -0,0 +1,16 @@
+package gitignore
+
+import (
+ . "gopkg.in/check.v1"
+)
+
+func (s *MatcherSuite) TestMatcher_Match(c *C) {
+ ps := []Pattern{
+ ParsePattern("**/middle/v[uo]l?ano", nil),
+ ParsePattern("!volcano", nil),
+ }
+
+ m := NewMatcher(ps)
+ c.Assert(m.Match([]string{"head", "middle", "vulkano"}, false), Equals, true)
+ c.Assert(m.Match([]string{"head", "middle", "volcano"}, false), Equals, false)
+}
diff --git a/plumbing/format/gitignore/pattern.go b/plumbing/format/gitignore/pattern.go
new file mode 100644
index 0000000..bd0825b
--- /dev/null
+++ b/plumbing/format/gitignore/pattern.go
@@ -0,0 +1,150 @@
+package gitignore
+
+import (
+ "path/filepath"
+ "strings"
+)
+
+// MatchResult defines outcomes of a match, no match, exclusion or inclusion.
+type MatchResult int
+
+const (
+ // NoMatch defines the no match outcome of a match check
+ NoMatch MatchResult = iota
+ // Exclude defines an exclusion of a file as a result of a match check
+ Exclude
+ // Exclude defines an explicit inclusion of a file as a result of a match check
+ Include
+)
+
+const (
+ inclusionPrefix = "!"
+ zeroToManyDirs = "**"
+ patternDirSep = "/"
+)
+
+// Pattern defines a single gitignore pattern.
+type Pattern interface {
+ // Match matches the given path to the pattern.
+ Match(path []string, isDir bool) MatchResult
+}
+
+type pattern struct {
+ domain []string
+ pattern []string
+ inclusion bool
+ dirOnly bool
+ isGlob bool
+}
+
+// ParsePattern parses a gitignore pattern string into the Pattern structure.
+func ParsePattern(p string, domain []string) Pattern {
+ res := pattern{domain: domain}
+
+ if strings.HasPrefix(p, inclusionPrefix) {
+ res.inclusion = true
+ p = p[1:]
+ }
+
+ if !strings.HasSuffix(p, "\\ ") {
+ p = strings.TrimRight(p, " ")
+ }
+
+ if strings.HasSuffix(p, patternDirSep) {
+ res.dirOnly = true
+ p = p[:len(p)-1]
+ }
+
+ if strings.Contains(p, patternDirSep) {
+ res.isGlob = true
+ }
+
+ res.pattern = strings.Split(p, patternDirSep)
+ return &res
+}
+
+func (p *pattern) Match(path []string, isDir bool) MatchResult {
+ if len(path) <= len(p.domain) {
+ return NoMatch
+ }
+ for i, e := range p.domain {
+ if path[i] != e {
+ return NoMatch
+ }
+ }
+
+ path = path[len(p.domain):]
+ if p.isGlob && !p.globMatch(path, isDir) {
+ return NoMatch
+ } else if !p.isGlob && !p.simpleNameMatch(path, isDir) {
+ return NoMatch
+ }
+
+ if p.inclusion {
+ return Include
+ } else {
+ return Exclude
+ }
+}
+
+func (p *pattern) simpleNameMatch(path []string, isDir bool) bool {
+ for i, name := range path {
+ if match, err := filepath.Match(p.pattern[0], name); err != nil {
+ return false
+ } else if !match {
+ continue
+ }
+ if p.dirOnly && !isDir && i == len(path)-1 {
+ return false
+ }
+ return true
+ }
+ return false
+}
+
+func (p *pattern) globMatch(path []string, isDir bool) bool {
+ matched := false
+ canTraverse := false
+ for i, pattern := range p.pattern {
+ if pattern == "" {
+ canTraverse = false
+ continue
+ }
+ if pattern == zeroToManyDirs {
+ if i == len(p.pattern)-1 {
+ break
+ }
+ canTraverse = true
+ continue
+ }
+ if strings.Contains(pattern, zeroToManyDirs) {
+ return false
+ }
+ if len(path) == 0 {
+ return false
+ }
+ if canTraverse {
+ canTraverse = false
+ for len(path) > 0 {
+ e := path[0]
+ path = path[1:]
+ if match, err := filepath.Match(pattern, e); err != nil {
+ return false
+ } else if match {
+ matched = true
+ break
+ }
+ }
+ } else {
+ if match, err := filepath.Match(pattern, path[0]); err != nil || !match {
+ return false
+ }
+ matched = true
+ path = path[1:]
+ }
+ }
+ if matched && p.dirOnly && !isDir && len(path) == 0 {
+ matched = false
+ }
+ return matched
+}
diff --git a/plumbing/format/gitignore/pattern_test.go b/plumbing/format/gitignore/pattern_test.go
new file mode 100644
index 0000000..f94cef3
--- /dev/null
+++ b/plumbing/format/gitignore/pattern_test.go
@@ -0,0 +1,283 @@
+package gitignore
+
+import (
+ "testing"
+
+ . "gopkg.in/check.v1"
+)
+
+func Test(t *testing.T) { TestingT(t) }
+
+type PatternSuite struct{}
+
+var _ = Suite(&PatternSuite{})
+
+func (s *PatternSuite) TestSimpleMatch_inclusion(c *C) {
+ p := ParsePattern("!vul?ano", nil)
+ r := p.Match([]string{"value", "vulkano", "tail"}, false)
+ c.Assert(r, Equals, Include)
+}
+
+func (s *PatternSuite) TestMatch_domainLonger_mismatch(c *C) {
+ p := ParsePattern("value", []string{"head", "middle", "tail"})
+ r := p.Match([]string{"head", "middle"}, false)
+ c.Assert(r, Equals, NoMatch)
+}
+
+func (s *PatternSuite) TestMatch_domainSameLength_mismatch(c *C) {
+ p := ParsePattern("value", []string{"head", "middle", "tail"})
+ r := p.Match([]string{"head", "middle", "tail"}, false)
+ c.Assert(r, Equals, NoMatch)
+}
+
+func (s *PatternSuite) TestMatch_domainMismatch_mismatch(c *C) {
+ p := ParsePattern("value", []string{"head", "middle", "tail"})
+ r := p.Match([]string{"head", "middle", "_tail_", "value"}, false)
+ c.Assert(r, Equals, NoMatch)
+}
+
+func (s *PatternSuite) TestSimpleMatch_withDomain(c *C) {
+ p := ParsePattern("middle/", []string{"value", "volcano"})
+ r := p.Match([]string{"value", "volcano", "middle", "tail"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestSimpleMatch_onlyMatchInDomain_mismatch(c *C) {
+ p := ParsePattern("volcano/", []string{"value", "volcano"})
+ r := p.Match([]string{"value", "volcano", "tail"}, true)
+ c.Assert(r, Equals, NoMatch)
+}
+
+func (s *PatternSuite) TestSimpleMatch_atStart(c *C) {
+ p := ParsePattern("value", nil)
+ r := p.Match([]string{"value", "tail"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestSimpleMatch_inTheMiddle(c *C) {
+ p := ParsePattern("value", nil)
+ r := p.Match([]string{"head", "value", "tail"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestSimpleMatch_atEnd(c *C) {
+ p := ParsePattern("value", nil)
+ r := p.Match([]string{"head", "value"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestSimpleMatch_atStart_dirWanted(c *C) {
+ p := ParsePattern("value/", nil)
+ r := p.Match([]string{"value", "tail"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestSimpleMatch_inTheMiddle_dirWanted(c *C) {
+ p := ParsePattern("value/", nil)
+ r := p.Match([]string{"head", "value", "tail"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestSimpleMatch_atEnd_dirWanted(c *C) {
+ p := ParsePattern("value/", nil)
+ r := p.Match([]string{"head", "value"}, true)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestSimpleMatch_atEnd_dirWanted_notADir_mismatch(c *C) {
+ p := ParsePattern("value/", nil)
+ r := p.Match([]string{"head", "value"}, false)
+ c.Assert(r, Equals, NoMatch)
+}
+
+func (s *PatternSuite) TestSimpleMatch_mismatch(c *C) {
+ p := ParsePattern("value", nil)
+ r := p.Match([]string{"head", "val", "tail"}, false)
+ c.Assert(r, Equals, NoMatch)
+}
+
+func (s *PatternSuite) TestSimpleMatch_valueLonger_mismatch(c *C) {
+ p := ParsePattern("val", nil)
+ r := p.Match([]string{"head", "value", "tail"}, false)
+ c.Assert(r, Equals, NoMatch)
+}
+
+func (s *PatternSuite) TestSimpleMatch_withAsterisk(c *C) {
+ p := ParsePattern("v*o", nil)
+ r := p.Match([]string{"value", "vulkano", "tail"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestSimpleMatch_withQuestionMark(c *C) {
+ p := ParsePattern("vul?ano", nil)
+ r := p.Match([]string{"value", "vulkano", "tail"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestSimpleMatch_magicChars(c *C) {
+ p := ParsePattern("v[ou]l[kc]ano", nil)
+ r := p.Match([]string{"value", "volcano"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestSimpleMatch_wrongPattern_mismatch(c *C) {
+ p := ParsePattern("v[ou]l[", nil)
+ r := p.Match([]string{"value", "vol["}, false)
+ c.Assert(r, Equals, NoMatch)
+}
+
+func (s *PatternSuite) TestGlobMatch_fromRootWithSlash(c *C) {
+ p := ParsePattern("/value/vul?ano", nil)
+ r := p.Match([]string{"value", "vulkano", "tail"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestGlobMatch_withDomain(c *C) {
+ p := ParsePattern("middle/tail/", []string{"value", "volcano"})
+ r := p.Match([]string{"value", "volcano", "middle", "tail"}, true)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestGlobMatch_onlyMatchInDomain_mismatch(c *C) {
+ p := ParsePattern("volcano/tail", []string{"value", "volcano"})
+ r := p.Match([]string{"value", "volcano", "tail"}, false)
+ c.Assert(r, Equals, NoMatch)
+}
+
+func (s *PatternSuite) TestGlobMatch_fromRootWithoutSlash(c *C) {
+ p := ParsePattern("value/vul?ano", nil)
+ r := p.Match([]string{"value", "vulkano", "tail"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestGlobMatch_fromRoot_mismatch(c *C) {
+ p := ParsePattern("value/vulkano", nil)
+ r := p.Match([]string{"value", "volcano"}, false)
+ c.Assert(r, Equals, NoMatch)
+}
+
+func (s *PatternSuite) TestGlobMatch_fromRoot_tooShort_mismatch(c *C) {
+ p := ParsePattern("value/vul?ano", nil)
+ r := p.Match([]string{"value"}, false)
+ c.Assert(r, Equals, NoMatch)
+}
+
+func (s *PatternSuite) TestGlobMatch_fromRoot_notAtRoot_mismatch(c *C) {
+ p := ParsePattern("/value/volcano", nil)
+ r := p.Match([]string{"value", "value", "volcano"}, false)
+ c.Assert(r, Equals, NoMatch)
+}
+
+func (s *PatternSuite) TestGlobMatch_leadingAsterisks_atStart(c *C) {
+ p := ParsePattern("**/*lue/vol?ano", nil)
+ r := p.Match([]string{"value", "volcano", "tail"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestGlobMatch_leadingAsterisks_notAtStart(c *C) {
+ p := ParsePattern("**/*lue/vol?ano", nil)
+ r := p.Match([]string{"head", "value", "volcano", "tail"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestGlobMatch_leadingAsterisks_mismatch(c *C) {
+ p := ParsePattern("**/*lue/vol?ano", nil)
+ r := p.Match([]string{"head", "value", "Volcano", "tail"}, false)
+ c.Assert(r, Equals, NoMatch)
+}
+
+func (s *PatternSuite) TestGlobMatch_leadingAsterisks_isDir(c *C) {
+ p := ParsePattern("**/*lue/vol?ano/", nil)
+ r := p.Match([]string{"head", "value", "volcano", "tail"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestGlobMatch_leadingAsterisks_isDirAtEnd(c *C) {
+ p := ParsePattern("**/*lue/vol?ano/", nil)
+ r := p.Match([]string{"head", "value", "volcano"}, true)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestGlobMatch_leadingAsterisks_isDir_mismatch(c *C) {
+ p := ParsePattern("**/*lue/vol?ano/", nil)
+ r := p.Match([]string{"head", "value", "Colcano"}, true)
+ c.Assert(r, Equals, NoMatch)
+}
+
+func (s *PatternSuite) TestGlobMatch_leadingAsterisks_isDirNoDirAtEnd_mismatch(c *C) {
+ p := ParsePattern("**/*lue/vol?ano/", nil)
+ r := p.Match([]string{"head", "value", "volcano"}, false)
+ c.Assert(r, Equals, NoMatch)
+}
+
+func (s *PatternSuite) TestGlobMatch_tailingAsterisks(c *C) {
+ p := ParsePattern("/*lue/vol?ano/**", nil)
+ r := p.Match([]string{"value", "volcano", "tail", "moretail"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestGlobMatch_tailingAsterisks_exactMatch(c *C) {
+ p := ParsePattern("/*lue/vol?ano/**", nil)
+ r := p.Match([]string{"value", "volcano"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestGlobMatch_middleAsterisks_emptyMatch(c *C) {
+ p := ParsePattern("/*lue/**/vol?ano", nil)
+ r := p.Match([]string{"value", "volcano"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestGlobMatch_middleAsterisks_oneMatch(c *C) {
+ p := ParsePattern("/*lue/**/vol?ano", nil)
+ r := p.Match([]string{"value", "middle", "volcano"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestGlobMatch_middleAsterisks_multiMatch(c *C) {
+ p := ParsePattern("/*lue/**/vol?ano", nil)
+ r := p.Match([]string{"value", "middle1", "middle2", "volcano"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestGlobMatch_middleAsterisks_isDir_trailing(c *C) {
+ p := ParsePattern("/*lue/**/vol?ano/", nil)
+ r := p.Match([]string{"value", "middle1", "middle2", "volcano"}, true)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestGlobMatch_middleAsterisks_isDir_trailing_mismatch(c *C) {
+ p := ParsePattern("/*lue/**/vol?ano/", nil)
+ r := p.Match([]string{"value", "middle1", "middle2", "volcano"}, false)
+ c.Assert(r, Equals, NoMatch)
+}
+
+func (s *PatternSuite) TestGlobMatch_middleAsterisks_isDir(c *C) {
+ p := ParsePattern("/*lue/**/vol?ano/", nil)
+ r := p.Match([]string{"value", "middle1", "middle2", "volcano", "tail"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestGlobMatch_wrongDoubleAsterisk_mismatch(c *C) {
+ p := ParsePattern("/*lue/**foo/vol?ano", nil)
+ r := p.Match([]string{"value", "foo", "volcano", "tail"}, false)
+ c.Assert(r, Equals, NoMatch)
+}
+
+func (s *PatternSuite) TestGlobMatch_magicChars(c *C) {
+ p := ParsePattern("**/head/v[ou]l[kc]ano", nil)
+ r := p.Match([]string{"value", "head", "volcano"}, false)
+ c.Assert(r, Equals, Exclude)
+}
+
+func (s *PatternSuite) TestGlobMatch_wrongPattern_noTraversal_mismatch(c *C) {
+ p := ParsePattern("**/head/v[ou]l[", nil)
+ r := p.Match([]string{"value", "head", "vol["}, false)
+ c.Assert(r, Equals, NoMatch)
+}
+
+func (s *PatternSuite) TestGlobMatch_wrongPattern_onTraversal_mismatch(c *C) {
+ p := ParsePattern("/value/**/v[ou]l[", nil)
+ r := p.Match([]string{"value", "head", "vol["}, false)
+ c.Assert(r, Equals, NoMatch)
+}