sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/gitattributes/pattern.go (about) 1 /* 2 Copyright 2019 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package gitattributes 18 19 import ( 20 "fmt" 21 "path/filepath" 22 "strings" 23 ) 24 25 const ( 26 negativePrefix = "!" 27 patternDirSep = "/" 28 zeroToManyDirs = "**" 29 ) 30 31 type pattern struct { 32 pattern []string 33 isPath bool 34 } 35 36 // parsePattern parses a gitattributes pattern string into the Pattern structure. 37 // The rules by which the pattern matches paths are the same as in .gitignore files (see https://git-scm.com/docs/gitignore), with a few exceptions: 38 // - negative patterns are forbidden 39 // - patterns that match a directory do not recursively match paths inside that directory 40 // 41 // https://git-scm.com/docs/gitattributes 42 func parsePattern(p string) (Pattern, error) { 43 res := pattern{} 44 45 // negative patterns are forbidden 46 if strings.HasPrefix(p, negativePrefix) { 47 return nil, fmt.Errorf("negative patterns are forbidden: <%s>", p) 48 } 49 50 // trailing spaces are ignored unless they are quoted with backslash 51 if !strings.HasSuffix(p, `\ `) { 52 p = strings.TrimRight(p, " ") 53 } 54 55 // patterns that match a directory do not recursively match paths inside that directory 56 if strings.HasSuffix(p, patternDirSep) { 57 return nil, fmt.Errorf("directory patterns are not matched recursively, use path/** instead: <%s>", p) 58 } 59 60 if strings.Contains(p, patternDirSep) { 61 res.isPath = true 62 } 63 64 res.pattern = strings.Split(p, patternDirSep) 65 return &res, nil 66 } 67 68 func (p *pattern) Match(path string) bool { 69 pathElements := strings.Split(path, "/") 70 if p.isPath && p.pathMatch(pathElements) { 71 return true 72 } else if !p.isPath && p.nameMatch(pathElements) { 73 return true 74 } 75 return false 76 } 77 78 func (p *pattern) nameMatch(path []string) bool { 79 for _, name := range path { 80 if match, err := filepath.Match(p.pattern[0], name); err != nil { 81 return false 82 } else if !match { 83 continue 84 } 85 return true 86 } 87 return false 88 } 89 90 func (p *pattern) pathMatch(path []string) bool { 91 matched := false 92 canTraverse := false 93 for i, pattern := range p.pattern { 94 if pattern == "" { 95 canTraverse = false 96 continue 97 } 98 if pattern == zeroToManyDirs { 99 if i == len(p.pattern)-1 { 100 break 101 } 102 canTraverse = true 103 continue 104 } 105 if strings.Contains(pattern, zeroToManyDirs) { 106 return false 107 } 108 if len(path) == 0 { 109 return false 110 } 111 if canTraverse { 112 canTraverse = false 113 for len(path) > 0 { 114 e := path[0] 115 path = path[1:] 116 if match, err := filepath.Match(pattern, e); err != nil { 117 return false 118 } else if match { 119 matched = true 120 break 121 } else if len(path) == 0 { 122 // if nothing left then fail 123 matched = false 124 } 125 } 126 } else { 127 if match, err := filepath.Match(pattern, path[0]); err != nil || !match { 128 return false 129 } 130 matched = true 131 path = path[1:] 132 } 133 } 134 return matched 135 }