24 from datetime
import timezone
27 from urllib.parse
import urlparse
29 import dateutil.parser
34 params = {
"o": [
"CURRENT_REVISION",
"LABELS",
"DETAILED_ACCOUNTS"]}
35 r = requests.get(f
"{args.url}/changes/{args.changeid}", params=params)
37 json_txt = r.text.removeprefix(
")]}'\n")
38 json_data = json.loads(json_txt)
39 assert len(json_data[
"revisions"]) == 1
40 revision = json_data[
"revisions"].popitem()[1][
"_number"]
41 assert "Code-Review" in json_data[
"labels"]
43 for reviewer
in json_data[
"labels"][
"Code-Review"][
"all"]:
44 if "value" in reviewer:
45 assert reviewer[
"value"] >= 0
46 if reviewer[
"value"] == 2:
48 reviewer_name = reviewer.get(
"display_name", reviewer[
"name"])
49 reviewer_mail = reviewer.get(
"email", reviewer[
"name"])
50 ack = f
"{reviewer_name} <{reviewer_mail}>"
51 print(f
"Acked-by: {ack}")
54 owner = json_data[
"owner"]
55 owner_name = owner.get(
"display_name", owner[
"name"])
56 owner_mail = owner.get(
"email", owner[
"name"])
57 sign_off = f
"{owner_name} <{owner_mail}>"
58 print(f
"Signed-off-by: {sign_off}")
59 change_id = json_data[
"change_id"]
62 dateutil.parser.parse(json_data[
"created"])
63 .replace(tzinfo=timezone.utc)
67 created_stamp = int(utc_stamp * 1000)
68 hostname = urlparse(args.url).hostname
69 msg_id = f
"gerrit.{created_stamp}.{change_id}@{hostname}"
72 "project": json_data[
"project"],
73 "target": json_data[
"branch"],
82 f
"{args.url}/changes/{args.changeid}/revisions/{details['revision']}/patch?download"
85 patch_text = base64.b64decode(r.text).decode()
90 comment_start = patch_text.index(
"\n---\n") + len(
"\n---\n")
92 signed_off_comment =
""
94 signed_off_start = patch_text.rindex(
"\nSigned-off-by: ")
95 signed_off_end = patch_text.index(
"\n", signed_off_start + 1) + 1
97 signed_off_text = f
"Signed-off-by: {details['sign_off']}\n"
98 signed_off_comment =
"\nSigned-off-by line for the author was added as per our policy.\n"
99 signed_off_end = patch_text.index(
"\n---\n") + 1
100 assert comment_start > signed_off_end
103 for ack
in details[
"acked_by"]:
104 acked_by_text += f
"Acked-by: {ack}\n"
105 acked_by_names += f
"{ack}\n"
107 patch_text[:signed_off_end]
110 + patch_text[signed_off_end:comment_start]
112 This change was reviewed on Gerrit and approved by at least one
113 developer. I request to merge it to {details["target"]}.
115 Gerrit URL: {args.url}/c/{details["project"]}/+/{args.changeid}
116 This mail reflects revision {details["revision"]} of this Change.
118 Acked-by according to Gerrit (reflected above):
121 + patch_text[comment_start:]
123 filename = f
"gerrit-{args.changeid}-{details['revision']}.patch"
124 patch_text_final = patch_text_mod.replace(
"Subject: [PATCH]", f
"Subject: [PATCH v{details['revision']}]")
125 with open(filename,
"w", encoding=
"utf-8", newline=
"\n")
as patch_file:
126 patch_file.write(patch_text_final)
128 print(f
"git send-email --in-reply-to {details['msg_id']} {filename}")
132 parser = argparse.ArgumentParser(
133 prog=
"gerrit-send-mail",
134 description=
"Send patchset from Gerrit to mailing list",
136 parser.add_argument(
"changeid")
137 parser.add_argument(
"-u",
"--url", default=
"https://gerrit.openvpn.net")
138 args = parser.parse_args()
145 if __name__ ==
"__main__":