| /* |
| * 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" |
| "flag" |
| "fmt" |
| "log" |
| "os" |
| "os/exec" |
| "regexp" |
| "strings" |
| ) |
| |
| type subsystem struct { |
| name string |
| maintainer []string |
| paths []string |
| globs []*regexp.Regexp |
| } |
| |
| 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 path_to_regexstr(path string) string { |
| // if prefix, allow all subdirectories |
| if path[len(path)-1] == '/' { |
| path += "*" |
| } |
| return glob_to_regex(path) |
| } |
| |
| func path_to_regex(path string) *regexp.Regexp { |
| regexstr := path_to_regexstr(path) |
| return regexp.MustCompile(regexstr) |
| } |
| |
| 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', 'M': |
| /* Add subsystem maintainer */ |
| current.maintainer = append(current.maintainer, line[3:len(line)]) |
| case 'F': |
| // add files |
| current.paths = append(current.paths, line[3:len(line)]) |
| current.globs = append(current.globs, path_to_regex(line[3:len(line)])) |
| break |
| case 'L', 'S', 'T', 'W': // ignore |
| default: |
| fmt.Println("No such specifier: ", line) |
| } |
| } |
| } |
| } |
| |
| func print_maintainers() { |
| for _, subsystem := range subsystems { |
| fmt.Println(subsystem.name) |
| fmt.Println(" ", subsystem.maintainer) |
| fmt.Println(" ", subsystem.paths) |
| } |
| } |
| |
| func match_file(fname string, component subsystem) bool { |
| for _, glob := range component.globs { |
| if glob.Match([]byte(fname)) { |
| return true |
| } |
| } |
| return false |
| } |
| |
| func find_maintainer(fname string) { |
| var success bool |
| |
| for _, subsystem := range subsystems { |
| matched := match_file(fname, subsystem) |
| if matched { |
| success = true |
| fmt.Println(fname, "is in subsystem", |
| subsystem.name) |
| fmt.Println("Maintainers: ", strings.Join(subsystem.maintainer, ", ")) |
| } |
| } |
| if !success { |
| fmt.Println(fname, "has no subsystem defined in MAINTAINERS") |
| } |
| } |
| |
| func find_unmaintained(fname string) { |
| var success bool |
| |
| for _, subsystem := range subsystems { |
| matched := match_file(fname, subsystem) |
| if matched { |
| success = true |
| fmt.Println(fname, "is in subsystem", |
| subsystem.name) |
| } |
| } |
| if !success { |
| fmt.Println(fname, "has no subsystem defined in MAINTAINERS") |
| } |
| } |
| |
| // taken from https://github.com/zyedidia/glob/blob/master/glob.go which is |
| // Copyright (c) 2016: Zachary Yedidia. |
| // and was published under the MIT "Expat" license. |
| // |
| // only change: return the string, instead of a compiled golang regex |
| func glob_to_regex(glob string) string { |
| regex := "" |
| inGroup := 0 |
| inClass := 0 |
| firstIndexInClass := -1 |
| arr := []byte(glob) |
| |
| for i := 0; i < len(arr); i++ { |
| ch := arr[i] |
| |
| switch ch { |
| case '\\': |
| i++ |
| if i >= len(arr) { |
| regex += "\\" |
| } else { |
| next := arr[i] |
| switch next { |
| case ',': |
| // Nothing |
| case 'Q', 'E': |
| regex += "\\\\" |
| default: |
| regex += "\\" |
| } |
| regex += string(next) |
| } |
| case '*': |
| if inClass == 0 { |
| regex += ".*" |
| } else { |
| regex += "*" |
| } |
| case '?': |
| if inClass == 0 { |
| regex += "." |
| } else { |
| regex += "?" |
| } |
| case '[': |
| inClass++ |
| firstIndexInClass = i + 1 |
| regex += "[" |
| case ']': |
| inClass-- |
| regex += "]" |
| case '.', '(', ')', '+', '|', '^', '$', '@', '%': |
| if inClass == 0 || (firstIndexInClass == i && ch == '^') { |
| regex += "\\" |
| } |
| regex += string(ch) |
| case '!': |
| if firstIndexInClass == i { |
| regex += "^" |
| } else { |
| regex += "!" |
| } |
| case '{': |
| inGroup++ |
| regex += "(" |
| case '}': |
| inGroup-- |
| regex += ")" |
| case ',': |
| if inGroup > 0 { |
| regex += "|" |
| } else { |
| regex += "," |
| } |
| default: |
| regex += string(ch) |
| } |
| } |
| return "^" + regex + "$" |
| } |
| |
| var is_email *regexp.Regexp |
| |
| func extract_maintainer(maintainer string) string { |
| if is_email == nil { |
| is_email = regexp.MustCompile("<[^>]*>") |
| } |
| |
| if match := is_email.FindStringSubmatch(maintainer); match != nil { |
| return match[0][1 : len(match[0])-1] |
| } |
| return maintainer |
| } |
| |
| func do_print_gerrit_rules() { |
| for _, subsystem := range subsystems { |
| if len(subsystem.paths) == 0 || len(subsystem.maintainer) == 0 { |
| continue |
| } |
| fmt.Println("#", subsystem.name) |
| for _, path := range subsystem.paths { |
| fmt.Println("[filter \"file:\\\"" + path_to_regexstr(path) + "\\\"\"]") |
| for _, maint := range subsystem.maintainer { |
| fmt.Println(" reviewer =", extract_maintainer(maint)) |
| } |
| } |
| fmt.Println() |
| } |
| } |
| |
| func main() { |
| var ( |
| files []string |
| err error |
| print_gerrit_rules = flag.Bool("print-gerrit-rules", false, "emit the MAINTAINERS rules in a format suitable for Gerrit's reviewers plugin") |
| debug = flag.Bool("debug", false, "emit additional debug output") |
| ) |
| flag.Parse() |
| |
| /* get and build subsystem database */ |
| maintainers, err := get_maintainers() |
| if err != nil { |
| log.Fatalf("Oops.") |
| return |
| } |
| build_maintainers(maintainers) |
| |
| if *debug { |
| print_maintainers() |
| } |
| |
| if *print_gerrit_rules { |
| do_print_gerrit_rules() |
| return |
| } |
| |
| args := flag.Args() |
| if len(args) == 0 { |
| /* get the filenames */ |
| files, err = get_git_files() |
| if err != nil { |
| log.Fatalf("Oops.") |
| return |
| } |
| for _, file := range files { |
| find_unmaintained(file) |
| } |
| } else { |
| files = args |
| |
| /* Find maintainers for each file */ |
| for _, file := range files { |
| find_maintainer(file) |
| } |
| } |
| } |