From af4a8cc636e9a0ef6333ac1d3d91c9043268478a Mon Sep 17 00:00:00 2001 From: Alzbeta Kucerova Date: Tue, 23 Sep 2025 12:43:31 +0200 Subject: [PATCH 1/8] Add GitPullRequestHelpHandler class --- packit_service/constants.py | 1 + packit_service/worker/handlers/__init__.py | 2 + packit_service/worker/handlers/abstract.py | 1 + packit_service/worker/handlers/forges.py | 65 ++++- packit_service/worker/jobs.py | 30 ++ packit_service/worker/mixin.py | 16 ++ packit_service/worker/tasks.py | 15 + .../data/webhooks/github/pr_comment_help.json | 265 ++++++++++++++++++ tests/integration/test_pr_comment.py | 79 ++++++ 9 files changed, 473 insertions(+), 1 deletion(-) create mode 100644 tests/data/webhooks/github/pr_comment_help.json diff --git a/packit_service/constants.py b/packit_service/constants.py index ba4d0c5a4..744641c60 100644 --- a/packit_service/constants.py +++ b/packit_service/constants.py @@ -232,6 +232,7 @@ def from_number(number: int): FASJSON_URL = "https://fasjson.fedoraproject.org" PACKIT_VERIFY_FAS_COMMAND = "verify-fas" +PACKIT_HELP_COMMAND = "help" MISSING_PERMISSIONS_TO_BUILD_IN_COPR = "You don't have permissions to build in this copr." NOT_ALLOWED_TO_BUILD_IN_COPR = "is not allowed to build in the copr" diff --git a/packit_service/worker/handlers/__init__.py b/packit_service/worker/handlers/__init__.py index 3146145da..461d841f1 100644 --- a/packit_service/worker/handlers/__init__.py +++ b/packit_service/worker/handlers/__init__.py @@ -25,6 +25,7 @@ from packit_service.worker.handlers.forges import ( GithubAppInstallationHandler, GithubFasVerificationHandler, + GitPullRequestHelpHandler, ) from packit_service.worker.handlers.koji import ( KojiBuildHandler, @@ -61,6 +62,7 @@ TestingFarmHandler.__name__, TestingFarmResultsHandler.__name__, GithubFasVerificationHandler.__name__, + GitPullRequestHelpHandler.__name__, VMImageBuildHandler.__name__, VMImageBuildResultHandler.__name__, CoprOpenScanHubTaskFinishedHandler.__name__, diff --git a/packit_service/worker/handlers/abstract.py b/packit_service/worker/handlers/abstract.py index 3fbba6abb..dacc10d24 100644 --- a/packit_service/worker/handlers/abstract.py +++ b/packit_service/worker/handlers/abstract.py @@ -218,6 +218,7 @@ def _add_to_mapping(kls: type["JobHandler"]): class TaskName(str, enum.Enum): + help = "task.run_help_handler" copr_build_start = "task.run_copr_build_start_handler" copr_build_end = "task.run_copr_build_end_handler" copr_build = "task.run_copr_build_handler" diff --git a/packit_service/worker/handlers/forges.py b/packit_service/worker/handlers/forges.py index d1feda8f7..8c9169c01 100644 --- a/packit_service/worker/handlers/forges.py +++ b/packit_service/worker/handlers/forges.py @@ -17,13 +17,17 @@ from packit_service.constants import CONTACTS_URL, DOCS_APPROVAL_URL, NOTIFICATION_REPO from packit_service.events import ( github, + gitlab, + pagure, ) from packit_service.models import ( AllowlistModel, AllowlistStatus, GithubInstallationModel, ) -from packit_service.utils import get_packit_commands_from_comment +from packit_service.utils import ( + get_packit_commands_from_comment, # , get_pr_comment_parser, get_pr_comment_parser_fedora_ci +) from packit_service.worker.allowlist import Allowlist from packit_service.worker.checker.abstract import Checker from packit_service.worker.checker.forges import IsIssueInNotificationRepoChecker @@ -35,6 +39,7 @@ from packit_service.worker.mixin import ( ConfigFromEventMixin, GetIssueMixin, + GetPullRequestMixin, PackitAPIWithDownstreamMixin, ) from packit_service.worker.reporting import create_issue_if_needed @@ -279,3 +284,61 @@ def verify(self, namespace: str, fas_account: str) -> TaskResults: self.issue.comment(msg) return TaskResults(success=True, details={"msg": msg}) + + +@reacts_to(event=github.pr.Comment) +@reacts_to(event=gitlab.mr.Comment) +@reacts_to(event=pagure.pr.Comment) +class GitPullRequestHelpHandler( + JobHandler, + PackitAPIWithDownstreamMixin, + GetPullRequestMixin, +): + task_name = TaskName.help + + def __init__( + self, + package_config: PackageConfig, + job_config: JobConfig, + event: dict, + ): + super().__init__( + package_config=package_config, + job_config=job_config, + event=event, + ) + self.sender_login = self.data.actor + self.comment = self.data.event_dict.get("comment") + + def run(self) -> TaskResults: + help_message = "PLACEHOLDER_HELP_MESSAGE_DELETE_ME_LATER" + + # TODO UNCOMMENT THE FOLLOWING ONCE COMMENT PARSERS ARE AVAILABLE + # commands = get_packit_commands_from_comment( + # self.comment, # type: ignore + # self.service_config.comment_command_prefix, + # ) + # if self.comment.startswith("/packit-ci"): # type: ignore + # parser = get_pr_comment_parser_fedora_ci( + # prog=HELP_COMMENT_PROG_FEDORA_CI, + # description=HELP_COMMENT_DESCRIPTION, + # epilog=HELP_COMMENT_EPILOG, + # ) + # else: + # parser = get_pr_comment_parser( + # prog=HELP_COMMENT_PROG, + # description=HELP_COMMENT_DESCRIPTION, + # epilog=HELP_COMMENT_EPILOG, + # ) + + # # prevent help message from being printed to stdout + # # save help message to buffer + # help_message_buffer = io.StringIO() + # backup_stdout = sys.stdout + # sys.stdout = help_message_buffer + # parser.print_help() + # sys.stdout = backup_stdout + # help_message = help_message_buffer.getvalue() + + self.pr.comment(body=help_message) + return TaskResults(success=True, details={"msg": help_message}) diff --git a/packit_service/worker/jobs.py b/packit_service/worker/jobs.py index 4c5db9430..36f9f687e 100644 --- a/packit_service/worker/jobs.py +++ b/packit_service/worker/jobs.py @@ -19,12 +19,14 @@ from packit_service.config import ServiceConfig from packit_service.constants import ( COMMENT_REACTION, + PACKIT_HELP_COMMAND, PACKIT_VERIFY_FAS_COMMAND, TASK_ACCEPTED, ) from packit_service.events import ( abstract, github, + gitlab, koji, pagure, testing_farm, @@ -42,6 +44,7 @@ CoprBuildHandler, GithubAppInstallationHandler, GithubFasVerificationHandler, + GitPullRequestHelpHandler, KojiBuildHandler, ProposeDownstreamHandler, TestingFarmHandler, @@ -264,6 +267,15 @@ def process(self) -> list[TaskResults]: ).apply_async() # should we comment about not processing if the comment is not # on the issue created by us or not in packit/notifications? + elif isinstance( + self.event, + (github.pr.Comment, gitlab.mr.Comment, pagure.pr.Comment), + ) and self.is_help_comment(self.event.comment): + self.event.comment_object.add_reaction(COMMENT_REACTION) + GitPullRequestHelpHandler.get_signature( + event=self.event, + job=None, + ).apply_async() else: if ( isinstance( @@ -1116,6 +1128,24 @@ def is_fas_verification_comment(self, comment: str) -> bool: return bool(command and command[0] == PACKIT_VERIFY_FAS_COMMAND) + def is_help_comment(self, comment: str) -> bool: + """ + Checks whether the comment contains Packit help command: + `/packit(-stg) | /packit-ci(-stg) help` + + Args: + comment: Comment to be checked. + + Returns: + `True`, if is help comment, `False` otherwise. + """ + command = get_packit_commands_from_comment( + comment, + self.service_config.comment_command_prefix, + ) + + return bool(command and command[0] == PACKIT_HELP_COMMAND) + def report_task_accepted_for_downstream_retrigger_comments( self, handler_kls: type[JobHandler], diff --git a/packit_service/worker/mixin.py b/packit_service/worker/mixin.py index b68f0acf9..2f00f1a5e 100644 --- a/packit_service/worker/mixin.py +++ b/packit_service/worker/mixin.py @@ -289,6 +289,22 @@ def issue(self): return self._issue +class GetPullRequest(Protocol): + @property + @abstractmethod + def pr(self) -> PullRequest: ... + + +class GetPullRequestMixin(GetPullRequest, ConfigFromEventMixin): + _pr: Optional[PullRequest] = None + + @property + def pr(self): + if not self._pr: + self._pr = self.project.get_pr(self.data.pr_id) + return self._pr + + class GetBranches(Protocol): @property @abstractmethod diff --git a/packit_service/worker/tasks.py b/packit_service/worker/tasks.py index 97309534a..50db16239 100644 --- a/packit_service/worker/tasks.py +++ b/packit_service/worker/tasks.py @@ -55,6 +55,7 @@ DownstreamTestingFarmHandler, DownstreamTestingFarmResultsHandler, GithubAppInstallationHandler, + GitPullRequestHelpHandler, KojiBuildHandler, KojiTaskReportHandler, ProposeDownstreamHandler, @@ -296,6 +297,20 @@ def run_github_fas_verification_handler( return get_handlers_task_results(handler.run_job(), event) +@celery_app.task(name=TaskName.help, base=TaskWithRetry) +def run_pr_help_handler( + event: dict, + package_config: dict, + job_config: dict, +): + handler = GitPullRequestHelpHandler( + package_config=None, + job_config=None, + event=event, + ) + return get_handlers_task_results(handler.run_job(), event) + + @celery_app.task(bind=True, name=TaskName.testing_farm, base=TaskWithRetry) def run_testing_farm_handler( self, diff --git a/tests/data/webhooks/github/pr_comment_help.json b/tests/data/webhooks/github/pr_comment_help.json new file mode 100644 index 000000000..96d38280d --- /dev/null +++ b/tests/data/webhooks/github/pr_comment_help.json @@ -0,0 +1,265 @@ +{ + "action": "created", + "issue": { + "url": "https://api.github.com/repos/packit/hello-world/issues/1418", + "repository_url": "https://api.github.com/repos/packit/hello-world", + "labels_url": "https://api.github.com/repos/packit/hello-world/issues/1418/labels{/name}", + "comments_url": "https://api.github.com/repos/packit/hello-world/issues/1418/comments", + "events_url": "https://api.github.com/repos/packit/hello-world/issues/1418/events", + "html_url": "https://github.com/packit/hello-world/pull/1418", + "id": 1690562185, + "node_id": "PR_kwDOCwFO9M5PfRos", + "number": 1418, + "title": "Demo monorepo setup", + "user": { + "login": "lachmanfrantisek", + "id": 20214043, + "node_id": "MDQ6VXNlcjIwMjE0MDQz", + "avatar_url": "https://avatars.githubusercontent.com/u/20214043?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/lachmanfrantisek", + "html_url": "https://github.com/lachmanfrantisek", + "followers_url": "https://api.github.com/users/lachmanfrantisek/followers", + "following_url": "https://api.github.com/users/lachmanfrantisek/following{/other_user}", + "gists_url": "https://api.github.com/users/lachmanfrantisek/gists{/gist_id}", + "starred_url": "https://api.github.com/users/lachmanfrantisek/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/lachmanfrantisek/subscriptions", + "organizations_url": "https://api.github.com/users/lachmanfrantisek/orgs", + "repos_url": "https://api.github.com/users/lachmanfrantisek/repos", + "events_url": "https://api.github.com/users/lachmanfrantisek/events{/privacy}", + "received_events_url": "https://api.github.com/users/lachmanfrantisek/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "labels": [], + "state": "open", + "locked": false, + "assignee": null, + "assignees": [], + "milestone": null, + "comments": 2, + "created_at": "2023-05-01T08:41:02Z", + "updated_at": "2025-09-16T09:16:32Z", + "closed_at": null, + "author_association": "MEMBER", + "type": null, + "active_lock_reason": null, + "draft": false, + "pull_request": { + "url": "https://api.github.com/repos/packit/hello-world/pulls/1418", + "html_url": "https://github.com/packit/hello-world/pull/1418", + "diff_url": "https://github.com/packit/hello-world/pull/1418.diff", + "patch_url": "https://github.com/packit/hello-world/pull/1418.patch", + "merged_at": null + }, + "body": "Show the packages syntax to perform builds for two packages:\r\n\r\n```yaml\r\npackages:\r\n\r\n hello:\r\n downstream_package_name: hello\r\n upstream_package_name: hello\r\n paths: \r\n - ./hello\r\n specfile_path: hello.spec\r\n files_to_sync: \r\n - hello.spec\r\n\r\n world:\r\n downstream_package_name: world\r\n upstream_package_name: world\r\n paths: \r\n - ./world\r\n specfile_path: world.spec\r\n files_to_sync: \r\n - world.spec\r\n```", + "reactions": { + "url": "https://api.github.com/repos/packit/hello-world/issues/1418/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "timeline_url": "https://api.github.com/repos/packit/hello-world/issues/1418/timeline", + "performed_via_github_app": null, + "state_reason": null + }, + "comment": { + "url": "https://api.github.com/repos/packit/hello-world/issues/comments/3296937722", + "html_url": "https://github.com/packit/hello-world/pull/1418#issuecomment-3296937722", + "issue_url": "https://api.github.com/repos/packit/hello-world/issues/1418", + "id": 3296937722, + "node_id": "IC_kwDOCwFO9M7Eg0b6", + "user": { + "login": "betulependule", + "id": 33840358, + "node_id": "MDQ6VXNlcjMzODQwMzU4", + "avatar_url": "https://avatars.githubusercontent.com/u/33840358?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/betulependule", + "html_url": "https://github.com/betulependule", + "followers_url": "https://api.github.com/users/betulependule/followers", + "following_url": "https://api.github.com/users/betulependule/following{/other_user}", + "gists_url": "https://api.github.com/users/betulependule/gists{/gist_id}", + "starred_url": "https://api.github.com/users/betulependule/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/betulependule/subscriptions", + "organizations_url": "https://api.github.com/users/betulependule/orgs", + "repos_url": "https://api.github.com/users/betulependule/repos", + "events_url": "https://api.github.com/users/betulependule/events{/privacy}", + "received_events_url": "https://api.github.com/users/betulependule/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "created_at": "2025-09-16T09:16:32Z", + "updated_at": "2025-09-16T09:16:32Z", + "body": "/packit help", + "author_association": "MEMBER", + "reactions": { + "url": "https://api.github.com/repos/packit/hello-world/issues/comments/3296937722/reactions", + "total_count": 0, + "+1": 0, + "-1": 0, + "laugh": 0, + "hooray": 0, + "confused": 0, + "heart": 0, + "rocket": 0, + "eyes": 0 + }, + "performed_via_github_app": null + }, + "repository": { + "id": 184635124, + "node_id": "MDEwOlJlcG9zaXRvcnkxODQ2MzUxMjQ=", + "name": "hello-world", + "full_name": "packit/hello-world", + "private": false, + "owner": { + "login": "packit", + "id": 46870917, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2ODcwOTE3", + "avatar_url": "https://avatars.githubusercontent.com/u/46870917?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/packit", + "html_url": "https://github.com/packit", + "followers_url": "https://api.github.com/users/packit/followers", + "following_url": "https://api.github.com/users/packit/following{/other_user}", + "gists_url": "https://api.github.com/users/packit/gists{/gist_id}", + "starred_url": "https://api.github.com/users/packit/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/packit/subscriptions", + "organizations_url": "https://api.github.com/users/packit/orgs", + "repos_url": "https://api.github.com/users/packit/repos", + "events_url": "https://api.github.com/users/packit/events{/privacy}", + "received_events_url": "https://api.github.com/users/packit/received_events", + "type": "Organization", + "user_view_type": "public", + "site_admin": false + }, + "html_url": "https://github.com/packit/hello-world", + "description": "The most progresive command-line tool in the world.", + "fork": false, + "url": "https://api.github.com/repos/packit/hello-world", + "forks_url": "https://api.github.com/repos/packit/hello-world/forks", + "keys_url": "https://api.github.com/repos/packit/hello-world/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/packit/hello-world/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/packit/hello-world/teams", + "hooks_url": "https://api.github.com/repos/packit/hello-world/hooks", + "issue_events_url": "https://api.github.com/repos/packit/hello-world/issues/events{/number}", + "events_url": "https://api.github.com/repos/packit/hello-world/events", + "assignees_url": "https://api.github.com/repos/packit/hello-world/assignees{/user}", + "branches_url": "https://api.github.com/repos/packit/hello-world/branches{/branch}", + "tags_url": "https://api.github.com/repos/packit/hello-world/tags", + "blobs_url": "https://api.github.com/repos/packit/hello-world/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/packit/hello-world/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/packit/hello-world/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/packit/hello-world/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/packit/hello-world/statuses/{sha}", + "languages_url": "https://api.github.com/repos/packit/hello-world/languages", + "stargazers_url": "https://api.github.com/repos/packit/hello-world/stargazers", + "contributors_url": "https://api.github.com/repos/packit/hello-world/contributors", + "subscribers_url": "https://api.github.com/repos/packit/hello-world/subscribers", + "subscription_url": "https://api.github.com/repos/packit/hello-world/subscription", + "commits_url": "https://api.github.com/repos/packit/hello-world/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/packit/hello-world/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/packit/hello-world/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/packit/hello-world/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/packit/hello-world/contents/{+path}", + "compare_url": "https://api.github.com/repos/packit/hello-world/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/packit/hello-world/merges", + "archive_url": "https://api.github.com/repos/packit/hello-world/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/packit/hello-world/downloads", + "issues_url": "https://api.github.com/repos/packit/hello-world/issues{/number}", + "pulls_url": "https://api.github.com/repos/packit/hello-world/pulls{/number}", + "milestones_url": "https://api.github.com/repos/packit/hello-world/milestones{/number}", + "notifications_url": "https://api.github.com/repos/packit/hello-world/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/packit/hello-world/labels{/name}", + "releases_url": "https://api.github.com/repos/packit/hello-world/releases{/id}", + "deployments_url": "https://api.github.com/repos/packit/hello-world/deployments", + "created_at": "2019-05-02T18:54:46Z", + "updated_at": "2023-01-31T17:16:23Z", + "pushed_at": "2025-09-16T04:17:28Z", + "git_url": "git://github.com/packit/hello-world.git", + "ssh_url": "git@github.com:packit/hello-world.git", + "clone_url": "https://github.com/packit/hello-world.git", + "svn_url": "https://github.com/packit/hello-world", + "homepage": null, + "size": 249, + "stargazers_count": 4, + "watchers_count": 4, + "language": "Python", + "has_issues": true, + "has_projects": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "has_discussions": false, + "forks_count": 23, + "mirror_url": null, + "archived": false, + "disabled": false, + "open_issues_count": 130, + "license": { + "key": "mit", + "name": "MIT License", + "spdx_id": "MIT", + "url": "https://api.github.com/licenses/mit", + "node_id": "MDc6TGljZW5zZTEz" + }, + "allow_forking": true, + "is_template": false, + "web_commit_signoff_required": false, + "topics": [], + "visibility": "public", + "forks": 23, + "open_issues": 130, + "watchers": 4, + "default_branch": "main", + "custom_properties": {} + }, + "organization": { + "login": "packit", + "id": 46870917, + "node_id": "MDEyOk9yZ2FuaXphdGlvbjQ2ODcwOTE3", + "url": "https://api.github.com/orgs/packit", + "repos_url": "https://api.github.com/orgs/packit/repos", + "events_url": "https://api.github.com/orgs/packit/events", + "hooks_url": "https://api.github.com/orgs/packit/hooks", + "issues_url": "https://api.github.com/orgs/packit/issues", + "members_url": "https://api.github.com/orgs/packit/members{/member}", + "public_members_url": "https://api.github.com/orgs/packit/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/46870917?v=4", + "description": "Packit service: package it in an automated way." + }, + "sender": { + "login": "betulependule", + "id": 33840358, + "node_id": "MDQ6VXNlcjMzODQwMzU4", + "avatar_url": "https://avatars.githubusercontent.com/u/33840358?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/betulependule", + "html_url": "https://github.com/betulependule", + "followers_url": "https://api.github.com/users/betulependule/followers", + "following_url": "https://api.github.com/users/betulependule/following{/other_user}", + "gists_url": "https://api.github.com/users/betulependule/gists{/gist_id}", + "starred_url": "https://api.github.com/users/betulependule/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/betulependule/subscriptions", + "organizations_url": "https://api.github.com/users/betulependule/orgs", + "repos_url": "https://api.github.com/users/betulependule/repos", + "events_url": "https://api.github.com/users/betulependule/events{/privacy}", + "received_events_url": "https://api.github.com/users/betulependule/received_events", + "type": "User", + "user_view_type": "public", + "site_admin": false + }, + "installation": { + "id": 1924121, + "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTkyNDEyMQ==" + } +} diff --git a/tests/integration/test_pr_comment.py b/tests/integration/test_pr_comment.py index ccfe06fad..b42e9a38e 100644 --- a/tests/integration/test_pr_comment.py +++ b/tests/integration/test_pr_comment.py @@ -104,6 +104,7 @@ run_downstream_koji_scratch_build_handler, run_downstream_testing_farm_handler, run_koji_build_handler, + run_pr_help_handler, run_pull_from_upstream_handler, run_retrigger_bodhi_update, run_tag_into_sidetag_handler, @@ -126,6 +127,13 @@ def pr_build_comment_event(): ) +@pytest.fixture(scope="module") +def pr_help_comment_event(): + return json.loads( + (DATA_DIR / "webhooks" / "github" / "pr_comment_help.json").read_text(), + ) + + @pytest.fixture(scope="module") def pr_production_build_comment_event(): return json.loads( @@ -217,6 +225,77 @@ def one_job_finished_with_msg(results: list[TaskResults], msg: str): raise AssertionError(f"None of the jobs finished with {msg!r}") +@pytest.mark.parametrize( + "mock_pr_comment_functionality", + ( + [ + [], + ] + ), + indirect=True, +) +def test_pr_comment_help_handler_github( + mock_pr_comment_functionality, + pr_help_comment_event, +): + flexmock(Signature).should_receive("apply_async").once() + flexmock(GithubProject).should_receive("is_private").and_return(False) + pr = flexmock(head_commit="12345") + flexmock(GithubProject).should_receive("get_pr").and_return(pr) + comment = flexmock() + flexmock(pr).should_receive("get_comment").and_return(comment) + flexmock(comment).should_receive("add_reaction").with_args(COMMENT_REACTION).once() + + processing_results = SteveJobs().process_message(pr_help_comment_event) + event_dict, job, job_config, package_config = get_parameters_from_results( + processing_results, + ) + assert len(processing_results) == 1 + + flexmock(pr).should_receive("comment").once() + + results = run_pr_help_handler( + package_config=package_config, + event=event_dict, + job_config=job_config, + ) + + assert first_dict_value(results["job"])["success"] + + +def test_pr_comment_help_handler_pagure(pagure_pr_comment_added): + flexmock(Signature).should_receive("apply_async").once() + pagure_pr_comment_added["pullrequest"]["comments"][0]["comment"] = "/packit help" + + pr = flexmock(target_branch="the_distgit_branch").should_receive("comment").mock() + + comment = flexmock() + flexmock(pr).should_receive("get_comment").and_return(comment) + flexmock(comment).should_receive("add_reaction").with_args(COMMENT_REACTION).once() + + flexmock( + PagureProject, + full_repo_name="rpms/jouduv-dort", + get_web_url=lambda: "https://src.fedoraproject.org/rpms/jouduv-dort", + default_branch="main", + get_pr=lambda id: pr, + ) + + processing_results = SteveJobs().process_message(pagure_pr_comment_added) + event_dict, job, job_config, package_config = get_parameters_from_results( + processing_results, + ) + assert len(processing_results) == 1 + + results = run_pr_help_handler( + package_config=package_config, + event=event_dict, + job_config=job_config, + ) + + assert first_dict_value(results["job"])["success"] + + @pytest.mark.parametrize( "mock_pr_comment_functionality", ( From e4bcfa431791b91cbcf58df70ea936fa4c587475 Mon Sep 17 00:00:00 2001 From: Alzbeta Kucerova Date: Wed, 26 Nov 2025 11:06:42 +0100 Subject: [PATCH 2/8] Add missing imports Imports are commented-out for now until necessary implementation is available. --- packit_service/worker/handlers/forges.py | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/packit_service/worker/handlers/forges.py b/packit_service/worker/handlers/forges.py index 8c9169c01..b5ada22fd 100644 --- a/packit_service/worker/handlers/forges.py +++ b/packit_service/worker/handlers/forges.py @@ -6,15 +6,25 @@ TODO: The build and test handlers are independent and should be moved away. """ +# import io import logging +# import sys from packit.config import ( Deployment, JobConfig, ) from packit.config.package_config import PackageConfig -from packit_service.constants import CONTACTS_URL, DOCS_APPROVAL_URL, NOTIFICATION_REPO +from packit_service.constants import ( + CONTACTS_URL, + DOCS_APPROVAL_URL, + # HELP_COMMENT_PROG, + # HELP_COMMENT_PROG_FEDORA_CI, + # HELP_COMMENT_DESCRIPTION, + # HELP_COMMENT_EPILOG, + NOTIFICATION_REPO, +) from packit_service.events import ( github, gitlab, @@ -26,7 +36,9 @@ GithubInstallationModel, ) from packit_service.utils import ( - get_packit_commands_from_comment, # , get_pr_comment_parser, get_pr_comment_parser_fedora_ci + get_packit_commands_from_comment, + # get_pr_comment_parser, + # get_pr_comment_parser_fedora_ci, ) from packit_service.worker.allowlist import Allowlist from packit_service.worker.checker.abstract import Checker From fb96c441245bf6f3d92194221d4ad82753b02562 Mon Sep 17 00:00:00 2001 From: Alzbeta Kucerova Date: Wed, 26 Nov 2025 11:15:13 +0100 Subject: [PATCH 3/8] Remove unused variable to satisfy ruff --- tests/integration/test_pr_comment.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/integration/test_pr_comment.py b/tests/integration/test_pr_comment.py index b42e9a38e..7ca458828 100644 --- a/tests/integration/test_pr_comment.py +++ b/tests/integration/test_pr_comment.py @@ -247,7 +247,7 @@ def test_pr_comment_help_handler_github( flexmock(comment).should_receive("add_reaction").with_args(COMMENT_REACTION).once() processing_results = SteveJobs().process_message(pr_help_comment_event) - event_dict, job, job_config, package_config = get_parameters_from_results( + event_dict, _, job_config, package_config = get_parameters_from_results( processing_results, ) assert len(processing_results) == 1 @@ -282,7 +282,7 @@ def test_pr_comment_help_handler_pagure(pagure_pr_comment_added): ) processing_results = SteveJobs().process_message(pagure_pr_comment_added) - event_dict, job, job_config, package_config = get_parameters_from_results( + event_dict, _, job_config, package_config = get_parameters_from_results( processing_results, ) assert len(processing_results) == 1 From 381fc59861763a27213626a5d7ce80e49b31a742 Mon Sep 17 00:00:00 2001 From: Alzbeta Kucerova Date: Wed, 26 Nov 2025 12:45:08 +0100 Subject: [PATCH 4/8] Simplify retrieval of help message and prevent thread-unsafe behavior The `print_help` method from the `argparse` module was previously used, followed by a thread-unsafe redirect of stdout. This was removed and replaced with the use of the `format_help` method, which outputs a string containing the needed help message in a much better way. --- packit_service/worker/handlers/forges.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/packit_service/worker/handlers/forges.py b/packit_service/worker/handlers/forges.py index b5ada22fd..721e7c3a1 100644 --- a/packit_service/worker/handlers/forges.py +++ b/packit_service/worker/handlers/forges.py @@ -6,10 +6,8 @@ TODO: The build and test handlers are independent and should be moved away. """ -# import io import logging -# import sys from packit.config import ( Deployment, JobConfig, @@ -343,14 +341,7 @@ def run(self) -> TaskResults: # epilog=HELP_COMMENT_EPILOG, # ) - # # prevent help message from being printed to stdout - # # save help message to buffer - # help_message_buffer = io.StringIO() - # backup_stdout = sys.stdout - # sys.stdout = help_message_buffer - # parser.print_help() - # sys.stdout = backup_stdout - # help_message = help_message_buffer.getvalue() + # help_message = parser.format_help() self.pr.comment(body=help_message) return TaskResults(success=True, details={"msg": help_message}) From 858eed3e8be8d5743f7f8606d83c5a6404b38ba0 Mon Sep 17 00:00:00 2001 From: Alzbeta Kucerova Date: Wed, 26 Nov 2025 12:57:44 +0100 Subject: [PATCH 5/8] Improve type hints --- packit_service/worker/handlers/forges.py | 5 +++-- packit_service/worker/mixin.py | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packit_service/worker/handlers/forges.py b/packit_service/worker/handlers/forges.py index 721e7c3a1..526a120db 100644 --- a/packit_service/worker/handlers/forges.py +++ b/packit_service/worker/handlers/forges.py @@ -7,6 +7,7 @@ """ import logging +from typing import Optional from packit.config import ( Deployment, @@ -308,8 +309,8 @@ class GitPullRequestHelpHandler( def __init__( self, - package_config: PackageConfig, - job_config: JobConfig, + package_config: Optional[PackageConfig], + job_config: Optional[JobConfig], event: dict, ): super().__init__( diff --git a/packit_service/worker/mixin.py b/packit_service/worker/mixin.py index 2f00f1a5e..b67cf5a00 100644 --- a/packit_service/worker/mixin.py +++ b/packit_service/worker/mixin.py @@ -299,7 +299,7 @@ class GetPullRequestMixin(GetPullRequest, ConfigFromEventMixin): _pr: Optional[PullRequest] = None @property - def pr(self): + def pr(self) -> PullRequest: if not self._pr: self._pr = self.project.get_pr(self.data.pr_id) return self._pr From 5b5eaa61c5e132a1d9872872f83136ec375368cc Mon Sep 17 00:00:00 2001 From: Alzbeta Kucerova Date: Fri, 28 Nov 2025 10:55:34 +0100 Subject: [PATCH 6/8] Make sure to work with the correct command prefix The previously used variable seemed to always be initialized with the "/packit" value, which would result in incorrect behaviour in Fedora CI as the expected value in this case would be "/packit-ci" instead. Correct value should now be in use. --- packit_service/worker/jobs.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packit_service/worker/jobs.py b/packit_service/worker/jobs.py index 36f9f687e..052865070 100644 --- a/packit_service/worker/jobs.py +++ b/packit_service/worker/jobs.py @@ -1139,9 +1139,13 @@ def is_help_comment(self, comment: str) -> bool: Returns: `True`, if is help comment, `False` otherwise. """ + packit_comment_command_prefix = ( + "/packit-ci" if comment.startswith("/packit-ci") else "/packit" + ) + command = get_packit_commands_from_comment( comment, - self.service_config.comment_command_prefix, + packit_comment_command_prefix, ) return bool(command and command[0] == PACKIT_HELP_COMMAND) From a4ae68ae928f429cd75f25ea7804fadbce08e25a Mon Sep 17 00:00:00 2001 From: Alzbeta Kucerova Date: Fri, 28 Nov 2025 11:48:31 +0100 Subject: [PATCH 7/8] Take prefixes related to staging instances to consideration --- packit_service/worker/jobs.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/packit_service/worker/jobs.py b/packit_service/worker/jobs.py index 052865070..e6fca6495 100644 --- a/packit_service/worker/jobs.py +++ b/packit_service/worker/jobs.py @@ -1128,6 +1128,24 @@ def is_fas_verification_comment(self, comment: str) -> bool: return bool(command and command[0] == PACKIT_VERIFY_FAS_COMMAND) + def retieve_comment_command_prefix(self, comment: str) -> Optional[str]: + """ + Retrieves the Packit prefix used in comment. + + Args: + comment: Comment to retrieve prefix from. + + Returns: + Packit comment command prefix or None if none is found. + """ + prefixes = ["/packit-ci-stg", "/packit-ci", "/packit-stg", "/packit"] + + for prefix in prefixes: + if comment.startswith(prefix): + return prefix + + return None + def is_help_comment(self, comment: str) -> bool: """ Checks whether the comment contains Packit help command: @@ -1139,9 +1157,8 @@ def is_help_comment(self, comment: str) -> bool: Returns: `True`, if is help comment, `False` otherwise. """ - packit_comment_command_prefix = ( - "/packit-ci" if comment.startswith("/packit-ci") else "/packit" - ) + if not (packit_comment_command_prefix := self.retieve_comment_command_prefix(comment)): + return False command = get_packit_commands_from_comment( comment, From 3ca3c90e39f734817c79859f3a4ce71359e4a05f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Al=C5=BEb=C4=9Bta=20Ku=C4=8Derov=C3=A1?= <33840358+betulependule@users.noreply.github.com> Date: Fri, 28 Nov 2025 11:59:17 +0100 Subject: [PATCH 8/8] Fix typos Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- packit_service/worker/jobs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packit_service/worker/jobs.py b/packit_service/worker/jobs.py index e6fca6495..8fb039eb7 100644 --- a/packit_service/worker/jobs.py +++ b/packit_service/worker/jobs.py @@ -1128,7 +1128,7 @@ def is_fas_verification_comment(self, comment: str) -> bool: return bool(command and command[0] == PACKIT_VERIFY_FAS_COMMAND) - def retieve_comment_command_prefix(self, comment: str) -> Optional[str]: + def retrieve_comment_command_prefix(self, comment: str) -> Optional[str]: """ Retrieves the Packit prefix used in comment. @@ -1157,7 +1157,7 @@ def is_help_comment(self, comment: str) -> bool: Returns: `True`, if is help comment, `False` otherwise. """ - if not (packit_comment_command_prefix := self.retieve_comment_command_prefix(comment)): + if not (packit_comment_command_prefix := self.retrieve_comment_command_prefix(comment)): return False command = get_packit_commands_from_comment(