From 1ecf252c38dd64dc83c0851abbce03da72d9509d Mon Sep 17 00:00:00 2001 From: Stefan Reinauer Date: Wed, 21 Oct 2015 13:12:51 -0700 Subject: MAINTAINERS: Add script to test database and find maintainers This utility should make it easier to complete and maintain the database of coreboot subsystem maintainers (MAINTAINERS file) This will need a bit of tender love and care to print information in an easily machine readable output for the build system, but its a first start to query the maintainers database. Build with: $ go build util/scripts/maintainers.go Find a maintainer for a set of files with: $ ./maintainers Makefile Makefile.inc Makefile is in subsystem BUILD SYSTEM Maintainers: [Patrick Georgi ] Makefile.inc is in subsystem BUILD SYSTEM Maintainers: [Patrick Georgi ] Check the maintainer database with: $ ./maintainers .gitignore has no subsystem defined in MAINTAINERS .gitmodules has no subsystem defined in MAINTAINERS .gitreview has no subsystem defined in MAINTAINERS 3rdparty/arm-trusted-firmware has no subsystem defined in MAINTAINERS 3rdparty/blobs has no subsystem defined in MAINTAINERS 3rdparty/vboot has no subsystem defined in MAINTAINERS COPYING has no subsystem defined in MAINTAINERS Documentation/AMD-S3.txt has no subsystem defined in MAINTAINERS Documentation/CorebootBuildingGuide.tex has no subsystem defined in MAINTAINERS Documentation/Doxyfile.coreboot has no subsystem defined in MAINTAINERS [..] Change-Id: I49c43911971152b0e4d626ccdeb33c088e362695 Signed-off-by: Stefan Reinauer Reviewed-on: http://review.coreboot.org/12119 Tested-by: build bot (Jenkins) Reviewed-by: Ronald G. Minnich --- util/scripts/maintainers.go | 280 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 280 insertions(+) create mode 100644 util/scripts/maintainers.go (limited to 'util') 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) + } + } +} -- cgit v1.2.3