blob: 03c7709c73869960b1918b5ce4509fa08295f37a [file] [log] [blame]
Stefan Reinauer1ecf2522015-10-21 13:12:51 -07001/*
2 * Copyright 2015 Google Inc.
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; version 2 of the License.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 */
13
14package main
15
16import (
17 "bufio"
18 "fmt"
19 "log"
20 "os"
21 "os/exec"
22 "path/filepath"
23)
24
25type subsystem struct {
26 name string
27 maintainer []string
28 file []string
29}
30
31var subsystems []subsystem
32
33func get_git_files() ([]string, error) {
34 var files []string
35
36 /* Read in list of all files in the git repository */
37 cmd := exec.Command("git", "ls-files")
38 out, err := cmd.StdoutPipe()
39 if err != nil {
40 log.Fatalf("git ls-files failed: %v", err)
41 return files, err
42 }
43 if err := cmd.Start(); err != nil {
44 log.Fatalf("Could not start %v: %v", cmd, err)
45 return files, err
46 }
47
48 r := bufio.NewScanner(out)
49 for r.Scan() {
50 /* Cut out leading tab */
51 files = append(files, r.Text())
52 }
53
54 cmd.Wait()
55
56 return files, nil
57}
58
59func get_maintainers() ([]string, error) {
60 var maintainers []string
61
62 /* Read in all maintainers */
63 file, err := os.Open("MAINTAINERS")
64 if err != nil {
65 log.Fatalf("Can't open MAINTAINERS file: %v", err)
66 log.Fatalf("Are you running from the top-level directory?")
67 return maintainers, err
68 }
69 defer file.Close()
70
71 keep := false
72 s := bufio.NewScanner(file)
73 for s.Scan() {
74 /* Are we in the "data" section and have a non-empty line? */
75 if keep && s.Text() != "" {
76 maintainers = append(maintainers, s.Text())
77 }
78 /* Skip everything before the delimiter */
79 if s.Text() == "\t\t-----------------------------------" {
80 keep = true
81 }
82 }
83
84 return maintainers, nil
85}
86
87func build_maintainers(maintainers []string) {
88 var current *subsystem
89 for _, line := range maintainers {
90 if line[1] != ':' {
91 /* Create new subsystem entry */
92 var tmp subsystem
93 subsystems = append(subsystems, tmp)
94 current = &subsystems[len(subsystems)-1]
95 current.name = line
96 } else {
97 switch line[0] {
98 case 'R':
99 case 'M':
100 {
101 /* Add subsystem maintainer */
102 current.maintainer =
103 append(current.maintainer,
104 line[3:len(line)])
105 break
106 }
107 case 'S':
108 {
109 break
110 }
111 case 'L':
112 {
113 break
114 }
115 case 'T':
116 {
117 break
118 }
119 case 'F':
120 {
121 // add files
122 current.file =
123 append(current.file,
124 line[3:len(line)])
125 break
126 }
127 default:
128 {
129 fmt.Println("No such specifier: ", line)
130 break
131 }
132 }
133 }
134 }
135}
136
137func print_maintainers() {
138 for _, subsystem := range subsystems {
139 fmt.Println(subsystem.name)
140 fmt.Println(" ", subsystem.maintainer)
141 fmt.Println(" ", subsystem.file)
142 }
143}
144
145func match_file(fname string, files []string) (bool, error) {
146 var matched bool
147 var err error
148
149 for _, file := range files {
150 /* Direct match */
151 matched, err = filepath.Match(file, fname)
152 if err != nil {
153 return false, err
154 }
155 if matched {
156 return true, nil
157 }
158
159 /* There are three cases that match_file can handle:
160 *
161 * dirname/filename
162 * dirname/*
163 * dirname/
164 *
165 * The first case is an exact match, the second case is a
166 * direct match of everything in that directory, and the third
167 * is a direct match of everything in that directory and its
168 * subdirectories.
169 *
170 * The first two cases are handled above, the code below is
171 * only for that latter case, so if file doesn't end in /,
172 * skip to the next file.
173 */
174 if file[len(file)-1] != '/' {
175 continue
176 }
177
178 /* Remove / because we add it again below */
179 file = file[:len(file)-1]
180
181 /* Maximum tree depth, as calculated by
182 * $(( `git ls-files | tr -d "[a-z][A-Z][0-9]\-\_\." | \
183 * sort -u | tail -1 | wc -c` - 1 ))
184 * 11
185 */
186 max_depth := 11
187
188 for i := 0; i < max_depth; i++ {
189 /* Subdirectory match */
190 file += "/*"
191
192 if matched, err = filepath.Match(file, fname); err != nil {
193 return false, err
194 }
195 if matched {
196 return true, nil
197 }
198
199 }
200 }
201 return false, nil
202}
203
204func find_maintainer(fname string) {
205 for _, subsystem := range subsystems {
206 matched, err := match_file(fname, subsystem.file)
207 if err != nil {
208 log.Fatalf("match_file failed: %v", err)
209 return
210 }
211 if matched && subsystem.name != "THE REST" {
212 fmt.Println(fname, "is in subsystem",
213 subsystem.name)
214 fmt.Println("Maintainers: ", subsystem.maintainer)
215 return
216 }
217 }
218 fmt.Println(fname, "has no subsystem defined in MAINTAINERS")
219}
220
221func find_unmaintained(fname string) {
222 for _, subsystem := range subsystems {
223 matched, err := match_file(fname, subsystem.file)
224 if err != nil {
225 log.Fatalf("match_file failed: %v", err)
226 return
227 }
228 if matched && subsystem.name != "THE REST" {
229 fmt.Println(fname, "is in subsystem",
230 subsystem.name)
231 return
232 }
233 }
234 fmt.Println(fname, "has no subsystem defined in MAINTAINERS")
235}
236
237func main() {
238 var files []string
239 var maint bool
240 var debug bool
241 var err error
242
243 args := os.Args[1:]
244 if len(args) == 0 {
245 /* get the filenames */
246 files, err = get_git_files()
247 if err != nil {
248 log.Fatalf("Oops.")
249 return
250 }
251 maint = false
252 } else {
253 files = args
254 maint = true
255 }
256
257 maintainers, err := get_maintainers()
258 if err != nil {
259 log.Fatalf("Oops.")
260 return
261 }
262
263 /* build subsystem database */
264 build_maintainers(maintainers)
265
266 if debug {
267 print_maintainers()
268 }
269
270 if maint {
271 /* Find maintainers for each file */
272 for _, file := range files {
273 find_maintainer(file)
274 }
275 } else {
276 for _, file := range files {
277 find_unmaintained(file)
278 }
279 }
280}