diff --git a/patchwork/forge/github/__init__.py b/patchwork/forge/github/__init__.py
index ded7402cd5f3..4441c2b11ab0 100644
--- a/patchwork/forge/github/__init__.py
+++ b/patchwork/forge/github/__init__.py
@@ -14,8 +14,12 @@ import logging
from patchwork.forge import ForgeBackend
from patchwork.forge import register_backend
+from patchwork.forge.github.to_ml import handle_issue_comment
from patchwork.forge.github.to_ml import handle_pull_request
+from patchwork.forge.github.to_ml import handle_review
+from patchwork.forge.github.webhook import parse_issue_comment
from patchwork.forge.github.webhook import parse_pull_request
+from patchwork.forge.github.webhook import parse_review
logger = logging.getLogger(__name__)
@@ -40,7 +44,9 @@ class GitHubBackend(ForgeBackend):
payload = json.loads(body)
parsers = {
+ 'issue_comment': parse_issue_comment,
'pull_request': parse_pull_request,
+ 'pull_request_review': parse_review,
}
parser = parsers.get(event_type)
@@ -73,7 +79,9 @@ class GitHubBackend(ForgeBackend):
def process_webhook_event(self, forge_config, event):
handlers = {
+ 'issue_comment': handle_issue_comment,
'pull_request': handle_pull_request,
+ 'review': handle_review,
}
handler = handlers.get(event.type)
if handler:
diff --git a/patchwork/forge/github/to_ml.py b/patchwork/forge/github/to_ml.py
index fac07b180a51..bfb4f3d42691 100644
--- a/patchwork/forge/github/to_ml.py
+++ b/patchwork/forge/github/to_ml.py
@@ -3,11 +3,20 @@
#
# SPDX-License-Identifier: GPL-2.0-or-later
+import email
+import logging
+
from patchwork.forge.git import GitMirror
+from patchwork.forge.util import bytes_to_mbox
+from patchwork.forge.util import find_series_by_pr
from patchwork.forge.util import ingest_emails
from patchwork.forge.util import next_version
+from patchwork.forge.util import reply_to_msgid
from patchwork.forge.util import sanitize_pr_body
from patchwork.forge.util import send_emails
+from patchwork.forge.util import sender_identity
+
+logger = logging.getLogger(__name__)
def handle_pull_request(gh, forge_config, event):
@@ -52,3 +61,71 @@ def handle_pull_request(gh, forge_config, event):
ingest_emails(mbox, gh, forge_config, event)
send_emails(mbox, forge_config)
+
+
+def handle_issue_comment(gh, forge_config, event):
+ series = find_series_by_pr(gh, forge_config, event.pr_number).last()
+ if not series:
+ logger.warning('no series found for PR #%d', event.pr_number)
+ return
+
+ subject = f'Re: {series.name} (comment)'
+ reply(gh, forge_config, event, series, subject, event.body)
+
+
+def handle_review(gh, forge_config, event):
+ series = find_series_by_pr(gh, forge_config, event.pr_number).last()
+ if not series:
+ logger.warning('no series found for PR #%d', event.pr_number)
+ return
+ subject = f'Re: {series.name} (review: {event.review_state})'
+
+ body = ''
+ if event.review_state:
+ body = f'Review: {event.review_state}\n\n'
+ if event.body:
+ body += f'{event.body}\n\n'
+ for c in event.review_comments:
+ body += f'--- {c.path}\n'
+ if c.diff_hunk:
+ for line in c.diff_hunk.split('\n'):
+ body += f'> {line}\n'
+ body += '\n'
+ body += f'{c.body}\n\n'
+
+ reply(gh, forge_config, event, series, subject, body.rstrip())
+
+
+def reply(gh, forge_config, event, series, subject, body):
+ """
+ Build a reply email as an mbox, ingest it into the database and
+ send it to the mailing list.
+ """
+ in_reply_to = reply_to_msgid(series)
+ if not in_reply_to:
+ logger.warning('no message-id for series %d', series.id)
+ return
+
+ _, addr = email.utils.parseaddr(forge_config.sender_addr)
+ uid, domain = addr.rsplit('@', 1)
+
+ msg = email.mime.text.MIMEText(body)
+ msg['From'] = email.utils.formataddr(
+ sender_identity(event.author, forge_config)
+ )
+ msg['Sender'] = email.utils.formataddr(
+ email.utils.parseaddr(forge_config.sender_addr)
+ )
+ msg['To'] = forge_config.project.listemail
+ msg['Subject'] = email.header.Header(subject, 'utf-8')
+ msg['Date'] = email.utils.formatdate(localtime=True)
+ msg['Message-ID'] = email.utils.make_msgid(uid, domain)
+ msg['In-Reply-To'] = in_reply_to
+ msg['References'] = in_reply_to
+ msg['Reply-To'] = forge_config.project.listemail
+ msg['List-ID'] = f'<{forge_config.project.listid}>'
+ msg['X-Patchwork-Hint'] = 'ignore'
+
+ mbox = bytes_to_mbox(msg.as_bytes(unixfrom=True))
+ ingest_emails(mbox, gh, forge_config, event)
+ send_emails(mbox, forge_config)
diff --git a/patchwork/forge/github/webhook.py b/patchwork/forge/github/webhook.py
index f85cc5db1ddf..5ae0f5f12b60 100644
--- a/patchwork/forge/github/webhook.py
+++ b/patchwork/forge/github/webhook.py
@@ -5,6 +5,7 @@
from patchwork.forge import ForgeEvent
from patchwork.forge import ForgeUser
+from patchwork.forge import ReviewComment
def parse_pull_request(payload):
@@ -40,3 +41,44 @@ def parse_user(user):
name=user.get('name', ''),
email=user.get('email', ''),
)
+
+
+def parse_issue_comment(payload):
+ if payload.get('action') != 'created':
+ return None
+ issue = payload.get('issue', {})
+ if 'pull_request' not in issue:
+ return None
+ comment = payload.get('comment', {})
+ return ForgeEvent(
+ type='issue_comment',
+ repo_key=get_repo_key(payload),
+ pr_number=issue.get('number', 0),
+ author=parse_user(comment.get('user')),
+ body=comment.get('body', ''),
+ )
+
+
+def parse_review(self, payload):
+ if payload.get('action') != 'submitted':
+ return None
+ review = payload.get('review', {})
+ pr = payload.get('pull_request', {})
+ comments = []
+ for c in review.get('comments', []):
+ comments.append(
+ ReviewComment(
+ path=c.get('path', ''),
+ diff_hunk=c.get('diff_hunk', ''),
+ body=c.get('body', ''),
+ )
+ )
+ return ForgeEvent(
+ type='review',
+ repo_key=get_repo_key(payload),
+ pr_number=pr.get('number', 0),
+ author=parse_user(review.get('user')),
+ body=review.get('body', ''),
+ review_state=review.get('state', ''),
+ review_comments=comments,
+ )