Merge pull request #44230 from thaJeztah/22.06_backport_migrate_filematcher
[22.06 backport] replace pkg/fileutils Matching funcs with github.com/moby/patternmatcher
This commit is contained in:
commit
3956644474
11 changed files with 738 additions and 1013 deletions
|
@ -13,9 +13,9 @@ import (
|
|||
"github.com/docker/docker/builder"
|
||||
"github.com/docker/docker/builder/remotecontext/urlutil"
|
||||
"github.com/docker/docker/errdefs"
|
||||
"github.com/docker/docker/pkg/fileutils"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/dockerignore"
|
||||
"github.com/moby/buildkit/frontend/dockerfile/parser"
|
||||
"github.com/moby/patternmatcher"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
@ -130,7 +130,7 @@ func removeDockerfile(c modifiableContext, filesToRemove ...string) error {
|
|||
f.Close()
|
||||
filesToRemove = append([]string{".dockerignore"}, filesToRemove...)
|
||||
for _, fileToRemove := range filesToRemove {
|
||||
if rm, _ := fileutils.MatchesOrParentMatches(fileToRemove, excludes); rm {
|
||||
if rm, _ := patternmatcher.MatchesOrParentMatches(fileToRemove, excludes); rm {
|
||||
if err := c.Remove(fileToRemove); err != nil {
|
||||
logrus.Errorf("failed to remove %s: %v", fileToRemove, err)
|
||||
}
|
||||
|
|
|
@ -19,12 +19,12 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/containerd/containerd/pkg/userns"
|
||||
"github.com/docker/docker/pkg/fileutils"
|
||||
"github.com/docker/docker/pkg/idtools"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/pools"
|
||||
"github.com/docker/docker/pkg/system"
|
||||
"github.com/klauspost/compress/zstd"
|
||||
"github.com/moby/patternmatcher"
|
||||
"github.com/moby/sys/sequential"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
@ -854,7 +854,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
|
|||
// on platforms other than Windows.
|
||||
srcPath = fixVolumePathPrefix(srcPath)
|
||||
|
||||
pm, err := fileutils.NewPatternMatcher(options.ExcludePatterns)
|
||||
pm, err := patternmatcher.New(options.ExcludePatterns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -929,7 +929,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
|
|||
rebaseName := options.RebaseNames[include]
|
||||
|
||||
var (
|
||||
parentMatchInfo []fileutils.MatchInfo
|
||||
parentMatchInfo []patternmatcher.MatchInfo
|
||||
parentDirs []string
|
||||
)
|
||||
|
||||
|
@ -968,11 +968,11 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
|
|||
parentMatchInfo = parentMatchInfo[:len(parentMatchInfo)-1]
|
||||
}
|
||||
|
||||
var matchInfo fileutils.MatchInfo
|
||||
var matchInfo patternmatcher.MatchInfo
|
||||
if len(parentMatchInfo) != 0 {
|
||||
skip, matchInfo, err = pm.MatchesUsingParentResults(relFilePath, parentMatchInfo[len(parentMatchInfo)-1])
|
||||
} else {
|
||||
skip, matchInfo, err = pm.MatchesUsingParentResults(relFilePath, fileutils.MatchInfo{})
|
||||
skip, matchInfo, err = pm.MatchesUsingParentResults(relFilePath, patternmatcher.MatchInfo{})
|
||||
}
|
||||
if err != nil {
|
||||
logrus.Errorf("Error matching %s: %v", relFilePath, err)
|
||||
|
|
44
pkg/fileutils/deprecated.go
Normal file
44
pkg/fileutils/deprecated.go
Normal file
|
@ -0,0 +1,44 @@
|
|||
package fileutils
|
||||
|
||||
import "github.com/moby/patternmatcher"
|
||||
|
||||
type (
|
||||
// PatternMatcher allows checking paths against a list of patterns.
|
||||
//
|
||||
// Deprecated: use github.com/moby/patternmatcher.PatternMatcher
|
||||
PatternMatcher = patternmatcher.PatternMatcher
|
||||
|
||||
// MatchInfo tracks information about parent dir matches while traversing a
|
||||
// filesystem.
|
||||
//
|
||||
// Deprecated: use github.com/moby/patternmatcher.MatchInfo
|
||||
MatchInfo = patternmatcher.MatchInfo
|
||||
|
||||
// Pattern defines a single regexp used to filter file paths.
|
||||
//
|
||||
// Deprecated: use github.com/moby/patternmatcher.Pattern
|
||||
Pattern = patternmatcher.Pattern
|
||||
)
|
||||
|
||||
var (
|
||||
// NewPatternMatcher creates a new matcher object for specific patterns that can
|
||||
// be used later to match against patterns against paths
|
||||
//
|
||||
// Deprecated: use github.com/moby/patternmatcher.New
|
||||
NewPatternMatcher = patternmatcher.New
|
||||
|
||||
// Matches returns true if file matches any of the patterns
|
||||
// and isn't excluded by any of the subsequent patterns.
|
||||
//
|
||||
// This implementation is buggy (it only checks a single parent dir against the
|
||||
// pattern) and will be removed soon. Use MatchesOrParentMatches instead.
|
||||
//
|
||||
// Deprecated: use github.com/moby/patternmatcher.Matches
|
||||
Matches = patternmatcher.Matches
|
||||
|
||||
// MatchesOrParentMatches returns true if file matches any of the patterns
|
||||
// and isn't excluded by any of the subsequent patterns.
|
||||
//
|
||||
// Deprecated: use github.com/moby/patternmatcher.MatchesOrParentMatches
|
||||
MatchesOrParentMatches = patternmatcher.MatchesOrParentMatches
|
||||
)
|
|
@ -1,480 +1,12 @@
|
|||
package fileutils // import "github.com/docker/docker/pkg/fileutils"
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// escapeBytes is a bitmap used to check whether a character should be escaped when creating the regex.
|
||||
var escapeBytes [8]byte
|
||||
|
||||
// shouldEscape reports whether a rune should be escaped as part of the regex.
|
||||
//
|
||||
// This only includes characters that require escaping in regex but are also NOT valid filepath pattern characters.
|
||||
// Additionally, '\' is not excluded because there is specific logic to properly handle this, as it's a path separator
|
||||
// on Windows.
|
||||
//
|
||||
// Adapted from regexp::QuoteMeta in go stdlib.
|
||||
// See https://cs.opensource.google/go/go/+/refs/tags/go1.17.2:src/regexp/regexp.go;l=703-715;drc=refs%2Ftags%2Fgo1.17.2
|
||||
func shouldEscape(b rune) bool {
|
||||
return b < utf8.RuneSelf && escapeBytes[b%8]&(1<<(b/8)) != 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
for _, b := range []byte(`.+()|{}$`) {
|
||||
escapeBytes[b%8] |= 1 << (b / 8)
|
||||
}
|
||||
}
|
||||
|
||||
// PatternMatcher allows checking paths against a list of patterns
|
||||
type PatternMatcher struct {
|
||||
patterns []*Pattern
|
||||
exclusions bool
|
||||
}
|
||||
|
||||
// NewPatternMatcher creates a new matcher object for specific patterns that can
|
||||
// be used later to match against patterns against paths
|
||||
func NewPatternMatcher(patterns []string) (*PatternMatcher, error) {
|
||||
pm := &PatternMatcher{
|
||||
patterns: make([]*Pattern, 0, len(patterns)),
|
||||
}
|
||||
for _, p := range patterns {
|
||||
// Eliminate leading and trailing whitespace.
|
||||
p = strings.TrimSpace(p)
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
p = filepath.Clean(p)
|
||||
newp := &Pattern{}
|
||||
if p[0] == '!' {
|
||||
if len(p) == 1 {
|
||||
return nil, errors.New("illegal exclusion pattern: \"!\"")
|
||||
}
|
||||
newp.exclusion = true
|
||||
p = p[1:]
|
||||
pm.exclusions = true
|
||||
}
|
||||
// Do some syntax checking on the pattern.
|
||||
// filepath's Match() has some really weird rules that are inconsistent
|
||||
// so instead of trying to dup their logic, just call Match() for its
|
||||
// error state and if there is an error in the pattern return it.
|
||||
// If this becomes an issue we can remove this since its really only
|
||||
// needed in the error (syntax) case - which isn't really critical.
|
||||
if _, err := filepath.Match(p, "."); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newp.cleanedPattern = p
|
||||
newp.dirs = strings.Split(p, string(os.PathSeparator))
|
||||
pm.patterns = append(pm.patterns, newp)
|
||||
}
|
||||
return pm, nil
|
||||
}
|
||||
|
||||
// Matches returns true if "file" matches any of the patterns
|
||||
// and isn't excluded by any of the subsequent patterns.
|
||||
//
|
||||
// The "file" argument should be a slash-delimited path.
|
||||
//
|
||||
// Matches is not safe to call concurrently.
|
||||
//
|
||||
// Deprecated: This implementation is buggy (it only checks a single parent dir
|
||||
// against the pattern) and will be removed soon. Use either
|
||||
// MatchesOrParentMatches or MatchesUsingParentResults instead.
|
||||
func (pm *PatternMatcher) Matches(file string) (bool, error) {
|
||||
matched := false
|
||||
file = filepath.FromSlash(file)
|
||||
parentPath := filepath.Dir(file)
|
||||
parentPathDirs := strings.Split(parentPath, string(os.PathSeparator))
|
||||
|
||||
for _, pattern := range pm.patterns {
|
||||
// Skip evaluation if this is an inclusion and the filename
|
||||
// already matched the pattern, or it's an exclusion and it has
|
||||
// not matched the pattern yet.
|
||||
if pattern.exclusion != matched {
|
||||
continue
|
||||
}
|
||||
|
||||
match, err := pattern.match(file)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !match && parentPath != "." {
|
||||
// Check to see if the pattern matches one of our parent dirs.
|
||||
if len(pattern.dirs) <= len(parentPathDirs) {
|
||||
match, _ = pattern.match(strings.Join(parentPathDirs[:len(pattern.dirs)], string(os.PathSeparator)))
|
||||
}
|
||||
}
|
||||
|
||||
if match {
|
||||
matched = !pattern.exclusion
|
||||
}
|
||||
}
|
||||
|
||||
return matched, nil
|
||||
}
|
||||
|
||||
// MatchesOrParentMatches returns true if "file" matches any of the patterns
|
||||
// and isn't excluded by any of the subsequent patterns.
|
||||
//
|
||||
// The "file" argument should be a slash-delimited path.
|
||||
//
|
||||
// Matches is not safe to call concurrently.
|
||||
func (pm *PatternMatcher) MatchesOrParentMatches(file string) (bool, error) {
|
||||
matched := false
|
||||
file = filepath.FromSlash(file)
|
||||
parentPath := filepath.Dir(file)
|
||||
parentPathDirs := strings.Split(parentPath, string(os.PathSeparator))
|
||||
|
||||
for _, pattern := range pm.patterns {
|
||||
// Skip evaluation if this is an inclusion and the filename
|
||||
// already matched the pattern, or it's an exclusion and it has
|
||||
// not matched the pattern yet.
|
||||
if pattern.exclusion != matched {
|
||||
continue
|
||||
}
|
||||
|
||||
match, err := pattern.match(file)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !match && parentPath != "." {
|
||||
// Check to see if the pattern matches one of our parent dirs.
|
||||
for i := range parentPathDirs {
|
||||
match, _ = pattern.match(strings.Join(parentPathDirs[:i+1], string(os.PathSeparator)))
|
||||
if match {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if match {
|
||||
matched = !pattern.exclusion
|
||||
}
|
||||
}
|
||||
|
||||
return matched, nil
|
||||
}
|
||||
|
||||
// MatchesUsingParentResult returns true if "file" matches any of the patterns
|
||||
// and isn't excluded by any of the subsequent patterns. The functionality is
|
||||
// the same as Matches, but as an optimization, the caller keeps track of
|
||||
// whether the parent directory matched.
|
||||
//
|
||||
// The "file" argument should be a slash-delimited path.
|
||||
//
|
||||
// MatchesUsingParentResult is not safe to call concurrently.
|
||||
//
|
||||
// Deprecated: this function does behave correctly in some cases (see
|
||||
// https://github.com/docker/buildx/issues/850).
|
||||
//
|
||||
// Use MatchesUsingParentResults instead.
|
||||
func (pm *PatternMatcher) MatchesUsingParentResult(file string, parentMatched bool) (bool, error) {
|
||||
matched := parentMatched
|
||||
file = filepath.FromSlash(file)
|
||||
|
||||
for _, pattern := range pm.patterns {
|
||||
// Skip evaluation if this is an inclusion and the filename
|
||||
// already matched the pattern, or it's an exclusion and it has
|
||||
// not matched the pattern yet.
|
||||
if pattern.exclusion != matched {
|
||||
continue
|
||||
}
|
||||
|
||||
match, err := pattern.match(file)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if match {
|
||||
matched = !pattern.exclusion
|
||||
}
|
||||
}
|
||||
return matched, nil
|
||||
}
|
||||
|
||||
// MatchInfo tracks information about parent dir matches while traversing a
|
||||
// filesystem.
|
||||
type MatchInfo struct {
|
||||
parentMatched []bool
|
||||
}
|
||||
|
||||
// MatchesUsingParentResults returns true if "file" matches any of the patterns
|
||||
// and isn't excluded by any of the subsequent patterns. The functionality is
|
||||
// the same as Matches, but as an optimization, the caller passes in
|
||||
// intermediate results from matching the parent directory.
|
||||
//
|
||||
// The "file" argument should be a slash-delimited path.
|
||||
//
|
||||
// MatchesUsingParentResults is not safe to call concurrently.
|
||||
func (pm *PatternMatcher) MatchesUsingParentResults(file string, parentMatchInfo MatchInfo) (bool, MatchInfo, error) {
|
||||
parentMatched := parentMatchInfo.parentMatched
|
||||
if len(parentMatched) != 0 && len(parentMatched) != len(pm.patterns) {
|
||||
return false, MatchInfo{}, errors.New("wrong number of values in parentMatched")
|
||||
}
|
||||
|
||||
file = filepath.FromSlash(file)
|
||||
matched := false
|
||||
|
||||
matchInfo := MatchInfo{
|
||||
parentMatched: make([]bool, len(pm.patterns)),
|
||||
}
|
||||
for i, pattern := range pm.patterns {
|
||||
match := false
|
||||
// If the parent matched this pattern, we don't need to recheck.
|
||||
if len(parentMatched) != 0 {
|
||||
match = parentMatched[i]
|
||||
}
|
||||
|
||||
if !match {
|
||||
// Skip evaluation if this is an inclusion and the filename
|
||||
// already matched the pattern, or it's an exclusion and it has
|
||||
// not matched the pattern yet.
|
||||
if pattern.exclusion != matched {
|
||||
continue
|
||||
}
|
||||
|
||||
var err error
|
||||
match, err = pattern.match(file)
|
||||
if err != nil {
|
||||
return false, matchInfo, err
|
||||
}
|
||||
|
||||
// If the zero value of MatchInfo was passed in, we don't have
|
||||
// any information about the parent dir's match results, and we
|
||||
// apply the same logic as MatchesOrParentMatches.
|
||||
if !match && len(parentMatched) == 0 {
|
||||
if parentPath := filepath.Dir(file); parentPath != "." {
|
||||
parentPathDirs := strings.Split(parentPath, string(os.PathSeparator))
|
||||
// Check to see if the pattern matches one of our parent dirs.
|
||||
for i := range parentPathDirs {
|
||||
match, _ = pattern.match(strings.Join(parentPathDirs[:i+1], string(os.PathSeparator)))
|
||||
if match {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
matchInfo.parentMatched[i] = match
|
||||
|
||||
if match {
|
||||
matched = !pattern.exclusion
|
||||
}
|
||||
}
|
||||
return matched, matchInfo, nil
|
||||
}
|
||||
|
||||
// Exclusions returns true if any of the patterns define exclusions
|
||||
func (pm *PatternMatcher) Exclusions() bool {
|
||||
return pm.exclusions
|
||||
}
|
||||
|
||||
// Patterns returns array of active patterns
|
||||
func (pm *PatternMatcher) Patterns() []*Pattern {
|
||||
return pm.patterns
|
||||
}
|
||||
|
||||
// Pattern defines a single regexp used to filter file paths.
|
||||
type Pattern struct {
|
||||
matchType matchType
|
||||
cleanedPattern string
|
||||
dirs []string
|
||||
regexp *regexp.Regexp
|
||||
exclusion bool
|
||||
}
|
||||
|
||||
type matchType int
|
||||
|
||||
const (
|
||||
unknownMatch matchType = iota
|
||||
exactMatch
|
||||
prefixMatch
|
||||
suffixMatch
|
||||
regexpMatch
|
||||
)
|
||||
|
||||
func (p *Pattern) String() string {
|
||||
return p.cleanedPattern
|
||||
}
|
||||
|
||||
// Exclusion returns true if this pattern defines exclusion
|
||||
func (p *Pattern) Exclusion() bool {
|
||||
return p.exclusion
|
||||
}
|
||||
|
||||
func (p *Pattern) match(path string) (bool, error) {
|
||||
if p.matchType == unknownMatch {
|
||||
if err := p.compile(string(os.PathSeparator)); err != nil {
|
||||
return false, filepath.ErrBadPattern
|
||||
}
|
||||
}
|
||||
|
||||
switch p.matchType {
|
||||
case exactMatch:
|
||||
return path == p.cleanedPattern, nil
|
||||
case prefixMatch:
|
||||
// strip trailing **
|
||||
return strings.HasPrefix(path, p.cleanedPattern[:len(p.cleanedPattern)-2]), nil
|
||||
case suffixMatch:
|
||||
// strip leading **
|
||||
suffix := p.cleanedPattern[2:]
|
||||
if strings.HasSuffix(path, suffix) {
|
||||
return true, nil
|
||||
}
|
||||
// **/foo matches "foo"
|
||||
return suffix[0] == os.PathSeparator && path == suffix[1:], nil
|
||||
case regexpMatch:
|
||||
return p.regexp.MatchString(path), nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (p *Pattern) compile(sl string) error {
|
||||
regStr := "^"
|
||||
pattern := p.cleanedPattern
|
||||
// Go through the pattern and convert it to a regexp.
|
||||
// We use a scanner so we can support utf-8 chars.
|
||||
var scan scanner.Scanner
|
||||
scan.Init(strings.NewReader(pattern))
|
||||
|
||||
escSL := sl
|
||||
if sl == `\` {
|
||||
escSL += `\`
|
||||
}
|
||||
|
||||
p.matchType = exactMatch
|
||||
for i := 0; scan.Peek() != scanner.EOF; i++ {
|
||||
ch := scan.Next()
|
||||
|
||||
if ch == '*' {
|
||||
if scan.Peek() == '*' {
|
||||
// is some flavor of "**"
|
||||
scan.Next()
|
||||
|
||||
// Treat **/ as ** so eat the "/"
|
||||
if string(scan.Peek()) == sl {
|
||||
scan.Next()
|
||||
}
|
||||
|
||||
if scan.Peek() == scanner.EOF {
|
||||
// is "**EOF" - to align with .gitignore just accept all
|
||||
if p.matchType == exactMatch {
|
||||
p.matchType = prefixMatch
|
||||
} else {
|
||||
regStr += ".*"
|
||||
p.matchType = regexpMatch
|
||||
}
|
||||
} else {
|
||||
// is "**"
|
||||
// Note that this allows for any # of /'s (even 0) because
|
||||
// the .* will eat everything, even /'s
|
||||
regStr += "(.*" + escSL + ")?"
|
||||
p.matchType = regexpMatch
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
p.matchType = suffixMatch
|
||||
}
|
||||
} else {
|
||||
// is "*" so map it to anything but "/"
|
||||
regStr += "[^" + escSL + "]*"
|
||||
p.matchType = regexpMatch
|
||||
}
|
||||
} else if ch == '?' {
|
||||
// "?" is any char except "/"
|
||||
regStr += "[^" + escSL + "]"
|
||||
p.matchType = regexpMatch
|
||||
} else if shouldEscape(ch) {
|
||||
// Escape some regexp special chars that have no meaning
|
||||
// in golang's filepath.Match
|
||||
regStr += `\` + string(ch)
|
||||
} else if ch == '\\' {
|
||||
// escape next char. Note that a trailing \ in the pattern
|
||||
// will be left alone (but need to escape it)
|
||||
if sl == `\` {
|
||||
// On windows map "\" to "\\", meaning an escaped backslash,
|
||||
// and then just continue because filepath.Match on
|
||||
// Windows doesn't allow escaping at all
|
||||
regStr += escSL
|
||||
continue
|
||||
}
|
||||
if scan.Peek() != scanner.EOF {
|
||||
regStr += `\` + string(scan.Next())
|
||||
p.matchType = regexpMatch
|
||||
} else {
|
||||
regStr += `\`
|
||||
}
|
||||
} else if ch == '[' || ch == ']' {
|
||||
regStr += string(ch)
|
||||
p.matchType = regexpMatch
|
||||
} else {
|
||||
regStr += string(ch)
|
||||
}
|
||||
}
|
||||
|
||||
if p.matchType != regexpMatch {
|
||||
return nil
|
||||
}
|
||||
|
||||
regStr += "$"
|
||||
|
||||
re, err := regexp.Compile(regStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.regexp = re
|
||||
p.matchType = regexpMatch
|
||||
return nil
|
||||
}
|
||||
|
||||
// Matches returns true if file matches any of the patterns
|
||||
// and isn't excluded by any of the subsequent patterns.
|
||||
//
|
||||
// This implementation is buggy (it only checks a single parent dir against the
|
||||
// pattern) and will be removed soon. Use MatchesOrParentMatches instead.
|
||||
func Matches(file string, patterns []string) (bool, error) {
|
||||
pm, err := NewPatternMatcher(patterns)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
file = filepath.Clean(file)
|
||||
|
||||
if file == "." {
|
||||
// Don't let them exclude everything, kind of silly.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return pm.Matches(file)
|
||||
}
|
||||
|
||||
// MatchesOrParentMatches returns true if file matches any of the patterns
|
||||
// and isn't excluded by any of the subsequent patterns.
|
||||
func MatchesOrParentMatches(file string, patterns []string) (bool, error) {
|
||||
pm, err := NewPatternMatcher(patterns)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
file = filepath.Clean(file)
|
||||
|
||||
if file == "." {
|
||||
// Don't let them exclude everything, kind of silly.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return pm.MatchesOrParentMatches(file)
|
||||
}
|
||||
|
||||
// CopyFile copies from src to dst until either EOF is reached
|
||||
// on src or an error occurs. It verifies src exists and removes
|
||||
// the dst if it exists.
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
package fileutils // import "github.com/docker/docker/pkg/fileutils"
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
@ -187,376 +185,6 @@ func TestReadSymlinkedDirectoryToFile(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestWildcardMatches(t *testing.T) {
|
||||
match, _ := Matches("fileutils.go", []string{"*"})
|
||||
if !match {
|
||||
t.Errorf("failed to get a wildcard match, got %v", match)
|
||||
}
|
||||
}
|
||||
|
||||
// A simple pattern match should return true.
|
||||
func TestPatternMatches(t *testing.T) {
|
||||
match, _ := Matches("fileutils.go", []string{"*.go"})
|
||||
if !match {
|
||||
t.Errorf("failed to get a match, got %v", match)
|
||||
}
|
||||
}
|
||||
|
||||
// An exclusion followed by an inclusion should return true.
|
||||
func TestExclusionPatternMatchesPatternBefore(t *testing.T) {
|
||||
match, _ := Matches("fileutils.go", []string{"!fileutils.go", "*.go"})
|
||||
if !match {
|
||||
t.Errorf("failed to get true match on exclusion pattern, got %v", match)
|
||||
}
|
||||
}
|
||||
|
||||
// A folder pattern followed by an exception should return false.
|
||||
func TestPatternMatchesFolderExclusions(t *testing.T) {
|
||||
match, _ := Matches("docs/README.md", []string{"docs", "!docs/README.md"})
|
||||
if match {
|
||||
t.Errorf("failed to get a false match on exclusion pattern, got %v", match)
|
||||
}
|
||||
}
|
||||
|
||||
// A folder pattern followed by an exception should return false.
|
||||
func TestPatternMatchesFolderWithSlashExclusions(t *testing.T) {
|
||||
match, _ := Matches("docs/README.md", []string{"docs/", "!docs/README.md"})
|
||||
if match {
|
||||
t.Errorf("failed to get a false match on exclusion pattern, got %v", match)
|
||||
}
|
||||
}
|
||||
|
||||
// A folder pattern followed by an exception should return false.
|
||||
func TestPatternMatchesFolderWildcardExclusions(t *testing.T) {
|
||||
match, _ := Matches("docs/README.md", []string{"docs/*", "!docs/README.md"})
|
||||
if match {
|
||||
t.Errorf("failed to get a false match on exclusion pattern, got %v", match)
|
||||
}
|
||||
}
|
||||
|
||||
// A pattern followed by an exclusion should return false.
|
||||
func TestExclusionPatternMatchesPatternAfter(t *testing.T) {
|
||||
match, _ := Matches("fileutils.go", []string{"*.go", "!fileutils.go"})
|
||||
if match {
|
||||
t.Errorf("failed to get false match on exclusion pattern, got %v", match)
|
||||
}
|
||||
}
|
||||
|
||||
// A filename evaluating to . should return false.
|
||||
func TestExclusionPatternMatchesWholeDirectory(t *testing.T) {
|
||||
match, _ := Matches(".", []string{"*.go"})
|
||||
if match {
|
||||
t.Errorf("failed to get false match on ., got %v", match)
|
||||
}
|
||||
}
|
||||
|
||||
// A single ! pattern should return an error.
|
||||
func TestSingleExclamationError(t *testing.T) {
|
||||
_, err := Matches("fileutils.go", []string{"!"})
|
||||
if err == nil {
|
||||
t.Errorf("failed to get an error for a single exclamation point, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Matches with no patterns
|
||||
func TestMatchesWithNoPatterns(t *testing.T) {
|
||||
matches, err := Matches("/any/path/there", []string{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if matches {
|
||||
t.Fatalf("Should not have match anything")
|
||||
}
|
||||
}
|
||||
|
||||
// Matches with malformed patterns
|
||||
func TestMatchesWithMalformedPatterns(t *testing.T) {
|
||||
matches, err := Matches("/any/path/there", []string{"["})
|
||||
if err == nil {
|
||||
t.Fatal("Should have failed because of a malformed syntax in the pattern")
|
||||
}
|
||||
if matches {
|
||||
t.Fatalf("Should not have match anything")
|
||||
}
|
||||
}
|
||||
|
||||
type matchesTestCase struct {
|
||||
pattern string
|
||||
text string
|
||||
pass bool
|
||||
}
|
||||
|
||||
type multiPatternTestCase struct {
|
||||
patterns []string
|
||||
text string
|
||||
pass bool
|
||||
}
|
||||
|
||||
func TestMatches(t *testing.T) {
|
||||
tests := []matchesTestCase{
|
||||
{"**", "file", true},
|
||||
{"**", "file/", true},
|
||||
{"**/", "file", true}, // weird one
|
||||
{"**/", "file/", true},
|
||||
{"**", "/", true},
|
||||
{"**/", "/", true},
|
||||
{"**", "dir/file", true},
|
||||
{"**/", "dir/file", true},
|
||||
{"**", "dir/file/", true},
|
||||
{"**/", "dir/file/", true},
|
||||
{"**/**", "dir/file", true},
|
||||
{"**/**", "dir/file/", true},
|
||||
{"dir/**", "dir/file", true},
|
||||
{"dir/**", "dir/file/", true},
|
||||
{"dir/**", "dir/dir2/file", true},
|
||||
{"dir/**", "dir/dir2/file/", true},
|
||||
{"**/dir", "dir", true},
|
||||
{"**/dir", "dir/file", true},
|
||||
{"**/dir2/*", "dir/dir2/file", true},
|
||||
{"**/dir2/*", "dir/dir2/file/", true},
|
||||
{"**/dir2/**", "dir/dir2/dir3/file", true},
|
||||
{"**/dir2/**", "dir/dir2/dir3/file/", true},
|
||||
{"**file", "file", true},
|
||||
{"**file", "dir/file", true},
|
||||
{"**/file", "dir/file", true},
|
||||
{"**file", "dir/dir/file", true},
|
||||
{"**/file", "dir/dir/file", true},
|
||||
{"**/file*", "dir/dir/file", true},
|
||||
{"**/file*", "dir/dir/file.txt", true},
|
||||
{"**/file*txt", "dir/dir/file.txt", true},
|
||||
{"**/file*.txt", "dir/dir/file.txt", true},
|
||||
{"**/file*.txt*", "dir/dir/file.txt", true},
|
||||
{"**/**/*.txt", "dir/dir/file.txt", true},
|
||||
{"**/**/*.txt2", "dir/dir/file.txt", false},
|
||||
{"**/*.txt", "file.txt", true},
|
||||
{"**/**/*.txt", "file.txt", true},
|
||||
{"a**/*.txt", "a/file.txt", true},
|
||||
{"a**/*.txt", "a/dir/file.txt", true},
|
||||
{"a**/*.txt", "a/dir/dir/file.txt", true},
|
||||
{"a/*.txt", "a/dir/file.txt", false},
|
||||
{"a/*.txt", "a/file.txt", true},
|
||||
{"a/*.txt**", "a/file.txt", true},
|
||||
{"a[b-d]e", "ae", false},
|
||||
{"a[b-d]e", "ace", true},
|
||||
{"a[b-d]e", "aae", false},
|
||||
{"a[^b-d]e", "aze", true},
|
||||
{".*", ".foo", true},
|
||||
{".*", "foo", false},
|
||||
{"abc.def", "abcdef", false},
|
||||
{"abc.def", "abc.def", true},
|
||||
{"abc.def", "abcZdef", false},
|
||||
{"abc?def", "abcZdef", true},
|
||||
{"abc?def", "abcdef", false},
|
||||
{"a\\\\", "a\\", true},
|
||||
{"**/foo/bar", "foo/bar", true},
|
||||
{"**/foo/bar", "dir/foo/bar", true},
|
||||
{"**/foo/bar", "dir/dir2/foo/bar", true},
|
||||
{"abc/**", "abc", false},
|
||||
{"abc/**", "abc/def", true},
|
||||
{"abc/**", "abc/def/ghi", true},
|
||||
{"**/.foo", ".foo", true},
|
||||
{"**/.foo", "bar.foo", false},
|
||||
{"a(b)c/def", "a(b)c/def", true},
|
||||
{"a(b)c/def", "a(b)c/xyz", false},
|
||||
{"a.|)$(}+{bc", "a.|)$(}+{bc", true},
|
||||
{"dist/proxy.py-2.4.0rc3.dev36+g08acad9-py3-none-any.whl", "dist/proxy.py-2.4.0rc3.dev36+g08acad9-py3-none-any.whl", true},
|
||||
{"dist/*.whl", "dist/proxy.py-2.4.0rc3.dev36+g08acad9-py3-none-any.whl", true},
|
||||
}
|
||||
multiPatternTests := []multiPatternTestCase{
|
||||
{[]string{"**", "!util/docker/web"}, "util/docker/web/foo", false},
|
||||
{[]string{"**", "!util/docker/web", "util/docker/web/foo"}, "util/docker/web/foo", true},
|
||||
{[]string{"**", "!dist/proxy.py-2.4.0rc3.dev36+g08acad9-py3-none-any.whl"}, "dist/proxy.py-2.4.0rc3.dev36+g08acad9-py3-none-any.whl", false},
|
||||
{[]string{"**", "!dist/*.whl"}, "dist/proxy.py-2.4.0rc3.dev36+g08acad9-py3-none-any.whl", false},
|
||||
}
|
||||
|
||||
if runtime.GOOS != "windows" {
|
||||
tests = append(tests, []matchesTestCase{
|
||||
{"a\\*b", "a*b", true},
|
||||
}...)
|
||||
}
|
||||
|
||||
t.Run("MatchesOrParentMatches", func(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
pm, err := NewPatternMatcher([]string{test.pattern})
|
||||
if err != nil {
|
||||
t.Fatalf("%v (pattern=%q, text=%q)", err, test.pattern, test.text)
|
||||
}
|
||||
res, _ := pm.MatchesOrParentMatches(test.text)
|
||||
if test.pass != res {
|
||||
t.Fatalf("%v (pattern=%q, text=%q)", err, test.pattern, test.text)
|
||||
}
|
||||
}
|
||||
|
||||
for _, test := range multiPatternTests {
|
||||
pm, err := NewPatternMatcher(test.patterns)
|
||||
if err != nil {
|
||||
t.Fatalf("%v (patterns=%q, text=%q)", err, test.patterns, test.text)
|
||||
}
|
||||
res, _ := pm.MatchesOrParentMatches(test.text)
|
||||
if test.pass != res {
|
||||
t.Errorf("expected: %v, got: %v (patterns=%q, text=%q)", test.pass, res, test.patterns, test.text)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("MatchesUsingParentResult", func(t *testing.T) {
|
||||
for _, test := range tests {
|
||||
pm, err := NewPatternMatcher([]string{test.pattern})
|
||||
if err != nil {
|
||||
t.Fatalf("%v (pattern=%q, text=%q)", err, test.pattern, test.text)
|
||||
}
|
||||
|
||||
parentPath := filepath.Dir(filepath.FromSlash(test.text))
|
||||
parentPathDirs := strings.Split(parentPath, string(os.PathSeparator))
|
||||
|
||||
parentMatched := false
|
||||
if parentPath != "." {
|
||||
for i := range parentPathDirs {
|
||||
parentMatched, _ = pm.MatchesUsingParentResult(strings.Join(parentPathDirs[:i+1], "/"), parentMatched)
|
||||
}
|
||||
}
|
||||
|
||||
res, _ := pm.MatchesUsingParentResult(test.text, parentMatched)
|
||||
if test.pass != res {
|
||||
t.Errorf("expected: %v, got: %v (pattern=%q, text=%q)", test.pass, res, test.pattern, test.text)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("MatchesUsingParentResults", func(t *testing.T) {
|
||||
check := func(pm *PatternMatcher, text string, pass bool, desc string) {
|
||||
parentPath := filepath.Dir(filepath.FromSlash(text))
|
||||
parentPathDirs := strings.Split(parentPath, string(os.PathSeparator))
|
||||
|
||||
parentMatchInfo := MatchInfo{}
|
||||
if parentPath != "." {
|
||||
for i := range parentPathDirs {
|
||||
_, parentMatchInfo, _ = pm.MatchesUsingParentResults(strings.Join(parentPathDirs[:i+1], "/"), parentMatchInfo)
|
||||
}
|
||||
}
|
||||
|
||||
res, _, _ := pm.MatchesUsingParentResults(text, parentMatchInfo)
|
||||
if pass != res {
|
||||
t.Errorf("expected: %v, got: %v %s", pass, res, desc)
|
||||
}
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
desc := fmt.Sprintf("(pattern=%q text=%q)", test.pattern, test.text)
|
||||
pm, err := NewPatternMatcher([]string{test.pattern})
|
||||
if err != nil {
|
||||
t.Fatal(err, desc)
|
||||
}
|
||||
|
||||
check(pm, test.text, test.pass, desc)
|
||||
}
|
||||
|
||||
for _, test := range multiPatternTests {
|
||||
desc := fmt.Sprintf("pattern=%q text=%q", test.patterns, test.text)
|
||||
pm, err := NewPatternMatcher(test.patterns)
|
||||
if err != nil {
|
||||
t.Fatal(err, desc)
|
||||
}
|
||||
|
||||
check(pm, test.text, test.pass, desc)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("MatchesUsingParentResultsNoContext", func(t *testing.T) {
|
||||
check := func(pm *PatternMatcher, text string, pass bool, desc string) {
|
||||
res, _, _ := pm.MatchesUsingParentResults(text, MatchInfo{})
|
||||
if pass != res {
|
||||
t.Errorf("expected: %v, got: %v %s", pass, res, desc)
|
||||
}
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
desc := fmt.Sprintf("(pattern=%q text=%q)", test.pattern, test.text)
|
||||
pm, err := NewPatternMatcher([]string{test.pattern})
|
||||
if err != nil {
|
||||
t.Fatal(err, desc)
|
||||
}
|
||||
|
||||
check(pm, test.text, test.pass, desc)
|
||||
}
|
||||
|
||||
for _, test := range multiPatternTests {
|
||||
desc := fmt.Sprintf("(pattern=%q text=%q)", test.patterns, test.text)
|
||||
pm, err := NewPatternMatcher(test.patterns)
|
||||
if err != nil {
|
||||
t.Fatal(err, desc)
|
||||
}
|
||||
|
||||
check(pm, test.text, test.pass, desc)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestCleanPatterns(t *testing.T) {
|
||||
patterns := []string{"docs", "config"}
|
||||
pm, err := NewPatternMatcher(patterns)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid pattern %v", patterns)
|
||||
}
|
||||
cleaned := pm.Patterns()
|
||||
if len(cleaned) != 2 {
|
||||
t.Errorf("expected 2 element slice, got %v", len(cleaned))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanPatternsStripEmptyPatterns(t *testing.T) {
|
||||
patterns := []string{"docs", "config", ""}
|
||||
pm, err := NewPatternMatcher(patterns)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid pattern %v", patterns)
|
||||
}
|
||||
cleaned := pm.Patterns()
|
||||
if len(cleaned) != 2 {
|
||||
t.Errorf("expected 2 element slice, got %v", len(cleaned))
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanPatternsExceptionFlag(t *testing.T) {
|
||||
patterns := []string{"docs", "!docs/README.md"}
|
||||
pm, err := NewPatternMatcher(patterns)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid pattern %v", patterns)
|
||||
}
|
||||
if !pm.Exclusions() {
|
||||
t.Errorf("expected exceptions to be true, got %v", pm.Exclusions())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanPatternsLeadingSpaceTrimmed(t *testing.T) {
|
||||
patterns := []string{"docs", " !docs/README.md"}
|
||||
pm, err := NewPatternMatcher(patterns)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid pattern %v", patterns)
|
||||
}
|
||||
if !pm.Exclusions() {
|
||||
t.Errorf("expected exceptions to be true, got %v", pm.Exclusions())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanPatternsTrailingSpaceTrimmed(t *testing.T) {
|
||||
patterns := []string{"docs", "!docs/README.md "}
|
||||
pm, err := NewPatternMatcher(patterns)
|
||||
if err != nil {
|
||||
t.Fatalf("invalid pattern %v", patterns)
|
||||
}
|
||||
if !pm.Exclusions() {
|
||||
t.Errorf("expected exceptions to be true, got %v", pm.Exclusions())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanPatternsErrorSingleException(t *testing.T) {
|
||||
patterns := []string{"!"}
|
||||
_, err := NewPatternMatcher(patterns)
|
||||
if err == nil {
|
||||
t.Errorf("expected error on single exclamation point, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateIfNotExistsDir(t *testing.T) {
|
||||
folderToCreate := filepath.Join(t.TempDir(), "tocreate")
|
||||
|
||||
|
@ -588,169 +216,3 @@ func TestCreateIfNotExistsFile(t *testing.T) {
|
|||
t.Fatalf("Should have been a file, seems it's not")
|
||||
}
|
||||
}
|
||||
|
||||
// These matchTests are stolen from go's filepath Match tests.
|
||||
type matchTest struct {
|
||||
pattern, s string
|
||||
match bool
|
||||
err error
|
||||
}
|
||||
|
||||
var matchTests = []matchTest{
|
||||
{"abc", "abc", true, nil},
|
||||
{"*", "abc", true, nil},
|
||||
{"*c", "abc", true, nil},
|
||||
{"a*", "a", true, nil},
|
||||
{"a*", "abc", true, nil},
|
||||
{"a*", "ab/c", true, nil},
|
||||
{"a*/b", "abc/b", true, nil},
|
||||
{"a*/b", "a/c/b", false, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxe/f", true, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxexxx/f", true, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxe/xxx/f", false, nil},
|
||||
{"a*b*c*d*e*/f", "axbxcxdxexxx/fff", false, nil},
|
||||
{"a*b?c*x", "abxbbxdbxebxczzx", true, nil},
|
||||
{"a*b?c*x", "abxbbxdbxebxczzy", false, nil},
|
||||
{"ab[c]", "abc", true, nil},
|
||||
{"ab[b-d]", "abc", true, nil},
|
||||
{"ab[e-g]", "abc", false, nil},
|
||||
{"ab[^c]", "abc", false, nil},
|
||||
{"ab[^b-d]", "abc", false, nil},
|
||||
{"ab[^e-g]", "abc", true, nil},
|
||||
{"a\\*b", "a*b", true, nil},
|
||||
{"a\\*b", "ab", false, nil},
|
||||
{"a?b", "a☺b", true, nil},
|
||||
{"a[^a]b", "a☺b", true, nil},
|
||||
{"a???b", "a☺b", false, nil},
|
||||
{"a[^a][^a][^a]b", "a☺b", false, nil},
|
||||
{"[a-ζ]*", "α", true, nil},
|
||||
{"*[a-ζ]", "A", false, nil},
|
||||
{"a?b", "a/b", false, nil},
|
||||
{"a*b", "a/b", false, nil},
|
||||
{"[\\]a]", "]", true, nil},
|
||||
{"[\\-]", "-", true, nil},
|
||||
{"[x\\-]", "x", true, nil},
|
||||
{"[x\\-]", "-", true, nil},
|
||||
{"[x\\-]", "z", false, nil},
|
||||
{"[\\-x]", "x", true, nil},
|
||||
{"[\\-x]", "-", true, nil},
|
||||
{"[\\-x]", "a", false, nil},
|
||||
{"[]a]", "]", false, filepath.ErrBadPattern},
|
||||
{"[-]", "-", false, filepath.ErrBadPattern},
|
||||
{"[x-]", "x", false, filepath.ErrBadPattern},
|
||||
{"[x-]", "-", false, filepath.ErrBadPattern},
|
||||
{"[x-]", "z", false, filepath.ErrBadPattern},
|
||||
{"[-x]", "x", false, filepath.ErrBadPattern},
|
||||
{"[-x]", "-", false, filepath.ErrBadPattern},
|
||||
{"[-x]", "a", false, filepath.ErrBadPattern},
|
||||
{"\\", "a", false, filepath.ErrBadPattern},
|
||||
{"[a-b-c]", "a", false, filepath.ErrBadPattern},
|
||||
{"[", "a", false, filepath.ErrBadPattern},
|
||||
{"[^", "a", false, filepath.ErrBadPattern},
|
||||
{"[^bc", "a", false, filepath.ErrBadPattern},
|
||||
{"a[", "a", false, filepath.ErrBadPattern}, // was nil but IMO its wrong
|
||||
{"a[", "ab", false, filepath.ErrBadPattern},
|
||||
{"*x", "xxx", true, nil},
|
||||
}
|
||||
|
||||
func errp(e error) string {
|
||||
if e == nil {
|
||||
return "<nil>"
|
||||
}
|
||||
return e.Error()
|
||||
}
|
||||
|
||||
// TestMatch tests our version of filepath.Match, called Matches.
|
||||
func TestMatch(t *testing.T) {
|
||||
for _, tt := range matchTests {
|
||||
pattern := tt.pattern
|
||||
s := tt.s
|
||||
if runtime.GOOS == "windows" {
|
||||
if strings.Contains(pattern, "\\") {
|
||||
// no escape allowed on windows.
|
||||
continue
|
||||
}
|
||||
pattern = filepath.Clean(pattern)
|
||||
s = filepath.Clean(s)
|
||||
}
|
||||
ok, err := Matches(s, []string{pattern})
|
||||
if ok != tt.match || err != tt.err {
|
||||
t.Fatalf("Match(%#q, %#q) = %v, %q want %v, %q", pattern, s, ok, errp(err), tt.match, errp(tt.err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type compileTestCase struct {
|
||||
pattern string
|
||||
matchType matchType
|
||||
compiledRegexp string
|
||||
windowsCompiledRegexp string
|
||||
}
|
||||
|
||||
var compileTests = []compileTestCase{
|
||||
{"*", regexpMatch, `^[^/]*$`, `^[^\\]*$`},
|
||||
{"file*", regexpMatch, `^file[^/]*$`, `^file[^\\]*$`},
|
||||
{"*file", regexpMatch, `^[^/]*file$`, `^[^\\]*file$`},
|
||||
{"a*/b", regexpMatch, `^a[^/]*/b$`, `^a[^\\]*\\b$`},
|
||||
{"**", suffixMatch, "", ""},
|
||||
{"**/**", regexpMatch, `^(.*/)?.*$`, `^(.*\\)?.*$`},
|
||||
{"dir/**", prefixMatch, "", ""},
|
||||
{"**/dir", suffixMatch, "", ""},
|
||||
{"**/dir2/*", regexpMatch, `^(.*/)?dir2/[^/]*$`, `^(.*\\)?dir2\\[^\\]*$`},
|
||||
{"**/dir2/**", regexpMatch, `^(.*/)?dir2/.*$`, `^(.*\\)?dir2\\.*$`},
|
||||
{"**file", suffixMatch, "", ""},
|
||||
{"**/file*txt", regexpMatch, `^(.*/)?file[^/]*txt$`, `^(.*\\)?file[^\\]*txt$`},
|
||||
{"**/**/*.txt", regexpMatch, `^(.*/)?(.*/)?[^/]*\.txt$`, `^(.*\\)?(.*\\)?[^\\]*\.txt$`},
|
||||
{"a[b-d]e", regexpMatch, `^a[b-d]e$`, `^a[b-d]e$`},
|
||||
{".*", regexpMatch, `^\.[^/]*$`, `^\.[^\\]*$`},
|
||||
{"abc.def", exactMatch, "", ""},
|
||||
{"abc?def", regexpMatch, `^abc[^/]def$`, `^abc[^\\]def$`},
|
||||
{"**/foo/bar", suffixMatch, "", ""},
|
||||
{"a(b)c/def", exactMatch, "", ""},
|
||||
{"a.|)$(}+{bc", exactMatch, "", ""},
|
||||
{"dist/proxy.py-2.4.0rc3.dev36+g08acad9-py3-none-any.whl", exactMatch, "", ""},
|
||||
}
|
||||
|
||||
// TestCompile confirms that "compile" assigns the correct match type to a
|
||||
// variety of test case patterns. If the match type is regexp, it also confirms
|
||||
// that the compiled regexp matches the expected regexp.
|
||||
func TestCompile(t *testing.T) {
|
||||
t.Run("slash", testCompile("/"))
|
||||
t.Run("backslash", testCompile(`\`))
|
||||
}
|
||||
|
||||
func testCompile(sl string) func(*testing.T) {
|
||||
return func(t *testing.T) {
|
||||
for _, tt := range compileTests {
|
||||
// Avoid NewPatternMatcher, which has platform-specific behavior
|
||||
pm := &PatternMatcher{
|
||||
patterns: make([]*Pattern, 1),
|
||||
}
|
||||
pattern := path.Clean(tt.pattern)
|
||||
if sl != "/" {
|
||||
pattern = strings.ReplaceAll(pattern, "/", sl)
|
||||
}
|
||||
newp := &Pattern{}
|
||||
newp.cleanedPattern = pattern
|
||||
newp.dirs = strings.Split(pattern, sl)
|
||||
pm.patterns[0] = newp
|
||||
|
||||
if err := pm.patterns[0].compile(sl); err != nil {
|
||||
t.Fatalf("Failed to compile pattern %q: %v", pattern, err)
|
||||
}
|
||||
if pm.patterns[0].matchType != tt.matchType {
|
||||
t.Errorf("pattern %q: matchType = %v, want %v", pattern, pm.patterns[0].matchType, tt.matchType)
|
||||
continue
|
||||
}
|
||||
if tt.matchType == regexpMatch {
|
||||
if sl == `\` {
|
||||
if pm.patterns[0].regexp.String() != tt.windowsCompiledRegexp {
|
||||
t.Errorf("pattern %q: regexp = %s, want %s", pattern, pm.patterns[0].regexp, tt.windowsCompiledRegexp)
|
||||
}
|
||||
} else if pm.patterns[0].regexp.String() != tt.compiledRegexp {
|
||||
t.Errorf("pattern %q: regexp = %s, want %s", pattern, pm.patterns[0].regexp, tt.compiledRegexp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,7 @@ require (
|
|||
github.com/moby/buildkit v0.10.4
|
||||
github.com/moby/ipvs v1.0.2
|
||||
github.com/moby/locker v1.0.1
|
||||
github.com/moby/patternmatcher v0.5.0
|
||||
github.com/moby/pubsub v1.0.0
|
||||
github.com/moby/swarmkit/v2 v2.0.0-20220721174824-48dd89375d0a
|
||||
github.com/moby/sys/mount v0.3.3
|
||||
|
|
|
@ -770,6 +770,8 @@ github.com/moby/ipvs v1.0.2 h1:NSbzuRTvfneftLU3VwPU5QuA6NZ0IUmqq9+VHcQxqHw=
|
|||
github.com/moby/ipvs v1.0.2/go.mod h1:2pngiyseZbIKXNv7hsKj3O9UEz30c53MT9005gt2hxQ=
|
||||
github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg=
|
||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||
github.com/moby/patternmatcher v0.5.0 h1:YCZgJOeULcxLw1Q+sVR636pmS7sPEn1Qo2iAN6M7DBo=
|
||||
github.com/moby/patternmatcher v0.5.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||
github.com/moby/pubsub v1.0.0 h1:jkp/imWsmJz2f6LyFsk7EkVeN2HxR/HTTOY8kHrsxfA=
|
||||
github.com/moby/pubsub v1.0.0/go.mod h1:bXSO+3h5MNXXCaEG+6/NlAIk7MMZbySZlnB+cUQhKKc=
|
||||
github.com/moby/spdystream v0.2.0/go.mod h1:f7i0iNDQJ059oMTcWxx8MA/zKFIuD/lY+0GqbN2Wy8c=
|
||||
|
|
191
vendor/github.com/moby/patternmatcher/LICENSE
generated
vendored
Normal file
191
vendor/github.com/moby/patternmatcher/LICENSE
generated
vendored
Normal file
|
@ -0,0 +1,191 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
https://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2013-2018 Docker, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
https://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
16
vendor/github.com/moby/patternmatcher/NOTICE
generated
vendored
Normal file
16
vendor/github.com/moby/patternmatcher/NOTICE
generated
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
Docker
|
||||
Copyright 2012-2017 Docker, Inc.
|
||||
|
||||
This product includes software developed at Docker, Inc. (https://www.docker.com).
|
||||
|
||||
The following is courtesy of our legal counsel:
|
||||
|
||||
|
||||
Use and transfer of Docker may be subject to certain restrictions by the
|
||||
United States and other governments.
|
||||
It is your responsibility to ensure that your use and/or transfer does not
|
||||
violate applicable laws.
|
||||
|
||||
For more information, please see https://www.bis.doc.gov
|
||||
|
||||
See also https://www.apache.org/dev/crypto.html and/or seek legal counsel.
|
474
vendor/github.com/moby/patternmatcher/patternmatcher.go
generated
vendored
Normal file
474
vendor/github.com/moby/patternmatcher/patternmatcher.go
generated
vendored
Normal file
|
@ -0,0 +1,474 @@
|
|||
package patternmatcher
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"text/scanner"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
// escapeBytes is a bitmap used to check whether a character should be escaped when creating the regex.
|
||||
var escapeBytes [8]byte
|
||||
|
||||
// shouldEscape reports whether a rune should be escaped as part of the regex.
|
||||
//
|
||||
// This only includes characters that require escaping in regex but are also NOT valid filepath pattern characters.
|
||||
// Additionally, '\' is not excluded because there is specific logic to properly handle this, as it's a path separator
|
||||
// on Windows.
|
||||
//
|
||||
// Adapted from regexp::QuoteMeta in go stdlib.
|
||||
// See https://cs.opensource.google/go/go/+/refs/tags/go1.17.2:src/regexp/regexp.go;l=703-715;drc=refs%2Ftags%2Fgo1.17.2
|
||||
func shouldEscape(b rune) bool {
|
||||
return b < utf8.RuneSelf && escapeBytes[b%8]&(1<<(b/8)) != 0
|
||||
}
|
||||
|
||||
func init() {
|
||||
for _, b := range []byte(`.+()|{}$`) {
|
||||
escapeBytes[b%8] |= 1 << (b / 8)
|
||||
}
|
||||
}
|
||||
|
||||
// PatternMatcher allows checking paths against a list of patterns
|
||||
type PatternMatcher struct {
|
||||
patterns []*Pattern
|
||||
exclusions bool
|
||||
}
|
||||
|
||||
// New creates a new matcher object for specific patterns that can
|
||||
// be used later to match against patterns against paths
|
||||
func New(patterns []string) (*PatternMatcher, error) {
|
||||
pm := &PatternMatcher{
|
||||
patterns: make([]*Pattern, 0, len(patterns)),
|
||||
}
|
||||
for _, p := range patterns {
|
||||
// Eliminate leading and trailing whitespace.
|
||||
p = strings.TrimSpace(p)
|
||||
if p == "" {
|
||||
continue
|
||||
}
|
||||
p = filepath.Clean(p)
|
||||
newp := &Pattern{}
|
||||
if p[0] == '!' {
|
||||
if len(p) == 1 {
|
||||
return nil, errors.New("illegal exclusion pattern: \"!\"")
|
||||
}
|
||||
newp.exclusion = true
|
||||
p = p[1:]
|
||||
pm.exclusions = true
|
||||
}
|
||||
// Do some syntax checking on the pattern.
|
||||
// filepath's Match() has some really weird rules that are inconsistent
|
||||
// so instead of trying to dup their logic, just call Match() for its
|
||||
// error state and if there is an error in the pattern return it.
|
||||
// If this becomes an issue we can remove this since its really only
|
||||
// needed in the error (syntax) case - which isn't really critical.
|
||||
if _, err := filepath.Match(p, "."); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
newp.cleanedPattern = p
|
||||
newp.dirs = strings.Split(p, string(os.PathSeparator))
|
||||
pm.patterns = append(pm.patterns, newp)
|
||||
}
|
||||
return pm, nil
|
||||
}
|
||||
|
||||
// Matches returns true if "file" matches any of the patterns
|
||||
// and isn't excluded by any of the subsequent patterns.
|
||||
//
|
||||
// The "file" argument should be a slash-delimited path.
|
||||
//
|
||||
// Matches is not safe to call concurrently.
|
||||
//
|
||||
// Deprecated: This implementation is buggy (it only checks a single parent dir
|
||||
// against the pattern) and will be removed soon. Use either
|
||||
// MatchesOrParentMatches or MatchesUsingParentResults instead.
|
||||
func (pm *PatternMatcher) Matches(file string) (bool, error) {
|
||||
matched := false
|
||||
file = filepath.FromSlash(file)
|
||||
parentPath := filepath.Dir(file)
|
||||
parentPathDirs := strings.Split(parentPath, string(os.PathSeparator))
|
||||
|
||||
for _, pattern := range pm.patterns {
|
||||
// Skip evaluation if this is an inclusion and the filename
|
||||
// already matched the pattern, or it's an exclusion and it has
|
||||
// not matched the pattern yet.
|
||||
if pattern.exclusion != matched {
|
||||
continue
|
||||
}
|
||||
|
||||
match, err := pattern.match(file)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !match && parentPath != "." {
|
||||
// Check to see if the pattern matches one of our parent dirs.
|
||||
if len(pattern.dirs) <= len(parentPathDirs) {
|
||||
match, _ = pattern.match(strings.Join(parentPathDirs[:len(pattern.dirs)], string(os.PathSeparator)))
|
||||
}
|
||||
}
|
||||
|
||||
if match {
|
||||
matched = !pattern.exclusion
|
||||
}
|
||||
}
|
||||
|
||||
return matched, nil
|
||||
}
|
||||
|
||||
// MatchesOrParentMatches returns true if "file" matches any of the patterns
|
||||
// and isn't excluded by any of the subsequent patterns.
|
||||
//
|
||||
// The "file" argument should be a slash-delimited path.
|
||||
//
|
||||
// Matches is not safe to call concurrently.
|
||||
func (pm *PatternMatcher) MatchesOrParentMatches(file string) (bool, error) {
|
||||
matched := false
|
||||
file = filepath.FromSlash(file)
|
||||
parentPath := filepath.Dir(file)
|
||||
parentPathDirs := strings.Split(parentPath, string(os.PathSeparator))
|
||||
|
||||
for _, pattern := range pm.patterns {
|
||||
// Skip evaluation if this is an inclusion and the filename
|
||||
// already matched the pattern, or it's an exclusion and it has
|
||||
// not matched the pattern yet.
|
||||
if pattern.exclusion != matched {
|
||||
continue
|
||||
}
|
||||
|
||||
match, err := pattern.match(file)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !match && parentPath != "." {
|
||||
// Check to see if the pattern matches one of our parent dirs.
|
||||
for i := range parentPathDirs {
|
||||
match, _ = pattern.match(strings.Join(parentPathDirs[:i+1], string(os.PathSeparator)))
|
||||
if match {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if match {
|
||||
matched = !pattern.exclusion
|
||||
}
|
||||
}
|
||||
|
||||
return matched, nil
|
||||
}
|
||||
|
||||
// MatchesUsingParentResult returns true if "file" matches any of the patterns
|
||||
// and isn't excluded by any of the subsequent patterns. The functionality is
|
||||
// the same as Matches, but as an optimization, the caller keeps track of
|
||||
// whether the parent directory matched.
|
||||
//
|
||||
// The "file" argument should be a slash-delimited path.
|
||||
//
|
||||
// MatchesUsingParentResult is not safe to call concurrently.
|
||||
//
|
||||
// Deprecated: this function does behave correctly in some cases (see
|
||||
// https://github.com/docker/buildx/issues/850).
|
||||
//
|
||||
// Use MatchesUsingParentResults instead.
|
||||
func (pm *PatternMatcher) MatchesUsingParentResult(file string, parentMatched bool) (bool, error) {
|
||||
matched := parentMatched
|
||||
file = filepath.FromSlash(file)
|
||||
|
||||
for _, pattern := range pm.patterns {
|
||||
// Skip evaluation if this is an inclusion and the filename
|
||||
// already matched the pattern, or it's an exclusion and it has
|
||||
// not matched the pattern yet.
|
||||
if pattern.exclusion != matched {
|
||||
continue
|
||||
}
|
||||
|
||||
match, err := pattern.match(file)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if match {
|
||||
matched = !pattern.exclusion
|
||||
}
|
||||
}
|
||||
return matched, nil
|
||||
}
|
||||
|
||||
// MatchInfo tracks information about parent dir matches while traversing a
|
||||
// filesystem.
|
||||
type MatchInfo struct {
|
||||
parentMatched []bool
|
||||
}
|
||||
|
||||
// MatchesUsingParentResults returns true if "file" matches any of the patterns
|
||||
// and isn't excluded by any of the subsequent patterns. The functionality is
|
||||
// the same as Matches, but as an optimization, the caller passes in
|
||||
// intermediate results from matching the parent directory.
|
||||
//
|
||||
// The "file" argument should be a slash-delimited path.
|
||||
//
|
||||
// MatchesUsingParentResults is not safe to call concurrently.
|
||||
func (pm *PatternMatcher) MatchesUsingParentResults(file string, parentMatchInfo MatchInfo) (bool, MatchInfo, error) {
|
||||
parentMatched := parentMatchInfo.parentMatched
|
||||
if len(parentMatched) != 0 && len(parentMatched) != len(pm.patterns) {
|
||||
return false, MatchInfo{}, errors.New("wrong number of values in parentMatched")
|
||||
}
|
||||
|
||||
file = filepath.FromSlash(file)
|
||||
matched := false
|
||||
|
||||
matchInfo := MatchInfo{
|
||||
parentMatched: make([]bool, len(pm.patterns)),
|
||||
}
|
||||
for i, pattern := range pm.patterns {
|
||||
match := false
|
||||
// If the parent matched this pattern, we don't need to recheck.
|
||||
if len(parentMatched) != 0 {
|
||||
match = parentMatched[i]
|
||||
}
|
||||
|
||||
if !match {
|
||||
// Skip evaluation if this is an inclusion and the filename
|
||||
// already matched the pattern, or it's an exclusion and it has
|
||||
// not matched the pattern yet.
|
||||
if pattern.exclusion != matched {
|
||||
continue
|
||||
}
|
||||
|
||||
var err error
|
||||
match, err = pattern.match(file)
|
||||
if err != nil {
|
||||
return false, matchInfo, err
|
||||
}
|
||||
|
||||
// If the zero value of MatchInfo was passed in, we don't have
|
||||
// any information about the parent dir's match results, and we
|
||||
// apply the same logic as MatchesOrParentMatches.
|
||||
if !match && len(parentMatched) == 0 {
|
||||
if parentPath := filepath.Dir(file); parentPath != "." {
|
||||
parentPathDirs := strings.Split(parentPath, string(os.PathSeparator))
|
||||
// Check to see if the pattern matches one of our parent dirs.
|
||||
for i := range parentPathDirs {
|
||||
match, _ = pattern.match(strings.Join(parentPathDirs[:i+1], string(os.PathSeparator)))
|
||||
if match {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
matchInfo.parentMatched[i] = match
|
||||
|
||||
if match {
|
||||
matched = !pattern.exclusion
|
||||
}
|
||||
}
|
||||
return matched, matchInfo, nil
|
||||
}
|
||||
|
||||
// Exclusions returns true if any of the patterns define exclusions
|
||||
func (pm *PatternMatcher) Exclusions() bool {
|
||||
return pm.exclusions
|
||||
}
|
||||
|
||||
// Patterns returns array of active patterns
|
||||
func (pm *PatternMatcher) Patterns() []*Pattern {
|
||||
return pm.patterns
|
||||
}
|
||||
|
||||
// Pattern defines a single regexp used to filter file paths.
|
||||
type Pattern struct {
|
||||
matchType matchType
|
||||
cleanedPattern string
|
||||
dirs []string
|
||||
regexp *regexp.Regexp
|
||||
exclusion bool
|
||||
}
|
||||
|
||||
type matchType int
|
||||
|
||||
const (
|
||||
unknownMatch matchType = iota
|
||||
exactMatch
|
||||
prefixMatch
|
||||
suffixMatch
|
||||
regexpMatch
|
||||
)
|
||||
|
||||
func (p *Pattern) String() string {
|
||||
return p.cleanedPattern
|
||||
}
|
||||
|
||||
// Exclusion returns true if this pattern defines exclusion
|
||||
func (p *Pattern) Exclusion() bool {
|
||||
return p.exclusion
|
||||
}
|
||||
|
||||
func (p *Pattern) match(path string) (bool, error) {
|
||||
if p.matchType == unknownMatch {
|
||||
if err := p.compile(string(os.PathSeparator)); err != nil {
|
||||
return false, filepath.ErrBadPattern
|
||||
}
|
||||
}
|
||||
|
||||
switch p.matchType {
|
||||
case exactMatch:
|
||||
return path == p.cleanedPattern, nil
|
||||
case prefixMatch:
|
||||
// strip trailing **
|
||||
return strings.HasPrefix(path, p.cleanedPattern[:len(p.cleanedPattern)-2]), nil
|
||||
case suffixMatch:
|
||||
// strip leading **
|
||||
suffix := p.cleanedPattern[2:]
|
||||
if strings.HasSuffix(path, suffix) {
|
||||
return true, nil
|
||||
}
|
||||
// **/foo matches "foo"
|
||||
return suffix[0] == os.PathSeparator && path == suffix[1:], nil
|
||||
case regexpMatch:
|
||||
return p.regexp.MatchString(path), nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (p *Pattern) compile(sl string) error {
|
||||
regStr := "^"
|
||||
pattern := p.cleanedPattern
|
||||
// Go through the pattern and convert it to a regexp.
|
||||
// We use a scanner so we can support utf-8 chars.
|
||||
var scan scanner.Scanner
|
||||
scan.Init(strings.NewReader(pattern))
|
||||
|
||||
escSL := sl
|
||||
if sl == `\` {
|
||||
escSL += `\`
|
||||
}
|
||||
|
||||
p.matchType = exactMatch
|
||||
for i := 0; scan.Peek() != scanner.EOF; i++ {
|
||||
ch := scan.Next()
|
||||
|
||||
if ch == '*' {
|
||||
if scan.Peek() == '*' {
|
||||
// is some flavor of "**"
|
||||
scan.Next()
|
||||
|
||||
// Treat **/ as ** so eat the "/"
|
||||
if string(scan.Peek()) == sl {
|
||||
scan.Next()
|
||||
}
|
||||
|
||||
if scan.Peek() == scanner.EOF {
|
||||
// is "**EOF" - to align with .gitignore just accept all
|
||||
if p.matchType == exactMatch {
|
||||
p.matchType = prefixMatch
|
||||
} else {
|
||||
regStr += ".*"
|
||||
p.matchType = regexpMatch
|
||||
}
|
||||
} else {
|
||||
// is "**"
|
||||
// Note that this allows for any # of /'s (even 0) because
|
||||
// the .* will eat everything, even /'s
|
||||
regStr += "(.*" + escSL + ")?"
|
||||
p.matchType = regexpMatch
|
||||
}
|
||||
|
||||
if i == 0 {
|
||||
p.matchType = suffixMatch
|
||||
}
|
||||
} else {
|
||||
// is "*" so map it to anything but "/"
|
||||
regStr += "[^" + escSL + "]*"
|
||||
p.matchType = regexpMatch
|
||||
}
|
||||
} else if ch == '?' {
|
||||
// "?" is any char except "/"
|
||||
regStr += "[^" + escSL + "]"
|
||||
p.matchType = regexpMatch
|
||||
} else if shouldEscape(ch) {
|
||||
// Escape some regexp special chars that have no meaning
|
||||
// in golang's filepath.Match
|
||||
regStr += `\` + string(ch)
|
||||
} else if ch == '\\' {
|
||||
// escape next char. Note that a trailing \ in the pattern
|
||||
// will be left alone (but need to escape it)
|
||||
if sl == `\` {
|
||||
// On windows map "\" to "\\", meaning an escaped backslash,
|
||||
// and then just continue because filepath.Match on
|
||||
// Windows doesn't allow escaping at all
|
||||
regStr += escSL
|
||||
continue
|
||||
}
|
||||
if scan.Peek() != scanner.EOF {
|
||||
regStr += `\` + string(scan.Next())
|
||||
p.matchType = regexpMatch
|
||||
} else {
|
||||
regStr += `\`
|
||||
}
|
||||
} else if ch == '[' || ch == ']' {
|
||||
regStr += string(ch)
|
||||
p.matchType = regexpMatch
|
||||
} else {
|
||||
regStr += string(ch)
|
||||
}
|
||||
}
|
||||
|
||||
if p.matchType != regexpMatch {
|
||||
return nil
|
||||
}
|
||||
|
||||
regStr += "$"
|
||||
|
||||
re, err := regexp.Compile(regStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p.regexp = re
|
||||
p.matchType = regexpMatch
|
||||
return nil
|
||||
}
|
||||
|
||||
// Matches returns true if file matches any of the patterns
|
||||
// and isn't excluded by any of the subsequent patterns.
|
||||
//
|
||||
// This implementation is buggy (it only checks a single parent dir against the
|
||||
// pattern) and will be removed soon. Use MatchesOrParentMatches instead.
|
||||
func Matches(file string, patterns []string) (bool, error) {
|
||||
pm, err := New(patterns)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
file = filepath.Clean(file)
|
||||
|
||||
if file == "." {
|
||||
// Don't let them exclude everything, kind of silly.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return pm.Matches(file)
|
||||
}
|
||||
|
||||
// MatchesOrParentMatches returns true if file matches any of the patterns
|
||||
// and isn't excluded by any of the subsequent patterns.
|
||||
func MatchesOrParentMatches(file string, patterns []string) (bool, error) {
|
||||
pm, err := New(patterns)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
file = filepath.Clean(file)
|
||||
|
||||
if file == "." {
|
||||
// Don't let them exclude everything, kind of silly.
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return pm.MatchesOrParentMatches(file)
|
||||
}
|
3
vendor/modules.txt
vendored
3
vendor/modules.txt
vendored
|
@ -594,6 +594,9 @@ github.com/moby/ipvs
|
|||
# github.com/moby/locker v1.0.1
|
||||
## explicit; go 1.13
|
||||
github.com/moby/locker
|
||||
# github.com/moby/patternmatcher v0.5.0
|
||||
## explicit; go 1.19
|
||||
github.com/moby/patternmatcher
|
||||
# github.com/moby/pubsub v1.0.0
|
||||
## explicit; go 1.19
|
||||
github.com/moby/pubsub
|
||||
|
|
Loading…
Reference in a new issue