Skip to content

Commit 93d560d

Browse files
authored
Merge pull request moby#43037 from aaronlehmann/pattern-matcher-parent-results
pkg/fileutils: Track incremental pattern match results against each pattern
2 parents d44ccaf + 0f1b68d commit 93d560d

File tree

3 files changed

+138
-10
lines changed

3 files changed

+138
-10
lines changed

pkg/archive/archive.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -866,8 +866,8 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
866866
rebaseName := options.RebaseNames[include]
867867

868868
var (
869-
parentMatched []bool
870-
parentDirs []string
869+
parentMatchInfo []fileutils.MatchInfo
870+
parentDirs []string
871871
)
872872

873873
walkRoot := getWalkRoot(srcPath, include)
@@ -902,13 +902,14 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
902902
break
903903
}
904904
parentDirs = parentDirs[:len(parentDirs)-1]
905-
parentMatched = parentMatched[:len(parentMatched)-1]
905+
parentMatchInfo = parentMatchInfo[:len(parentMatchInfo)-1]
906906
}
907907

908-
if len(parentMatched) != 0 {
909-
skip, err = pm.MatchesUsingParentResult(relFilePath, parentMatched[len(parentMatched)-1])
908+
var matchInfo fileutils.MatchInfo
909+
if len(parentMatchInfo) != 0 {
910+
skip, matchInfo, err = pm.MatchesUsingParentResults(relFilePath, parentMatchInfo[len(parentMatchInfo)-1])
910911
} else {
911-
skip, err = pm.MatchesOrParentMatches(relFilePath)
912+
skip, matchInfo, err = pm.MatchesUsingParentResults(relFilePath, fileutils.MatchInfo{})
912913
}
913914
if err != nil {
914915
logrus.Errorf("Error matching %s: %v", relFilePath, err)
@@ -917,7 +918,7 @@ func TarWithOptions(srcPath string, options *TarOptions) (io.ReadCloser, error)
917918

918919
if f.IsDir() {
919920
parentDirs = append(parentDirs, relFilePath)
920-
parentMatched = append(parentMatched, skip)
921+
parentMatchInfo = append(parentMatchInfo, matchInfo)
921922
}
922923
}
923924

pkg/fileutils/fileutils.go

+80-3
Original file line numberDiff line numberDiff line change
@@ -84,9 +84,9 @@ func NewPatternMatcher(patterns []string) (*PatternMatcher, error) {
8484
//
8585
// Matches is not safe to call concurrently.
8686
//
87-
// This implementation is buggy (it only checks a single parent dir against the
88-
// pattern) and will be removed soon. Use either MatchesOrParentMatches or
89-
// MatchesUsingParentResult instead.
87+
// Deprecated: This implementation is buggy (it only checks a single parent dir
88+
// against the pattern) and will be removed soon. Use either
89+
// MatchesOrParentMatches or MatchesUsingParentResults instead.
9090
func (pm *PatternMatcher) Matches(file string) (bool, error) {
9191
matched := false
9292
file = filepath.FromSlash(file)
@@ -172,6 +172,11 @@ func (pm *PatternMatcher) MatchesOrParentMatches(file string) (bool, error) {
172172
// The "file" argument should be a slash-delimited path.
173173
//
174174
// MatchesUsingParentResult is not safe to call concurrently.
175+
//
176+
// Deprecated: this function does behave correctly in some cases (see
177+
// https://github.com/docker/buildx/issues/850).
178+
//
179+
// Use MatchesUsingParentResults instead.
175180
func (pm *PatternMatcher) MatchesUsingParentResult(file string, parentMatched bool) (bool, error) {
176181
matched := parentMatched
177182
file = filepath.FromSlash(file)
@@ -196,6 +201,78 @@ func (pm *PatternMatcher) MatchesUsingParentResult(file string, parentMatched bo
196201
return matched, nil
197202
}
198203

204+
// MatchInfo tracks information about parent dir matches while traversing a
205+
// filesystem.
206+
type MatchInfo struct {
207+
parentMatched []bool
208+
}
209+
210+
// MatchesUsingParentResults returns true if "file" matches any of the patterns
211+
// and isn't excluded by any of the subsequent patterns. The functionality is
212+
// the same as Matches, but as an optimization, the caller passes in
213+
// intermediate results from matching the parent directory.
214+
//
215+
// The "file" argument should be a slash-delimited path.
216+
//
217+
// MatchesUsingParentResults is not safe to call concurrently.
218+
func (pm *PatternMatcher) MatchesUsingParentResults(file string, parentMatchInfo MatchInfo) (bool, MatchInfo, error) {
219+
parentMatched := parentMatchInfo.parentMatched
220+
if len(parentMatched) != 0 && len(parentMatched) != len(pm.patterns) {
221+
return false, MatchInfo{}, errors.New("wrong number of values in parentMatched")
222+
}
223+
224+
file = filepath.FromSlash(file)
225+
matched := false
226+
227+
matchInfo := MatchInfo{
228+
parentMatched: make([]bool, len(pm.patterns)),
229+
}
230+
for i, pattern := range pm.patterns {
231+
match := false
232+
// If the parent matched this pattern, we don't need to recheck.
233+
if len(parentMatched) != 0 {
234+
match = parentMatched[i]
235+
}
236+
237+
if !match {
238+
// Skip evaluation if this is an inclusion and the filename
239+
// already matched the pattern, or it's an exclusion and it has
240+
// not matched the pattern yet.
241+
if pattern.exclusion != matched {
242+
continue
243+
}
244+
245+
var err error
246+
match, err = pattern.match(file)
247+
if err != nil {
248+
return false, matchInfo, err
249+
}
250+
251+
// If the zero value of MatchInfo was passed in, we don't have
252+
// any information about the parent dir's match results, and we
253+
// apply the same logic as MatchesOrParentMatches.
254+
if len(parentMatched) == 0 {
255+
if parentPath := filepath.Dir(file); parentPath != "." {
256+
parentPathDirs := strings.Split(parentPath, string(os.PathSeparator))
257+
// Check to see if the pattern matches one of our parent dirs.
258+
for i := range parentPathDirs {
259+
match, _ = pattern.match(strings.Join(parentPathDirs[:i+1], string(os.PathSeparator)))
260+
if match {
261+
break
262+
}
263+
}
264+
}
265+
}
266+
}
267+
matchInfo.parentMatched[i] = match
268+
269+
if match {
270+
matched = !pattern.exclusion
271+
}
272+
}
273+
return matched, matchInfo, nil
274+
}
275+
199276
// Exclusions returns true if any of the patterns define exclusions
200277
func (pm *PatternMatcher) Exclusions() bool {
201278
return pm.exclusions

pkg/fileutils/fileutils_test.go

+50
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,12 @@ type matchesTestCase struct {
309309
pass bool
310310
}
311311

312+
type multiPatternTestCase struct {
313+
patterns []string
314+
text string
315+
pass bool
316+
}
317+
312318
func TestMatches(t *testing.T) {
313319
tests := []matchesTestCase{
314320
{"**", "file", true},
@@ -377,6 +383,10 @@ func TestMatches(t *testing.T) {
377383
{"a(b)c/def", "a(b)c/xyz", false},
378384
{"a.|)$(}+{bc", "a.|)$(}+{bc", true},
379385
}
386+
multiPatternTests := []multiPatternTestCase{
387+
{[]string{"**", "!util/docker/web"}, "util/docker/web/foo", false},
388+
{[]string{"**", "!util/docker/web", "util/docker/web/foo"}, "util/docker/web/foo", true},
389+
}
380390

381391
if runtime.GOOS != "windows" {
382392
tests = append(tests, []matchesTestCase{
@@ -392,6 +402,14 @@ func TestMatches(t *testing.T) {
392402
res, _ := pm.MatchesOrParentMatches(test.text)
393403
assert.Check(t, is.Equal(test.pass, res), desc)
394404
}
405+
406+
for _, test := range multiPatternTests {
407+
desc := fmt.Sprintf("patterns=%q text=%q", test.patterns, test.text)
408+
pm, err := NewPatternMatcher(test.patterns)
409+
assert.NilError(t, err, desc)
410+
res, _ := pm.MatchesOrParentMatches(test.text)
411+
assert.Check(t, is.Equal(test.pass, res), desc)
412+
}
395413
})
396414

397415
t.Run("MatchesUsingParentResult", func(t *testing.T) {
@@ -415,6 +433,38 @@ func TestMatches(t *testing.T) {
415433
}
416434
})
417435

436+
t.Run("MatchesUsingParentResults", func(t *testing.T) {
437+
check := func(pm *PatternMatcher, text string, pass bool, desc string) {
438+
parentPath := filepath.Dir(filepath.FromSlash(text))
439+
parentPathDirs := strings.Split(parentPath, string(os.PathSeparator))
440+
441+
parentMatchInfo := MatchInfo{}
442+
if parentPath != "." {
443+
for i := range parentPathDirs {
444+
_, parentMatchInfo, _ = pm.MatchesUsingParentResults(strings.Join(parentPathDirs[:i+1], "/"), parentMatchInfo)
445+
}
446+
}
447+
448+
res, _, _ := pm.MatchesUsingParentResults(text, parentMatchInfo)
449+
assert.Check(t, is.Equal(pass, res), desc)
450+
}
451+
452+
for _, test := range tests {
453+
desc := fmt.Sprintf("pattern=%q text=%q", test.pattern, test.text)
454+
pm, err := NewPatternMatcher([]string{test.pattern})
455+
assert.NilError(t, err, desc)
456+
457+
check(pm, test.text, test.pass, desc)
458+
}
459+
460+
for _, test := range multiPatternTests {
461+
desc := fmt.Sprintf("pattern=%q text=%q", test.patterns, test.text)
462+
pm, err := NewPatternMatcher(test.patterns)
463+
assert.NilError(t, err, desc)
464+
465+
check(pm, test.text, test.pass, desc)
466+
}
467+
})
418468
}
419469

420470
func TestCleanPatterns(t *testing.T) {

0 commit comments

Comments
 (0)