diff --git a/patchwork/forge/__init__.py b/patchwork/forge/__init__.py
index cadca53..79a1366 100644
--- a/patchwork/forge/__init__.py
+++ b/patchwork/forge/__init__.py
@@ -28,6 +28,7 @@ from dataclasses import field
from django.conf import settings
from django.db import transaction
+from django.db.models.signals import post_save
from patchwork.models import Event
from patchwork.models import ForgeConfig
@@ -180,6 +181,16 @@ class ForgeBackend(ABC):
"""
raise NotImplementedError
+ @abstractmethod
+ def handle_comment_created(self, forge_config, comment, series):
+ """
+ Handle a comment on a patch or cover letter.
+
+ Called by the comment-created signal handler. The backend
+ decides whether to forward the comment to the forge PR.
+ """
+ raise NotImplementedError
+
_backends = {}
@@ -217,12 +228,46 @@ def _on_series_completed(sender, instance, raw, **kwargs):
transaction.on_commit(do_sync)
-def load_backends():
- from django.db.models.signals import post_save
+def _on_comment_created(sender, instance, raw, **kwargs):
+ if raw:
+ return
+
+ if instance.category == Event.CATEGORY_PATCH_COMMENT_CREATED:
+ comment = instance.patch_comment
+ if not comment:
+ return
+ series = comment.patch.series
+ elif instance.category == Event.CATEGORY_COVER_COMMENT_CREATED:
+ comment = instance.cover_comment
+ if not comment:
+ return
+ series = comment.cover.series
+ else:
+ return
+
+ if not series:
+ return
+
+ def do_sync():
+ for forge_config in ForgeConfig.objects.filter(project=series.project):
+ backend = get_backend(forge_config.backend)
+ if not backend:
+ continue
+ try:
+ backend.handle_comment_created(forge_config, comment, series)
+ except Exception:
+ logger.exception(
+ 'forge comment sync failed for series %d on %s',
+ series.id,
+ forge_config.backend,
+ )
+
+ transaction.on_commit(do_sync)
- from patchwork.models import Event
+def load_backends():
for module_path in settings.FORGE_BACKENDS:
importlib.import_module(module_path)
post_save.connect(_on_series_completed, sender=Event)
+ post_save.connect(_on_comment_created, sender=Event)
diff --git a/patchwork/forge/github/__init__.py b/patchwork/forge/github/__init__.py
index b2dbf23..18200a4 100644
--- a/patchwork/forge/github/__init__.py
+++ b/patchwork/forge/github/__init__.py
@@ -15,6 +15,7 @@ import logging
from patchwork.forge import ForgeBackend
from patchwork.forge import register_backend
from patchwork.forge.github.from_ml import create_or_update_pr
+from patchwork.forge.github.from_ml import post_pr_comment
from patchwork.forge.github.to_ml import handle_check_pending
from patchwork.forge.github.to_ml import handle_check_result
from patchwork.forge.github.to_ml import handle_issue_comment
@@ -99,5 +100,8 @@ class GitHubBackend(ForgeBackend):
def handle_series_completed(self, forge_config, series):
create_or_update_pr(self, forge_config, series)
+ def handle_comment_created(self, forge_config, comment, series):
+ post_pr_comment(self, forge_config, comment, series)
+
register_backend('github', GitHubBackend())
diff --git a/patchwork/forge/github/api.py b/patchwork/forge/github/api.py
index d2332a7..92b4a73 100644
--- a/patchwork/forge/github/api.py
+++ b/patchwork/forge/github/api.py
@@ -11,6 +11,7 @@ import urllib.request
from patchwork.forge import CheckRun
from patchwork.forge import ReviewComment
+from patchwork.forge.util import COMMENT_MARKER
logger = logging.getLogger(__name__)
@@ -55,7 +56,7 @@ def fetch_review_comments(gh, forge_config, pr_number, review_id):
comments = []
for r in results:
body = r.get('body') or ''
- if body == '':
+ if body == '' or COMMENT_MARKER in body:
continue
comments.append(
ReviewComment(
diff --git a/patchwork/forge/github/from_ml.py b/patchwork/forge/github/from_ml.py
index ccb44c8..4b1acf4 100644
--- a/patchwork/forge/github/from_ml.py
+++ b/patchwork/forge/github/from_ml.py
@@ -4,12 +4,14 @@
# SPDX-License-Identifier: GPL-2.0-or-later
+import email
import logging
from patchwork.forge.git import GitMirror
from patchwork.forge.github.api import base_branch
from patchwork.forge.github.api import create_pr
from patchwork.forge.github.api import post_comment
+from patchwork.forge.util import COMMENT_MARKER
from patchwork.forge.util import build_pr_body
from patchwork.forge.util import forge_branch_name
from patchwork.forge.util import series_from_forge
@@ -106,3 +108,26 @@ def store_series_metadata(gh, forge_config, series, pr_ref, branch):
key=f'{forge_config.backend}_branch',
defaults={'value': branch},
)
+
+
+def post_pr_comment(gh, forge_config, comment, series):
+ if not forge_config.sync_ml_to_forge:
+ return
+
+ if comment.headers:
+ parsed = email.message_from_string(comment.headers)
+ hint = parsed.get('X-Patchwork-Hint', '')
+ if hint.lower() == 'ignore':
+ return
+
+ pr_meta = SeriesMetadata.objects.filter(
+ series=series, key=gh.meta_key_pr()
+ ).first()
+ if not pr_meta:
+ return
+
+ pr_number = int(pr_meta.value.rsplit('/', 1)[-1])
+ author = comment.submitter.name or comment.submitter.email
+ quoted = '\n'.join(f'> {line}' for line in comment.content.splitlines())
+ body = f'**{author}** commented:\n\n{quoted}\n\n{COMMENT_MARKER}'
+ post_comment(gh, forge_config, pr_number, body)