github.com/google/osv-scalibr@v0.4.1/extractor/filesystem/internal/gitignore.go (about)

     1  // Copyright 2025 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package internal
    16  
    17  import (
    18  	"bufio"
    19  	"os"
    20  	"path"
    21  	"strings"
    22  
    23  	"github.com/go-git/go-git/v5/plumbing/format/gitignore"
    24  	scalibrfs "github.com/google/osv-scalibr/fs"
    25  )
    26  
    27  // GitignorePattern is a list of patterns found inside a .gitignore file.
    28  type GitignorePattern gitignore.Matcher
    29  
    30  // EmptyGitignore returns an empty matcher that doesn't match on any pattern.
    31  func EmptyGitignore() GitignorePattern {
    32  	return gitignore.NewMatcher(nil)
    33  }
    34  
    35  // GitignoreMatch returns whether the specified file path should be ignored
    36  // according to the specified .gitignore patterns.
    37  func GitignoreMatch(gitignores []GitignorePattern, filePath []string, isDir bool) bool {
    38  	for _, p := range gitignores {
    39  		if p != nil && p.Match(filePath, isDir) {
    40  			return true
    41  		}
    42  	}
    43  	return false
    44  }
    45  
    46  // ParseDirForGitignore parses .gitignore patterns found in the
    47  // specified directory.
    48  func ParseDirForGitignore(fs scalibrfs.FS, dirPath string) (GitignorePattern, error) {
    49  	dirPath = strings.TrimSuffix(dirPath, "/")
    50  	filePath := path.Join(dirPath, ".gitignore")
    51  	f, err := fs.Open(filePath)
    52  	if err != nil {
    53  		if os.IsNotExist(err) {
    54  			return nil, nil
    55  		}
    56  		return nil, err
    57  	}
    58  	defer f.Close()
    59  	scanner := bufio.NewScanner(f)
    60  	ps := []gitignore.Pattern{}
    61  	pathTokens := strings.Split(dirPath, "/")
    62  	for scanner.Scan() {
    63  		s := scanner.Text()
    64  		if !strings.HasPrefix(s, "#") && len(strings.TrimSpace(s)) > 0 {
    65  			ps = append(ps, gitignore.ParsePattern(s, pathTokens))
    66  		}
    67  	}
    68  	return gitignore.NewMatcher(ps), nil
    69  }
    70  
    71  // ParseParentGitignores parses all .gitignore patterns between the current dir
    72  // and the scan root, excluding the current directory.
    73  func ParseParentGitignores(fs scalibrfs.FS, dirPath string) ([]GitignorePattern, error) {
    74  	var filePath strings.Builder
    75  	result := []GitignorePattern{}
    76  	components := strings.Split(dirPath, "/")
    77  	for _, dir := range components[:len(components)-1] {
    78  		filePath.WriteString(dir + "/")
    79  		gitignores, err := ParseDirForGitignore(fs, filePath.String())
    80  		if err != nil {
    81  			return nil, err
    82  		}
    83  		result = append(result, gitignores)
    84  	}
    85  	return result, nil
    86  }