1#!/bin/env python3 2import argparse 3import json 4import yaml 5 6from typing import List, TypedDict 7from yaml.loader import SafeLoader 8 9# A list of Gerrit users (email addresses). 10UsersList = List[str] 11 12# A YAML node with an extra line number. 13class NumberedNode(TypedDict): 14 line_number: int 15 16 17# The root YAML node of an OWNERS file 18class OwnersData(NumberedNode, TypedDict, total=False): 19 owners: UsersList 20 reviewers: UsersList 21 22 23# A YAML loader that adds the start line number onto each node (for 24# later linting support) 25class YamlLoader(SafeLoader): 26 def construct_mapping( 27 self, node: yaml.nodes.Node, deep: bool = False 28 ) -> NumberedNode: 29 mapping: NumberedNode = super(YamlLoader, self).construct_mapping( 30 node, deep=deep 31 ) # type: ignore 32 mapping["line_number"] = node.start_mark.line + 1 33 return mapping 34 35 # Load a file and return the OwnersData. 36 @staticmethod 37 def load(file: str) -> OwnersData: 38 data: OwnersData 39 with open(file, "r") as f: 40 data = yaml.load(f, Loader=YamlLoader) 41 return data 42 43 44# Class to match commit information with OWNERS files. 45# TODO: git commit piece not yet supported. 46class CommitMatch: 47 def __init__(self, owners: OwnersData): 48 self.data = owners 49 50 def owners(self) -> UsersList: 51 return self.data["owners"] if "owners" in self.data else [] 52 53 def reviewers(self) -> UsersList: 54 return self.data["reviewers"] if "reviewers" in self.data else [] 55 56 57# The subcommand to get the reviewers. 58def subcmd_reviewers(args: argparse.Namespace, data: OwnersData) -> None: 59 matcher = CommitMatch(data) 60 61 # Print in `git push refs/for/branch%<reviewers>` format. 62 if args.push_args: 63 result = [] 64 for o in matcher.owners(): 65 # Gerrit uses 'r' for the required reviewers (owners). 66 result.append(f"r={o}") 67 for r in matcher.reviewers(): 68 # Gerrit uses 'cc' for the optional reviewers. 69 result.append(f"cc={r}") 70 print(",".join(result)) 71 # Print as Gerrit Add Reviewers POST format. 72 # https://gerrit.openbmc.org/Documentation/rest-api-changes.html#add-reviewer 73 else: 74 for o in matcher.owners(): 75 print(json.dumps({"reviewer": o, "state": "REVIEWER"})) 76 for r in matcher.reviewers(): 77 print(json.dumps({"reviewer": r, "state": "CC"})) 78 79 80def main() -> None: 81 parser = argparse.ArgumentParser() 82 parser.add_argument( 83 "-p", "--path", default=".", help="Root path to analyse" 84 ) 85 subparsers = parser.add_subparsers() 86 87 parser_reviewers = subparsers.add_parser( 88 "reviewers", help="Generate List of Reviewers" 89 ) 90 parser_reviewers.add_argument( 91 "--push-args", 92 action=argparse.BooleanOptionalAction, 93 help="Format as git push options", 94 ) 95 parser_reviewers.set_defaults(func=subcmd_reviewers) 96 97 args = parser.parse_args() 98 99 file = YamlLoader.load(args.path + "/OWNERS") 100 args.func(args, file) 101 102 103if __name__ == "__main__": 104 main() 105