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  }