summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--util/scripts/maintainers.go280
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)
+ }
+ }
+}