blob: 53beb2ccf73da10d7d92094c14a986fa31716076 [file] [log] [blame]
Patrick Georgi7333a112020-05-08 20:48:04 +02001/* SPDX-License-Identifier: GPL-2.0-only */
Stefan Reinauer1ecf2522015-10-21 13:12:51 -07002
3package main
4
5import (
6 "bufio"
Patrick Georgi62a27382018-11-12 18:48:40 +01007 "flag"
Stefan Reinauer1ecf2522015-10-21 13:12:51 -07008 "fmt"
9 "log"
10 "os"
11 "os/exec"
Patrick Georgi0cadafa2018-11-12 17:31:48 +010012 "regexp"
Patrick Georgi89bd4892018-11-12 17:52:24 +010013 "strings"
Stefan Reinauer1ecf2522015-10-21 13:12:51 -070014)
15
16type subsystem struct {
17 name string
18 maintainer []string
Patrick Georgi0cadafa2018-11-12 17:31:48 +010019 paths []string
20 globs []*regexp.Regexp
Stefan Reinauer1ecf2522015-10-21 13:12:51 -070021}
22
23var subsystems []subsystem
24
25func get_git_files() ([]string, error) {
26 var files []string
27
28 /* Read in list of all files in the git repository */
29 cmd := exec.Command("git", "ls-files")
30 out, err := cmd.StdoutPipe()
31 if err != nil {
32 log.Fatalf("git ls-files failed: %v", err)
33 return files, err
34 }
35 if err := cmd.Start(); err != nil {
36 log.Fatalf("Could not start %v: %v", cmd, err)
37 return files, err
38 }
39
40 r := bufio.NewScanner(out)
41 for r.Scan() {
42 /* Cut out leading tab */
43 files = append(files, r.Text())
44 }
45
46 cmd.Wait()
47
48 return files, nil
49}
50
51func get_maintainers() ([]string, error) {
52 var maintainers []string
53
54 /* Read in all maintainers */
55 file, err := os.Open("MAINTAINERS")
56 if err != nil {
57 log.Fatalf("Can't open MAINTAINERS file: %v", err)
58 log.Fatalf("Are you running from the top-level directory?")
59 return maintainers, err
60 }
61 defer file.Close()
62
63 keep := false
64 s := bufio.NewScanner(file)
65 for s.Scan() {
66 /* Are we in the "data" section and have a non-empty line? */
67 if keep && s.Text() != "" {
68 maintainers = append(maintainers, s.Text())
69 }
70 /* Skip everything before the delimiter */
71 if s.Text() == "\t\t-----------------------------------" {
72 keep = true
73 }
74 }
75
76 return maintainers, nil
77}
78
Patrick Georgi0cadafa2018-11-12 17:31:48 +010079func path_to_regexstr(path string) string {
Michael Niewöhnera0c7f342021-04-13 00:44:21 +020080 /* Add missing trailing slash if path is a directory */
81 if path[len(path)-1] != '/' {
82 fileInfo, err := os.Stat(path)
83 if err == nil && fileInfo.IsDir() {
84 path += "/"
85 }
86 }
87
Michael Niewöhner158fed92021-04-13 00:17:39 +020088 regexstr := glob_to_regex(path)
89
90 /* Handle path with trailing '/' as prefix */
91 if regexstr[len(regexstr)-2:] == "/$" {
92 regexstr = regexstr[:len(regexstr)-1] + ".*$"
Patrick Georgi0cadafa2018-11-12 17:31:48 +010093 }
Michael Niewöhner158fed92021-04-13 00:17:39 +020094
95 return regexstr;
Patrick Georgi0cadafa2018-11-12 17:31:48 +010096}
97
98func path_to_regex(path string) *regexp.Regexp {
99 regexstr := path_to_regexstr(path)
100 return regexp.MustCompile(regexstr)
101}
102
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700103func build_maintainers(maintainers []string) {
104 var current *subsystem
105 for _, line := range maintainers {
106 if line[1] != ':' {
107 /* Create new subsystem entry */
108 var tmp subsystem
109 subsystems = append(subsystems, tmp)
110 current = &subsystems[len(subsystems)-1]
111 current.name = line
112 } else {
113 switch line[0] {
Patrick Georgi92332632018-11-12 17:09:40 +0100114 case 'R', 'M':
115 /* Add subsystem maintainer */
116 current.maintainer = append(current.maintainer, line[3:len(line)])
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700117 case 'F':
Patrick Georgi92332632018-11-12 17:09:40 +0100118 // add files
Patrick Georgi0cadafa2018-11-12 17:31:48 +0100119 current.paths = append(current.paths, line[3:len(line)])
120 current.globs = append(current.globs, path_to_regex(line[3:len(line)]))
121 break
Patrick Georgi92332632018-11-12 17:09:40 +0100122 case 'L', 'S', 'T', 'W': // ignore
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700123 default:
Patrick Georgi92332632018-11-12 17:09:40 +0100124 fmt.Println("No such specifier: ", line)
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700125 }
126 }
127 }
128}
129
130func print_maintainers() {
131 for _, subsystem := range subsystems {
132 fmt.Println(subsystem.name)
133 fmt.Println(" ", subsystem.maintainer)
Patrick Georgi0cadafa2018-11-12 17:31:48 +0100134 fmt.Println(" ", subsystem.paths)
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700135 }
136}
137
Patrick Georgi0cadafa2018-11-12 17:31:48 +0100138func match_file(fname string, component subsystem) bool {
139 for _, glob := range component.globs {
140 if glob.Match([]byte(fname)) {
141 return true
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700142 }
143 }
Patrick Georgi0cadafa2018-11-12 17:31:48 +0100144 return false
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700145}
146
147func find_maintainer(fname string) {
Patrick Georgi2e5d6a82018-11-12 17:29:49 +0100148 var success bool
149
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700150 for _, subsystem := range subsystems {
Patrick Georgi0cadafa2018-11-12 17:31:48 +0100151 matched := match_file(fname, subsystem)
Patrick Georgi96510582018-11-16 12:41:19 +0100152 if matched {
Patrick Georgi2e5d6a82018-11-12 17:29:49 +0100153 success = true
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700154 fmt.Println(fname, "is in subsystem",
155 subsystem.name)
Patrick Georgi89bd4892018-11-12 17:52:24 +0100156 fmt.Println("Maintainers: ", strings.Join(subsystem.maintainer, ", "))
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700157 }
158 }
Patrick Georgi2e5d6a82018-11-12 17:29:49 +0100159 if !success {
160 fmt.Println(fname, "has no subsystem defined in MAINTAINERS")
161 }
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700162}
163
164func find_unmaintained(fname string) {
Patrick Georgi2e5d6a82018-11-12 17:29:49 +0100165 var success bool
166
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700167 for _, subsystem := range subsystems {
Patrick Georgi0cadafa2018-11-12 17:31:48 +0100168 matched := match_file(fname, subsystem)
Patrick Georgi96510582018-11-16 12:41:19 +0100169 if matched {
Patrick Georgi2e5d6a82018-11-12 17:29:49 +0100170 success = true
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700171 fmt.Println(fname, "is in subsystem",
172 subsystem.name)
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700173 }
174 }
Patrick Georgi2e5d6a82018-11-12 17:29:49 +0100175 if !success {
176 fmt.Println(fname, "has no subsystem defined in MAINTAINERS")
177 }
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700178}
179
Patrick Georgi0cadafa2018-11-12 17:31:48 +0100180// taken from https://github.com/zyedidia/glob/blob/master/glob.go which is
181// Copyright (c) 2016: Zachary Yedidia.
182// and was published under the MIT "Expat" license.
183//
184// only change: return the string, instead of a compiled golang regex
185func glob_to_regex(glob string) string {
186 regex := ""
187 inGroup := 0
188 inClass := 0
189 firstIndexInClass := -1
190 arr := []byte(glob)
191
192 for i := 0; i < len(arr); i++ {
193 ch := arr[i]
194
195 switch ch {
196 case '\\':
197 i++
198 if i >= len(arr) {
199 regex += "\\"
200 } else {
201 next := arr[i]
202 switch next {
203 case ',':
204 // Nothing
205 case 'Q', 'E':
206 regex += "\\\\"
207 default:
208 regex += "\\"
209 }
210 regex += string(next)
211 }
212 case '*':
213 if inClass == 0 {
Michael Niewöhner158fed92021-04-13 00:17:39 +0200214 regex += "[^/]*"
Patrick Georgi0cadafa2018-11-12 17:31:48 +0100215 } else {
216 regex += "*"
217 }
218 case '?':
219 if inClass == 0 {
220 regex += "."
221 } else {
222 regex += "?"
223 }
224 case '[':
225 inClass++
226 firstIndexInClass = i + 1
227 regex += "["
228 case ']':
229 inClass--
230 regex += "]"
231 case '.', '(', ')', '+', '|', '^', '$', '@', '%':
232 if inClass == 0 || (firstIndexInClass == i && ch == '^') {
233 regex += "\\"
234 }
235 regex += string(ch)
236 case '!':
237 if firstIndexInClass == i {
238 regex += "^"
239 } else {
240 regex += "!"
241 }
242 case '{':
243 inGroup++
244 regex += "("
245 case '}':
246 inGroup--
247 regex += ")"
248 case ',':
249 if inGroup > 0 {
250 regex += "|"
251 } else {
252 regex += ","
253 }
254 default:
255 regex += string(ch)
256 }
257 }
258 return "^" + regex + "$"
259}
260
Patrick Georgie874df92018-11-12 18:49:09 +0100261var is_email *regexp.Regexp
262
263func extract_maintainer(maintainer string) string {
264 if is_email == nil {
265 is_email = regexp.MustCompile("<[^>]*>")
266 }
267
268 if match := is_email.FindStringSubmatch(maintainer); match != nil {
269 return match[0][1 : len(match[0])-1]
270 }
271 return maintainer
272}
273
274func do_print_gerrit_rules() {
275 for _, subsystem := range subsystems {
276 if len(subsystem.paths) == 0 || len(subsystem.maintainer) == 0 {
277 continue
278 }
279 fmt.Println("#", subsystem.name)
280 for _, path := range subsystem.paths {
Patrick Georgi571477b2018-11-21 22:07:38 +0100281 fmt.Println("[filter \"file:\\\"" + path_to_regexstr(path) + "\\\"\"]")
Patrick Georgie874df92018-11-12 18:49:09 +0100282 for _, maint := range subsystem.maintainer {
283 fmt.Println(" reviewer =", extract_maintainer(maint))
284 }
285 }
286 fmt.Println()
287 }
288}
289
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700290func main() {
Patrick Georgi62a27382018-11-12 18:48:40 +0100291 var (
Patrick Georgie874df92018-11-12 18:49:09 +0100292 files []string
293 err error
294 print_gerrit_rules = flag.Bool("print-gerrit-rules", false, "emit the MAINTAINERS rules in a format suitable for Gerrit's reviewers plugin")
295 debug = flag.Bool("debug", false, "emit additional debug output")
Patrick Georgi62a27382018-11-12 18:48:40 +0100296 )
297 flag.Parse()
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700298
Patrick Georgi62a27382018-11-12 18:48:40 +0100299 /* get and build subsystem database */
300 maintainers, err := get_maintainers()
301 if err != nil {
302 log.Fatalf("Oops.")
303 return
304 }
305 build_maintainers(maintainers)
306
307 if *debug {
308 print_maintainers()
309 }
310
Patrick Georgie874df92018-11-12 18:49:09 +0100311 if *print_gerrit_rules {
312 do_print_gerrit_rules()
313 return
314 }
315
Patrick Georgi62a27382018-11-12 18:48:40 +0100316 args := flag.Args()
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700317 if len(args) == 0 {
318 /* get the filenames */
319 files, err = get_git_files()
320 if err != nil {
321 log.Fatalf("Oops.")
322 return
323 }
Patrick Georgi62a27382018-11-12 18:48:40 +0100324 for _, file := range files {
325 find_unmaintained(file)
326 }
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700327 } else {
328 files = args
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700329
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700330 /* Find maintainers for each file */
331 for _, file := range files {
332 find_maintainer(file)
333 }
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700334 }
335}