1 /* SPDX-License-Identifier: GPL-2.0-only */
16 type subsystem
struct {
20 globs
[]*regexp
.Regexp
23 var subsystems
[]subsystem
25 func get_git_files() ([]string, error
) {
28 /* Read in list of all files in the git repository */
29 cmd
:= exec
.Command("git", "ls-files")
30 out
, err
:= cmd
.StdoutPipe()
32 log
.Fatalf("git ls-files failed: %v", err
)
35 if err
:= cmd
.Start(); err
!= nil {
36 log
.Fatalf("Could not start %v: %v", cmd
, err
)
40 r
:= bufio
.NewScanner(out
)
42 /* Cut out leading tab */
43 files
= append(files
, r
.Text())
51 func get_maintainers() ([]string, error
) {
52 var maintainers
[]string
54 /* Read in all maintainers */
55 file
, err
:= os
.Open("MAINTAINERS")
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
64 s
:= bufio
.NewScanner(file
)
66 /* Are we in the "data" section and have a non-empty line? */
67 if keep
&& s
.Text() != "" {
68 maintainers
= append(maintainers
, s
.Text())
70 /* Skip everything before the delimiter */
71 if s
.Text() == "\t\t-----------------------------------" {
76 return maintainers
, nil
79 func path_to_regexstr(path
string) string {
80 /* 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() {
88 regexstr
:= glob_to_regex(path
)
90 /* Handle path with trailing '/' as prefix */
91 if regexstr
[len(regexstr
)-2:] == "/$" {
92 regexstr
= regexstr
[:len(regexstr
)-1] + ".*$"
98 func path_to_regex(path
string) *regexp
.Regexp
{
99 regexstr
:= path_to_regexstr(path
)
100 return regexp
.MustCompile(regexstr
)
103 func build_maintainers(maintainers
[]string) {
104 var current
*subsystem
105 for _
, line
:= range maintainers
{
107 /* Create new subsystem entry */
109 subsystems
= append(subsystems
, tmp
)
110 current
= &subsystems
[len(subsystems
)-1]
115 /* Add subsystem maintainer */
116 current
.maintainer
= append(current
.maintainer
, line
[3:len(line
)])
119 current
.paths
= append(current
.paths
, line
[3:len(line
)])
120 current
.globs
= append(current
.globs
, path_to_regex(line
[3:len(line
)]))
122 case 'L', 'S', 'T', 'W': // ignore
124 fmt
.Println("No such specifier: ", line
)
130 func print_maintainers() {
131 for _
, subsystem
:= range subsystems
{
132 fmt
.Println(subsystem
.name
)
133 fmt
.Println(" ", subsystem
.maintainer
)
134 fmt
.Println(" ", subsystem
.paths
)
138 func match_file(fname
string, component subsystem
) bool {
139 for _
, glob
:= range component
.globs
{
140 if glob
.Match([]byte(fname
)) {
147 func find_maintainer(fname
string) {
150 for _
, subsystem
:= range subsystems
{
151 matched
:= match_file(fname
, subsystem
)
154 fmt
.Println(fname
, "is in subsystem",
156 fmt
.Println("Maintainers: ", strings
.Join(subsystem
.maintainer
, ", "))
160 fmt
.Println(fname
, "has no subsystem defined in MAINTAINERS")
164 func find_unmaintained(fname
string) {
167 for _
, subsystem
:= range subsystems
{
168 matched
:= match_file(fname
, subsystem
)
171 fmt
.Println(fname
, "is in subsystem",
176 fmt
.Println(fname
, "has no subsystem defined in MAINTAINERS")
180 // 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.
184 // only change: return the string, instead of a compiled golang regex
185 func glob_to_regex(glob
string) string {
189 firstIndexInClass
:= -1
192 for i
:= 0; i
< len(arr
); i
++ {
210 regex
+= string(next
)
226 firstIndexInClass
= i
+ 1
231 case '.', '(', ')', '+', '|', '^', '$', '@', '%':
232 if inClass
== 0 ||
(firstIndexInClass
== i
&& ch
== '^') {
237 if firstIndexInClass
== i
{
258 return "^" + regex
+ "$"
261 var is_email
*regexp
.Regexp
263 func extract_maintainer(maintainer
string) string {
265 is_email
= regexp
.MustCompile("<[^>]*>")
268 if match
:= is_email
.FindStringSubmatch(maintainer
); match
!= nil {
269 return match
[0][1 : len(match
[0])-1]
274 func do_print_gerrit_rules() {
275 for _
, subsystem
:= range subsystems
{
276 if len(subsystem
.paths
) == 0 ||
len(subsystem
.maintainer
) == 0 {
279 fmt
.Println("#", subsystem
.name
)
280 for _
, path
:= range subsystem
.paths
{
281 fmt
.Println("[filter \"file:\\\"" + path_to_regexstr(path
) + "\\\"\"]")
282 for _
, maint
:= range subsystem
.maintainer
{
283 fmt
.Println(" reviewer =", extract_maintainer(maint
))
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")
299 /* get and build subsystem database */
300 maintainers
, err
:= get_maintainers()
305 build_maintainers(maintainers
)
311 if *print_gerrit_rules
{
312 do_print_gerrit_rules()
318 /* get the filenames */
319 files
, err
= get_git_files()
324 for _
, file
:= range files
{
325 find_unmaintained(file
)
330 /* Find maintainers for each file */
331 for _
, file
:= range files
{
332 find_maintainer(file
)