obmc-scripts: Add maintainers
maintainers is a python library and collection of scripts for
parsing and generating OpenBMC MAINTAINERS files. The culmination of the
effort is the `obmc-gerrit` git wrapper that automatically adds people
listed in the per-repository MAINTAINERS file as reviewers on changes
pushed to Gerrit.
Change-Id: I4a3c3efc5899b80a65836c1ad948ec1153dd6796
Signed-off-by: Andrew Jeffery <andrew@aj.id.au>
diff --git a/amboar/obmc-scripts/maintainers/split_maintainers.py b/amboar/obmc-scripts/maintainers/split_maintainers.py
new file mode 100755
index 0000000..bf0a143
--- /dev/null
+++ b/amboar/obmc-scripts/maintainers/split_maintainers.py
@@ -0,0 +1,178 @@
+#!/usr/bin/env python3
+#
+# SPDX-License-Identifier: Apache-2.0
+# Copyright (C) 2018 IBM Corp.
+
+import argparse
+import sh
+import os
+import maintainers
+from pprint import pprint
+import requests
+import json
+from typing import List, Dict, Union, cast, Iterator
+import sys
+import itertools
+
+git = sh.git.bake()
+
+mailmap = {
+ 'andrewg@us.ibm.com' : 'geissonator@yahoo.com',
+}
+
+def gerrit_url(name: str, user: str) -> str:
+ return "ssh://{}@gerrit.openbmc-project.xyz:29418/openbmc/{}".format(user, name)
+
+def gerrit_push_args(reviewers: Iterator[maintainers.Identity]) -> str:
+ addrs = (i.email.address for i in reviewers)
+ maddrs = (mailmap[a] if a in mailmap else a for a in addrs)
+ return ','.join("r={}".format(ma) for ma in maddrs)
+
+def gerrit_push(name: str, user: str, reviewers: Iterator[maintainers.Identity]) -> None:
+ refspec = 'HEAD:refs/for/master/maintainers%{}'.format(gerrit_push_args(reviewers))
+ git.push(gerrit_url(name, user), refspec)
+
+def org_repos_url(name) -> str:
+ return "https://api.github.com/users/{}/repos?per_page=100".format(name)
+
+V = Union[Dict[str, str], str]
+E = Dict[str, V]
+R = List[E]
+
+def org_repos(name: str) -> R:
+ r = requests.get(org_repos_url(name))
+ if not r.ok:
+ raise ValueError("Bad organisation name")
+ return json.loads(r.text or r.content)
+
+def git_reset_upstream(name: str) -> None:
+ cwd = os.getcwd()
+ os.chdir(name)
+ git.fetch("origin")
+ git.reset("--hard", "origin/master")
+ os.chdir(cwd)
+
+def ensure_org_repo(name: str, user: str) -> str:
+ if os.path.exists(os.path.join(name, ".git")):
+ # git_reset_upstream(name)
+ pass
+ else:
+ git.clone(gerrit_url(name, user), name)
+ scp_src = "{}@gerrit.openbmc-project.xyz:hooks/commit-msg".format(user)
+ scp_dst = "{}/.git/hooks/".format(name)
+ sh.scp("-p", "-P", 29418, scp_src, scp_dst)
+ return name
+
+def repo_url(name: str) -> str:
+ return "https://github.com/openbmc/{}.git".format(name)
+
+def ensure_repo(name: str) -> str:
+ if os.path.exists(os.path.join(name, ".git")):
+ # git_reset_upstream(name)
+ pass
+ else:
+ git.clone(repo_url(name), name)
+ return name
+
+preamble_text = """\
+How to use this list:
+ Find the most specific section entry (described below) that matches where
+ your change lives and add the reviewers (R) and maintainers (M) as
+ reviewers. You can use the same method to track down who knows a particular
+ code base best.
+
+ Your change/query may span multiple entries; that is okay.
+
+ If you do not find an entry that describes your request at all, someone
+ forgot to update this list; please at least file an issue or send an email
+ to a maintainer, but preferably you should just update this document.
+
+Description of section entries:
+
+ Section entries are structured according to the following scheme:
+
+ X: NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>
+ X: ...
+ .
+ .
+ .
+
+ Where REPO_NAME is the name of the repository within the OpenBMC GitHub
+ organization; FILE_PATH is a file path within the repository, possibly with
+ wildcards; X is a tag of one of the following types:
+
+ M: Denotes maintainer; has fields NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>;
+ if omitted from an entry, assume one of the maintainers from the
+ MAINTAINERS entry.
+ R: Denotes reviewer; has fields NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>;
+ these people are to be added as reviewers for a change matching the repo
+ path.
+ F: Denotes forked from an external repository; has fields URL.
+
+ Line comments are to be denoted "# SOME COMMENT" (typical shell style
+ comment); it is important to follow the correct syntax and semantics as we
+ may want to use automated tools with this file in the future.
+
+ A change cannot be added to an OpenBMC repository without a MAINTAINER's
+ approval; thus, a MAINTAINER should always be listed as a reviewer.
+
+START OF MAINTAINERS LIST
+-------------------------
+
+"""
+
+def generate_maintainers_change(name: str, block: maintainers.B,
+ default: maintainers.B, user: str) -> None:
+ cwd = os.getcwd()
+ os.chdir(name)
+ mpath = "MAINTAINERS"
+ try:
+ if os.path.exists(mpath):
+ print("{} already exists, skipping".format(mpath))
+ return
+ with open(mpath, 'w') as m:
+ m.write(preamble_text)
+ maintainers.assemble_block(block, default, m)
+ git.add(mpath)
+ git.commit("-s", "-m", "Add {} file".format(mpath), _out=sys.stdout)
+ with open(mpath, 'r') as m:
+ maintainers.trash_preamble(m)
+ block = maintainers.parse_block(m)
+ pprint(block)
+ audience = cast(List[maintainers.Identity],
+ block[maintainers.LineType.MAINTAINER][:])
+ if maintainers.LineType.REVIEWER in block:
+ reviewers = cast(List[maintainers.Identity],
+ block[maintainers.LineType.REVIEWER])
+ audience.extend(reviewers)
+ gerrit_push(name, user, iter(audience))
+ finally:
+ os.chdir(cwd)
+
+def main() -> None:
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--organisation", type=str, default="openbmc")
+ parser.add_argument("--user", type=str, default="amboar")
+ args = parser.parse_args()
+ ensure_repo("docs")
+ with open('docs/MAINTAINERS', 'r') as mfile:
+ mast = maintainers.parse_maintainers(mfile)
+
+ # Don't leak the generic comment into the repo-specific MAINTAINERS file
+ del mast['MAINTAINERS'][maintainers.LineType.COMMENT]
+
+ for e in org_repos(args.organisation):
+ print("Ensuring MAINTAINERS for {}".format(e['name']))
+ name = cast(str, e['name'])
+ try:
+ ensure_org_repo(name, args.user)
+ default = mast['MAINTAINERS']
+ block = mast[name] if name in mast else default
+ if not maintainers.LineType.FORKED in block:
+ generate_maintainers_change(name, block, default, args.user)
+ except sh.ErrorReturnCode_128:
+ print("{} has not been imported into Gerrit, skipping".format(name))
+ print()
+
+if __name__ == "__main__":
+ main()