# HG changeset patch # User Daniel Dourvaris # Date 2020-04-15 16:00:59 # Node ID de8db8daf7bb52e1ee8c00f5a353634686074a25 # Parent a588c9e66902ceb8746f4f01022cef51ec748e19 hooks: added new hooks for comments on pull requests and commits. - allows writing custom actions on top of comments - new rcextension hooks - organized and renamed some hooks - new helpers for rcextensions - fixes #5583 diff --git a/rhodecode/api/views/repo_api.py b/rhodecode/api/views/repo_api.py --- a/rhodecode/api/views/repo_api.py +++ b/rhodecode/api/views/repo_api.py @@ -1599,7 +1599,8 @@ def comment_commit( validate_repo_permissions(apiuser, repoid, repo, _perms) try: - commit_id = repo.scm_instance().get_commit(commit_id=commit_id).raw_id + commit = repo.scm_instance().get_commit(commit_id=commit_id) + commit_id = commit.raw_id except Exception as e: log.exception('Failed to fetch commit') raise JSONRPCError(safe_str(e)) @@ -1659,6 +1660,10 @@ def comment_commit( 'a closed pull request is not allowed') raise JSONRPCError(msg) + CommentsModel().trigger_commit_comment_hook( + repo, apiuser, 'create', + data={'comment': comment, 'commit': commit}) + Session().commit() return { 'msg': ( diff --git a/rhodecode/apps/repository/views/repo_commits.py b/rhodecode/apps/repository/views/repo_commits.py --- a/rhodecode/apps/repository/views/repo_commits.py +++ b/rhodecode/apps/repository/views/repo_commits.py @@ -380,6 +380,11 @@ class RepoCommitsView(RepoAppView): 'repo_commit', repo_name=self.db_repo_name, commit_id=current_id)) + commit = self.db_repo.get_commit(current_id) + CommentsModel().trigger_commit_comment_hook( + self.db_repo, self._rhodecode_user, 'create', + data={'comment': comment, 'commit': commit}) + # finalize, commit and redirect Session().commit() diff --git a/rhodecode/config/rcextensions/__init__.py b/rhodecode/config/rcextensions/__init__.py --- a/rhodecode/config/rcextensions/__init__.py +++ b/rhodecode/config/rcextensions/__init__.py @@ -25,6 +25,7 @@ from .hooks import ( _create_repo_group_hook, _pre_create_user_hook, _create_user_hook, + _comment_commit_repo_hook, _delete_repo_hook, _delete_user_hook, _pre_push_hook, @@ -33,6 +34,7 @@ from .hooks import ( _pull_hook, _create_pull_request_hook, _review_pull_request_hook, + _comment_pull_request_hook, _update_pull_request_hook, _merge_pull_request_hook, _close_pull_request_hook, @@ -40,6 +42,7 @@ from .hooks import ( # set as module attributes, we use those to call hooks. *do not change this* CREATE_REPO_HOOK = _create_repo_hook +COMMENT_COMMIT_REPO_HOOK = _comment_commit_repo_hook CREATE_REPO_GROUP_HOOK = _create_repo_group_hook PRE_CREATE_USER_HOOK = _pre_create_user_hook CREATE_USER_HOOK = _create_user_hook @@ -51,6 +54,7 @@ PRE_PULL_HOOK = _pre_pull_hook PULL_HOOK = _pull_hook CREATE_PULL_REQUEST = _create_pull_request_hook REVIEW_PULL_REQUEST = _review_pull_request_hook +COMMENT_PULL_REQUEST = _comment_pull_request_hook UPDATE_PULL_REQUEST = _update_pull_request_hook MERGE_PULL_REQUEST = _merge_pull_request_hook CLOSE_PULL_REQUEST = _close_pull_request_hook diff --git a/rhodecode/config/rcextensions/examples/http_call_on_push.py b/rhodecode/config/rcextensions/examples/http_call_on_push.py --- a/rhodecode/config/rcextensions/examples/http_call_on_push.py +++ b/rhodecode/config/rcextensions/examples/http_call_on_push.py @@ -24,14 +24,13 @@ def _push_hook(*args, **kwargs): # returns list of dicts with key-val fetched from extra fields repo_extra_fields = extra_fields.run(**kwargs) - if repo_extra_fields.get('endpoint_url'): - field_metadata = repo_extra_fields['endpoint_url'] - endpoint = field_metadata['field_value'] - if endpoint: - data = { - 'project': kwargs['repository'], - } - response = http_call.run(url=endpoint, params=data) - return HookResponse(0, 'Called endpoint {}, with response {}\n'.format(endpoint, response)) + endpoint_url = extra_fields.get_field(repo_extra_fields, key='endpoint_url', default='') + + if endpoint_url: + data = { + 'project': kwargs['repository'], + } + response = http_call.run(url=endpoint_url, params=data) + return HookResponse(0, 'Called endpoint {}, with response {}\n'.format(endpoint_url, response)) return HookResponse(0, '') diff --git a/rhodecode/config/rcextensions/examples/trigger_ci_call.py b/rhodecode/config/rcextensions/examples/trigger_ci_call.py --- a/rhodecode/config/rcextensions/examples/trigger_ci_call.py +++ b/rhodecode/config/rcextensions/examples/trigger_ci_call.py @@ -24,14 +24,12 @@ def _push_hook(*args, **kwargs): # returns list of dicts with key-val fetched from extra fields repo_extra_fields = extra_fields.run(**kwargs) - if repo_extra_fields.get('endpoint_url'): - field_metadata = repo_extra_fields['endpoint_url'] - endpoint = field_metadata['field_value'] - if endpoint: - data = { - 'some_key': 'val' - } - response = http_call.run(url=endpoint, json_data=data) - return HookResponse(0, 'Called endpoint {}, with response {}'.format(endpoint, response)) + endpoint_url = extra_fields.get_field(repo_extra_fields, key='endpoint_url', default='') + if endpoint_url: + data = { + 'some_key': 'val' + } + response = http_call.run(url=endpoint_url, json_data=data) + return HookResponse(0, 'Called endpoint {}, with response {}'.format(endpoint_url, response)) return HookResponse(0, '') diff --git a/rhodecode/config/rcextensions/examples/trigger_ci_call_on_comment.py b/rhodecode/config/rcextensions/examples/trigger_ci_call_on_comment.py new file mode 100644 --- /dev/null +++ b/rhodecode/config/rcextensions/examples/trigger_ci_call_on_comment.py @@ -0,0 +1,60 @@ +# Example to trigger a CI call action on specific comment text, e.g chatops and ci +# rebuild on mention of ci bot + +@has_kwargs({ + 'repo_name': '', + 'repo_type': '', + 'description': '', + 'private': '', + 'created_on': '', + 'enable_downloads': '', + 'repo_id': '', + 'user_id': '', + 'enable_statistics': '', + 'clone_uri': '', + 'fork_id': '', + 'group_id': '', + 'created_by': '', + 'repository': '', + 'comment': '', + 'commit': '' +}) +def _comment_commit_repo_hook(*args, **kwargs): + """ + POST CREATE REPOSITORY COMMENT ON COMMIT HOOK. This function will be executed after + a comment is made on this repository commit. + + """ + from .helpers import http_call, extra_fields + from .utils import UrlTemplate + # returns list of dicts with key-val fetched from extra fields + repo_extra_fields = extra_fields.run(**kwargs) + + import rhodecode + from rc_integrations.jenkins_ci import csrf_call, get_auth, requests_retry_call + + endpoint_url = extra_fields.get_field( + repo_extra_fields, key='ci_endpoint_url', + default='http://ci.rc.com/job/rc-ce-commits/build?COMMIT_ID=${commit}') + mention_text = extra_fields.get_field( + repo_extra_fields, key='ci_mention_text', + default='@jenkins build') + + endpoint_url = UrlTemplate(endpoint_url).safe_substitute( + commit=kwargs['commit']['raw_id']) + + trigger_ci = False + comment = kwargs['comment']['comment_text'] + if mention_text in comment: + trigger_ci = True + + if trigger_ci is False: + return HookResponse(0, '') + + # call some CI based on the special coment mention marker + data = { + 'project': kwargs['repository'], + } + response = http_call.run(url=endpoint_url, params=data) + + return HookResponse(0, '') \ No newline at end of file diff --git a/rhodecode/config/rcextensions/examples/validate_commit_message_author.py b/rhodecode/config/rcextensions/examples/validate_commit_message_author.py --- a/rhodecode/config/rcextensions/examples/validate_commit_message_author.py +++ b/rhodecode/config/rcextensions/examples/validate_commit_message_author.py @@ -41,11 +41,13 @@ def _pre_push_hook(*args, **kwargs): repo_extra_fields = extra_fields.run(**kwargs) # optionally use 'extra fields' to control the logic per repo - validate_author = repo_extra_fields.get('validate_author', {}).get('field_value') + validate_author = extra_fields.get_field( + repo_extra_fields, key='validate_author', default=False) should_validate = str2bool(validate_author) # optionally store validation regex into extra fields - validation_regex = repo_extra_fields.get('validation_regex', {}).get('field_value') + validation_regex = extra_fields.get_field( + repo_extra_fields, key='validation_regex', default='') def validate_commit_message(commit_message, message_regex=None): """ diff --git a/rhodecode/config/rcextensions/examples/validate_pushed_files_name_and_size.py b/rhodecode/config/rcextensions/examples/validate_pushed_files_name_and_size.py --- a/rhodecode/config/rcextensions/examples/validate_pushed_files_name_and_size.py +++ b/rhodecode/config/rcextensions/examples/validate_pushed_files_name_and_size.py @@ -44,13 +44,16 @@ def _pre_push_hook(*args, **kwargs): # optionally use 'extra fields' to control the logic per repo # e.g store a list of patterns to be forbidden e.g `*.exe, *.dump` - forbid_files = repo_extra_fields.get('forbid_files_glob', {}).get('field_value') + forbid_files = extra_fields.get_field(repo_extra_fields, key='forbid_files_glob', + convert_type=False, default=[]) forbid_files = aslist(forbid_files) # forbid_files = ['*'] # example pattern # optionally get bytes limit for a single file, e.g 1024 for 1KB - forbid_size_over = repo_extra_fields.get('forbid_size_over', {}).get('field_value') + forbid_size_over = extra_fields.get_field(repo_extra_fields, key='forbid_size_over', + convert_type=False, default=0) + forbid_size_over = int(forbid_size_over or 0) # forbid_size_over = 1024 # example 1024 diff --git a/rhodecode/config/rcextensions/helpers/extra_fields.py b/rhodecode/config/rcextensions/helpers/extra_fields.py --- a/rhodecode/config/rcextensions/helpers/extra_fields.py +++ b/rhodecode/config/rcextensions/helpers/extra_fields.py @@ -53,3 +53,36 @@ def run(*args, **kwargs): fields[field.field_key] = field.get_dict() return fields + + +class _Undefined(object): + pass + + +def get_field(extra_fields_data, key, default=_Undefined(), convert_type=True): + """ + field_value = get_field(extra_fields, key='ci_endpoint_url', default='') + """ + from ..utils import str2bool, aslist + + if key not in extra_fields_data: + if isinstance(default, _Undefined): + raise ValueError('key {} not present in extra_fields'.format(key)) + return default + + # NOTE(dan): from metadata we get field_label, field_value, field_desc, field_type + field_metadata = extra_fields_data[key] + + field_value = field_metadata['field_value'] + + # NOTE(dan): empty value, use default + if not field_value and not isinstance(default, _Undefined): + return default + + if convert_type: + # 'str', 'unicode', 'list', 'tuple' + _type = field_metadata['field_type'] + if _type in ['list', 'tuple']: + field_value = aslist(field_value) + + return field_value diff --git a/rhodecode/config/rcextensions/hooks.py b/rhodecode/config/rcextensions/hooks.py --- a/rhodecode/config/rcextensions/hooks.py +++ b/rhodecode/config/rcextensions/hooks.py @@ -15,11 +15,12 @@ # This program is dual-licensed. If you wish to learn more about the # RhodeCode Enterprise Edition, including its added features, Support services, # and proprietary license terms, please see https://rhodecode.com/licenses/ + import logging from .utils import DotDict, HookResponse, has_kwargs + log = logging.getLogger('rhodecode.' + __name__) - # Config shortcut to keep, all configuration in one place # Example: api_key = CONFIG.my_config.api_key CONFIG = DotDict( @@ -55,13 +56,40 @@ def _create_repo_hook(*args, **kwargs): @has_kwargs({ - 'group_name': '', - 'group_parent_id': '', + 'repo_name': '', + 'repo_type': '', + 'description': '', + 'private': '', + 'created_on': '', + 'enable_downloads': '', + 'repo_id': '', + 'user_id': '', + 'enable_statistics': '', + 'clone_uri': '', + 'fork_id': '', + 'group_id': '', + 'created_by': '', + 'repository': '', + 'comment': '', + 'commit': '' +}) +def _comment_commit_repo_hook(*args, **kwargs): + """ + POST CREATE REPOSITORY COMMENT ON COMMIT HOOK. This function will be executed after + a comment is made on this repository commit. + + """ + return HookResponse(0, '') + + +@has_kwargs({ + 'group_name': '', + 'group_parent_id': '', 'group_description': '', - 'group_id': '', - 'user_id': '', - 'created_by': '', - 'created_on': '', + 'group_id': '', + 'user_id': '', + 'created_by': '', + 'created_on': '', 'enable_locking': '' }) def _create_repo_group_hook(*args, **kwargs): @@ -73,13 +101,13 @@ def _create_repo_group_hook(*args, **kwa @has_kwargs({ - 'username': '', - 'password': '', - 'email': '', + 'username': '', + 'password': '', + 'email': '', 'firstname': '', - 'lastname': '', - 'active': '', - 'admin': '', + 'lastname': '', + 'active': '', + 'admin': '', 'created_by': '', }) def _pre_create_user_hook(*args, **kwargs): @@ -173,7 +201,7 @@ def _delete_repo_hook(*args, **kwargs): 'emails': '', 'inherit_default_permissions': '', 'deleted_by': '', - }) +}) def _delete_user_hook(*args, **kwargs): """ POST DELETE USER HOOK, this function will be executed after each @@ -348,6 +376,38 @@ def _review_pull_request_hook(*args, **k 'scm': 'type of version control "git", "hg", "svn"', 'username': 'username of actor who triggered this event', 'ip': 'ip address of actor who triggered this hook', + + 'action': '', + 'repository': 'repository name', + 'pull_request_id': '', + 'url': '', + 'title': '', + 'description': '', + 'status': '', + 'comment': '', + 'created_on': '', + 'updated_on': '', + 'commit_ids': '', + 'review_status': '', + 'mergeable': '', + 'source': '', + 'target': '', + 'author': '', + 'reviewers': '', +}) +def _comment_pull_request_hook(*args, **kwargs): + """ + This hook will be executed after comment is made on a pull request + """ + return HookResponse(0, '') + + +@has_kwargs({ + 'server_url': 'url of instance that triggered this hook', + 'config': 'path to .ini config used', + 'scm': 'type of version control "git", "hg", "svn"', + 'username': 'username of actor who triggered this event', + 'ip': 'ip address of actor who triggered this hook', 'action': '', 'repository': 'repository name', 'pull_request_id': '', diff --git a/rhodecode/config/rcextensions/utils.py b/rhodecode/config/rcextensions/utils.py --- a/rhodecode/config/rcextensions/utils.py +++ b/rhodecode/config/rcextensions/utils.py @@ -18,8 +18,10 @@ import logging import os +import string import functools import collections +import urllib log = logging.getLogger('rhodecode.' + __name__) @@ -186,4 +188,12 @@ def aslist(obj, sep=None, strip=True): elif obj is None: return [] else: - return [obj] \ No newline at end of file + return [obj] + + +class UrlTemplate(string.Template): + + def safe_substitute(self, **kws): + # url encode the kw for usage in url + kws = {k: urllib.quote(str(v)) for k, v in kws.items()} + return super(UrlTemplate, self).safe_substitute(**kws) diff --git a/rhodecode/events/__init__.py b/rhodecode/events/__init__.py --- a/rhodecode/events/__init__.py +++ b/rhodecode/events/__init__.py @@ -53,7 +53,7 @@ from rhodecode.events.user import ( # p ) from rhodecode.events.repo import ( # pragma: no cover - RepoEvent, + RepoEvent, RepoCommitCommentEvent, RepoPreCreateEvent, RepoCreateEvent, RepoPreDeleteEvent, RepoDeleteEvent, RepoPrePushEvent, RepoPushEvent, diff --git a/rhodecode/events/repo.py b/rhodecode/events/repo.py --- a/rhodecode/events/repo.py +++ b/rhodecode/events/repo.py @@ -181,6 +181,20 @@ class RepoEvent(RhodeCodeIntegrationEven return data +class RepoCommitCommentEvent(RepoEvent): + """ + An instance of this class is emitted as an :term:`event` after a comment is made + on repository commit. + """ + def __init__(self, repo, commit, comment): + super(RepoCommitCommentEvent, self).__init__(repo) + self.commit = commit + self.comment = comment + + name = 'repo-commit-comment' + display_name = lazy_ugettext('repository commit comment') + + class RepoPreCreateEvent(RepoEvent): """ An instance of this class is emitted as an :term:`event` before a repo is diff --git a/rhodecode/lib/hooks_base.py b/rhodecode/lib/hooks_base.py --- a/rhodecode/lib/hooks_base.py +++ b/rhodecode/lib/hooks_base.py @@ -24,7 +24,6 @@ Set of hooks run by RhodeCode Enterprise """ import os -import collections import logging import rhodecode @@ -349,8 +348,8 @@ class ExtensionCallback(object): try: kwargs_to_pass[key] = kwargs[key] except KeyError: - log.error('Failed to fetch %s key. Expected keys: %s', - key, self._kwargs_keys) + log.error('Failed to fetch %s key from given kwargs. ' + 'Expected keys: %s', key, self._kwargs_keys) raise # backward compat for removed api_key for old hooks. This was it works @@ -437,6 +436,15 @@ log_review_pull_request = ExtensionCallb 'mergeable', 'source', 'target', 'author', 'reviewers')) +log_comment_pull_request = ExtensionCallback( + hook_name='COMMENT_PULL_REQUEST', + kwargs_keys=( + 'server_url', 'config', 'scm', 'username', 'ip', 'action', + 'repository', 'pull_request_id', 'url', 'title', 'description', + 'status', 'comment', 'created_on', 'updated_on', 'commit_ids', 'review_status', + 'mergeable', 'source', 'target', 'author', 'reviewers')) + + log_update_pull_request = ExtensionCallback( hook_name='UPDATE_PULL_REQUEST', kwargs_keys=( @@ -484,6 +492,15 @@ log_delete_repository = ExtensionCallbac 'clone_uri', 'fork_id', 'group_id', 'deleted_by', 'deleted_on')) +log_comment_commit_repository = ExtensionCallback( + hook_name='COMMENT_COMMIT_REPO_HOOK', + kwargs_keys=( + 'repo_name', 'repo_type', 'description', 'private', 'created_on', + 'enable_downloads', 'repo_id', 'user_id', 'enable_statistics', + 'clone_uri', 'fork_id', 'group_id', + 'repository', 'created_by', 'comment', 'commit')) + + log_create_repository_group = ExtensionCallback( hook_name='CREATE_REPO_GROUP_HOOK', kwargs_keys=( diff --git a/rhodecode/lib/hooks_utils.py b/rhodecode/lib/hooks_utils.py --- a/rhodecode/lib/hooks_utils.py +++ b/rhodecode/lib/hooks_utils.py @@ -26,144 +26,190 @@ from rhodecode.lib import hooks_base from rhodecode.lib import utils2 -def _get_rc_scm_extras(username, repo_name, repo_alias, action): - # TODO: johbo: Replace by vcs_operation_context and remove fully +def _supports_repo_type(repo_type): + if repo_type in ('hg', 'git'): + return True + return False + + +def _get_vcs_operation_context(username, repo_name, repo_type, action): + # NOTE(dan): import loop from rhodecode.lib.base import vcs_operation_context + check_locking = action in ('pull', 'push') request = get_current_request() - # default - dummy_environ = webob.Request.blank('').environ try: - environ = request.environ or dummy_environ + environ = request.environ except TypeError: # we might use this outside of request context - environ = dummy_environ + environ = {} - extras = vcs_operation_context( - environ, repo_name, username, action, repo_alias, check_locking) + if not environ: + environ = webob.Request.blank('').environ + + extras = vcs_operation_context(environ, repo_name, username, action, repo_type, check_locking) return utils2.AttributeDict(extras) -def trigger_post_push_hook( - username, action, hook_type, repo_name, repo_alias, commit_ids): +def trigger_post_push_hook(username, action, hook_type, repo_name, repo_type, commit_ids): """ Triggers push action hooks :param username: username who pushes :param action: push/push_local/push_remote + :param hook_type: type of hook executed :param repo_name: name of repo - :param repo_alias: the type of SCM repo + :param repo_type: the type of SCM repo :param commit_ids: list of commit ids that we pushed """ - extras = _get_rc_scm_extras(username, repo_name, repo_alias, action) + extras = _get_vcs_operation_context(username, repo_name, repo_type, action) extras.commit_ids = commit_ids extras.hook_type = hook_type hooks_base.post_push(extras) -def trigger_log_create_pull_request_hook(username, repo_name, repo_alias, - pull_request, data=None): +def trigger_comment_commit_hooks(username, repo_name, repo_type, repo, data=None): + """ + Triggers when a comment is made on a commit + + :param username: username who creates the comment + :param repo_name: name of target repo + :param repo_type: the type of SCM target repo + :param repo: the repo object we trigger the event for + :param data: extra data for specific events e.g {'comment': comment_obj, 'commit': commit_obj} + """ + if not _supports_repo_type(repo_type): + return + + extras = _get_vcs_operation_context(username, repo_name, repo_type, 'comment_commit') + + comment = data['comment'] + commit = data['commit'] + + events.trigger(events.RepoCommitCommentEvent(repo, commit, comment)) + extras.update(repo.get_dict()) + + extras.commit = commit.serialize() + extras.comment = comment.get_api_data() + extras.created_by = username + hooks_base.log_comment_commit_repository(**extras) + + +def trigger_create_pull_request_hook(username, repo_name, repo_type, pull_request, data=None): """ Triggers create pull request action hooks :param username: username who creates the pull request :param repo_name: name of target repo - :param repo_alias: the type of SCM target repo + :param repo_type: the type of SCM target repo :param pull_request: the pull request that was created :param data: extra data for specific events e.g {'comment': comment_obj} """ - if repo_alias not in ('hg', 'git'): + if not _supports_repo_type(repo_type): return - extras = _get_rc_scm_extras(username, repo_name, repo_alias, - 'create_pull_request') + extras = _get_vcs_operation_context(username, repo_name, repo_type, 'create_pull_request') events.trigger(events.PullRequestCreateEvent(pull_request)) extras.update(pull_request.get_api_data(with_merge_state=False)) hooks_base.log_create_pull_request(**extras) -def trigger_log_merge_pull_request_hook(username, repo_name, repo_alias, - pull_request, data=None): +def trigger_merge_pull_request_hook(username, repo_name, repo_type, pull_request, data=None): """ Triggers merge pull request action hooks :param username: username who creates the pull request :param repo_name: name of target repo - :param repo_alias: the type of SCM target repo + :param repo_type: the type of SCM target repo :param pull_request: the pull request that was merged :param data: extra data for specific events e.g {'comment': comment_obj} """ - if repo_alias not in ('hg', 'git'): + if not _supports_repo_type(repo_type): return - extras = _get_rc_scm_extras(username, repo_name, repo_alias, - 'merge_pull_request') + extras = _get_vcs_operation_context(username, repo_name, repo_type, 'merge_pull_request') events.trigger(events.PullRequestMergeEvent(pull_request)) extras.update(pull_request.get_api_data()) hooks_base.log_merge_pull_request(**extras) -def trigger_log_close_pull_request_hook(username, repo_name, repo_alias, - pull_request, data=None): +def trigger_close_pull_request_hook(username, repo_name, repo_type, pull_request, data=None): """ Triggers close pull request action hooks :param username: username who creates the pull request :param repo_name: name of target repo - :param repo_alias: the type of SCM target repo + :param repo_type: the type of SCM target repo :param pull_request: the pull request that was closed :param data: extra data for specific events e.g {'comment': comment_obj} """ - if repo_alias not in ('hg', 'git'): + if not _supports_repo_type(repo_type): return - extras = _get_rc_scm_extras(username, repo_name, repo_alias, - 'close_pull_request') + extras = _get_vcs_operation_context(username, repo_name, repo_type, 'close_pull_request') events.trigger(events.PullRequestCloseEvent(pull_request)) extras.update(pull_request.get_api_data()) hooks_base.log_close_pull_request(**extras) -def trigger_log_review_pull_request_hook(username, repo_name, repo_alias, - pull_request, data=None): +def trigger_review_pull_request_hook(username, repo_name, repo_type, pull_request, data=None): """ Triggers review status change pull request action hooks :param username: username who creates the pull request :param repo_name: name of target repo - :param repo_alias: the type of SCM target repo + :param repo_type: the type of SCM target repo :param pull_request: the pull request that review status changed :param data: extra data for specific events e.g {'comment': comment_obj} """ - if repo_alias not in ('hg', 'git'): + if not _supports_repo_type(repo_type): return - extras = _get_rc_scm_extras(username, repo_name, repo_alias, - 'review_pull_request') + extras = _get_vcs_operation_context(username, repo_name, repo_type, 'review_pull_request') status = data.get('status') events.trigger(events.PullRequestReviewEvent(pull_request, status)) extras.update(pull_request.get_api_data()) hooks_base.log_review_pull_request(**extras) -def trigger_log_update_pull_request_hook(username, repo_name, repo_alias, - pull_request, data=None): +def trigger_comment_pull_request_hook(username, repo_name, repo_type, pull_request, data=None): + """ + Triggers when a comment is made on a pull request + + :param username: username who creates the pull request + :param repo_name: name of target repo + :param repo_type: the type of SCM target repo + :param pull_request: the pull request that comment was made on + :param data: extra data for specific events e.g {'comment': comment_obj} + """ + if not _supports_repo_type(repo_type): + return + + extras = _get_vcs_operation_context(username, repo_name, repo_type, 'comment_pull_request') + + comment = data['comment'] + events.trigger(events.PullRequestCommentEvent(pull_request, comment)) + extras.update(pull_request.get_api_data()) + extras.comment = comment.get_api_data() + hooks_base.log_comment_pull_request(**extras) + + +def trigger_update_pull_request_hook(username, repo_name, repo_type, pull_request, data=None): """ Triggers update pull request action hooks :param username: username who creates the pull request :param repo_name: name of target repo - :param repo_alias: the type of SCM target repo + :param repo_type: the type of SCM target repo :param pull_request: the pull request that was updated :param data: extra data for specific events e.g {'comment': comment_obj} """ - if repo_alias not in ('hg', 'git'): + if not _supports_repo_type(repo_type): return - extras = _get_rc_scm_extras(username, repo_name, repo_alias, - 'update_pull_request') + extras = _get_vcs_operation_context(username, repo_name, repo_type, 'update_pull_request') events.trigger(events.PullRequestUpdateEvent(pull_request)) extras.update(pull_request.get_api_data()) hooks_base.log_update_pull_request(**extras) diff --git a/rhodecode/lib/vcs/backends/base.py b/rhodecode/lib/vcs/backends/base.py --- a/rhodecode/lib/vcs/backends/base.py +++ b/rhodecode/lib/vcs/backends/base.py @@ -925,6 +925,9 @@ class BaseCommit(object): d.pop('repository', None) return d + def serialize(self): + return self.__json__() + def _get_refs(self): return { 'branches': [self.branch] if self.branch else [], diff --git a/rhodecode/model/comment.py b/rhodecode/model/comment.py --- a/rhodecode/model/comment.py +++ b/rhodecode/model/comment.py @@ -30,7 +30,7 @@ from pyramid.threadlocal import get_curr from sqlalchemy.sql.expression import null from sqlalchemy.sql.functions import coalesce -from rhodecode.lib import helpers as h, diffs, channelstream +from rhodecode.lib import helpers as h, diffs, channelstream, hooks_utils from rhodecode.lib import audit_logger from rhodecode.lib.utils2 import extract_mentioned_users, safe_str from rhodecode.model import BaseModel @@ -720,6 +720,26 @@ class CommentsModel(BaseModel): settings = settings_model.get_general_settings() return settings.get('rhodecode_use_outdated_comments', False) + def trigger_commit_comment_hook(self, repo, user, action, data=None): + repo = self._get_repo(repo) + target_scm = repo.scm_instance() + if action == 'create': + trigger_hook = hooks_utils.trigger_comment_commit_hooks + elif action == 'edit': + # TODO(dan): when this is supported we trigger edit hook too + return + else: + return + + log.debug('Handling repo %s trigger_commit_comment_hook with action %s: %s', + repo, action, trigger_hook) + trigger_hook( + username=user.username, + repo_name=repo.repo_name, + repo_type=target_scm.alias, + repo=repo, + data=data) + def _parse_comment_line_number(line_no): """ diff --git a/rhodecode/model/pull_request.py b/rhodecode/model/pull_request.py --- a/rhodecode/model/pull_request.py +++ b/rhodecode/model/pull_request.py @@ -542,8 +542,7 @@ class PullRequestModel(BaseModel): pull_request, auth_user=auth_user, translator=translator) self.notify_reviewers(pull_request, reviewer_ids) - self.trigger_pull_request_hook( - pull_request, created_by_user, 'create') + self.trigger_pull_request_hook(pull_request, created_by_user, 'create') creation_data = pull_request.get_api_data(with_merge_state=False) self._log_audit_action( @@ -556,28 +555,26 @@ class PullRequestModel(BaseModel): pull_request = self.__get_pull_request(pull_request) target_scm = pull_request.target_repo.scm_instance() if action == 'create': - trigger_hook = hooks_utils.trigger_log_create_pull_request_hook + trigger_hook = hooks_utils.trigger_create_pull_request_hook elif action == 'merge': - trigger_hook = hooks_utils.trigger_log_merge_pull_request_hook + trigger_hook = hooks_utils.trigger_merge_pull_request_hook elif action == 'close': - trigger_hook = hooks_utils.trigger_log_close_pull_request_hook + trigger_hook = hooks_utils.trigger_close_pull_request_hook elif action == 'review_status_change': - trigger_hook = hooks_utils.trigger_log_review_pull_request_hook + trigger_hook = hooks_utils.trigger_review_pull_request_hook elif action == 'update': - trigger_hook = hooks_utils.trigger_log_update_pull_request_hook + trigger_hook = hooks_utils.trigger_update_pull_request_hook elif action == 'comment': - # dummy hook ! for comment. We want this function to handle all cases - def trigger_hook(*args, **kwargs): - pass - comment = data['comment'] - events.trigger(events.PullRequestCommentEvent(pull_request, comment)) + trigger_hook = hooks_utils.trigger_comment_pull_request_hook else: return + log.debug('Handling pull_request %s trigger_pull_request_hook with action %s and hook: %s', + pull_request, action, trigger_hook) trigger_hook( username=user.username, repo_name=pull_request.target_repo.repo_name, - repo_alias=target_scm.alias, + repo_type=target_scm.alias, pull_request=pull_request, data=data) @@ -1286,8 +1283,7 @@ class PullRequestModel(BaseModel): pull_request.status = PullRequest.STATUS_CLOSED pull_request.updated_on = datetime.datetime.now() Session().add(pull_request) - self.trigger_pull_request_hook( - pull_request, pull_request.author, 'close') + self.trigger_pull_request_hook(pull_request, pull_request.author, 'close') pr_data = pull_request.get_api_data(with_merge_state=False) self._log_audit_action( @@ -1333,20 +1329,21 @@ class PullRequestModel(BaseModel): ) Session().flush() - events.trigger(events.PullRequestCommentEvent(pull_request, comment)) + + self.trigger_pull_request_hook(pull_request, user, 'comment', + data={'comment': comment}) + # we now calculate the status of pull request again, and based on that # calculation trigger status change. This might happen in cases # that non-reviewer admin closes a pr, which means his vote doesn't # change the status, while if he's a reviewer this might change it. calculated_status = pull_request.calculated_review_status() if old_calculated_status != calculated_status: - self.trigger_pull_request_hook( - pull_request, user, 'review_status_change', - data={'status': calculated_status}) + self.trigger_pull_request_hook(pull_request, user, 'review_status_change', + data={'status': calculated_status}) # finally close the PR - PullRequestModel().close_pull_request( - pull_request.pull_request_id, user) + PullRequestModel().close_pull_request(pull_request.pull_request_id, user) return comment, status diff --git a/rhodecode/model/scm.py b/rhodecode/model/scm.py --- a/rhodecode/model/scm.py +++ b/rhodecode/model/scm.py @@ -473,7 +473,7 @@ class ScmModel(BaseModel): # We trigger the post-push action hooks_utils.trigger_post_push_hook( username=user.username, action='push_local', hook_type='post_push', - repo_name=repo_name, repo_alias=repo.alias, commit_ids=[tip.raw_id]) + repo_name=repo_name, repo_type=repo.alias, commit_ids=[tip.raw_id]) return tip def _sanitize_path(self, f_path): @@ -799,7 +799,7 @@ class ScmModel(BaseModel): if trigger_push_hook: hooks_utils.trigger_post_push_hook( username=user.username, action='push_local', - repo_name=repo.repo_name, repo_alias=scm_instance.alias, + repo_name=repo.repo_name, repo_type=scm_instance.alias, hook_type='post_push', commit_ids=[tip.raw_id]) return tip @@ -864,7 +864,7 @@ class ScmModel(BaseModel): if trigger_push_hook: hooks_utils.trigger_post_push_hook( username=user.username, action='push_local', hook_type='post_push', - repo_name=repo.repo_name, repo_alias=scm_instance.alias, + repo_name=repo.repo_name, repo_type=scm_instance.alias, commit_ids=[tip.raw_id]) return tip @@ -926,7 +926,7 @@ class ScmModel(BaseModel): if trigger_push_hook: hooks_utils.trigger_post_push_hook( username=user.username, action='push_local', hook_type='post_push', - repo_name=repo.repo_name, repo_alias=scm_instance.alias, + repo_name=repo.repo_name, repo_type=scm_instance.alias, commit_ids=[tip.raw_id]) return tip