diff options
Diffstat (limited to 'plumbing')
-rw-r--r-- | plumbing/format/gitignore/dir.go | 57 | ||||
-rw-r--r-- | plumbing/format/gitignore/dir_test.go | 51 | ||||
-rw-r--r-- | plumbing/format/gitignore/doc.go | 70 | ||||
-rw-r--r-- | plumbing/format/gitignore/matcher.go | 30 | ||||
-rw-r--r-- | plumbing/format/gitignore/matcher_test.go | 16 | ||||
-rw-r--r-- | plumbing/format/gitignore/pattern.go | 150 | ||||
-rw-r--r-- | plumbing/format/gitignore/pattern_test.go | 283 |
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) +} |