sigs.k8s.io/prow@v0.0.0-20240503223140-c5e374dc7eb1/pkg/gitattributes/gitattributes.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 "bufio" 21 "bytes" 22 "fmt" 23 "io" 24 "strings" 25 26 "k8s.io/apimachinery/pkg/util/sets" 27 28 "sigs.k8s.io/prow/pkg/github" 29 ) 30 31 // Pattern defines a single gitattributes pattern. 32 type Pattern interface { 33 // Match matches the given path to the pattern. 34 Match(path string) bool 35 } 36 37 // Group is a logical collection of files. Check for a file's 38 // inclusion in the group using the Match method. 39 type Group struct { 40 LinguistGeneratedPatterns []Pattern 41 } 42 43 // NewGroup reads the .gitattributes file in the root of the repository only. 44 func NewGroup(gitAttributesContent func() ([]byte, error)) (*Group, error) { 45 g := &Group{ 46 LinguistGeneratedPatterns: []Pattern{}, 47 } 48 49 bs, err := gitAttributesContent() 50 if err != nil { 51 switch err.(type) { 52 case *github.FileNotFound: 53 return g, nil 54 default: 55 return nil, fmt.Errorf("could not get .gitattributes: %w", err) 56 } 57 } 58 59 if err := g.load(bytes.NewBuffer(bs)); err != nil { 60 return nil, err 61 } 62 63 return g, nil 64 } 65 66 // Use load to read a .gitattributes file, and populate g with the commands. 67 // Each line in gitattributes file is of form: 68 // 69 // pattern attr1 attr2 ... 70 // 71 // That is, a pattern followed by an attributes list, separated by whitespaces. 72 func (g *Group) load(r io.Reader) error { 73 s := bufio.NewScanner(r) 74 for s.Scan() { 75 // Leading and trailing whitespaces are ignored. 76 l := strings.TrimSpace(s.Text()) 77 // Lines that begin with # are ignored. 78 if l == "" || l[0] == '#' { 79 continue 80 } 81 82 fs := strings.Fields(l) 83 if len(fs) < 2 { 84 continue 85 } 86 87 // When the pattern matches the path in question, the attributes listed on the line are given to the path. 88 attributes := sets.New[string](fs[1:]...) 89 if attributes.Has("linguist-generated=true") { 90 p, err := parsePattern(fs[0]) 91 if err != nil { 92 return fmt.Errorf("error parsing pattern: %w", err) 93 } 94 g.LinguistGeneratedPatterns = append(g.LinguistGeneratedPatterns, p) 95 } 96 } 97 98 if err := s.Err(); err != nil { 99 return fmt.Errorf("scan error: %w", err) 100 } 101 102 return nil 103 } 104 105 // IsLinguistGenerated determines whether a file, given here by its full path 106 // is included in the .gitattributes linguist-generated group. 107 // These files are excluded from language stats and suppressed in diffs. 108 // https://github.com/github/linguist/#generated-code 109 // Unmarked paths (linguist-generated=false) are not supported. 110 func (g *Group) IsLinguistGenerated(path string) bool { 111 for _, p := range g.LinguistGeneratedPatterns { 112 if p.Match(path) { 113 return true 114 } 115 } 116 return false 117 }