diff options
Diffstat (limited to 'util/scripts')
-rw-r--r-- | util/scripts/maintainers.go | 280 |
1 files changed, 280 insertions, 0 deletions
diff --git a/util/scripts/maintainers.go b/util/scripts/maintainers.go new file mode 100644 index 0000000000..03c7709c73 --- /dev/null +++ b/util/scripts/maintainers.go @@ -0,0 +1,280 @@ +/* + * Copyright 2015 Google Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +package main + +import ( + "bufio" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" +) + +type subsystem struct { + name string + maintainer []string + file []string +} + +var subsystems []subsystem + +func get_git_files() ([]string, error) { + var files []string + + /* Read in list of all files in the git repository */ + cmd := exec.Command("git", "ls-files") + out, err := cmd.StdoutPipe() + if err != nil { + log.Fatalf("git ls-files failed: %v", err) + return files, err + } + if err := cmd.Start(); err != nil { + log.Fatalf("Could not start %v: %v", cmd, err) + return files, err + } + + r := bufio.NewScanner(out) + for r.Scan() { + /* Cut out leading tab */ + files = append(files, r.Text()) + } + + cmd.Wait() + + return files, nil +} + +func get_maintainers() ([]string, error) { + var maintainers []string + + /* Read in all maintainers */ + file, err := os.Open("MAINTAINERS") + if err != nil { + log.Fatalf("Can't open MAINTAINERS file: %v", err) + log.Fatalf("Are you running from the top-level directory?") + return maintainers, err + } + defer file.Close() + + keep := false + s := bufio.NewScanner(file) + for s.Scan() { + /* Are we in the "data" section and have a non-empty line? */ + if keep && s.Text() != "" { + maintainers = append(maintainers, s.Text()) + } + /* Skip everything before the delimiter */ + if s.Text() == "\t\t-----------------------------------" { + keep = true + } + } + + return maintainers, nil +} + +func build_maintainers(maintainers []string) { + var current *subsystem + for _, line := range maintainers { + if line[1] != ':' { + /* Create new subsystem entry */ + var tmp subsystem + subsystems = append(subsystems, tmp) + current = &subsystems[len(subsystems)-1] + current.name = line + } else { + switch line[0] { + case 'R': + case 'M': + { + /* Add subsystem maintainer */ + current.maintainer = + append(current.maintainer, + line[3:len(line)]) + break + } + case 'S': + { + break + } + case 'L': + { + break + } + case 'T': + { + break + } + case 'F': + { + // add files + current.file = + append(current.file, + line[3:len(line)]) + break + } + default: + { + fmt.Println("No such specifier: ", line) + break + } + } + } + } +} + +func print_maintainers() { + for _, subsystem := range subsystems { + fmt.Println(subsystem.name) + fmt.Println(" ", subsystem.maintainer) + fmt.Println(" ", subsystem.file) + } +} + +func match_file(fname string, files []string) (bool, error) { + var matched bool + var err error + + for _, file := range files { + /* Direct match */ + matched, err = filepath.Match(file, fname) + if err != nil { + return false, err + } + if matched { + return true, nil + } + + /* There are three cases that match_file can handle: + * + * dirname/filename + * dirname/* + * dirname/ + * + * The first case is an exact match, the second case is a + * direct match of everything in that directory, and the third + * is a direct match of everything in that directory and its + * subdirectories. + * + * The first two cases are handled above, the code below is + * only for that latter case, so if file doesn't end in /, + * skip to the next file. + */ + if file[len(file)-1] != '/' { + continue + } + + /* Remove / because we add it again below */ + file = file[:len(file)-1] + + /* Maximum tree depth, as calculated by + * $(( `git ls-files | tr -d "[a-z][A-Z][0-9]\-\_\." | \ + * sort -u | tail -1 | wc -c` - 1 )) + * 11 + */ + max_depth := 11 + + for i := 0; i < max_depth; i++ { + /* Subdirectory match */ + file += "/*" + + if matched, err = filepath.Match(file, fname); err != nil { + return false, err + } + if matched { + return true, nil + } + + } + } + return false, nil +} + +func find_maintainer(fname string) { + for _, subsystem := range subsystems { + matched, err := match_file(fname, subsystem.file) + if err != nil { + log.Fatalf("match_file failed: %v", err) + return + } + if matched && subsystem.name != "THE REST" { + fmt.Println(fname, "is in subsystem", + subsystem.name) + fmt.Println("Maintainers: ", subsystem.maintainer) + return + } + } + fmt.Println(fname, "has no subsystem defined in MAINTAINERS") +} + +func find_unmaintained(fname string) { + for _, subsystem := range subsystems { + matched, err := match_file(fname, subsystem.file) + if err != nil { + log.Fatalf("match_file failed: %v", err) + return + } + if matched && subsystem.name != "THE REST" { + fmt.Println(fname, "is in subsystem", + subsystem.name) + return + } + } + fmt.Println(fname, "has no subsystem defined in MAINTAINERS") +} + +func main() { + var files []string + var maint bool + var debug bool + var err error + + args := os.Args[1:] + if len(args) == 0 { + /* get the filenames */ + files, err = get_git_files() + if err != nil { + log.Fatalf("Oops.") + return + } + maint = false + } else { + files = args + maint = true + } + + maintainers, err := get_maintainers() + if err != nil { + log.Fatalf("Oops.") + return + } + + /* build subsystem database */ + build_maintainers(maintainers) + + if debug { + print_maintainers() + } + + if maint { + /* Find maintainers for each file */ + for _, file := range files { + find_maintainer(file) + } + } else { + for _, file := range files { + find_unmaintained(file) + } + } +} |