blob: bf0a1439c27a1cdf53085e7ec890a6b421619b7a [file] [log] [blame]
Andrew Jefferyf4019fe2018-05-18 16:42:18 +09301#!/usr/bin/env python3
2#
3# SPDX-License-Identifier: Apache-2.0
4# Copyright (C) 2018 IBM Corp.
5
6import argparse
7import sh
8import os
9import maintainers
10from pprint import pprint
11import requests
12import json
13from typing import List, Dict, Union, cast, Iterator
14import sys
15import itertools
16
17git = sh.git.bake()
18
19mailmap = {
20 'andrewg@us.ibm.com' : 'geissonator@yahoo.com',
21}
22
23def gerrit_url(name: str, user: str) -> str:
24 return "ssh://{}@gerrit.openbmc-project.xyz:29418/openbmc/{}".format(user, name)
25
26def gerrit_push_args(reviewers: Iterator[maintainers.Identity]) -> str:
27 addrs = (i.email.address for i in reviewers)
28 maddrs = (mailmap[a] if a in mailmap else a for a in addrs)
29 return ','.join("r={}".format(ma) for ma in maddrs)
30
31def gerrit_push(name: str, user: str, reviewers: Iterator[maintainers.Identity]) -> None:
32 refspec = 'HEAD:refs/for/master/maintainers%{}'.format(gerrit_push_args(reviewers))
33 git.push(gerrit_url(name, user), refspec)
34
35def org_repos_url(name) -> str:
36 return "https://api.github.com/users/{}/repos?per_page=100".format(name)
37
38V = Union[Dict[str, str], str]
39E = Dict[str, V]
40R = List[E]
41
42def org_repos(name: str) -> R:
43 r = requests.get(org_repos_url(name))
44 if not r.ok:
45 raise ValueError("Bad organisation name")
46 return json.loads(r.text or r.content)
47
48def git_reset_upstream(name: str) -> None:
49 cwd = os.getcwd()
50 os.chdir(name)
51 git.fetch("origin")
52 git.reset("--hard", "origin/master")
53 os.chdir(cwd)
54
55def ensure_org_repo(name: str, user: str) -> str:
56 if os.path.exists(os.path.join(name, ".git")):
57 # git_reset_upstream(name)
58 pass
59 else:
60 git.clone(gerrit_url(name, user), name)
61 scp_src = "{}@gerrit.openbmc-project.xyz:hooks/commit-msg".format(user)
62 scp_dst = "{}/.git/hooks/".format(name)
63 sh.scp("-p", "-P", 29418, scp_src, scp_dst)
64 return name
65
66def repo_url(name: str) -> str:
67 return "https://github.com/openbmc/{}.git".format(name)
68
69def ensure_repo(name: str) -> str:
70 if os.path.exists(os.path.join(name, ".git")):
71 # git_reset_upstream(name)
72 pass
73 else:
74 git.clone(repo_url(name), name)
75 return name
76
77preamble_text = """\
78How to use this list:
79 Find the most specific section entry (described below) that matches where
80 your change lives and add the reviewers (R) and maintainers (M) as
81 reviewers. You can use the same method to track down who knows a particular
82 code base best.
83
84 Your change/query may span multiple entries; that is okay.
85
86 If you do not find an entry that describes your request at all, someone
87 forgot to update this list; please at least file an issue or send an email
88 to a maintainer, but preferably you should just update this document.
89
90Description of section entries:
91
92 Section entries are structured according to the following scheme:
93
94 X: NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>
95 X: ...
96 .
97 .
98 .
99
100 Where REPO_NAME is the name of the repository within the OpenBMC GitHub
101 organization; FILE_PATH is a file path within the repository, possibly with
102 wildcards; X is a tag of one of the following types:
103
104 M: Denotes maintainer; has fields NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>;
105 if omitted from an entry, assume one of the maintainers from the
106 MAINTAINERS entry.
107 R: Denotes reviewer; has fields NAME <EMAIL_USERNAME@DOMAIN> <IRC_USERNAME!>;
108 these people are to be added as reviewers for a change matching the repo
109 path.
110 F: Denotes forked from an external repository; has fields URL.
111
112 Line comments are to be denoted "# SOME COMMENT" (typical shell style
113 comment); it is important to follow the correct syntax and semantics as we
114 may want to use automated tools with this file in the future.
115
116 A change cannot be added to an OpenBMC repository without a MAINTAINER's
117 approval; thus, a MAINTAINER should always be listed as a reviewer.
118
119START OF MAINTAINERS LIST
120-------------------------
121
122"""
123
124def generate_maintainers_change(name: str, block: maintainers.B,
125 default: maintainers.B, user: str) -> None:
126 cwd = os.getcwd()
127 os.chdir(name)
128 mpath = "MAINTAINERS"
129 try:
130 if os.path.exists(mpath):
131 print("{} already exists, skipping".format(mpath))
132 return
133 with open(mpath, 'w') as m:
134 m.write(preamble_text)
135 maintainers.assemble_block(block, default, m)
136 git.add(mpath)
137 git.commit("-s", "-m", "Add {} file".format(mpath), _out=sys.stdout)
138 with open(mpath, 'r') as m:
139 maintainers.trash_preamble(m)
140 block = maintainers.parse_block(m)
141 pprint(block)
142 audience = cast(List[maintainers.Identity],
143 block[maintainers.LineType.MAINTAINER][:])
144 if maintainers.LineType.REVIEWER in block:
145 reviewers = cast(List[maintainers.Identity],
146 block[maintainers.LineType.REVIEWER])
147 audience.extend(reviewers)
148 gerrit_push(name, user, iter(audience))
149 finally:
150 os.chdir(cwd)
151
152def main() -> None:
153 parser = argparse.ArgumentParser()
154 parser.add_argument("--organisation", type=str, default="openbmc")
155 parser.add_argument("--user", type=str, default="amboar")
156 args = parser.parse_args()
157 ensure_repo("docs")
158 with open('docs/MAINTAINERS', 'r') as mfile:
159 mast = maintainers.parse_maintainers(mfile)
160
161 # Don't leak the generic comment into the repo-specific MAINTAINERS file
162 del mast['MAINTAINERS'][maintainers.LineType.COMMENT]
163
164 for e in org_repos(args.organisation):
165 print("Ensuring MAINTAINERS for {}".format(e['name']))
166 name = cast(str, e['name'])
167 try:
168 ensure_org_repo(name, args.user)
169 default = mast['MAINTAINERS']
170 block = mast[name] if name in mast else default
171 if not maintainers.LineType.FORKED in block:
172 generate_maintainers_change(name, block, default, args.user)
173 except sh.ErrorReturnCode_128:
174 print("{} has not been imported into Gerrit, skipping".format(name))
175 print()
176
177if __name__ == "__main__":
178 main()