blob: 459c030d1360b4056a8d179d7203ef7572c1424c [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 {
80 // if prefix, allow all subdirectories
81 if path[len(path)-1] == '/' {
82 path += "*"
83 }
84 return glob_to_regex(path)
85}
86
87func path_to_regex(path string) *regexp.Regexp {
88 regexstr := path_to_regexstr(path)
89 return regexp.MustCompile(regexstr)
90}
91
Stefan Reinauer1ecf2522015-10-21 13:12:51 -070092func build_maintainers(maintainers []string) {
93 var current *subsystem
94 for _, line := range maintainers {
95 if line[1] != ':' {
96 /* Create new subsystem entry */
97 var tmp subsystem
98 subsystems = append(subsystems, tmp)
99 current = &subsystems[len(subsystems)-1]
100 current.name = line
101 } else {
102 switch line[0] {
Patrick Georgi92332632018-11-12 17:09:40 +0100103 case 'R', 'M':
104 /* Add subsystem maintainer */
105 current.maintainer = append(current.maintainer, line[3:len(line)])
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700106 case 'F':
Patrick Georgi92332632018-11-12 17:09:40 +0100107 // add files
Patrick Georgi0cadafa2018-11-12 17:31:48 +0100108 current.paths = append(current.paths, line[3:len(line)])
109 current.globs = append(current.globs, path_to_regex(line[3:len(line)]))
110 break
Patrick Georgi92332632018-11-12 17:09:40 +0100111 case 'L', 'S', 'T', 'W': // ignore
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700112 default:
Patrick Georgi92332632018-11-12 17:09:40 +0100113 fmt.Println("No such specifier: ", line)
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700114 }
115 }
116 }
117}
118
119func print_maintainers() {
120 for _, subsystem := range subsystems {
121 fmt.Println(subsystem.name)
122 fmt.Println(" ", subsystem.maintainer)
Patrick Georgi0cadafa2018-11-12 17:31:48 +0100123 fmt.Println(" ", subsystem.paths)
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700124 }
125}
126
Patrick Georgi0cadafa2018-11-12 17:31:48 +0100127func match_file(fname string, component subsystem) bool {
128 for _, glob := range component.globs {
129 if glob.Match([]byte(fname)) {
130 return true
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700131 }
132 }
Patrick Georgi0cadafa2018-11-12 17:31:48 +0100133 return false
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700134}
135
136func find_maintainer(fname string) {
Patrick Georgi2e5d6a82018-11-12 17:29:49 +0100137 var success bool
138
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700139 for _, subsystem := range subsystems {
Patrick Georgi0cadafa2018-11-12 17:31:48 +0100140 matched := match_file(fname, subsystem)
Patrick Georgi96510582018-11-16 12:41:19 +0100141 if matched {
Patrick Georgi2e5d6a82018-11-12 17:29:49 +0100142 success = true
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700143 fmt.Println(fname, "is in subsystem",
144 subsystem.name)
Patrick Georgi89bd4892018-11-12 17:52:24 +0100145 fmt.Println("Maintainers: ", strings.Join(subsystem.maintainer, ", "))
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700146 }
147 }
Patrick Georgi2e5d6a82018-11-12 17:29:49 +0100148 if !success {
149 fmt.Println(fname, "has no subsystem defined in MAINTAINERS")
150 }
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700151}
152
153func find_unmaintained(fname string) {
Patrick Georgi2e5d6a82018-11-12 17:29:49 +0100154 var success bool
155
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700156 for _, subsystem := range subsystems {
Patrick Georgi0cadafa2018-11-12 17:31:48 +0100157 matched := match_file(fname, subsystem)
Patrick Georgi96510582018-11-16 12:41:19 +0100158 if matched {
Patrick Georgi2e5d6a82018-11-12 17:29:49 +0100159 success = true
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700160 fmt.Println(fname, "is in subsystem",
161 subsystem.name)
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700162 }
163 }
Patrick Georgi2e5d6a82018-11-12 17:29:49 +0100164 if !success {
165 fmt.Println(fname, "has no subsystem defined in MAINTAINERS")
166 }
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700167}
168
Patrick Georgi0cadafa2018-11-12 17:31:48 +0100169// taken from https://github.com/zyedidia/glob/blob/master/glob.go which is
170// Copyright (c) 2016: Zachary Yedidia.
171// and was published under the MIT "Expat" license.
172//
173// only change: return the string, instead of a compiled golang regex
174func glob_to_regex(glob string) string {
175 regex := ""
176 inGroup := 0
177 inClass := 0
178 firstIndexInClass := -1
179 arr := []byte(glob)
180
181 for i := 0; i < len(arr); i++ {
182 ch := arr[i]
183
184 switch ch {
185 case '\\':
186 i++
187 if i >= len(arr) {
188 regex += "\\"
189 } else {
190 next := arr[i]
191 switch next {
192 case ',':
193 // Nothing
194 case 'Q', 'E':
195 regex += "\\\\"
196 default:
197 regex += "\\"
198 }
199 regex += string(next)
200 }
201 case '*':
202 if inClass == 0 {
203 regex += ".*"
204 } else {
205 regex += "*"
206 }
207 case '?':
208 if inClass == 0 {
209 regex += "."
210 } else {
211 regex += "?"
212 }
213 case '[':
214 inClass++
215 firstIndexInClass = i + 1
216 regex += "["
217 case ']':
218 inClass--
219 regex += "]"
220 case '.', '(', ')', '+', '|', '^', '$', '@', '%':
221 if inClass == 0 || (firstIndexInClass == i && ch == '^') {
222 regex += "\\"
223 }
224 regex += string(ch)
225 case '!':
226 if firstIndexInClass == i {
227 regex += "^"
228 } else {
229 regex += "!"
230 }
231 case '{':
232 inGroup++
233 regex += "("
234 case '}':
235 inGroup--
236 regex += ")"
237 case ',':
238 if inGroup > 0 {
239 regex += "|"
240 } else {
241 regex += ","
242 }
243 default:
244 regex += string(ch)
245 }
246 }
247 return "^" + regex + "$"
248}
249
Patrick Georgie874df92018-11-12 18:49:09 +0100250var is_email *regexp.Regexp
251
252func extract_maintainer(maintainer string) string {
253 if is_email == nil {
254 is_email = regexp.MustCompile("<[^>]*>")
255 }
256
257 if match := is_email.FindStringSubmatch(maintainer); match != nil {
258 return match[0][1 : len(match[0])-1]
259 }
260 return maintainer
261}
262
263func do_print_gerrit_rules() {
264 for _, subsystem := range subsystems {
265 if len(subsystem.paths) == 0 || len(subsystem.maintainer) == 0 {
266 continue
267 }
268 fmt.Println("#", subsystem.name)
269 for _, path := range subsystem.paths {
Patrick Georgi571477b2018-11-21 22:07:38 +0100270 fmt.Println("[filter \"file:\\\"" + path_to_regexstr(path) + "\\\"\"]")
Patrick Georgie874df92018-11-12 18:49:09 +0100271 for _, maint := range subsystem.maintainer {
272 fmt.Println(" reviewer =", extract_maintainer(maint))
273 }
274 }
275 fmt.Println()
276 }
277}
278
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700279func main() {
Patrick Georgi62a27382018-11-12 18:48:40 +0100280 var (
Patrick Georgie874df92018-11-12 18:49:09 +0100281 files []string
282 err error
283 print_gerrit_rules = flag.Bool("print-gerrit-rules", false, "emit the MAINTAINERS rules in a format suitable for Gerrit's reviewers plugin")
284 debug = flag.Bool("debug", false, "emit additional debug output")
Patrick Georgi62a27382018-11-12 18:48:40 +0100285 )
286 flag.Parse()
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700287
Patrick Georgi62a27382018-11-12 18:48:40 +0100288 /* get and build subsystem database */
289 maintainers, err := get_maintainers()
290 if err != nil {
291 log.Fatalf("Oops.")
292 return
293 }
294 build_maintainers(maintainers)
295
296 if *debug {
297 print_maintainers()
298 }
299
Patrick Georgie874df92018-11-12 18:49:09 +0100300 if *print_gerrit_rules {
301 do_print_gerrit_rules()
302 return
303 }
304
Patrick Georgi62a27382018-11-12 18:48:40 +0100305 args := flag.Args()
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700306 if len(args) == 0 {
307 /* get the filenames */
308 files, err = get_git_files()
309 if err != nil {
310 log.Fatalf("Oops.")
311 return
312 }
Patrick Georgi62a27382018-11-12 18:48:40 +0100313 for _, file := range files {
314 find_unmaintained(file)
315 }
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700316 } else {
317 files = args
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700318
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700319 /* Find maintainers for each file */
320 for _, file := range files {
321 find_maintainer(file)
322 }
Stefan Reinauer1ecf2522015-10-21 13:12:51 -0700323 }
324}