diff options
Diffstat (limited to 'scripts/rfr')
-rwxr-xr-x | scripts/rfr | 233 |
1 files changed, 233 insertions, 0 deletions
diff --git a/scripts/rfr b/scripts/rfr new file mode 100755 index 00000000..bca32d3b --- /dev/null +++ b/scripts/rfr | |||
@@ -0,0 +1,233 @@ | |||
1 | #!/usr/bin/python | ||
2 | # | ||
3 | # Simple script for formatting RFR messages for nvgpu changes. | ||
4 | # | ||
5 | |||
6 | import os | ||
7 | import re | ||
8 | import json | ||
9 | import argparse | ||
10 | import subprocess | ||
11 | |||
12 | VERSION = '1.0.0' | ||
13 | |||
14 | # Gerrit commit URL formats. These are regular expressions to match the | ||
15 | # incoming URLs against. | ||
16 | gr_fmts = [ r'http://git-master/r/(\d+)', | ||
17 | r'http://git-master/r/#/c/(\d+)/', | ||
18 | r'http://git-master.nvidia.com/r/(\d+)', | ||
19 | r'http://git-master.nvidia.com/r/#/c/(\d+)/', | ||
20 | r'https://git-master/r/(\d+)', | ||
21 | r'https://git-master/r/#/c/(\d+)/', | ||
22 | r'https://git-master.nvidia.com/r/(\d+)', | ||
23 | r'https://git-master.nvidia.com/r/#/c/(\d+)/' ] | ||
24 | |||
25 | # The user to use. May be overridden but the default comes from the environment. | ||
26 | user = os.environ['USER'] | ||
27 | |||
28 | # Gerrit query command to obtain the patch URL. The substitution will be the | ||
29 | # gerrit Change-ID parsed from the git commit message. | ||
30 | gr_query_cmd = 'ssh %s@git-master -p 29418 gerrit query --format json %s' | ||
31 | |||
32 | def parse_args(): | ||
33 | """ | ||
34 | Parse arguments to rfr. | ||
35 | """ | ||
36 | |||
37 | ep="""This program will format commit messages into something that can be | ||
38 | sent to the nvgpu mailing list for review | ||
39 | """ | ||
40 | help_msg="""Git or gerrit commits to describe. Can be either a git or gerrit | ||
41 | commit ID. If the ID starts with a 'I' then it will be treated as a gerrit ID. | ||
42 | If the commit ID looks like a gerrit URL then it is treated as a gerrit URL. | ||
43 | Otherwise it's treated as a git commit ID. | ||
44 | """ | ||
45 | parser = argparse.ArgumentParser(description='RFR formatting tool', | ||
46 | epilog=ep) | ||
47 | |||
48 | parser.add_argument('-V', '--version', action='store_true', default=False, | ||
49 | help='print the program version') | ||
50 | parser.add_argument('-m', '--msg', action='store', default=None, | ||
51 | help='Custom message to add to the RFR email') | ||
52 | |||
53 | # Positionals: the gerrit URLs. | ||
54 | parser.add_argument('commits', metavar='Commit-IDs', | ||
55 | nargs='+', | ||
56 | help=help_msg) | ||
57 | |||
58 | arg_parser = parser.parse_args() | ||
59 | |||
60 | return arg_parser | ||
61 | |||
62 | def get_gerrit_url_id(cmt): | ||
63 | """ | ||
64 | Determines if the passed cmt is a gerrit commit URL. If it is then this | ||
65 | returns the URL ID; otherwise it returns None. | ||
66 | """ | ||
67 | |||
68 | for fmt in gr_fmts: | ||
69 | p = re.compile(fmt) | ||
70 | m = p.search(cmt) | ||
71 | if m: | ||
72 | return m.group(1) | ||
73 | |||
74 | return None | ||
75 | |||
76 | def gerrit_query(change): | ||
77 | """ | ||
78 | Query gerrit for the JSON change information. Return a python object | ||
79 | describing the JSON data. | ||
80 | |||
81 | change can either be a Change-Id or the numeric change number from a URL. | ||
82 | |||
83 | Note there is an interesting limitation with this: gerrit can have multiple | ||
84 | changes with the same Change-Id (./sigh). So if you query a change ID that | ||
85 | points to multiple changes you get back all of them. | ||
86 | |||
87 | This script just uses the first. Ideally one could filter by branch or by | ||
88 | some other distinguishing factor. | ||
89 | """ | ||
90 | |||
91 | query_cmd = gr_query_cmd % (user, change) | ||
92 | |||
93 | prog = subprocess.Popen(query_cmd, shell=True, | ||
94 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||
95 | |||
96 | stdout_data, stderr_data = prog.communicate() | ||
97 | if prog.returncode != 0: | ||
98 | print('`%s\' failed!' % query_cmd) | ||
99 | return False | ||
100 | |||
101 | commit = json.loads(stdout_data.decode('utf-8').splitlines()[0]) | ||
102 | if 'id' not in commit: | ||
103 | print('%s is not a gerrit commit!?' % change) | ||
104 | print('Most likely you need to push the change.') | ||
105 | return None | ||
106 | |||
107 | return commit | ||
108 | |||
109 | def commit_info_from_gerrit_change_id(change_id): | ||
110 | """ | ||
111 | Return a dict with all the gerrit info from a gerrit change ID. | ||
112 | """ | ||
113 | |||
114 | return gerrit_query(change_id) | ||
115 | |||
116 | def commit_info_from_git_sha1(rev): | ||
117 | """ | ||
118 | Return a dict with all the gerrit info from a git sha1 rev. | ||
119 | """ | ||
120 | |||
121 | return gerrit_query(rev) | ||
122 | |||
123 | def commit_info_from_gerrit_cl(cmt): | ||
124 | """ | ||
125 | Return a dict with all the gerrit info from a Gerrit URL. | ||
126 | """ | ||
127 | |||
128 | cl = get_gerrit_url_id(cmt) | ||
129 | if not cl: | ||
130 | return None | ||
131 | |||
132 | return gerrit_query(cl) | ||
133 | |||
134 | def git_sha1_from_commit(commit_ish): | ||
135 | """ | ||
136 | Return sha1 revision from a commit-ish string, or None if this doesn't | ||
137 | appear to be a commit. | ||
138 | """ | ||
139 | |||
140 | prog = subprocess.Popen('git rev-parse %s' % commit_ish, shell=True, | ||
141 | stdout=subprocess.PIPE, stderr=subprocess.PIPE) | ||
142 | |||
143 | stdout_data, stderr_data = prog.communicate() | ||
144 | if prog.returncode != 0: | ||
145 | print('`git rev-parse %s\' failed?!' % commit_ish) | ||
146 | return None | ||
147 | |||
148 | return stdout_data.decode('utf-8') | ||
149 | |||
150 | def indent_lines(text, ind): | ||
151 | """ | ||
152 | Prepend each new line in the passed text with ind. | ||
153 | """ | ||
154 | return ''.join(ind + l + '\n' for l in text.splitlines()) | ||
155 | |||
156 | def display_commits(commits_info, extra_message): | ||
157 | """ | ||
158 | Takes a list of the commit info objects to print. | ||
159 | """ | ||
160 | |||
161 | whole_template = """ | ||
162 | Hi All, | ||
163 | |||
164 | I would like you to review the following changes. | ||
165 | {extra_message} | ||
166 | {cmt_descriptions} | ||
167 | Thanks! | ||
168 | {cmt_verbose}""" | ||
169 | |||
170 | cmt_template = """ | ||
171 | +---------------------------------------- | ||
172 | | {url} | ||
173 | | Author: {author} | ||
174 | |||
175 | {cmtmsg}""" | ||
176 | |||
177 | cmt_descriptions = '' | ||
178 | for c in commits_info: | ||
179 | cmt_descriptions += " %s - %s\n" % (c['url'], c['subject']) | ||
180 | |||
181 | # Add new lines around the extra_message, if applicable. Otherwise we don't | ||
182 | # want anything to show up for extra_message. | ||
183 | if extra_message: | ||
184 | extra_message = '\n%s\n' % extra_message | ||
185 | else: | ||
186 | extra_message = '' | ||
187 | |||
188 | cmt_verbose = '' | ||
189 | for c in commits_info: | ||
190 | cmt_verbose += cmt_template.format(url=c['url'], | ||
191 | author=c['owner']['name'], | ||
192 | cmtmsg=indent_lines( | ||
193 | c['commitMessage'], ' ')) | ||
194 | |||
195 | print(whole_template.format(cmt_descriptions=cmt_descriptions, | ||
196 | extra_message=extra_message, | ||
197 | cmt_verbose=cmt_verbose)) | ||
198 | |||
199 | def main(): | ||
200 | """ | ||
201 | The magic happens here. | ||
202 | """ | ||
203 | |||
204 | arg_parser = parse_args() | ||
205 | commits_info = [ ] | ||
206 | |||
207 | if arg_parser.version: | ||
208 | print('Version: %s' % VERSION) | ||
209 | exit(0) | ||
210 | |||
211 | # Builds a dictionary of Gerrit Change-Ids. From the Change-Ids we can then | ||
212 | # get the commit message and URL. | ||
213 | # | ||
214 | # This also builds an array of those same commit IDs to track the ordering | ||
215 | # of the commits so that the user can choose the order of the patches based | ||
216 | # on the order in which they pass the commits. | ||
217 | for cmt in arg_parser.commits: | ||
218 | if cmt[0] == 'I': | ||
219 | info = commit_info_from_gerrit_change_id(cmt) | ||
220 | elif get_gerrit_url_id(cmt): | ||
221 | info = commit_info_from_gerrit_cl(cmt) | ||
222 | else: | ||
223 | info = commit_info_from_git_sha1(git_sha1_from_commit(cmt)) | ||
224 | |||
225 | if info: | ||
226 | commits_info.append(info) | ||
227 | else: | ||
228 | print('Warning: \'%s\' doesn\'t appear to be a commit!' % cmt) | ||
229 | |||
230 | display_commits(commits_info, arg_parser.msg) | ||
231 | |||
232 | if __name__ == '__main__': | ||
233 | main() | ||