From patchwork Sun Oct 22 10:59:19 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Gert Doering X-Patchwork-Id: 3404 Return-Path: Delivered-To: patchwork@openvpn.net Received: by 2002:a05:7300:baa3:b0:f2:62eb:61c1 with SMTP id dt35csp825197dyb; Sun, 22 Oct 2023 04:00:01 -0700 (PDT) X-Google-Smtp-Source: AGHT+IE6zwrCme9NGCusE1iqi71AQ5Dz7HIpykrHtishqzxw3GkFfsaDilJwmGGQ9pDBt2i42H77 X-Received: by 2002:a05:6a20:7d9b:b0:15d:6fd3:8e74 with SMTP id v27-20020a056a207d9b00b0015d6fd38e74mr8631190pzj.3.1697972400922; Sun, 22 Oct 2023 04:00:00 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1697972400; cv=none; d=google.com; s=arc-20160816; b=mxW+TLXK7mRpxxlPXh8HHSpr7Pyi6Aiq89ae2JzRZeLs2IVLWYUX+7paV1yK5KcdCn Kbp4vsUWfHWt23cKKj/TNAjbYCTQkqPhgiAR1WlW4bd7C79uTqjnZs4l1uM+ZYjQ/1zb W4TvdaYaheJxdWZ2gV/swiNI3jJqSU6GDQTd4aOHkPZvhZ4DrziHfpcSkZfuwc9/X5oR V4NqaswIgMN/rJbdoobr8lqw19wHNCoR2j4QzIQ8Xm3+imPrp42IcrX0deUQEMtstjTX gzJbRHYwi51hCzl1c6C96op1LlxKn9wrbWiSambGFAQZIjFBh8Gf+ICxviqeFa7W8me7 Vnlg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=errors-to:content-transfer-encoding:list-subscribe:list-help :list-post:list-archive:list-unsubscribe:list-id:precedence:subject :mime-version:references:in-reply-to:message-id:date:to:from :dkim-signature:dkim-signature; bh=CFnmm87zRumF/+6wUJt2uXAiZD4fqx4RO8brx4cTo7s=; fh=4NbAC/LsuMLI0S0hprUlLSLCiHwg6SCAifhH718Jh0Q=; b=xTDbdf89nN80XADBuXzLe+f/PhHMOvRJ+XtS7uq/jiA479+Al60mbkorUEYn1kSHUK hRzoSxMSFPyNgO/NyeSDTW1R94SBld1nls9YckJ9QK0vpRBPmF9lf3NtgiOC1ATs+KA2 /0aMRvza9qoZCOw71AUBpbuElxEzzn2FIZ/ukzaBY/MA/x2nSnrcepTT5hkwiYD3Dxcp UwjBv/TvukDiykwaCDoE3Sv5d3rYhg/gf5qP+CdLshhJKYk2niWkSTdVFD7exHpMn3Nt ukKrsGcL96nzqOQzPm/o0R4FBds8/sG5byE2eSuJCPxS2kT04qV3s76iLnTaI5N1lLhL 7lAQ== ARC-Authentication-Results: i=1; mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=UrWU0YZ4; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=FoDeoxGR; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=muc.de Received: from lists.sourceforge.net (lists.sourceforge.net. [216.105.38.7]) by mx.google.com with ESMTPS id r23-20020aa79637000000b0068fea8a6169si4557087pfg.404.2023.10.22.04.00.00 (version=TLS1_2 cipher=ECDHE-ECDSA-AES128-GCM-SHA256 bits=128/128); Sun, 22 Oct 2023 04:00:00 -0700 (PDT) Received-SPF: pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) client-ip=216.105.38.7; Authentication-Results: mx.google.com; dkim=neutral (body hash did not verify) header.i=@sourceforge.net header.s=x header.b=UrWU0YZ4; dkim=neutral (body hash did not verify) header.i=@sf.net header.s=x header.b=FoDeoxGR; spf=pass (google.com: domain of openvpn-devel-bounces@lists.sourceforge.net designates 216.105.38.7 as permitted sender) smtp.mailfrom=openvpn-devel-bounces@lists.sourceforge.net; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=muc.de Received: from [127.0.0.1] (helo=sfs-ml-2.v29.lw.sourceforge.com) by sfs-ml-2.v29.lw.sourceforge.com with esmtp (Exim 4.95) (envelope-from ) id 1quWBL-0001Jb-86; Sun, 22 Oct 2023 10:59:43 +0000 Received: from [172.30.20.202] (helo=mx.sourceforge.net) by sfs-ml-2.v29.lw.sourceforge.com with esmtps (TLS1.2) tls TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (Exim 4.95) (envelope-from ) id 1quWBA-0001Iz-OB for openvpn-devel@lists.sourceforge.net; Sun, 22 Oct 2023 10:59:33 +0000 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sourceforge.net; s=x; h=Content-Transfer-Encoding:MIME-Version:References: In-Reply-To:Message-ID:Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type: Content-ID:Content-Description:Resent-Date:Resent-From:Resent-Sender: Resent-To:Resent-Cc:Resent-Message-ID:List-Id:List-Help:List-Unsubscribe: List-Subscribe:List-Post:List-Owner:List-Archive; bh=3105neXRmbkhQ6DZ20WrOqrt5kA7PpwEXX3W2rGAoo4=; b=UrWU0YZ4zUOm002xuwAX8RqLX1 6EO9DVDivTb7l+lR2m2w/9y1epK8oh4n4O0DTvhb8H+sWydRJu2IUO3XcSi8ios06VYPUFJ5AK0hQ oVG7SiPbpdP/t4JWchQNbabh5hpMcCHQ0Sim1FLQ17cfqIsC4bZB5SGILG0y+MZfdVOg=; DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=sf.net; s=x ; h=Content-Transfer-Encoding:MIME-Version:References:In-Reply-To:Message-ID: Date:Subject:To:From:Sender:Reply-To:Cc:Content-Type:Content-ID: Content-Description:Resent-Date:Resent-From:Resent-Sender:Resent-To:Resent-Cc :Resent-Message-ID:List-Id:List-Help:List-Unsubscribe:List-Subscribe: List-Post:List-Owner:List-Archive; bh=3105neXRmbkhQ6DZ20WrOqrt5kA7PpwEXX3W2rGAoo4=; b=FoDeoxGR8KKq1dRkybMgvcxyEC 2VGueEUqNu7jQcJqUw09qAJUETj3J2JtFZNfPBHMwsIwRO8A6wI4BK89eFZRqA/Dcx5DtTdJmORRO CiellXq00r2pyPGulI2R4r+y6QP7SWJXCmmoj6QYpzr0Xbw0M2THvUajjkWgdt+wwwpo=; Received: from dhcp-174.greenie.muc.de ([193.149.48.174] helo=blue.greenie.muc.de) by sfi-mx-2.v28.lw.sourceforge.com with esmtps (TLS1.2:ECDHE-RSA-AES256-GCM-SHA384:256) (Exim 4.95) id 1quWB6-00039W-Ce for openvpn-devel@lists.sourceforge.net; Sun, 22 Oct 2023 10:59:30 +0000 Received: from blue.greenie.muc.de (localhost [127.0.0.1]) by blue.greenie.muc.de (8.17.1.9/8.17.1.9) with ESMTP id 39MAxLIZ021789 for ; Sun, 22 Oct 2023 12:59:21 +0200 Received: (from gert@localhost) by blue.greenie.muc.de (8.17.1.9/8.17.1.9/Submit) id 39MAxLa6021788 for openvpn-devel@lists.sourceforge.net; Sun, 22 Oct 2023 12:59:21 +0200 From: Gert Doering To: openvpn-devel@lists.sourceforge.net Date: Sun, 22 Oct 2023 12:59:19 +0200 Message-ID: <20231022105919.21779-1-gert@greenie.muc.de> X-Mailer: git-send-email 2.41.0 In-Reply-To: References: MIME-Version: 1.0 X-Spam-Score: -0.0 (/) X-Spam-Report: Spam detection software, running on the system "util-spamd-2.v13.lw.sourceforge.com", has NOT identified this incoming email as spam. The original message has been attached to this so you can view it or label similar future email. If you have any questions, see the administrator of that system for details. Content preview: From: Frank Lichtenheld Since we're trying to use Gerrit for patch reviews, but the actual merge process is still implemented against the ML and Patchwork, I wrote a script that attempts to bridge the gap. Content analysis details: (-0.0 points, 6.0 required) pts rule name description ---- ---------------------- -------------------------------------------------- -0.0 SPF_HELO_PASS SPF: HELO matches SPF record -0.0 SPF_PASS SPF: sender matches SPF record X-Headers-End: 1quWB6-00039W-Ce Subject: [Openvpn-devel] [PATCH v5] dev-tools/gerrit-send-mail.py: tool to send Gerrit patchsets to Patchwork X-BeenThere: openvpn-devel@lists.sourceforge.net X-Mailman-Version: 2.1.21 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: openvpn-devel-bounces@lists.sourceforge.net X-getmail-retrieved-from-mailbox: Inbox X-GMAIL-THRID: =?utf-8?q?1780453108300382420?= X-GMAIL-MSGID: =?utf-8?q?1780453108300382420?= From: Frank Lichtenheld Since we're trying to use Gerrit for patch reviews, but the actual merge process is still implemented against the ML and Patchwork, I wrote a script that attempts to bridge the gap. It extracts all relevant information about a patch from Gerrit and converts it into a mail compatible to git-am. Mostly this work is done by Gerrit already, since we can get the original patch in git format-patch format. But we add Acked-by information according to the approvals in Gerrit and some other metadata. This should allow the merge to happen based on this one mail alone. v3: - handle missing display_name and email fields for reviewers gracefully - handle missing Signed-off-by line gracefully v4: - use formatted string consistently Change-Id: If4e9c2e58441efb3fd00872cd62d1cc6c607f160 Signed-off-by: Frank Lichtenheld Acked-by: Gert Doering --- This change was reviewed on Gerrit and approved by at least one developer. I request to merge it to master. Gerrit URL: https://gerrit.openvpn.net/c/openvpn/+/361 This mail reflects revision 5 of this Change. Acked-by according to Gerrit (reflected above): Gert Doering diff --git a/dev-tools/gerrit-send-mail.py b/dev-tools/gerrit-send-mail.py new file mode 100755 index 0000000..851a20a --- /dev/null +++ b/dev-tools/gerrit-send-mail.py @@ -0,0 +1,132 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2023 OpenVPN Inc +# Copyright (C) 2023 Frank Lichtenheld +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 +# as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +# Extract a patch from Gerrit and transform it in a file suitable as input +# for git send-email. + +import argparse +import base64 +from datetime import timezone +import json +import sys +from urllib.parse import urlparse + +import dateutil.parser +import requests + + +def get_details(args): + params = {"o": ["CURRENT_REVISION", "LABELS", "DETAILED_ACCOUNTS"]} + r = requests.get(f"{args.url}/changes/{args.changeid}", params=params) + print(r.url) + json_txt = r.text.removeprefix(")]}'\n") + json_data = json.loads(json_txt) + assert len(json_data["revisions"]) == 1 # CURRENT_REVISION works as expected + revision = json_data["revisions"].popitem()[1]["_number"] + assert "Code-Review" in json_data["labels"] + acked_by = [] + for reviewer in json_data["labels"]["Code-Review"]["all"]: + if "value" in reviewer: + assert reviewer["value"] >= 0 # no NACK + if reviewer["value"] == 2: + # fall back to user name if optional fields are not set + reviewer_name = reviewer.get("display_name", reviewer["name"]) + reviewer_mail = reviewer.get("email", reviewer["name"]) + ack = f"{reviewer_name} <{reviewer_mail}>" + print(f"Acked-by: {ack}") + acked_by.append(ack) + change_id = json_data["change_id"] + # assumes that the created date in Gerrit is in UTC + utc_stamp = ( + dateutil.parser.parse(json_data["created"]) + .replace(tzinfo=timezone.utc) + .timestamp() + ) + # convert to milliseconds as used in message id + created_stamp = int(utc_stamp * 1000) + hostname = urlparse(args.url).hostname + msg_id = f"gerrit.{created_stamp}.{change_id}@{hostname}" + return { + "revision": revision, + "project": json_data["project"], + "target": json_data["branch"], + "msg_id": msg_id, + "acked_by": acked_by, + } + + +def get_patch(details, args): + r = requests.get( + f"{args.url}/changes/{args.changeid}/revisions/{details['revision']}/patch?download" + ) + print(r.url) + patch_text = base64.b64decode(r.text).decode() + return patch_text + + +def apply_patch_mods(patch_text, details, args): + comment_start = patch_text.index("\n---\n") + len("\n---\n") + try: + signed_off_start = patch_text.rindex("\nSigned-off-by: ") + signed_off_end = patch_text.index("\n", signed_off_start + 1) + 1 + except ValueError: # Signed-off missing + signed_off_end = patch_text.index("\n---\n") + 1 + assert comment_start > signed_off_end + acked_by_text = "" + acked_by_names = "" + for ack in details["acked_by"]: + acked_by_text += f"Acked-by: {ack}\n" + acked_by_names += f"{ack}\n" + patch_text_mod = ( + patch_text[:signed_off_end] + + acked_by_text + + patch_text[signed_off_end:comment_start] + + f""" +This change was reviewed on Gerrit and approved by at least one +developer. I request to merge it to {details["target"]}. + +Gerrit URL: {args.url}/c/{details["project"]}/+/{args.changeid} +This mail reflects revision {details["revision"]} of this Change. +Acked-by according to Gerrit (reflected above): +{acked_by_names} + """ + + patch_text[comment_start:] + ) + filename = f"gerrit-{args.changeid}-{details['revision']}.patch" + with open(filename, "w") as patch_file: + patch_file.write(patch_text_mod) + print("send with:") + print(f"git send-email --in-reply-to {details['msg_id']} {filename}") + + +def main(): + parser = argparse.ArgumentParser( + prog="gerrit-send-mail", + description="Send patchset from Gerrit to mailing list", + ) + parser.add_argument("changeid") + parser.add_argument("-u", "--url", default="https://gerrit.openvpn.net") + args = parser.parse_args() + + details = get_details(args) + patch = get_patch(details, args) + apply_patch_mods(patch, details, args) + + +if __name__ == "__main__": + sys.exit(main())