Maciej Szulik added the comment:
Patch updated and most importantly tested with github. Comments inline below:
> I think you'll end up with a user_id in the >1 case and a list (assuming
> filter returns a list) in the <1 case. At the end you take the first element
> of the list, which is
> correct only for the <1 case (unless filters returns a single id, but in that
> case I'm not sure what the [0] at the end is for). I would also suggest to
> user user_ids and
> user_id to distinguish the list from the single id.
> Ezio seems to be right about user_id and getting a list or a name depending
> on which branch is taken based on the line that sets user_id initially. It
> also might be clearer if > you check for `not len(user_id)` or `len(user_id)
> == 0` instead of `len(user_id) < 1`.
Yeah, filter returns list always, I've updated the patch accordingly.
> I would rename set_current_user() to something more explicit, or, if the only
> purpose of this method is to affect the author=self.db.getuid(), perhaps
> remove the method and use
> a module/class-level constant. The same constant could also be used in the
> snippet I pasted above to avoid duplication.
I've changed it to set_roundup_user, not sure what kind of constant you were
talking about.
> Does produces the correct output in the history at the bottom of the issue?
> (i.e. does it show "set messages: +msgXXX" with only the new message, rather
> than the whole list
> of messages?)
Yes, it's 'messages +msgX', as expected.
> I would move the .encode('utf-8') from dispatch() to handle_action() (add it
> to the line "'branch': branch.encode('utf-8'),").
Done
> The commit_id variable in handle_action() is unused.
Removed
> Any reason why you used Template() instead of a simply using format() (or %)?
> You might also be able to get rid of all those encode if you use format().
Laziness, I just copy&pasted what I got from hglookup.py ;) Changed to format.
> github/roundup should be capitalized properly in the docstrings. The
> docstring for handle_action() could be a bit more explicit :)
Done
> And thanks for handling the case when there's no match. I wouldn't be
> surprised if people don't sign the CLA but still get a PR merged that e.g.
> fixes a single line of the
> documentation.
N/p :)
_______________________________________________________
PSF Meta Tracker <metatrac...@psf.upfronthosting.co.za>
<http://psf.upfronthosting.co.za/roundup/meta/issue611>
_______________________________________________________
diff --git a/roundup/github.py b/roundup/github.py
--- a/roundup/github.py
+++ b/roundup/github.py
@@ -1,5 +1,3 @@
-from roundup.exceptions import *
-
import hashlib
import hmac
import json
@@ -7,6 +5,8 @@
import os
import logging
+from roundup.exceptions import Unauthorised, MethodNotAllowed, \
+ UnsupportedMediaType, Reject
if hasattr(hmac, 'compare_digest'):
compare_digest = hmac.compare_digest
@@ -14,8 +14,15 @@
def compare_digest(a, b):
return a == b
-url_re = re.compile(r'https://github.com/python/cpython/pull/(?P<number>\d+)')
-issue_id_re = re.compile(r'bpo\s*(\d+)', re.I)
+URL_RE = re.compile(r'https://github.com/python/cpython/pull/(?P<number>\d+)')
+ISSUE_GH_RE = re.compile(r'bpo\s*(\d+)', re.I)
+ISSUE_BPO_RE = re.compile(r'(#|issue|bug)\s*(?P<issue_id>\d+)', re.I)
+
+COMMENT_TEMPLATE = """\
+New changeset {changeset_id} by {author} in branch '{branch}':
+{commit_msg}
+{changeset_url}
+"""
class GitHubHandler:
"""
@@ -59,6 +66,10 @@
data = json.loads(self.form.value)
handler = IssueComment(self.db, data)
handler.dispatch()
+ elif event == 'push':
+ data = json.loads(self.form.value)
+ handler = Push(self.db, data)
+ handler.dispatch()
def validate_webhook_secret(self):
"""
@@ -93,7 +104,7 @@
def get_event(self):
"""
- Extracts github event from header field.
+ Extracts GitHub event from header field.
"""
return self.request.headers.get('X-GitHub-Event', None)
@@ -107,24 +118,27 @@
self.db = db
self.data = data
- def set_current_user(self):
+ def set_roundup_user(self):
"""
- Helper method used for setting current user for roundup, based
- on the information from github about event author.
+ Helper method used for setting the current user for Roundup, based
+ on the information from GitHub about event author.
"""
github_username = self.get_github_username()
- user_id = self.db.user.filter(None, {'github': github_username})
- # TODO set bpobot as userid when none is found
- if len(user_id) == 1:
- # TODO what if multiple bpo users have the same github username?
- username = self.db.user.get(user_id[0], 'username')
- self.db.setCurrentUser(username)
+ user_ids = self.db.user.filter(None, {'github': github_username})
+ if not user_ids:
+ # set bpobot as userid when none is found
+ user_ids = self.db.user.filter(None, {'username': 'python-dev'})
+ if not user_ids:
+ # python-dev does not exists, anonymous will be used instead
+ return
+ username = self.db.user.get(user_ids[0], 'username')
+ self.db.setCurrentUser(username)
def dispatch(self):
"""
- Main method responsible for responding to incoming github event.
+ Main method responsible for responding to incoming GitHub event.
"""
- self.set_current_user()
+ self.set_roundup_user()
action = self.data.get('action', '').encode('utf-8')
issue_ids = self.get_issue_ids()
if not issue_ids:
@@ -220,7 +234,7 @@
raise Reject()
title = pull_request.get('title', '').encode('utf-8')
body = pull_request.get('body', '').encode('utf-8')
- return list(set(issue_id_re.findall(title) +
issue_id_re.findall(body)))
+ return list(set(ISSUE_GH_RE.findall(title) +
ISSUE_GH_RE.findall(body)))
def get_pr_details(self):
"""
@@ -234,7 +248,7 @@
raise Reject()
title = pull_request.get('title', '').encode('utf-8')
status = pull_request.get('state', '').encode('utf-8')
- # github has two states open and closed, information about pull request
+ # GitHub has two states open and closed, information about pull request
# being merged in kept in separate field
if pull_request.get('merged', False):
status = "merged"
@@ -242,13 +256,14 @@
def get_github_username(self):
"""
- Extract github username from a pull request.
+ Extract GitHub username from a pull request.
"""
pull_request = self.data.get('pull_request')
if pull_request is None:
raise Reject()
return pull_request.get('user', {}).get('login', '').encode('utf-8')
+
class IssueComment(Event):
"""
Class responsible for handling issue comment events, but only within the
@@ -274,7 +289,7 @@
raise Reject()
title = issue.get('title', '').encode('utf-8')
body = comment.get('body', '').encode('utf-8')
- return list(set(issue_id_re.findall(title) +
issue_id_re.findall(body)))
+ return list(set(ISSUE_GH_RE.findall(title) +
ISSUE_GH_RE.findall(body)))
def get_pr_details(self):
"""
@@ -284,16 +299,74 @@
if issue is None:
raise Reject()
url = issue.get('pull_request', {}).get('html_url')
- number_match = url_re.search(url)
+ number_match = URL_RE.search(url)
if not number_match:
return (None, None, None)
return number_match.group('number'), None, None
def get_github_username(self):
"""
- Extract github username from a comment.
+ Extract GitHub username from a comment.
"""
issue = self.data.get('issue')
if issue is None:
raise Reject()
return issue.get('user', {}).get('login', '').encode('utf-8')
+
+
+class Push(Event):
+ """
+ Class responsible for handling push events.
+ """
+
+ def dispatch(self):
+ """
+ Main method responsible for responding to incoming GitHub event.
+ """
+ self.set_roundup_user()
+ commits = self.data.get('commits', [])
+ ref = self.data.get('ref', 'refs/heads/master')
+ messages = {}
+ # extract commit messages
+ for commit in commits:
+ messages.update(self.handle_action(commit, ref))
+ if not messages:
+ return
+ # add comments to appropriate issues
+ for issue_id, msg in messages.iteritems():
+ issue_msgs = self.db.issue.get(issue_id, 'messages')
+ newmsg = self.db.msg.create(content=msg, author=self.db.getuid())
+ issue_msgs.append(newmsg)
+ self.db.issue.set(issue_id, messages=issue_msgs)
+ self.db.commit()
+
+ def get_github_username(self):
+ """
+ Extract GitHub username from a push event.
+ """
+ return self.data.get('pusher', []).get('name', '').encode('utf-8')
+
+ def handle_action(self, commit, ref):
+ """
+ This is implementing the same logic as the mercurial hook from here:
+ https://hg.python.org/hooks/file/tip/hgroundup.py
+ """
+ branch = ref.encode('utf-8').split('/')[-1]
+ description = commit.get('message', '').encode('utf-8')
+ matches = ISSUE_BPO_RE.finditer(description)
+ messages = {}
+ for match in matches:
+ # TODO: close issue when close(s|d) is seen
+ data = match.groupdict()
+ # check for duplicated issue numbers in the same commit msg
+ if data['issue_id'] in messages:
+ continue
+ messages[data['issue_id']] = COMMENT_TEMPLATE.format(
+ author=commit.get('committer', {}).get('name',
'').encode('utf-8'),
+ branch=branch,
+ changeset_id=commit.get('id', '').encode('utf-8'),
+ changeset_url=commit.get('url', '').encode('utf-8'),
+ commit_msg=description.splitlines()[0],
+ )
+ return messages
+
diff --git a/test/data/pushevent.txt b/test/data/pushevent.txt
new file mode 100644
--- /dev/null
+++ b/test/data/pushevent.txt
@@ -0,0 +1,11 @@
+POST /python-dev/pull_request HTTP/1.1
+Host: 3ab1787e.ngrok.io
+Accept: */*
+User-Agent: GitHub-Hookshot/98ea3cc
+X-GitHub-Event: push
+X-GitHub-Delivery: 3d4b5180-5c89-11e6-88fd-1aa99d941991
+content-type: application/json
+X-Hub-Signature: sha1=c760213ddccd47bf3bbd1735b70c787bb8d07724
+Content-Length: 5852
+
+{"ref":"refs/heads/test1","before":"6c5d08240c73014f282bc7336c08fe9ec0b557b0","after":"65c3a074262662a2c55109ff9a2456ee7647fcc9","created":false,"deleted":false,"forced":false,"base_ref":null,"compare":"https://github.com/python/cpython/compare/6c5d08240c73...65c3a0742626","commits":[{"id":"65c3a074262662a2c55109ff9a2456ee7647fcc9","tree_id":"2a35adbb6fce03b51dce23b4ed48526a5a74255a","distinct":true,"message":"#1:
fix
tests.","timestamp":"2017-01-19T22:19:07+01:00","url":"https://github.com/python/cpython/commit/65c3a074262662a2c55109ff9a2456ee7647fcc9","author":{"name":"Maciej
Szulik","email":"solt...@gmail.com","username":"soltysh"},"committer":{"name":"Maciej
Szulik","email":"solt...@gmail.com","username":"soltysh"},"added":[],"removed":[],"modified":["README"]}],"head_commit":{"id":"65c3a074262662a2c55109ff9a2456ee7647fcc9","tree_id":"2a35adbb6fce03b51dce23b4ed48526a5a74255a","distinct":true,"message":"Fixes#1","timestamp":"2017-01-19T22:19:07+01:00","url":"https://githu
b.com/python/cpython/commit/65c3a074262662a2c55109ff9a2456ee7647fcc9","author":{"name":"Maciej
Szulik","email":"solt...@gmail.com","username":"soltysh"},"committer":{"name":"Maciej
Szulik","email":"solt...@gmail.com","username":"soltysh"},"added":[],"removed":[],"modified":["README"]},"repository":{"id":76687672,"name":"cpython","full_name":"python/cpython","owner":{"name":"python"},"private":false,"html_url":"https://github.com/python/cpython","description":"Semi-officialread-onlymirroroftheCPythonMercurialrepository","fork":true,"url":"https://github.com/python/cpython","forks_url":"https://api.github.com/repos/python/cpython/forks","keys_url":"https://api.github.com/repos/python/cpython/keys{/key_id}","collaborators_url":"https://api.github.com/repos/python/cpython/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/python/cpython/teams","hooks_url":"https://api.github.com/repos/python/cpython/hooks","issue_events_url":"https://api.github.com/repos/pyt
hon/cpython/issues/events{/number}","events_url":"https://api.github.com/repos/python/cpython/events","assignees_url":"https://api.github.com/repos/python/cpython/assignees{/user}","branches_url":"https://api.github.com/repos/python/cpython/branches{/branch}","tags_url":"https://api.github.com/repos/python/cpython/tags","blobs_url":"https://api.github.com/repos/python/cpython/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/python/cpython/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/python/cpython/git/refs{/sha}","trees_url":"https://api.github.com/repos/python/cpython/git/trees{/sha}","statuses_url":"https://api.github.com/repos/python/cpython/statuses/{sha}","languages_url":"https://api.github.com/repos/python/cpython/languages","stargazers_url":"https://api.github.com/repos/python/cpython/stargazers","contributors_url":"https://api.github.com/repos/python/cpython/contributors","subscribers_url":"https://api.github.com/repos/python/cpython/subs
cribers","subscription_url":"https://api.github.com/repos/python/cpython/subscription","commits_url":"https://api.github.com/repos/python/cpython/commits{/sha}","git_commits_url":"https://api.github.com/repos/python/cpython/git/commits{/sha}","comments_url":"https://api.github.com/repos/python/cpython/comments{/number}","issue_comment_url":"https://api.github.com/repos/python/cpython/issues/comments{/number}","contents_url":"https://api.github.com/repos/python/cpython/contents/{+path}","compare_url":"https://api.github.com/repos/python/cpython/compare/{base}...{head}","merges_url":"https://api.github.com/repos/python/cpython/merges","archive_url":"https://api.github.com/repos/python/cpython/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/python/cpython/downloads","issues_url":"https://api.github.com/repos/python/cpython/issues{/number}","pulls_url":"https://api.github.com/repos/python/cpython/pulls{/number}","milestones_url":"https://api.github.com/repo
s/python/cpython/milestones{/number}","notifications_url":"https://api.github.com/repos/python/cpython/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/python/cpython/labels{/name}","releases_url":"https://api.github.com/repos/python/cpython/releases{/id}","deployments_url":"https://api.github.com/repos/python/cpython/deployments","created_at":1481924711,"updated_at":"2016-12-16T21:45:35Z","pushed_at":1484860759,"git_url":"git://github.com/python/cpython.git","ssh_url":"g...@github.com:python/cpython.git","clone_url":"https://github.com/python/cpython.git","svn_url":"https://github.com/python/cpython","homepage":"","size":269743,"stargazers_count":0,"watchers_count":0,"language":"Python","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master","stargazers":0,"master_branch":"master"},"pusher":{"name":"sol
tysh","email":"solt...@gmail.com"},"sender":{"login":"soltysh","id":576341,"avatar_url":"https://avatars.githubusercontent.com/u/576341?v=3","gravatar_id":"","url":"https://api.github.com/users/soltysh","html_url":"https://github.com/soltysh","followers_url":"https://api.github.com/users/soltysh/followers","following_url":"https://api.github.com/users/soltysh/following{/other_user}","gists_url":"https://api.github.com/users/soltysh/gists{/gist_id}","starred_url":"https://api.github.com/users/soltysh/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/soltysh/subscriptions","organizations_url":"https://api.github.com/users/soltysh/orgs","repos_url":"https://api.github.com/users/soltysh/repos","events_url":"https://api.github.com/users/soltysh/events{/privacy}","received_events_url":"https://api.github.com/users/soltysh/received_events","type":"User","site_admin":false}}
diff --git a/test/test_github.py b/test/test_github.py
--- a/test/test_github.py
+++ b/test/test_github.py
@@ -296,6 +296,18 @@
status = self.db.pull_request.get(prs[0], 'status')
self.assertEqual(status, 'closed')
+ def testPushEventAddsComment(self):
+ dummy_client = self._make_client('pushevent.txt')
+ handler = GitHubHandler(dummy_client)
+ handler.dispatch()
+ msgs = self.db.issue.get('1', 'messages')
+ self.assertEqual(len(msgs), 1)
+ content = self.db.msg.get(msgs[0], 'content')
+ self.assertIn("""New changeset
65c3a074262662a2c55109ff9a2456ee7647fcc9 by Maciej Szulik in branch 'test1':
+#1: fix tests.
+https://github.com/python/cpython/commit/65c3a074262662a2c55109ff9a2456ee7647fcc9
+""",
+ content)
def test_suite():
suite = unittest.TestSuite()
_______________________________________________
Tracker-discuss mailing list
Tracker-discuss@python.org
https://mail.python.org/mailman/listinfo/tracker-discuss
Code of Conduct: https://www.python.org/psf/codeofconduct/