Browse Source

replace pkg/fileutils Matching funcs with github.com/moby/patternmatcher

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
Sebastiaan van Stijn 2 years ago
parent
commit
3c69b9f2c5

+ 2 - 2
builder/remotecontext/detect.go

@@ -14,9 +14,9 @@ import (
 	"github.com/docker/docker/builder/remotecontext/urlutil"
 	"github.com/docker/docker/errdefs"
 	"github.com/docker/docker/pkg/containerfs"
-	"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"
 )
@@ -131,7 +131,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)
 			}

+ 5 - 5
pkg/archive/archive.go

@@ -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"
@@ -824,7 +824,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
 	}
@@ -899,7 +899,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
 			rebaseName := options.RebaseNames[include]
 
 			var (
-				parentMatchInfo []fileutils.MatchInfo
+				parentMatchInfo []patternmatcher.MatchInfo
 				parentDirs      []string
 			)
 
@@ -938,11 +938,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 - 0
pkg/fileutils/deprecated.go

@@ -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
+)

+ 0 - 468
pkg/fileutils/fileutils.go

@@ -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.

+ 0 - 538
pkg/fileutils/fileutils_test.go

@@ -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)
-				}
-			}
-		}
-	}
-}

+ 1 - 0
vendor.mod

@@ -53,6 +53,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

+ 2 - 0
vendor.sum

@@ -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 - 0
vendor/github.com/moby/patternmatcher/LICENSE

@@ -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 - 0
vendor/github.com/moby/patternmatcher/NOTICE

@@ -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 - 0
vendor/github.com/moby/patternmatcher/patternmatcher.go

@@ -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 - 0
vendor/modules.txt

@@ -593,6 +593,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