github.com/apache/skywalking-eyes@v0.6.0/pkg/header/check.go (about) 1 // Licensed to the Apache Software Foundation (ASF) under one 2 // or more contributor license agreements. See the NOTICE file 3 // distributed with this work for additional information 4 // regarding copyright ownership. The ASF licenses this file 5 // to you under the Apache License, Version 2.0 (the 6 // "License"); you may not use this file except in compliance 7 // with the License. You may obtain a copy of the License at 8 // 9 // http://www.apache.org/licenses/LICENSE-2.0 10 // 11 // Unless required by applicable law or agreed to in writing, 12 // software distributed under the License is distributed on an 13 // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 // KIND, either express or implied. See the License for the 15 // specific language governing permissions and limitations 16 // under the License. 17 18 package header 19 20 import ( 21 "errors" 22 "io/fs" 23 "net/http" 24 "os" 25 "path/filepath" 26 "regexp" 27 "strings" 28 29 "github.com/apache/skywalking-eyes/internal/logger" 30 lcs "github.com/apache/skywalking-eyes/pkg/license" 31 32 "github.com/bmatcuk/doublestar/v2" 33 "github.com/go-git/go-billy/v5/osfs" 34 "github.com/go-git/go-git/v5" 35 "github.com/go-git/go-git/v5/plumbing/format/gitignore" 36 "github.com/go-git/go-git/v5/plumbing/object" 37 ) 38 39 // Check checks the license headers of the specified paths/globs. 40 func Check(config *ConfigHeader, result *Result) error { 41 fileList, err := listFiles(config) 42 if err != nil { 43 return err 44 } 45 46 for _, file := range fileList { 47 if err := CheckFile(file, config, result); err != nil { 48 return err 49 } 50 } 51 52 return nil 53 } 54 55 func listFiles(config *ConfigHeader) ([]string, error) { 56 var fileList []string 57 58 repo, err := git.PlainOpen("./") 59 60 if err != nil { // we're not in a Git workspace, fallback to glob paths 61 var localFileList []string 62 for _, pattern := range config.Paths { 63 if pattern == "." { 64 pattern = "./" 65 } 66 files, err := doublestar.Glob(pattern) 67 if err != nil { 68 return nil, err 69 } 70 localFileList = append(localFileList, files...) 71 } 72 73 seen := make(map[string]bool) 74 for _, file := range localFileList { 75 files, err := walkFile(file, seen) 76 if err != nil { 77 return nil, err 78 } 79 fileList = append(fileList, files...) 80 } 81 } else { 82 var candidates []string 83 84 t, _ := repo.Worktree() 85 if t.Excludes == nil { 86 t.Excludes = make([]gitignore.Pattern, 0) 87 } 88 if ignorePattens, err := gitignore.LoadGlobalPatterns(osfs.New("")); err == nil { 89 t.Excludes = append(t.Excludes, ignorePattens...) 90 } 91 if ignorePattens, err := gitignore.LoadSystemPatterns(osfs.New("")); err == nil { 92 t.Excludes = append(t.Excludes, ignorePattens...) 93 } 94 s, _ := t.Status() 95 for file := range s { 96 candidates = append(candidates, file) 97 } 98 99 head, _ := repo.Head() 100 commit, _ := repo.CommitObject(head.Hash()) 101 tree, err := commit.Tree() 102 if err != nil { 103 return nil, err 104 } 105 if err := tree.Files().ForEach(func(file *object.File) error { 106 if file == nil { 107 return errors.New("file pointer is nil") 108 } 109 candidates = append(candidates, file.Name) 110 return nil 111 }); err != nil { 112 return nil, err 113 } 114 115 seen := make(map[string]bool) 116 for _, file := range candidates { 117 if !seen[file] { 118 seen[file] = true 119 _, err := os.Stat(file) 120 if err == nil { 121 fileList = append(fileList, file) 122 } else if !os.IsNotExist(err) { 123 return nil, err 124 } 125 } 126 } 127 } 128 129 return fileList, nil 130 } 131 132 func walkFile(file string, seen map[string]bool) ([]string, error) { 133 var files []string 134 135 if seen[file] { 136 return files, nil 137 } 138 seen[file] = true 139 140 if stat, err := os.Stat(file); err == nil { 141 switch mode := stat.Mode(); { 142 case mode.IsRegular(): 143 files = append(files, file) 144 case mode.IsDir(): 145 err := filepath.Walk(file, func(path string, info fs.FileInfo, err error) error { 146 if path == file { 147 // when path is symbolic link file, it causes infinite recursive calls 148 return nil 149 } 150 if seen[path] { 151 return nil 152 } 153 seen[path] = true 154 if info.Mode().IsRegular() { 155 files = append(files, path) 156 } 157 return nil 158 }) 159 if err != nil { 160 return files, err 161 } 162 } 163 } 164 165 return files, nil 166 } 167 168 // CheckFile checks whether the file contains the configured license header. 169 func CheckFile(file string, config *ConfigHeader, result *Result) error { 170 if yes, err := config.ShouldIgnore(file); yes || err != nil { 171 result.Ignore(file) 172 return err 173 } 174 175 logger.Log.Debugln("Checking file:", file) 176 177 bs, err := os.ReadFile(file) 178 if err != nil { 179 return err 180 } 181 if t := http.DetectContentType(bs); !strings.HasPrefix(t, "text/") { 182 logger.Log.Debugln("Ignoring file:", file, "; type:", t) 183 return nil 184 } 185 186 content := lcs.NormalizeHeader(string(bs)) 187 expected, pattern := config.NormalizedLicense(), config.NormalizedPattern() 188 189 if satisfy(content, config, expected, pattern) { 190 result.Succeed(file) 191 } else { 192 logger.Log.Debugln("Content is:", content) 193 194 result.Fail(file) 195 } 196 197 return nil 198 } 199 200 func satisfy(content string, config *ConfigHeader, license string, pattern *regexp.Regexp) bool { 201 if index := strings.Index(content, license); strings.TrimSpace(license) != "" && index >= 0 { 202 return index < config.LicenseLocationThreshold 203 } 204 205 if pattern == nil { 206 return false 207 } 208 index := pattern.FindStringIndex(content) 209 210 return len(index) == 2 && index[0] < config.LicenseLocationThreshold 211 }