mirror of
https://github.com/zulip/zulip.git
synced 2025-11-08 16:01:58 +00:00
Improve English grammar in comment messages in git integrations by adding 'on' between comment action and noun (PR, Issue, Commit).
302 lines
13 KiB
Python
302 lines
13 KiB
Python
from __future__ import absolute_import
|
|
from django.conf import settings
|
|
from zerver.models import get_client, UserProfile
|
|
from zerver.lib.response import json_success
|
|
from zerver.lib.validator import check_dict
|
|
from zerver.decorator import authenticated_api_view, REQ, has_request_variables, to_non_negative_int, flexible_boolean
|
|
from zerver.views.messages import send_message_backend
|
|
from zerver.lib.webhooks.git import get_push_commits_event_message,\
|
|
SUBJECT_WITH_BRANCH_TEMPLATE, get_force_push_commits_event_message, \
|
|
get_remove_branch_event_message, get_pull_request_event_message,\
|
|
get_issue_event_message, SUBJECT_WITH_PR_OR_ISSUE_INFO_TEMPLATE
|
|
import logging
|
|
import re
|
|
import ujson
|
|
|
|
from six import text_type
|
|
from typing import Any, Mapping, Optional, Sequence, Tuple
|
|
from zerver.lib.str_utils import force_str
|
|
from django.http import HttpRequest, HttpResponse
|
|
|
|
ZULIP_TEST_REPO_NAME = 'zulip-test'
|
|
ZULIP_TEST_REPO_ID = 6893087
|
|
|
|
def is_test_repository(repository):
|
|
# type: (Mapping[text_type, Any]) -> bool
|
|
return repository['name'] == ZULIP_TEST_REPO_NAME and repository['id'] == ZULIP_TEST_REPO_ID
|
|
|
|
class UnknownEventType(Exception):
|
|
pass
|
|
|
|
def github_pull_request_content(payload):
|
|
# type: (Mapping[text_type, Any]) -> text_type
|
|
pull_request = payload['pull_request']
|
|
action = get_pull_request_or_issue_action(payload)
|
|
|
|
if action in ('opened', 'edited'):
|
|
return get_pull_request_event_message(
|
|
payload['sender']['login'],
|
|
action,
|
|
pull_request['html_url'],
|
|
pull_request['head']['ref'],
|
|
pull_request['base']['ref'],
|
|
pull_request['body'],
|
|
get_pull_request_or_issue_assignee(pull_request)
|
|
)
|
|
return get_pull_request_event_message(
|
|
payload['sender']['login'],
|
|
action,
|
|
pull_request['html_url'],
|
|
)
|
|
|
|
def github_issues_content(payload):
|
|
# type: (Mapping[text_type, Any]) -> text_type
|
|
issue = payload['issue']
|
|
action = get_pull_request_or_issue_action(payload)
|
|
|
|
if action in ('opened', 'edited'):
|
|
return get_issue_event_message(
|
|
payload['sender']['login'],
|
|
action,
|
|
issue['html_url'],
|
|
issue['body'],
|
|
get_pull_request_or_issue_assignee(issue)
|
|
)
|
|
return get_issue_event_message(
|
|
payload['sender']['login'],
|
|
action,
|
|
issue['html_url'],
|
|
)
|
|
|
|
def github_object_commented_content(payload, type):
|
|
# type: (Mapping[text_type, Any], text_type) -> text_type
|
|
comment = payload['comment']
|
|
issue = payload['issue']
|
|
action = u'[commented]({}) on'.format(comment['html_url'])
|
|
|
|
return get_pull_request_event_message(
|
|
comment['user']['login'],
|
|
action,
|
|
issue['html_url'],
|
|
message=comment['body'],
|
|
type=type
|
|
)
|
|
|
|
def get_pull_request_or_issue_action(payload):
|
|
# type: (Mapping[text_type, Any]) -> text_type
|
|
return 'synchronized' if payload['action'] == 'synchronize' else payload['action']
|
|
|
|
def get_pull_request_or_issue_assignee(object_payload):
|
|
# type: (Mapping[text_type, Any]) -> text_type
|
|
assignee_dict = object_payload.get('assignee')
|
|
if assignee_dict:
|
|
return assignee_dict.get('login')
|
|
|
|
def get_pull_request_or_issue_subject(repository, payload_object, type):
|
|
# type: (Mapping[text_type, Any], Mapping[text_type, Any], text_type) -> text_type
|
|
return SUBJECT_WITH_PR_OR_ISSUE_INFO_TEMPLATE.format(
|
|
repo=repository['name'],
|
|
type=type,
|
|
id=payload_object['number'],
|
|
title=payload_object['title']
|
|
)
|
|
|
|
def github_generic_subject(noun, topic_focus, blob):
|
|
# type: (text_type, text_type, Mapping[text_type, Any]) -> text_type
|
|
# issue and pull_request objects have the same fields we're interested in
|
|
return u'%s: %s %d: %s' % (topic_focus, noun, blob['number'], blob['title'])
|
|
|
|
def api_github_v1(user_profile, event, payload, branches, stream, **kwargs):
|
|
# type: (UserProfile, text_type, Mapping[text_type, Any], text_type, text_type, **Any) -> Tuple[text_type, text_type, text_type]
|
|
"""
|
|
processes github payload with version 1 field specification
|
|
`payload` comes in unmodified from github
|
|
`stream` is set to 'commits' if otherwise unset
|
|
"""
|
|
commit_stream = stream
|
|
issue_stream = 'issues'
|
|
return api_github_v2(user_profile, event, payload, branches, stream, commit_stream, issue_stream, **kwargs)
|
|
|
|
|
|
def api_github_v2(user_profile, event, payload, branches, default_stream,
|
|
commit_stream, issue_stream, topic_focus = None):
|
|
# type: (UserProfile, text_type, Mapping[text_type, Any], text_type, text_type, text_type, text_type, Optional[text_type]) -> Tuple[text_type, text_type, text_type]
|
|
"""
|
|
processes github payload with version 2 field specification
|
|
`payload` comes in unmodified from github
|
|
`default_stream` is set to what `stream` is in v1 above
|
|
`commit_stream` and `issue_stream` fall back to `default_stream` if they are empty
|
|
This and allowing alternative endpoints is what distinguishes v1 from v2 of the github configuration
|
|
"""
|
|
target_stream = commit_stream if commit_stream else default_stream
|
|
issue_stream = issue_stream if issue_stream else default_stream
|
|
repository = payload['repository']
|
|
topic_focus = topic_focus if topic_focus else repository['name']
|
|
|
|
# Event Handlers
|
|
if event == 'pull_request':
|
|
subject = get_pull_request_or_issue_subject(repository, payload['pull_request'], 'PR')
|
|
content = github_pull_request_content(payload)
|
|
elif event == 'issues':
|
|
# in v1, we assume that this stream exists since it is
|
|
# deprecated and the few realms that use it already have the
|
|
# stream
|
|
target_stream = issue_stream
|
|
subject = get_pull_request_or_issue_subject(repository, payload['issue'], 'Issue')
|
|
content = github_issues_content(payload)
|
|
elif event == 'issue_comment':
|
|
# Comments on both issues and pull requests come in as issue_comment events
|
|
issue = payload['issue']
|
|
if 'pull_request' not in issue or issue['pull_request']['diff_url'] is None:
|
|
# It's an issues comment
|
|
target_stream = issue_stream
|
|
type = 'Issue'
|
|
subject = get_pull_request_or_issue_subject(repository, payload['issue'], type)
|
|
else:
|
|
# It's a pull request comment
|
|
type = 'PR'
|
|
subject = get_pull_request_or_issue_subject(repository, payload['issue'], type)
|
|
|
|
content = github_object_commented_content(payload, type)
|
|
|
|
elif event == 'push':
|
|
subject, content = build_message_from_gitlog(user_profile, topic_focus,
|
|
payload['ref'], payload['commits'],
|
|
payload['before'], payload['after'],
|
|
payload['compare'],
|
|
payload['pusher']['name'],
|
|
forced=payload['forced'],
|
|
created=payload['created'])
|
|
elif event == 'commit_comment':
|
|
comment = payload['comment']
|
|
subject = u'%s: commit %s' % (topic_focus, comment['commit_id'])
|
|
|
|
content = (u'%s [commented](%s)'
|
|
% (comment['user']['login'],
|
|
comment['html_url']))
|
|
|
|
if comment['line'] is not None:
|
|
content += u' on `%s`, line %d' % (comment['path'], comment['line'])
|
|
|
|
content += u'\n\n~~~ quote\n%s\n~~~' % (comment['body'],)
|
|
|
|
else:
|
|
raise UnknownEventType(force_str(u'Event %s is unknown and cannot be handled' % (event,)))
|
|
|
|
return target_stream, subject, content
|
|
|
|
@authenticated_api_view(is_webhook=True)
|
|
@has_request_variables
|
|
def api_github_landing(request, user_profile, event=REQ(),
|
|
payload=REQ(validator=check_dict([])),
|
|
branches=REQ(default=''),
|
|
stream=REQ(default=''),
|
|
version=REQ(converter=to_non_negative_int, default=1),
|
|
commit_stream=REQ(default=''),
|
|
issue_stream=REQ(default=''),
|
|
exclude_pull_requests=REQ(converter=flexible_boolean, default=False),
|
|
exclude_issues=REQ(converter=flexible_boolean, default=False),
|
|
exclude_commits=REQ(converter=flexible_boolean, default=False),
|
|
emphasize_branch_in_topic=REQ(converter=flexible_boolean, default=False),
|
|
):
|
|
# type: (HttpRequest, UserProfile, text_type, Mapping[text_type, Any], text_type, text_type, int, text_type, text_type, bool, bool, bool, bool) -> HttpResponse
|
|
|
|
repository = payload['repository']
|
|
|
|
# Special hook for capturing event data. If we see our special test repo, log the payload from github.
|
|
try:
|
|
if is_test_repository(repository) and settings.PRODUCTION:
|
|
with open('/var/log/zulip/github-payloads', 'a') as f:
|
|
f.write(ujson.dumps({'event': event,
|
|
'payload': payload,
|
|
'branches': branches,
|
|
'stream': stream,
|
|
'version': version,
|
|
'commit_stream': commit_stream,
|
|
'issue_stream': issue_stream,
|
|
'exclude_pull_requests': exclude_pull_requests,
|
|
'exclude_issues': exclude_issues,
|
|
'exclude_commits': exclude_commits,
|
|
'emphasize_branch_in_topic': emphasize_branch_in_topic,
|
|
}))
|
|
f.write('\n')
|
|
except Exception:
|
|
logging.exception('Error while capturing Github event')
|
|
|
|
if not stream:
|
|
stream = 'commits'
|
|
|
|
short_ref = re.sub(r'^refs/heads/', '', payload.get('ref', ''))
|
|
kwargs = dict()
|
|
|
|
if emphasize_branch_in_topic and short_ref:
|
|
kwargs['topic_focus'] = short_ref
|
|
|
|
allowed_events = set()
|
|
if not exclude_pull_requests:
|
|
allowed_events.add('pull_request')
|
|
|
|
if not exclude_issues:
|
|
allowed_events.add('issues')
|
|
allowed_events.add('issue_comment')
|
|
|
|
if not exclude_commits:
|
|
allowed_events.add('push')
|
|
allowed_events.add('commit_comment')
|
|
|
|
if event not in allowed_events:
|
|
return json_success()
|
|
|
|
# We filter issue_comment events for issue creation events
|
|
if event == 'issue_comment' and payload['action'] != 'created':
|
|
return json_success()
|
|
|
|
if event == 'push':
|
|
# If we are given a whitelist of branches, then we silently ignore
|
|
# any push notification on a branch that is not in our whitelist.
|
|
if branches and short_ref not in re.split('[\s,;|]+', branches):
|
|
return json_success()
|
|
|
|
# Map payload to the handler with the right version
|
|
if version == 2:
|
|
target_stream, subject, content = api_github_v2(user_profile, event, payload, branches,
|
|
stream, commit_stream, issue_stream,
|
|
**kwargs)
|
|
else:
|
|
target_stream, subject, content = api_github_v1(user_profile, event, payload, branches,
|
|
stream, **kwargs)
|
|
|
|
request.client = get_client('ZulipGitHubWebhook')
|
|
return send_message_backend(request, user_profile,
|
|
message_type_name='stream',
|
|
message_to=[target_stream],
|
|
forged=False, subject_name=subject,
|
|
message_content=content)
|
|
|
|
def build_message_from_gitlog(user_profile, name, ref, commits, before, after, url, pusher, forced=None, created=None):
|
|
# type: (UserProfile, text_type, text_type, List[Dict[str, str]], text_type, text_type, text_type, text_type, Optional[text_type], Optional[text_type]) -> Tuple[text_type, text_type]
|
|
short_ref = re.sub(r'^refs/heads/', '', ref)
|
|
subject = SUBJECT_WITH_BRANCH_TEMPLATE.format(repo=name, branch=short_ref)
|
|
|
|
if re.match(r'^0+$', after):
|
|
content = get_remove_branch_event_message(pusher, short_ref)
|
|
# 'created' and 'forced' are github flags; the second check is for beanstalk
|
|
elif (forced and not created) or (forced is None and len(commits) == 0):
|
|
content = get_force_push_commits_event_message(pusher, url, short_ref, after[:7])
|
|
else:
|
|
commits = _transform_commits_list_to_common_format(commits)
|
|
content = get_push_commits_event_message(pusher, url, short_ref, commits)
|
|
|
|
return subject, content
|
|
|
|
def _transform_commits_list_to_common_format(commits):
|
|
# type: (List[Dict[str, str]]) -> List[Dict[str, str]]
|
|
new_commits_list = []
|
|
for commit in commits:
|
|
new_commits_list.append({
|
|
'sha': commit.get('id'),
|
|
'url': commit.get('url'),
|
|
'message': commit.get('message'),
|
|
})
|
|
return new_commits_list
|