diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/README.md b/README.md index 7968217..fcfe4d3 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,46 @@ Highfive ======== -GitHub hooks to provide an encouraging atmosphere for new contributors +GitHub hooks to provide an encouraging atmosphere for new contributors. -Docs for this highfive live [on the Servo -wiki](https://github.com/servo/servo/wiki/Highfive) +Docs for the highfive instance for servo/servo repository live [on the Servo +wiki](https://github.com/servo/servo/wiki/Highfive). + +## Design + +Highfive is built as a modular, loosely-coupled set of handlers for Github +API events. Each time an API event is processed, each handler is given the +opportunity to respond to it, either by making direct API calls (such as +manipulating PR labels) or using cross-handler features such as logging a +warning (which are aggregated at the end and posted as a single comment). + +## Testing + +Per-handler tests can be run using `python test.py`. These consist of +a set of JSON documents collected from the `tests/` subdirectory of +each handler, using the following format: +```json +{ + "initial": { + // Initial state of the PR before any handlers process the payload. + // Defaults: + "labels": [], + "diff": "", + "new_contributor": false, + "assignee": null, + }, + "expected": { + // Expected state of the PR after all the handlers process + // the following payload. + // Only fields present in this object will be checked. + // comments: 5, + // labels: ["S-awaiting-review"], + // assignee: "jdm" + }, + "payload": { + // Github API event payload in JSON format + } +} +``` +Each test runs with a mock Github API provider, so no account information +or network connection is required to run the test suite. diff --git a/eventhandler.py b/eventhandler.py new file mode 100644 index 0000000..3e28e0d --- /dev/null +++ b/eventhandler.py @@ -0,0 +1,64 @@ +import imp +import json +import os + +_warnings = [] + +class EventHandler: + def on_pr_opened(self, api, payload): + pass + + def on_pr_updated(self, api, payload): + pass + + def on_new_comment(self, api, payload): + pass + + def warn(self, msg): + global _warnings + _warnings += [msg] + + def is_open_pr(self, payload): + return payload['issue']['state'] == 'open' and 'pull_request' in payload['issue'] + + def register_tests(self, path): + from test import create_test + tests_location = os.path.join(path, 'tests') + if not os.path.isdir(tests_location): + return + tests = [os.path.join(tests_location, f) for f in os.listdir(tests_location) if f.endswith('.json')] + for testfile in tests: + with open(testfile) as f: + contents = json.load(f) + if not isinstance(contents['initial'], list): + assert not isinstance(contents['expected'], list) + contents['initial'] = [contents['initial']] + contents['expected'] = [contents['expected']] + for initial, expected in zip(contents['initial'], contents['expected']): + yield create_test(testfile, initial, expected) + +def reset_test_state(): + global _warnings + _warnings = [] + +def get_warnings(): + global _warnings + return _warnings + +def get_handlers(): + modules = [] + handlers = [] + possiblehandlers = os.listdir('handlers') + for i in possiblehandlers: + location = os.path.join('handlers', i) + module = '__init__' + if not os.path.isdir(location) or not module + ".py" in os.listdir(location): + continue + try: + (file, pathname, description) = imp.find_module(module, [location]) + module = imp.load_module(module, file, pathname, description) + handlers.append(module.handler_interface()) + modules.append((module, location)) + finally: + file.close() + return (modules, handlers) diff --git a/handlers/assign_reviewer/__init__.py b/handlers/assign_reviewer/__init__.py new file mode 100644 index 0000000..fd55624 --- /dev/null +++ b/handlers/assign_reviewer/__init__.py @@ -0,0 +1,22 @@ +from eventhandler import EventHandler +import re + +# If the user specified a reviewer, return the username, otherwise returns None. +def find_reviewer(commit_msg): + reviewer_re = re.compile("\\b[rR]\?[:\- ]*@([a-zA-Z0-9\-]+)") + match = reviewer_re.search(commit_msg) + if not match: + return None + return match.group(1) + +class AssignReviewerHandler(EventHandler): + def on_new_comment(self, api, payload): + if not self.is_open_pr(payload): + return + + reviewer = find_reviewer(payload["comment"]["body"]) + if reviewer: + api.set_assignee(reviewer) + + +handler_interface = AssignReviewerHandler diff --git a/test_comment.json b/handlers/assign_reviewer/tests/comment.json similarity index 99% rename from test_comment.json rename to handlers/assign_reviewer/tests/comment.json index 9459c33..2192279 100644 --- a/test_comment.json +++ b/handlers/assign_reviewer/tests/comment.json @@ -1,3 +1,10 @@ +{ + "initial": { + }, + "expected": { + "assignee": "jdm" + }, + "payload": { "action": "created", "issue": { @@ -197,3 +204,4 @@ "site_admin": false } } +} diff --git a/handlers/homu_status/__init__.py b/handlers/homu_status/__init__.py new file mode 100644 index 0000000..5878f07 --- /dev/null +++ b/handlers/homu_status/__init__.py @@ -0,0 +1,34 @@ +from eventhandler import EventHandler + +class HomuStatusHandler(EventHandler): + def on_new_comment(self, api, payload): + if not self.is_open_pr(payload): + return + + if payload['comment']['user']['login'] != 'bors-servo': + return + + labels = api.get_labels(); + msg = payload["comment"]["body"] + + def remove_if_exists(label): + if label in labels: + api.remove_label(label) + + if 'has been approved by' in msg or 'Testing commit' in msg: + for label in ["S-awaiting-review", "S-needs-rebase", "S-tests-failed", + "S-needs-code-changes", "S-needs-squash", "S-awaiting-answer"]: + remove_if_exists(label) + if not "S-awaiting-merge" in labels: + api.add_label("S-awaiting-merge") + + elif 'Test failed' in msg: + remove_if_exists("S-awaiting-merge") + api.add_label("S-tests-failed") + + elif 'Please resolve the merge conflicts' in msg: + remove_if_exists("S-awaiting-merge") + api.add_label("S-needs-rebase") + + +handler_interface = HomuStatusHandler diff --git a/test_merge_approved.json b/handlers/homu_status/tests/merge_approved.json similarity index 97% rename from test_merge_approved.json rename to handlers/homu_status/tests/merge_approved.json index 8864e7d..e29469d 100644 --- a/test_merge_approved.json +++ b/handlers/homu_status/tests/merge_approved.json @@ -1,3 +1,12 @@ +{ + "initial": { + "labels": ["S-needs-code-changes", "S-needs-rebase", "S-tests-failed", + "S-needs-squash", "S-awaiting-review"] + }, + "expected": { + "labels": ["S-awaiting-merge"] + }, + "payload": { "action": "created", "issue": { @@ -197,3 +206,4 @@ "site_admin": false } } +} diff --git a/test_merge_conflict.json b/handlers/homu_status/tests/merge_conflict.json similarity index 98% rename from test_merge_conflict.json rename to handlers/homu_status/tests/merge_conflict.json index aacf788..1211e18 100644 --- a/test_merge_conflict.json +++ b/handlers/homu_status/tests/merge_conflict.json @@ -1,3 +1,11 @@ +{ + "initial": { + "labels": ["S-awaiting-merge"] + }, + "expected": { + "labels": ["S-needs-rebase"] + }, + "payload": { "action": "created", "issue": { @@ -197,3 +205,4 @@ "site_admin": false } } +} diff --git a/test_post_retry.json b/handlers/homu_status/tests/post_retry.json similarity index 97% rename from test_post_retry.json rename to handlers/homu_status/tests/post_retry.json index 9b8c521..4556ac8 100644 --- a/test_post_retry.json +++ b/handlers/homu_status/tests/post_retry.json @@ -1,3 +1,17 @@ +{ + "initial": [{ + "labels": ["S-tests-failed"] + }, + { + "labels": ["S-awaiting-merge"] + }], + "expected": [{ + "labels": ["S-awaiting-merge"] + }, + { + "labels": ["S-awaiting-merge"] + }], + "payload": { "action": "created", "issue": { @@ -197,3 +211,4 @@ "site_admin": false } } +} diff --git a/test_tests_failed.json b/handlers/homu_status/tests/tests_failed.json similarity index 98% rename from test_tests_failed.json rename to handlers/homu_status/tests/tests_failed.json index f98940d..1c37ee7 100644 --- a/test_tests_failed.json +++ b/handlers/homu_status/tests/tests_failed.json @@ -1,3 +1,11 @@ +{ + "initial": { + "labels": ["S-awaiting-merge"] + }, + "expected": { + "labels": ["S-tests-failed"] + }, + "payload": { "action": "created", "issue": { @@ -197,3 +205,4 @@ "site_admin": false } } +} diff --git a/handlers/missing_test/__init__.py b/handlers/missing_test/__init__.py new file mode 100644 index 0000000..4815a5a --- /dev/null +++ b/handlers/missing_test/__init__.py @@ -0,0 +1,20 @@ +from eventhandler import EventHandler + +reftest_required_msg = 'These commits modify layout code, but no reftests are modified. Please consider adding a reftest!' + +class MissingTestHandler(EventHandler): + def on_pr_opened(self, api, payload): + diff = api.get_diff() + layout_changed = False + for line in diff.split('\n'): + if line.startswith('diff --git') and line.find('components/layout/') > -1: + layout_changed = True + if line.startswith('diff --git') and \ + (line.find('tests/ref') > -1 or line.find('tests/wpt') > -1): + return + + if layout_changed: + self.warn(reftest_required_msg) + + +handler_interface = MissingTestHandler diff --git a/test_ignored_action.json b/handlers/missing_test/tests/new_pr.json similarity index 58% rename from test_ignored_action.json rename to handlers/missing_test/tests/new_pr.json index 2850291..89bbc74 100644 --- a/test_ignored_action.json +++ b/handlers/missing_test/tests/new_pr.json @@ -1,169 +1,165 @@ { - "action": "labeled", - "number": 7066, + "initial": [{ + "diff": "diff --git components/layout/" + }, + { + "diff": "diff --git components/layout/\ndiff --git tests/wpt" + }], + "expected": [{ + "comments": 1 + }, + { + "comments": 0 + }], + "payload": +{ + "action": "opened", + "number": 7076, "pull_request": { - "url": "https://api.github.com/repos/servo/servo/pulls/7066", - "id": 41874546, - "html_url": "https://github.com/servo/servo/pull/7066", - "diff_url": "https://github.com/servo/servo/pull/7066.diff", - "patch_url": "https://github.com/servo/servo/pull/7066.patch", - "issue_url": "https://api.github.com/repos/servo/servo/issues/7066", - "number": 7066, + "url": "https://api.github.com/repos/servo/servo/pulls/7076", + "id": 41896942, + "html_url": "https://github.com/servo/servo/pull/7076", + "diff_url": "https://github.com/servo/servo/pull/7076.diff", + "patch_url": "https://github.com/servo/servo/pull/7076.patch", + "issue_url": "https://api.github.com/repos/servo/servo/issues/7076", + "number": 7076, "state": "open", "locked": false, - "title": "Dispatch message events for WebSocket.", + "title": "Remove invalid file path in ignored_files for tidying", "user": { - "login": "Ms2ger", - "id": 111161, - "avatar_url": "https://avatars.githubusercontent.com/u/111161?v=3", + "login": "frewsxcv", + "id": 416575, + "avatar_url": "https://avatars.githubusercontent.com/u/416575?v=3", "gravatar_id": "", - "url": "https://api.github.com/users/Ms2ger", - "html_url": "https://github.com/Ms2ger", - "followers_url": "https://api.github.com/users/Ms2ger/followers", - "following_url": "https://api.github.com/users/Ms2ger/following{/other_user}", - "gists_url": "https://api.github.com/users/Ms2ger/gists{/gist_id}", - "starred_url": "https://api.github.com/users/Ms2ger/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/Ms2ger/subscriptions", - "organizations_url": "https://api.github.com/users/Ms2ger/orgs", - "repos_url": "https://api.github.com/users/Ms2ger/repos", - "events_url": "https://api.github.com/users/Ms2ger/events{/privacy}", - "received_events_url": "https://api.github.com/users/Ms2ger/received_events", + "url": "https://api.github.com/users/frewsxcv", + "html_url": "https://github.com/frewsxcv", + "followers_url": "https://api.github.com/users/frewsxcv/followers", + "following_url": "https://api.github.com/users/frewsxcv/following{/other_user}", + "gists_url": "https://api.github.com/users/frewsxcv/gists{/gist_id}", + "starred_url": "https://api.github.com/users/frewsxcv/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/frewsxcv/subscriptions", + "organizations_url": "https://api.github.com/users/frewsxcv/orgs", + "repos_url": "https://api.github.com/users/frewsxcv/repos", + "events_url": "https://api.github.com/users/frewsxcv/events{/privacy}", + "received_events_url": "https://api.github.com/users/frewsxcv/received_events", "type": "User", "site_admin": false }, - "body": "\n\n\n[\"Review](https://reviewable.io/reviews/servo/servo/7066)\n\n", - "created_at": "2015-08-07T09:32:45Z", - "updated_at": "2015-08-07T16:13:17Z", + "body": null, + "created_at": "2015-08-07T14:24:50Z", + "updated_at": "2015-08-07T14:24:50Z", "closed_at": null, "merged_at": null, - "merge_commit_sha": "155eb5fd6e65dbcc70613c2f614f6eb0fc791baa", - "assignee": { - "login": "metajack", - "id": 28357, - "avatar_url": "https://avatars.githubusercontent.com/u/28357?v=3", - "gravatar_id": "", - "url": "https://api.github.com/users/metajack", - "html_url": "https://github.com/metajack", - "followers_url": "https://api.github.com/users/metajack/followers", - "following_url": "https://api.github.com/users/metajack/following{/other_user}", - "gists_url": "https://api.github.com/users/metajack/gists{/gist_id}", - "starred_url": "https://api.github.com/users/metajack/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/metajack/subscriptions", - "organizations_url": "https://api.github.com/users/metajack/orgs", - "repos_url": "https://api.github.com/users/metajack/repos", - "events_url": "https://api.github.com/users/metajack/events{/privacy}", - "received_events_url": "https://api.github.com/users/metajack/received_events", - "type": "User", - "site_admin": false - }, + "merge_commit_sha": null, + "assignee": null, "milestone": null, - "commits_url": "https://api.github.com/repos/servo/servo/pulls/7066/commits", - "review_comments_url": "https://api.github.com/repos/servo/servo/pulls/7066/comments", + "commits_url": "https://api.github.com/repos/servo/servo/pulls/7076/commits", + "review_comments_url": "https://api.github.com/repos/servo/servo/pulls/7076/comments", "review_comment_url": "https://api.github.com/repos/servo/servo/pulls/comments{/number}", - "comments_url": "https://api.github.com/repos/servo/servo/issues/7066/comments", - "statuses_url": "https://api.github.com/repos/servo/servo/statuses/749743f424cddd137c5d78d0bf6b49016a640c29", + "comments_url": "https://api.github.com/repos/servo/servo/issues/7076/comments", + "statuses_url": "https://api.github.com/repos/servo/servo/statuses/da44f31cb1debe2c8161ee280121d79d3dd5ec18", "head": { - "label": "Ms2ger:ws-event", - "ref": "ws-event", - "sha": "749743f424cddd137c5d78d0bf6b49016a640c29", + "label": "frewsxcv:tidy-rm-invalid-file", + "ref": "tidy-rm-invalid-file", + "sha": "da44f31cb1debe2c8161ee280121d79d3dd5ec18", "user": { - "login": "Ms2ger", - "id": 111161, - "avatar_url": "https://avatars.githubusercontent.com/u/111161?v=3", + "login": "frewsxcv", + "id": 416575, + "avatar_url": "https://avatars.githubusercontent.com/u/416575?v=3", "gravatar_id": "", - "url": "https://api.github.com/users/Ms2ger", - "html_url": "https://github.com/Ms2ger", - "followers_url": "https://api.github.com/users/Ms2ger/followers", - "following_url": "https://api.github.com/users/Ms2ger/following{/other_user}", - "gists_url": "https://api.github.com/users/Ms2ger/gists{/gist_id}", - "starred_url": "https://api.github.com/users/Ms2ger/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/Ms2ger/subscriptions", - "organizations_url": "https://api.github.com/users/Ms2ger/orgs", - "repos_url": "https://api.github.com/users/Ms2ger/repos", - "events_url": "https://api.github.com/users/Ms2ger/events{/privacy}", - "received_events_url": "https://api.github.com/users/Ms2ger/received_events", + "url": "https://api.github.com/users/frewsxcv", + "html_url": "https://github.com/frewsxcv", + "followers_url": "https://api.github.com/users/frewsxcv/followers", + "following_url": "https://api.github.com/users/frewsxcv/following{/other_user}", + "gists_url": "https://api.github.com/users/frewsxcv/gists{/gist_id}", + "starred_url": "https://api.github.com/users/frewsxcv/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/frewsxcv/subscriptions", + "organizations_url": "https://api.github.com/users/frewsxcv/orgs", + "repos_url": "https://api.github.com/users/frewsxcv/repos", + "events_url": "https://api.github.com/users/frewsxcv/events{/privacy}", + "received_events_url": "https://api.github.com/users/frewsxcv/received_events", "type": "User", "site_admin": false }, "repo": { - "id": 12599239, + "id": 27014207, "name": "servo", - "full_name": "Ms2ger/servo", + "full_name": "frewsxcv/servo", "owner": { - "login": "Ms2ger", - "id": 111161, - "avatar_url": "https://avatars.githubusercontent.com/u/111161?v=3", + "login": "frewsxcv", + "id": 416575, + "avatar_url": "https://avatars.githubusercontent.com/u/416575?v=3", "gravatar_id": "", - "url": "https://api.github.com/users/Ms2ger", - "html_url": "https://github.com/Ms2ger", - "followers_url": "https://api.github.com/users/Ms2ger/followers", - "following_url": "https://api.github.com/users/Ms2ger/following{/other_user}", - "gists_url": "https://api.github.com/users/Ms2ger/gists{/gist_id}", - "starred_url": "https://api.github.com/users/Ms2ger/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/Ms2ger/subscriptions", - "organizations_url": "https://api.github.com/users/Ms2ger/orgs", - "repos_url": "https://api.github.com/users/Ms2ger/repos", - "events_url": "https://api.github.com/users/Ms2ger/events{/privacy}", - "received_events_url": "https://api.github.com/users/Ms2ger/received_events", + "url": "https://api.github.com/users/frewsxcv", + "html_url": "https://github.com/frewsxcv", + "followers_url": "https://api.github.com/users/frewsxcv/followers", + "following_url": "https://api.github.com/users/frewsxcv/following{/other_user}", + "gists_url": "https://api.github.com/users/frewsxcv/gists{/gist_id}", + "starred_url": "https://api.github.com/users/frewsxcv/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/frewsxcv/subscriptions", + "organizations_url": "https://api.github.com/users/frewsxcv/orgs", + "repos_url": "https://api.github.com/users/frewsxcv/repos", + "events_url": "https://api.github.com/users/frewsxcv/events{/privacy}", + "received_events_url": "https://api.github.com/users/frewsxcv/received_events", "type": "User", "site_admin": false }, "private": false, - "html_url": "https://github.com/Ms2ger/servo", + "html_url": "https://github.com/frewsxcv/servo", "description": "The Servo Browser Engine", "fork": true, - "url": "https://api.github.com/repos/Ms2ger/servo", - "forks_url": "https://api.github.com/repos/Ms2ger/servo/forks", - "keys_url": "https://api.github.com/repos/Ms2ger/servo/keys{/key_id}", - "collaborators_url": "https://api.github.com/repos/Ms2ger/servo/collaborators{/collaborator}", - "teams_url": "https://api.github.com/repos/Ms2ger/servo/teams", - "hooks_url": "https://api.github.com/repos/Ms2ger/servo/hooks", - "issue_events_url": "https://api.github.com/repos/Ms2ger/servo/issues/events{/number}", - "events_url": "https://api.github.com/repos/Ms2ger/servo/events", - "assignees_url": "https://api.github.com/repos/Ms2ger/servo/assignees{/user}", - "branches_url": "https://api.github.com/repos/Ms2ger/servo/branches{/branch}", - "tags_url": "https://api.github.com/repos/Ms2ger/servo/tags", - "blobs_url": "https://api.github.com/repos/Ms2ger/servo/git/blobs{/sha}", - "git_tags_url": "https://api.github.com/repos/Ms2ger/servo/git/tags{/sha}", - "git_refs_url": "https://api.github.com/repos/Ms2ger/servo/git/refs{/sha}", - "trees_url": "https://api.github.com/repos/Ms2ger/servo/git/trees{/sha}", - "statuses_url": "https://api.github.com/repos/Ms2ger/servo/statuses/{sha}", - "languages_url": "https://api.github.com/repos/Ms2ger/servo/languages", - "stargazers_url": "https://api.github.com/repos/Ms2ger/servo/stargazers", - "contributors_url": "https://api.github.com/repos/Ms2ger/servo/contributors", - "subscribers_url": "https://api.github.com/repos/Ms2ger/servo/subscribers", - "subscription_url": "https://api.github.com/repos/Ms2ger/servo/subscription", - "commits_url": "https://api.github.com/repos/Ms2ger/servo/commits{/sha}", - "git_commits_url": "https://api.github.com/repos/Ms2ger/servo/git/commits{/sha}", - "comments_url": "https://api.github.com/repos/Ms2ger/servo/comments{/number}", - "issue_comment_url": "https://api.github.com/repos/Ms2ger/servo/issues/comments{/number}", - "contents_url": "https://api.github.com/repos/Ms2ger/servo/contents/{+path}", - "compare_url": "https://api.github.com/repos/Ms2ger/servo/compare/{base}...{head}", - "merges_url": "https://api.github.com/repos/Ms2ger/servo/merges", - "archive_url": "https://api.github.com/repos/Ms2ger/servo/{archive_format}{/ref}", - "downloads_url": "https://api.github.com/repos/Ms2ger/servo/downloads", - "issues_url": "https://api.github.com/repos/Ms2ger/servo/issues{/number}", - "pulls_url": "https://api.github.com/repos/Ms2ger/servo/pulls{/number}", - "milestones_url": "https://api.github.com/repos/Ms2ger/servo/milestones{/number}", - "notifications_url": "https://api.github.com/repos/Ms2ger/servo/notifications{?since,all,participating}", - "labels_url": "https://api.github.com/repos/Ms2ger/servo/labels{/name}", - "releases_url": "https://api.github.com/repos/Ms2ger/servo/releases{/id}", - "created_at": "2013-09-04T18:49:05Z", - "updated_at": "2015-06-21T07:02:55Z", - "pushed_at": "2015-08-07T14:34:22Z", - "git_url": "git://github.com/Ms2ger/servo.git", - "ssh_url": "git@github.com:Ms2ger/servo.git", - "clone_url": "https://github.com/Ms2ger/servo.git", - "svn_url": "https://github.com/Ms2ger/servo", + "url": "https://api.github.com/repos/frewsxcv/servo", + "forks_url": "https://api.github.com/repos/frewsxcv/servo/forks", + "keys_url": "https://api.github.com/repos/frewsxcv/servo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/frewsxcv/servo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/frewsxcv/servo/teams", + "hooks_url": "https://api.github.com/repos/frewsxcv/servo/hooks", + "issue_events_url": "https://api.github.com/repos/frewsxcv/servo/issues/events{/number}", + "events_url": "https://api.github.com/repos/frewsxcv/servo/events", + "assignees_url": "https://api.github.com/repos/frewsxcv/servo/assignees{/user}", + "branches_url": "https://api.github.com/repos/frewsxcv/servo/branches{/branch}", + "tags_url": "https://api.github.com/repos/frewsxcv/servo/tags", + "blobs_url": "https://api.github.com/repos/frewsxcv/servo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/frewsxcv/servo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/frewsxcv/servo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/frewsxcv/servo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/frewsxcv/servo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/frewsxcv/servo/languages", + "stargazers_url": "https://api.github.com/repos/frewsxcv/servo/stargazers", + "contributors_url": "https://api.github.com/repos/frewsxcv/servo/contributors", + "subscribers_url": "https://api.github.com/repos/frewsxcv/servo/subscribers", + "subscription_url": "https://api.github.com/repos/frewsxcv/servo/subscription", + "commits_url": "https://api.github.com/repos/frewsxcv/servo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/frewsxcv/servo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/frewsxcv/servo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/frewsxcv/servo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/frewsxcv/servo/contents/{+path}", + "compare_url": "https://api.github.com/repos/frewsxcv/servo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/frewsxcv/servo/merges", + "archive_url": "https://api.github.com/repos/frewsxcv/servo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/frewsxcv/servo/downloads", + "issues_url": "https://api.github.com/repos/frewsxcv/servo/issues{/number}", + "pulls_url": "https://api.github.com/repos/frewsxcv/servo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/frewsxcv/servo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/frewsxcv/servo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/frewsxcv/servo/labels{/name}", + "releases_url": "https://api.github.com/repos/frewsxcv/servo/releases{/id}", + "created_at": "2014-11-22T22:10:35Z", + "updated_at": "2014-11-22T22:10:38Z", + "pushed_at": "2015-08-07T14:24:45Z", + "git_url": "git://github.com/frewsxcv/servo.git", + "ssh_url": "git@github.com:frewsxcv/servo.git", + "clone_url": "https://github.com/frewsxcv/servo.git", + "svn_url": "https://github.com/frewsxcv/servo", "homepage": "", - "size": 124200, + "size": 119557, "stargazers_count": 0, "watchers_count": 0, "language": "Rust", "has_issues": false, "has_downloads": true, "has_wiki": true, - "has_pages": false, + "has_pages": true, "forks_count": 0, "mirror_url": null, "open_issues_count": 0, @@ -176,7 +172,7 @@ "base": { "label": "servo:master", "ref": "master", - "sha": "44c4bb00c1cb8645ee2fc303848a5136108e594f", + "sha": "b4e30da3dbf58c16703864f4bec4b0b0132084fa", "user": { "login": "servo", "id": 2566135, @@ -261,13 +257,13 @@ "releases_url": "https://api.github.com/repos/servo/servo/releases{/id}", "created_at": "2012-02-08T19:07:25Z", "updated_at": "2015-08-07T09:37:49Z", - "pushed_at": "2015-08-07T16:09:30Z", + "pushed_at": "2015-08-07T14:10:08Z", "git_url": "git://github.com/servo/servo.git", "ssh_url": "git@github.com:servo/servo.git", "clone_url": "https://github.com/servo/servo.git", "svn_url": "https://github.com/servo/servo", "homepage": "", - "size": 1903023, + "size": 1904176, "stargazers_count": 4571, "watchers_count": 4571, "language": "Rust", @@ -277,54 +273,49 @@ "has_pages": false, "forks_count": 717, "mirror_url": null, - "open_issues_count": 1057, + "open_issues_count": 1060, "forks": 717, - "open_issues": 1057, + "open_issues": 1060, "watchers": 4571, "default_branch": "master" } }, "_links": { "self": { - "href": "https://api.github.com/repos/servo/servo/pulls/7066" + "href": "https://api.github.com/repos/servo/servo/pulls/7076" }, "html": { - "href": "https://github.com/servo/servo/pull/7066" + "href": "https://github.com/servo/servo/pull/7076" }, "issue": { - "href": "https://api.github.com/repos/servo/servo/issues/7066" + "href": "https://api.github.com/repos/servo/servo/issues/7076" }, "comments": { - "href": "https://api.github.com/repos/servo/servo/issues/7066/comments" + "href": "https://api.github.com/repos/servo/servo/issues/7076/comments" }, "review_comments": { - "href": "https://api.github.com/repos/servo/servo/pulls/7066/comments" + "href": "https://api.github.com/repos/servo/servo/pulls/7076/comments" }, "review_comment": { "href": "https://api.github.com/repos/servo/servo/pulls/comments{/number}" }, "commits": { - "href": "https://api.github.com/repos/servo/servo/pulls/7066/commits" + "href": "https://api.github.com/repos/servo/servo/pulls/7076/commits" }, "statuses": { - "href": "https://api.github.com/repos/servo/servo/statuses/749743f424cddd137c5d78d0bf6b49016a640c29" + "href": "https://api.github.com/repos/servo/servo/statuses/da44f31cb1debe2c8161ee280121d79d3dd5ec18" } }, "merged": false, - "mergeable": true, - "mergeable_state": "clean", + "mergeable": null, + "mergeable_state": "unknown", "merged_by": null, "comments": 0, "review_comments": 0, - "commits": 2, - "additions": 127, - "deletions": 421, - "changed_files": 86 - }, - "label": { - "url": "https://api.github.com/repos/servo/servo/labels/S-awaiting-merge", - "name": "S-awaiting-merge", - "color": "d4c5f9" + "commits": 1, + "additions": 0, + "deletions": 1, + "changed_files": 1 }, "repository": { "id": 3390243, @@ -391,13 +382,13 @@ "releases_url": "https://api.github.com/repos/servo/servo/releases{/id}", "created_at": "2012-02-08T19:07:25Z", "updated_at": "2015-08-07T09:37:49Z", - "pushed_at": "2015-08-07T16:09:30Z", + "pushed_at": "2015-08-07T14:10:08Z", "git_url": "git://github.com/servo/servo.git", "ssh_url": "git@github.com:servo/servo.git", "clone_url": "https://github.com/servo/servo.git", "svn_url": "https://github.com/servo/servo", "homepage": "", - "size": 1903023, + "size": 1904176, "stargazers_count": 4571, "watchers_count": 4571, "language": "Rust", @@ -407,9 +398,9 @@ "has_pages": false, "forks_count": 717, "mirror_url": null, - "open_issues_count": 1057, + "open_issues_count": 1060, "forks": 717, - "open_issues": 1057, + "open_issues": 1060, "watchers": 4571, "default_branch": "master" }, @@ -425,22 +416,23 @@ "description": null }, "sender": { - "login": "metajack", - "id": 28357, - "avatar_url": "https://avatars.githubusercontent.com/u/28357?v=3", + "login": "frewsxcv", + "id": 416575, + "avatar_url": "https://avatars.githubusercontent.com/u/416575?v=3", "gravatar_id": "", - "url": "https://api.github.com/users/metajack", - "html_url": "https://github.com/metajack", - "followers_url": "https://api.github.com/users/metajack/followers", - "following_url": "https://api.github.com/users/metajack/following{/other_user}", - "gists_url": "https://api.github.com/users/metajack/gists{/gist_id}", - "starred_url": "https://api.github.com/users/metajack/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/metajack/subscriptions", - "organizations_url": "https://api.github.com/users/metajack/orgs", - "repos_url": "https://api.github.com/users/metajack/repos", - "events_url": "https://api.github.com/users/metajack/events{/privacy}", - "received_events_url": "https://api.github.com/users/metajack/received_events", + "url": "https://api.github.com/users/frewsxcv", + "html_url": "https://github.com/frewsxcv", + "followers_url": "https://api.github.com/users/frewsxcv/followers", + "following_url": "https://api.github.com/users/frewsxcv/following{/other_user}", + "gists_url": "https://api.github.com/users/frewsxcv/gists{/gist_id}", + "starred_url": "https://api.github.com/users/frewsxcv/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/frewsxcv/subscriptions", + "organizations_url": "https://api.github.com/users/frewsxcv/orgs", + "repos_url": "https://api.github.com/users/frewsxcv/repos", + "events_url": "https://api.github.com/users/frewsxcv/events{/privacy}", + "received_events_url": "https://api.github.com/users/frewsxcv/received_events", "type": "User", "site_admin": false } } +} diff --git a/handlers/status_update/__init__.py b/handlers/status_update/__init__.py new file mode 100644 index 0000000..749f06d --- /dev/null +++ b/handlers/status_update/__init__.py @@ -0,0 +1,26 @@ +from eventhandler import EventHandler + +def manage_pr_state(api, payload): + labels = api.get_labels(); + + for label in ["S-awaiting-merge", "S-tests-failed", "S-needs-code-changes"]: + if label in labels: + api.remove_label(label) + if not "S-awaiting-review" in labels: + api.add_label("S-awaiting-review") + + # If mergeable is null, the data wasn't available yet. It would be nice to try to fetch that + # information again. + if payload["action"] == "synchronize" and payload['pull_request']['mergeable']: + if "S-needs-rebase" in labels: + api.remove_label("S-needs-rebase") + +class StatusUpdateHandler(EventHandler): + def on_pr_opened(self, api, payload): + manage_pr_state(api, payload) + + def on_pr_updated(self, api, payload): + manage_pr_state(api, payload) + + +handler_interface = StatusUpdateHandler diff --git a/test_new_pr.json b/handlers/status_update/tests/new_pr.json similarity index 99% rename from test_new_pr.json rename to handlers/status_update/tests/new_pr.json index 91bd48d..ab1cf33 100644 --- a/test_new_pr.json +++ b/handlers/status_update/tests/new_pr.json @@ -1,3 +1,10 @@ +{ + "initial": { + }, + "expected": { + "labels": ["S-awaiting-review"] + }, + "payload": { "action": "opened", "number": 7076, @@ -421,3 +428,4 @@ "site_admin": false } } +} diff --git a/test_synchronize.json b/handlers/status_update/tests/synchronize.json similarity index 99% rename from test_synchronize.json rename to handlers/status_update/tests/synchronize.json index 1b26d31..3b724f5 100644 --- a/test_synchronize.json +++ b/handlers/status_update/tests/synchronize.json @@ -1,3 +1,11 @@ +{ + "initial": { + "labels": ["S-needs-code-changes", "S-tests-failed", "S-awaiting-merge"] + }, + "expected": { + "labels": ["S-awaiting-review"] + }, + "payload": { "action": "synchronize", "number": 7062, @@ -421,3 +429,4 @@ "site_admin": false } } +} diff --git a/handlers/unsafe/__init__.py b/handlers/unsafe/__init__.py new file mode 100644 index 0000000..a64656c --- /dev/null +++ b/handlers/unsafe/__init__.py @@ -0,0 +1,14 @@ +from eventhandler import EventHandler + +unsafe_warning_msg = 'These commits modify **unsafe code**. Please review it carefully!' + +class UnsafeHandler(EventHandler): + def on_pr_opened(self, api, payload): + diff = api.get_diff() + for line in diff.split('\n'): + if line.startswith('+') and not line.startswith('+++') and line.find('unsafe ') > -1: + self.warn(unsafe_warning_msg) + return + + +handler_interface = UnsafeHandler diff --git a/handlers/unsafe/tests/new_pr.json b/handlers/unsafe/tests/new_pr.json new file mode 100644 index 0000000..d9b98d6 --- /dev/null +++ b/handlers/unsafe/tests/new_pr.json @@ -0,0 +1,432 @@ +{ + "initial": { + "diff": "+ unsafe fn foo()" + }, + "expected": { + "comments": 1 + }, + "payload": +{ + "action": "opened", + "number": 7076, + "pull_request": { + "url": "https://api.github.com/repos/servo/servo/pulls/7076", + "id": 41896942, + "html_url": "https://github.com/servo/servo/pull/7076", + "diff_url": "https://github.com/servo/servo/pull/7076.diff", + "patch_url": "https://github.com/servo/servo/pull/7076.patch", + "issue_url": "https://api.github.com/repos/servo/servo/issues/7076", + "number": 7076, + "state": "open", + "locked": false, + "title": "Remove invalid file path in ignored_files for tidying", + "user": { + "login": "frewsxcv", + "id": 416575, + "avatar_url": "https://avatars.githubusercontent.com/u/416575?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/frewsxcv", + "html_url": "https://github.com/frewsxcv", + "followers_url": "https://api.github.com/users/frewsxcv/followers", + "following_url": "https://api.github.com/users/frewsxcv/following{/other_user}", + "gists_url": "https://api.github.com/users/frewsxcv/gists{/gist_id}", + "starred_url": "https://api.github.com/users/frewsxcv/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/frewsxcv/subscriptions", + "organizations_url": "https://api.github.com/users/frewsxcv/orgs", + "repos_url": "https://api.github.com/users/frewsxcv/repos", + "events_url": "https://api.github.com/users/frewsxcv/events{/privacy}", + "received_events_url": "https://api.github.com/users/frewsxcv/received_events", + "type": "User", + "site_admin": false + }, + "body": null, + "created_at": "2015-08-07T14:24:50Z", + "updated_at": "2015-08-07T14:24:50Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": null, + "assignee": null, + "milestone": null, + "commits_url": "https://api.github.com/repos/servo/servo/pulls/7076/commits", + "review_comments_url": "https://api.github.com/repos/servo/servo/pulls/7076/comments", + "review_comment_url": "https://api.github.com/repos/servo/servo/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/servo/servo/issues/7076/comments", + "statuses_url": "https://api.github.com/repos/servo/servo/statuses/da44f31cb1debe2c8161ee280121d79d3dd5ec18", + "head": { + "label": "frewsxcv:tidy-rm-invalid-file", + "ref": "tidy-rm-invalid-file", + "sha": "da44f31cb1debe2c8161ee280121d79d3dd5ec18", + "user": { + "login": "frewsxcv", + "id": 416575, + "avatar_url": "https://avatars.githubusercontent.com/u/416575?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/frewsxcv", + "html_url": "https://github.com/frewsxcv", + "followers_url": "https://api.github.com/users/frewsxcv/followers", + "following_url": "https://api.github.com/users/frewsxcv/following{/other_user}", + "gists_url": "https://api.github.com/users/frewsxcv/gists{/gist_id}", + "starred_url": "https://api.github.com/users/frewsxcv/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/frewsxcv/subscriptions", + "organizations_url": "https://api.github.com/users/frewsxcv/orgs", + "repos_url": "https://api.github.com/users/frewsxcv/repos", + "events_url": "https://api.github.com/users/frewsxcv/events{/privacy}", + "received_events_url": "https://api.github.com/users/frewsxcv/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 27014207, + "name": "servo", + "full_name": "frewsxcv/servo", + "owner": { + "login": "frewsxcv", + "id": 416575, + "avatar_url": "https://avatars.githubusercontent.com/u/416575?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/frewsxcv", + "html_url": "https://github.com/frewsxcv", + "followers_url": "https://api.github.com/users/frewsxcv/followers", + "following_url": "https://api.github.com/users/frewsxcv/following{/other_user}", + "gists_url": "https://api.github.com/users/frewsxcv/gists{/gist_id}", + "starred_url": "https://api.github.com/users/frewsxcv/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/frewsxcv/subscriptions", + "organizations_url": "https://api.github.com/users/frewsxcv/orgs", + "repos_url": "https://api.github.com/users/frewsxcv/repos", + "events_url": "https://api.github.com/users/frewsxcv/events{/privacy}", + "received_events_url": "https://api.github.com/users/frewsxcv/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/frewsxcv/servo", + "description": "The Servo Browser Engine", + "fork": true, + "url": "https://api.github.com/repos/frewsxcv/servo", + "forks_url": "https://api.github.com/repos/frewsxcv/servo/forks", + "keys_url": "https://api.github.com/repos/frewsxcv/servo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/frewsxcv/servo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/frewsxcv/servo/teams", + "hooks_url": "https://api.github.com/repos/frewsxcv/servo/hooks", + "issue_events_url": "https://api.github.com/repos/frewsxcv/servo/issues/events{/number}", + "events_url": "https://api.github.com/repos/frewsxcv/servo/events", + "assignees_url": "https://api.github.com/repos/frewsxcv/servo/assignees{/user}", + "branches_url": "https://api.github.com/repos/frewsxcv/servo/branches{/branch}", + "tags_url": "https://api.github.com/repos/frewsxcv/servo/tags", + "blobs_url": "https://api.github.com/repos/frewsxcv/servo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/frewsxcv/servo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/frewsxcv/servo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/frewsxcv/servo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/frewsxcv/servo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/frewsxcv/servo/languages", + "stargazers_url": "https://api.github.com/repos/frewsxcv/servo/stargazers", + "contributors_url": "https://api.github.com/repos/frewsxcv/servo/contributors", + "subscribers_url": "https://api.github.com/repos/frewsxcv/servo/subscribers", + "subscription_url": "https://api.github.com/repos/frewsxcv/servo/subscription", + "commits_url": "https://api.github.com/repos/frewsxcv/servo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/frewsxcv/servo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/frewsxcv/servo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/frewsxcv/servo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/frewsxcv/servo/contents/{+path}", + "compare_url": "https://api.github.com/repos/frewsxcv/servo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/frewsxcv/servo/merges", + "archive_url": "https://api.github.com/repos/frewsxcv/servo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/frewsxcv/servo/downloads", + "issues_url": "https://api.github.com/repos/frewsxcv/servo/issues{/number}", + "pulls_url": "https://api.github.com/repos/frewsxcv/servo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/frewsxcv/servo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/frewsxcv/servo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/frewsxcv/servo/labels{/name}", + "releases_url": "https://api.github.com/repos/frewsxcv/servo/releases{/id}", + "created_at": "2014-11-22T22:10:35Z", + "updated_at": "2014-11-22T22:10:38Z", + "pushed_at": "2015-08-07T14:24:45Z", + "git_url": "git://github.com/frewsxcv/servo.git", + "ssh_url": "git@github.com:frewsxcv/servo.git", + "clone_url": "https://github.com/frewsxcv/servo.git", + "svn_url": "https://github.com/frewsxcv/servo", + "homepage": "", + "size": 119557, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Rust", + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + } + }, + "base": { + "label": "servo:master", + "ref": "master", + "sha": "b4e30da3dbf58c16703864f4bec4b0b0132084fa", + "user": { + "login": "servo", + "id": 2566135, + "avatar_url": "https://avatars.githubusercontent.com/u/2566135?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/servo", + "html_url": "https://github.com/servo", + "followers_url": "https://api.github.com/users/servo/followers", + "following_url": "https://api.github.com/users/servo/following{/other_user}", + "gists_url": "https://api.github.com/users/servo/gists{/gist_id}", + "starred_url": "https://api.github.com/users/servo/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/servo/subscriptions", + "organizations_url": "https://api.github.com/users/servo/orgs", + "repos_url": "https://api.github.com/users/servo/repos", + "events_url": "https://api.github.com/users/servo/events{/privacy}", + "received_events_url": "https://api.github.com/users/servo/received_events", + "type": "Organization", + "site_admin": false + }, + "repo": { + "id": 3390243, + "name": "servo", + "full_name": "servo/servo", + "owner": { + "login": "servo", + "id": 2566135, + "avatar_url": "https://avatars.githubusercontent.com/u/2566135?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/servo", + "html_url": "https://github.com/servo", + "followers_url": "https://api.github.com/users/servo/followers", + "following_url": "https://api.github.com/users/servo/following{/other_user}", + "gists_url": "https://api.github.com/users/servo/gists{/gist_id}", + "starred_url": "https://api.github.com/users/servo/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/servo/subscriptions", + "organizations_url": "https://api.github.com/users/servo/orgs", + "repos_url": "https://api.github.com/users/servo/repos", + "events_url": "https://api.github.com/users/servo/events{/privacy}", + "received_events_url": "https://api.github.com/users/servo/received_events", + "type": "Organization", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/servo/servo", + "description": "The Servo Browser Engine", + "fork": false, + "url": "https://api.github.com/repos/servo/servo", + "forks_url": "https://api.github.com/repos/servo/servo/forks", + "keys_url": "https://api.github.com/repos/servo/servo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/servo/servo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/servo/servo/teams", + "hooks_url": "https://api.github.com/repos/servo/servo/hooks", + "issue_events_url": "https://api.github.com/repos/servo/servo/issues/events{/number}", + "events_url": "https://api.github.com/repos/servo/servo/events", + "assignees_url": "https://api.github.com/repos/servo/servo/assignees{/user}", + "branches_url": "https://api.github.com/repos/servo/servo/branches{/branch}", + "tags_url": "https://api.github.com/repos/servo/servo/tags", + "blobs_url": "https://api.github.com/repos/servo/servo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/servo/servo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/servo/servo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/servo/servo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/servo/servo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/servo/servo/languages", + "stargazers_url": "https://api.github.com/repos/servo/servo/stargazers", + "contributors_url": "https://api.github.com/repos/servo/servo/contributors", + "subscribers_url": "https://api.github.com/repos/servo/servo/subscribers", + "subscription_url": "https://api.github.com/repos/servo/servo/subscription", + "commits_url": "https://api.github.com/repos/servo/servo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/servo/servo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/servo/servo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/servo/servo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/servo/servo/contents/{+path}", + "compare_url": "https://api.github.com/repos/servo/servo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/servo/servo/merges", + "archive_url": "https://api.github.com/repos/servo/servo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/servo/servo/downloads", + "issues_url": "https://api.github.com/repos/servo/servo/issues{/number}", + "pulls_url": "https://api.github.com/repos/servo/servo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/servo/servo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/servo/servo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/servo/servo/labels{/name}", + "releases_url": "https://api.github.com/repos/servo/servo/releases{/id}", + "created_at": "2012-02-08T19:07:25Z", + "updated_at": "2015-08-07T09:37:49Z", + "pushed_at": "2015-08-07T14:10:08Z", + "git_url": "git://github.com/servo/servo.git", + "ssh_url": "git@github.com:servo/servo.git", + "clone_url": "https://github.com/servo/servo.git", + "svn_url": "https://github.com/servo/servo", + "homepage": "", + "size": 1904176, + "stargazers_count": 4571, + "watchers_count": 4571, + "language": "Rust", + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 717, + "mirror_url": null, + "open_issues_count": 1060, + "forks": 717, + "open_issues": 1060, + "watchers": 4571, + "default_branch": "master" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/servo/servo/pulls/7076" + }, + "html": { + "href": "https://github.com/servo/servo/pull/7076" + }, + "issue": { + "href": "https://api.github.com/repos/servo/servo/issues/7076" + }, + "comments": { + "href": "https://api.github.com/repos/servo/servo/issues/7076/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/servo/servo/pulls/7076/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/servo/servo/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/servo/servo/pulls/7076/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/servo/servo/statuses/da44f31cb1debe2c8161ee280121d79d3dd5ec18" + } + }, + "merged": false, + "mergeable": null, + "mergeable_state": "unknown", + "merged_by": null, + "comments": 0, + "review_comments": 0, + "commits": 1, + "additions": 0, + "deletions": 1, + "changed_files": 1 + }, + "repository": { + "id": 3390243, + "name": "servo", + "full_name": "servo/servo", + "owner": { + "login": "servo", + "id": 2566135, + "avatar_url": "https://avatars.githubusercontent.com/u/2566135?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/servo", + "html_url": "https://github.com/servo", + "followers_url": "https://api.github.com/users/servo/followers", + "following_url": "https://api.github.com/users/servo/following{/other_user}", + "gists_url": "https://api.github.com/users/servo/gists{/gist_id}", + "starred_url": "https://api.github.com/users/servo/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/servo/subscriptions", + "organizations_url": "https://api.github.com/users/servo/orgs", + "repos_url": "https://api.github.com/users/servo/repos", + "events_url": "https://api.github.com/users/servo/events{/privacy}", + "received_events_url": "https://api.github.com/users/servo/received_events", + "type": "Organization", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/servo/servo", + "description": "The Servo Browser Engine", + "fork": false, + "url": "https://api.github.com/repos/servo/servo", + "forks_url": "https://api.github.com/repos/servo/servo/forks", + "keys_url": "https://api.github.com/repos/servo/servo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/servo/servo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/servo/servo/teams", + "hooks_url": "https://api.github.com/repos/servo/servo/hooks", + "issue_events_url": "https://api.github.com/repos/servo/servo/issues/events{/number}", + "events_url": "https://api.github.com/repos/servo/servo/events", + "assignees_url": "https://api.github.com/repos/servo/servo/assignees{/user}", + "branches_url": "https://api.github.com/repos/servo/servo/branches{/branch}", + "tags_url": "https://api.github.com/repos/servo/servo/tags", + "blobs_url": "https://api.github.com/repos/servo/servo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/servo/servo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/servo/servo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/servo/servo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/servo/servo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/servo/servo/languages", + "stargazers_url": "https://api.github.com/repos/servo/servo/stargazers", + "contributors_url": "https://api.github.com/repos/servo/servo/contributors", + "subscribers_url": "https://api.github.com/repos/servo/servo/subscribers", + "subscription_url": "https://api.github.com/repos/servo/servo/subscription", + "commits_url": "https://api.github.com/repos/servo/servo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/servo/servo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/servo/servo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/servo/servo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/servo/servo/contents/{+path}", + "compare_url": "https://api.github.com/repos/servo/servo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/servo/servo/merges", + "archive_url": "https://api.github.com/repos/servo/servo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/servo/servo/downloads", + "issues_url": "https://api.github.com/repos/servo/servo/issues{/number}", + "pulls_url": "https://api.github.com/repos/servo/servo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/servo/servo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/servo/servo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/servo/servo/labels{/name}", + "releases_url": "https://api.github.com/repos/servo/servo/releases{/id}", + "created_at": "2012-02-08T19:07:25Z", + "updated_at": "2015-08-07T09:37:49Z", + "pushed_at": "2015-08-07T14:10:08Z", + "git_url": "git://github.com/servo/servo.git", + "ssh_url": "git@github.com:servo/servo.git", + "clone_url": "https://github.com/servo/servo.git", + "svn_url": "https://github.com/servo/servo", + "homepage": "", + "size": 1904176, + "stargazers_count": 4571, + "watchers_count": 4571, + "language": "Rust", + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 717, + "mirror_url": null, + "open_issues_count": 1060, + "forks": 717, + "open_issues": 1060, + "watchers": 4571, + "default_branch": "master" + }, + "organization": { + "login": "servo", + "id": 2566135, + "url": "https://api.github.com/orgs/servo", + "repos_url": "https://api.github.com/orgs/servo/repos", + "events_url": "https://api.github.com/orgs/servo/events", + "members_url": "https://api.github.com/orgs/servo/members{/member}", + "public_members_url": "https://api.github.com/orgs/servo/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/2566135?v=3", + "description": null + }, + "sender": { + "login": "frewsxcv", + "id": 416575, + "avatar_url": "https://avatars.githubusercontent.com/u/416575?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/frewsxcv", + "html_url": "https://github.com/frewsxcv", + "followers_url": "https://api.github.com/users/frewsxcv/followers", + "following_url": "https://api.github.com/users/frewsxcv/following{/other_user}", + "gists_url": "https://api.github.com/users/frewsxcv/gists{/gist_id}", + "starred_url": "https://api.github.com/users/frewsxcv/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/frewsxcv/subscriptions", + "organizations_url": "https://api.github.com/users/frewsxcv/orgs", + "repos_url": "https://api.github.com/users/frewsxcv/repos", + "events_url": "https://api.github.com/users/frewsxcv/events{/privacy}", + "received_events_url": "https://api.github.com/users/frewsxcv/received_events", + "type": "User", + "site_admin": false + } +} +} diff --git a/handlers/welcome/__init__.py b/handlers/welcome/__init__.py new file mode 100644 index 0000000..0bf8f3e --- /dev/null +++ b/handlers/welcome/__init__.py @@ -0,0 +1,21 @@ +from eventhandler import EventHandler +import random + +welcome_msg = "Thanks for the pull request, and welcome! The Servo team is excited to review your changes, and you should hear from @%s (or someone else) soon." + +class WelcomeHandler(EventHandler): + def on_pr_opened(self, api, payload): + author = payload["pull_request"]['user']['login'] + if api.is_new_contributor(author): + collaborators = ['jdm', 'larsbergstrom', 'metajack', 'mbrubeck', + 'Ms2ger', 'Manishearth', 'glennw', 'pcwalton', + 'SimonSapin'] \ + if api.repo == 'servo' and api.owner == 'servo' \ + else ['test_user_selection_ignore_this'] + random.seed() + to_notify = random.choice(collaborators) + api.post_comment(welcome_msg % to_notify) + + +handler_interface = WelcomeHandler + diff --git a/handlers/welcome/tests/new_pr.json b/handlers/welcome/tests/new_pr.json new file mode 100644 index 0000000..cfcc1cb --- /dev/null +++ b/handlers/welcome/tests/new_pr.json @@ -0,0 +1,432 @@ +{ + "initial": { + "new_contributor": true + }, + "expected": { + "comments": 1 + }, + "payload": +{ + "action": "opened", + "number": 7076, + "pull_request": { + "url": "https://api.github.com/repos/servo/servo/pulls/7076", + "id": 41896942, + "html_url": "https://github.com/servo/servo/pull/7076", + "diff_url": "https://github.com/servo/servo/pull/7076.diff", + "patch_url": "https://github.com/servo/servo/pull/7076.patch", + "issue_url": "https://api.github.com/repos/servo/servo/issues/7076", + "number": 7076, + "state": "open", + "locked": false, + "title": "Remove invalid file path in ignored_files for tidying", + "user": { + "login": "frewsxcv", + "id": 416575, + "avatar_url": "https://avatars.githubusercontent.com/u/416575?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/frewsxcv", + "html_url": "https://github.com/frewsxcv", + "followers_url": "https://api.github.com/users/frewsxcv/followers", + "following_url": "https://api.github.com/users/frewsxcv/following{/other_user}", + "gists_url": "https://api.github.com/users/frewsxcv/gists{/gist_id}", + "starred_url": "https://api.github.com/users/frewsxcv/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/frewsxcv/subscriptions", + "organizations_url": "https://api.github.com/users/frewsxcv/orgs", + "repos_url": "https://api.github.com/users/frewsxcv/repos", + "events_url": "https://api.github.com/users/frewsxcv/events{/privacy}", + "received_events_url": "https://api.github.com/users/frewsxcv/received_events", + "type": "User", + "site_admin": false + }, + "body": null, + "created_at": "2015-08-07T14:24:50Z", + "updated_at": "2015-08-07T14:24:50Z", + "closed_at": null, + "merged_at": null, + "merge_commit_sha": null, + "assignee": null, + "milestone": null, + "commits_url": "https://api.github.com/repos/servo/servo/pulls/7076/commits", + "review_comments_url": "https://api.github.com/repos/servo/servo/pulls/7076/comments", + "review_comment_url": "https://api.github.com/repos/servo/servo/pulls/comments{/number}", + "comments_url": "https://api.github.com/repos/servo/servo/issues/7076/comments", + "statuses_url": "https://api.github.com/repos/servo/servo/statuses/da44f31cb1debe2c8161ee280121d79d3dd5ec18", + "head": { + "label": "frewsxcv:tidy-rm-invalid-file", + "ref": "tidy-rm-invalid-file", + "sha": "da44f31cb1debe2c8161ee280121d79d3dd5ec18", + "user": { + "login": "frewsxcv", + "id": 416575, + "avatar_url": "https://avatars.githubusercontent.com/u/416575?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/frewsxcv", + "html_url": "https://github.com/frewsxcv", + "followers_url": "https://api.github.com/users/frewsxcv/followers", + "following_url": "https://api.github.com/users/frewsxcv/following{/other_user}", + "gists_url": "https://api.github.com/users/frewsxcv/gists{/gist_id}", + "starred_url": "https://api.github.com/users/frewsxcv/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/frewsxcv/subscriptions", + "organizations_url": "https://api.github.com/users/frewsxcv/orgs", + "repos_url": "https://api.github.com/users/frewsxcv/repos", + "events_url": "https://api.github.com/users/frewsxcv/events{/privacy}", + "received_events_url": "https://api.github.com/users/frewsxcv/received_events", + "type": "User", + "site_admin": false + }, + "repo": { + "id": 27014207, + "name": "servo", + "full_name": "frewsxcv/servo", + "owner": { + "login": "frewsxcv", + "id": 416575, + "avatar_url": "https://avatars.githubusercontent.com/u/416575?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/frewsxcv", + "html_url": "https://github.com/frewsxcv", + "followers_url": "https://api.github.com/users/frewsxcv/followers", + "following_url": "https://api.github.com/users/frewsxcv/following{/other_user}", + "gists_url": "https://api.github.com/users/frewsxcv/gists{/gist_id}", + "starred_url": "https://api.github.com/users/frewsxcv/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/frewsxcv/subscriptions", + "organizations_url": "https://api.github.com/users/frewsxcv/orgs", + "repos_url": "https://api.github.com/users/frewsxcv/repos", + "events_url": "https://api.github.com/users/frewsxcv/events{/privacy}", + "received_events_url": "https://api.github.com/users/frewsxcv/received_events", + "type": "User", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/frewsxcv/servo", + "description": "The Servo Browser Engine", + "fork": true, + "url": "https://api.github.com/repos/frewsxcv/servo", + "forks_url": "https://api.github.com/repos/frewsxcv/servo/forks", + "keys_url": "https://api.github.com/repos/frewsxcv/servo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/frewsxcv/servo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/frewsxcv/servo/teams", + "hooks_url": "https://api.github.com/repos/frewsxcv/servo/hooks", + "issue_events_url": "https://api.github.com/repos/frewsxcv/servo/issues/events{/number}", + "events_url": "https://api.github.com/repos/frewsxcv/servo/events", + "assignees_url": "https://api.github.com/repos/frewsxcv/servo/assignees{/user}", + "branches_url": "https://api.github.com/repos/frewsxcv/servo/branches{/branch}", + "tags_url": "https://api.github.com/repos/frewsxcv/servo/tags", + "blobs_url": "https://api.github.com/repos/frewsxcv/servo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/frewsxcv/servo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/frewsxcv/servo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/frewsxcv/servo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/frewsxcv/servo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/frewsxcv/servo/languages", + "stargazers_url": "https://api.github.com/repos/frewsxcv/servo/stargazers", + "contributors_url": "https://api.github.com/repos/frewsxcv/servo/contributors", + "subscribers_url": "https://api.github.com/repos/frewsxcv/servo/subscribers", + "subscription_url": "https://api.github.com/repos/frewsxcv/servo/subscription", + "commits_url": "https://api.github.com/repos/frewsxcv/servo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/frewsxcv/servo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/frewsxcv/servo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/frewsxcv/servo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/frewsxcv/servo/contents/{+path}", + "compare_url": "https://api.github.com/repos/frewsxcv/servo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/frewsxcv/servo/merges", + "archive_url": "https://api.github.com/repos/frewsxcv/servo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/frewsxcv/servo/downloads", + "issues_url": "https://api.github.com/repos/frewsxcv/servo/issues{/number}", + "pulls_url": "https://api.github.com/repos/frewsxcv/servo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/frewsxcv/servo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/frewsxcv/servo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/frewsxcv/servo/labels{/name}", + "releases_url": "https://api.github.com/repos/frewsxcv/servo/releases{/id}", + "created_at": "2014-11-22T22:10:35Z", + "updated_at": "2014-11-22T22:10:38Z", + "pushed_at": "2015-08-07T14:24:45Z", + "git_url": "git://github.com/frewsxcv/servo.git", + "ssh_url": "git@github.com:frewsxcv/servo.git", + "clone_url": "https://github.com/frewsxcv/servo.git", + "svn_url": "https://github.com/frewsxcv/servo", + "homepage": "", + "size": 119557, + "stargazers_count": 0, + "watchers_count": 0, + "language": "Rust", + "has_issues": false, + "has_downloads": true, + "has_wiki": true, + "has_pages": true, + "forks_count": 0, + "mirror_url": null, + "open_issues_count": 0, + "forks": 0, + "open_issues": 0, + "watchers": 0, + "default_branch": "master" + } + }, + "base": { + "label": "servo:master", + "ref": "master", + "sha": "b4e30da3dbf58c16703864f4bec4b0b0132084fa", + "user": { + "login": "servo", + "id": 2566135, + "avatar_url": "https://avatars.githubusercontent.com/u/2566135?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/servo", + "html_url": "https://github.com/servo", + "followers_url": "https://api.github.com/users/servo/followers", + "following_url": "https://api.github.com/users/servo/following{/other_user}", + "gists_url": "https://api.github.com/users/servo/gists{/gist_id}", + "starred_url": "https://api.github.com/users/servo/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/servo/subscriptions", + "organizations_url": "https://api.github.com/users/servo/orgs", + "repos_url": "https://api.github.com/users/servo/repos", + "events_url": "https://api.github.com/users/servo/events{/privacy}", + "received_events_url": "https://api.github.com/users/servo/received_events", + "type": "Organization", + "site_admin": false + }, + "repo": { + "id": 3390243, + "name": "servo", + "full_name": "servo/servo", + "owner": { + "login": "servo", + "id": 2566135, + "avatar_url": "https://avatars.githubusercontent.com/u/2566135?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/servo", + "html_url": "https://github.com/servo", + "followers_url": "https://api.github.com/users/servo/followers", + "following_url": "https://api.github.com/users/servo/following{/other_user}", + "gists_url": "https://api.github.com/users/servo/gists{/gist_id}", + "starred_url": "https://api.github.com/users/servo/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/servo/subscriptions", + "organizations_url": "https://api.github.com/users/servo/orgs", + "repos_url": "https://api.github.com/users/servo/repos", + "events_url": "https://api.github.com/users/servo/events{/privacy}", + "received_events_url": "https://api.github.com/users/servo/received_events", + "type": "Organization", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/servo/servo", + "description": "The Servo Browser Engine", + "fork": false, + "url": "https://api.github.com/repos/servo/servo", + "forks_url": "https://api.github.com/repos/servo/servo/forks", + "keys_url": "https://api.github.com/repos/servo/servo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/servo/servo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/servo/servo/teams", + "hooks_url": "https://api.github.com/repos/servo/servo/hooks", + "issue_events_url": "https://api.github.com/repos/servo/servo/issues/events{/number}", + "events_url": "https://api.github.com/repos/servo/servo/events", + "assignees_url": "https://api.github.com/repos/servo/servo/assignees{/user}", + "branches_url": "https://api.github.com/repos/servo/servo/branches{/branch}", + "tags_url": "https://api.github.com/repos/servo/servo/tags", + "blobs_url": "https://api.github.com/repos/servo/servo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/servo/servo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/servo/servo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/servo/servo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/servo/servo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/servo/servo/languages", + "stargazers_url": "https://api.github.com/repos/servo/servo/stargazers", + "contributors_url": "https://api.github.com/repos/servo/servo/contributors", + "subscribers_url": "https://api.github.com/repos/servo/servo/subscribers", + "subscription_url": "https://api.github.com/repos/servo/servo/subscription", + "commits_url": "https://api.github.com/repos/servo/servo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/servo/servo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/servo/servo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/servo/servo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/servo/servo/contents/{+path}", + "compare_url": "https://api.github.com/repos/servo/servo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/servo/servo/merges", + "archive_url": "https://api.github.com/repos/servo/servo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/servo/servo/downloads", + "issues_url": "https://api.github.com/repos/servo/servo/issues{/number}", + "pulls_url": "https://api.github.com/repos/servo/servo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/servo/servo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/servo/servo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/servo/servo/labels{/name}", + "releases_url": "https://api.github.com/repos/servo/servo/releases{/id}", + "created_at": "2012-02-08T19:07:25Z", + "updated_at": "2015-08-07T09:37:49Z", + "pushed_at": "2015-08-07T14:10:08Z", + "git_url": "git://github.com/servo/servo.git", + "ssh_url": "git@github.com:servo/servo.git", + "clone_url": "https://github.com/servo/servo.git", + "svn_url": "https://github.com/servo/servo", + "homepage": "", + "size": 1904176, + "stargazers_count": 4571, + "watchers_count": 4571, + "language": "Rust", + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 717, + "mirror_url": null, + "open_issues_count": 1060, + "forks": 717, + "open_issues": 1060, + "watchers": 4571, + "default_branch": "master" + } + }, + "_links": { + "self": { + "href": "https://api.github.com/repos/servo/servo/pulls/7076" + }, + "html": { + "href": "https://github.com/servo/servo/pull/7076" + }, + "issue": { + "href": "https://api.github.com/repos/servo/servo/issues/7076" + }, + "comments": { + "href": "https://api.github.com/repos/servo/servo/issues/7076/comments" + }, + "review_comments": { + "href": "https://api.github.com/repos/servo/servo/pulls/7076/comments" + }, + "review_comment": { + "href": "https://api.github.com/repos/servo/servo/pulls/comments{/number}" + }, + "commits": { + "href": "https://api.github.com/repos/servo/servo/pulls/7076/commits" + }, + "statuses": { + "href": "https://api.github.com/repos/servo/servo/statuses/da44f31cb1debe2c8161ee280121d79d3dd5ec18" + } + }, + "merged": false, + "mergeable": null, + "mergeable_state": "unknown", + "merged_by": null, + "comments": 0, + "review_comments": 0, + "commits": 1, + "additions": 0, + "deletions": 1, + "changed_files": 1 + }, + "repository": { + "id": 3390243, + "name": "servo", + "full_name": "servo/servo", + "owner": { + "login": "servo", + "id": 2566135, + "avatar_url": "https://avatars.githubusercontent.com/u/2566135?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/servo", + "html_url": "https://github.com/servo", + "followers_url": "https://api.github.com/users/servo/followers", + "following_url": "https://api.github.com/users/servo/following{/other_user}", + "gists_url": "https://api.github.com/users/servo/gists{/gist_id}", + "starred_url": "https://api.github.com/users/servo/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/servo/subscriptions", + "organizations_url": "https://api.github.com/users/servo/orgs", + "repos_url": "https://api.github.com/users/servo/repos", + "events_url": "https://api.github.com/users/servo/events{/privacy}", + "received_events_url": "https://api.github.com/users/servo/received_events", + "type": "Organization", + "site_admin": false + }, + "private": false, + "html_url": "https://github.com/servo/servo", + "description": "The Servo Browser Engine", + "fork": false, + "url": "https://api.github.com/repos/servo/servo", + "forks_url": "https://api.github.com/repos/servo/servo/forks", + "keys_url": "https://api.github.com/repos/servo/servo/keys{/key_id}", + "collaborators_url": "https://api.github.com/repos/servo/servo/collaborators{/collaborator}", + "teams_url": "https://api.github.com/repos/servo/servo/teams", + "hooks_url": "https://api.github.com/repos/servo/servo/hooks", + "issue_events_url": "https://api.github.com/repos/servo/servo/issues/events{/number}", + "events_url": "https://api.github.com/repos/servo/servo/events", + "assignees_url": "https://api.github.com/repos/servo/servo/assignees{/user}", + "branches_url": "https://api.github.com/repos/servo/servo/branches{/branch}", + "tags_url": "https://api.github.com/repos/servo/servo/tags", + "blobs_url": "https://api.github.com/repos/servo/servo/git/blobs{/sha}", + "git_tags_url": "https://api.github.com/repos/servo/servo/git/tags{/sha}", + "git_refs_url": "https://api.github.com/repos/servo/servo/git/refs{/sha}", + "trees_url": "https://api.github.com/repos/servo/servo/git/trees{/sha}", + "statuses_url": "https://api.github.com/repos/servo/servo/statuses/{sha}", + "languages_url": "https://api.github.com/repos/servo/servo/languages", + "stargazers_url": "https://api.github.com/repos/servo/servo/stargazers", + "contributors_url": "https://api.github.com/repos/servo/servo/contributors", + "subscribers_url": "https://api.github.com/repos/servo/servo/subscribers", + "subscription_url": "https://api.github.com/repos/servo/servo/subscription", + "commits_url": "https://api.github.com/repos/servo/servo/commits{/sha}", + "git_commits_url": "https://api.github.com/repos/servo/servo/git/commits{/sha}", + "comments_url": "https://api.github.com/repos/servo/servo/comments{/number}", + "issue_comment_url": "https://api.github.com/repos/servo/servo/issues/comments{/number}", + "contents_url": "https://api.github.com/repos/servo/servo/contents/{+path}", + "compare_url": "https://api.github.com/repos/servo/servo/compare/{base}...{head}", + "merges_url": "https://api.github.com/repos/servo/servo/merges", + "archive_url": "https://api.github.com/repos/servo/servo/{archive_format}{/ref}", + "downloads_url": "https://api.github.com/repos/servo/servo/downloads", + "issues_url": "https://api.github.com/repos/servo/servo/issues{/number}", + "pulls_url": "https://api.github.com/repos/servo/servo/pulls{/number}", + "milestones_url": "https://api.github.com/repos/servo/servo/milestones{/number}", + "notifications_url": "https://api.github.com/repos/servo/servo/notifications{?since,all,participating}", + "labels_url": "https://api.github.com/repos/servo/servo/labels{/name}", + "releases_url": "https://api.github.com/repos/servo/servo/releases{/id}", + "created_at": "2012-02-08T19:07:25Z", + "updated_at": "2015-08-07T09:37:49Z", + "pushed_at": "2015-08-07T14:10:08Z", + "git_url": "git://github.com/servo/servo.git", + "ssh_url": "git@github.com:servo/servo.git", + "clone_url": "https://github.com/servo/servo.git", + "svn_url": "https://github.com/servo/servo", + "homepage": "", + "size": 1904176, + "stargazers_count": 4571, + "watchers_count": 4571, + "language": "Rust", + "has_issues": true, + "has_downloads": true, + "has_wiki": true, + "has_pages": false, + "forks_count": 717, + "mirror_url": null, + "open_issues_count": 1060, + "forks": 717, + "open_issues": 1060, + "watchers": 4571, + "default_branch": "master" + }, + "organization": { + "login": "servo", + "id": 2566135, + "url": "https://api.github.com/orgs/servo", + "repos_url": "https://api.github.com/orgs/servo/repos", + "events_url": "https://api.github.com/orgs/servo/events", + "members_url": "https://api.github.com/orgs/servo/members{/member}", + "public_members_url": "https://api.github.com/orgs/servo/public_members{/member}", + "avatar_url": "https://avatars.githubusercontent.com/u/2566135?v=3", + "description": null + }, + "sender": { + "login": "frewsxcv", + "id": 416575, + "avatar_url": "https://avatars.githubusercontent.com/u/416575?v=3", + "gravatar_id": "", + "url": "https://api.github.com/users/frewsxcv", + "html_url": "https://github.com/frewsxcv", + "followers_url": "https://api.github.com/users/frewsxcv/followers", + "following_url": "https://api.github.com/users/frewsxcv/following{/other_user}", + "gists_url": "https://api.github.com/users/frewsxcv/gists{/gist_id}", + "starred_url": "https://api.github.com/users/frewsxcv/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/frewsxcv/subscriptions", + "organizations_url": "https://api.github.com/users/frewsxcv/orgs", + "repos_url": "https://api.github.com/users/frewsxcv/repos", + "events_url": "https://api.github.com/users/frewsxcv/events{/privacy}", + "received_events_url": "https://api.github.com/users/frewsxcv/received_events", + "type": "User", + "site_admin": false + } +} +} diff --git a/newpr.py b/newpr.py index 929cb6e..4cbbd1c 100755 --- a/newpr.py +++ b/newpr.py @@ -1,6 +1,7 @@ #!/usr/bin/env python import base64 +import eventhandler import urllib, urllib2 import cgi import cgitb @@ -57,6 +58,8 @@ class GithubAPIProvider(APIProvider): def __init__(self, payload, user, token): APIProvider.__init__(self, payload, user) self.token = token + self._labels = None + self._diff = None if "pull_request" in payload: self.diff_url = payload["pull_request"]["diff_url"] @@ -132,6 +135,8 @@ def post_comment(self, body): raise e def add_label(self, label): + if self._labels: + self._labels += [label] try: result = self.api_req("POST", self.add_label_url % (self.owner, self.repo, self.issue), [label]) @@ -142,6 +147,8 @@ def add_label(self, label): raise e def remove_label(self, label): + if self._labels and label in self._labels: + self._labels.remove(label) try: result = self.api_req("DELETE", self.remove_label_url % (self.owner, self.repo, self.issue, label), {}) except urllib2.HTTPError, e: @@ -152,6 +159,8 @@ def remove_label(self, label): pass def get_labels(self): + if self._labels is not None: + return self._labels try: result = self.api_req("GET", self.get_label_url % (self.owner, self.repo, self.issue)) except urllib2.HTTPError, e: @@ -159,10 +168,14 @@ def get_labels(self): pass else: raise e - return map(lambda x: x["name"], json.loads(result['body'])) + self._labels = map(lambda x: x["name"], json.loads(result['body'])) + return self._labels def get_diff(self): - return self.api_req("GET", self.diff_url)['body'] + if self._diff: + return self._diff; + self._diff = self.api_req("GET", self.diff_url)['body'] + return self._diff def set_assignee(self, assignee): try: @@ -175,20 +188,7 @@ def set_assignee(self, assignee): raise e -# If the user specified a reviewer, return the username, otherwise returns None. -def find_reviewer(commit_msg): - match = reviewer_re.search(commit_msg) - if not match: - return None - return match.group(1) - - -welcome_msg = "Thanks for the pull request, and welcome! The Servo team is excited to review your changes, and you should hear from @%s (or someone else) soon." warning_summary = 'warning **Warning** warning\n\n%s' -unsafe_warning_msg = 'These commits modify **unsafe code**. Please review it carefully!' -reftest_required_msg = 'These commits modify layout code, but no reftests are modified. Please consider adding a reftest!' - -reviewer_re = re.compile("\\b[rR]\?[:\- ]*@([a-zA-Z0-9\-]+)") def extract_globals_from_payload(payload): if payload["action"] == "created": @@ -202,109 +202,26 @@ def extract_globals_from_payload(payload): return (owner, repo, issue) -def manage_pr_state(api, payload): - labels = api.get_labels(); - - if payload["action"] in ["synchronize", "opened"]: - for label in ["S-awaiting-merge", "S-tests-failed", "S-needs-code-changes"]: - if label in labels: - api.remove_label(label) - if not "S-awaiting-review" in labels: - api.add_label("S-awaiting-review") - - # If mergeable is null, the data wasn't available yet. It would be nice to try to fetch that - # information again. - if payload["action"] == "synchronize" and payload['pull_request']['mergeable']: - if "S-needs-rebase" in labels: - api.remove_label("S-needs-rebase") - - -def new_comment(api, payload): - # We only care about comments in open PRs - if payload['issue']['state'] != 'open' or 'pull_request' not in payload['issue']: - return - - commenter = payload['comment']['user']['login'] - # Ignore our own comments. - if commenter == api.user: - return - - msg = payload["comment"]["body"] - reviewer = find_reviewer(msg) - if reviewer: - api.set_assignee(reviewer) - - if commenter == 'bors-servo': - labels = api.get_labels(); - - if 'has been approved by' in msg or 'Testing commit' in msg: - for label in ["S-awaiting-review", "S-needs-rebase", "S-tests-failed", - "S-needs-code-changes", "S-needs-squash", "S-awaiting-answer"]: - if label in labels: - api.remove_label(label) - if not "S-awaiting-merge" in labels: - api.add_label("S-awaiting-merge") - - elif 'Test failed' in msg: - api.remove_label("S-awaiting-merge") - api.add_label("S-tests-failed") - - elif 'Please resolve the merge conflicts' in msg: - api.remove_label("S-awaiting-merge") - api.add_label("S-needs-rebase") - - -def new_pr(api, payload): - manage_pr_state(api, payload) - - author = payload["pull_request"]['user']['login'] - if api.is_new_contributor(author): - #collaborators = json.load(urllib2.urlopen(collaborators_url)) - collaborators = ['jdm', 'larsbergstrom', 'metajack', 'mbrubeck', 'Ms2ger', 'Manishearth', 'glennw', 'pcwalton', 'SimonSapin'] if api.repo == 'servo' and api.owner == 'servo' else ['test_user_selection_ignore_this'] - random.seed() - to_notify = random.choice(collaborators) - api.post_comment(welcome_msg % to_notify) - - warn_unsafe = False - layout_changed = False - saw_reftest = False - diff = api.get_diff() - for line in diff.split('\n'): - if line.startswith('+') and not line.startswith('+++') and line.find('unsafe') > -1: - warn_unsafe = True - if line.startswith('diff --git') and line.find('components/layout/') > -1: - layout_changed = True - if line.startswith('diff --git') and line.find('tests/ref') > -1: - saw_reftest = True - if line.startswith('diff --git') and line.find('tests/wpt') > -1: - saw_reftest = True - - warnings = [] - if warn_unsafe: - warnings += [unsafe_warning_msg] - - if layout_changed: - if not saw_reftest: - warnings += [reftest_required_msg] - - if warnings: - api.post_comment(warning_summary % '\n'.join(map(lambda x: '* ' + x, warnings))) - - -def update_pr(api, payload): - manage_pr_state(api, payload) - - def handle_payload(api, payload): + (modules, handlers) = eventhandler.get_handlers() + if payload["action"] == "opened": - new_pr(api, payload) + for handler in handlers: + handler.on_pr_opened(api, payload) elif payload["action"] == "synchronize": - update_pr(api, payload) + for handler in handlers: + handler.on_pr_updated(api, payload) elif payload["action"] == "created": - new_comment(api, payload) + for handler in handlers: + handler.on_new_comment(api, payload) else: pass + warnings = eventhandler.get_warnings() + if warnings: + api.post_comment(warning_summary % '\n'.join(map(lambda x: '* ' + x, warnings))) + + if __name__ == "__main__": print "Content-Type: text/html;charset=utf-8" print diff --git a/test.py b/test.py index 8917538..f050e5f 100644 --- a/test.py +++ b/test.py @@ -38,33 +38,36 @@ def get_payload(filename): with open(filename) as f: return json.load(f) -tests = [] -def add_test(filename, initial, expected): +def create_test(filename, initial, expected): global tests initial_values = {'new_contributor': initial.get('new_contributor', False), 'labels': initial.get('labels', []), 'diff': initial.get('diff', ''), 'assignee': initial.get('assignee', None)} - expected_values = {'labels': expected.get('labels', []), - 'assignee': expected.get('assignee', None), - 'comments': expected.get('comments', 0)} - tests += [{'filename': filename, - 'initial': initial_values, - 'expected': expected_values}] + return {'filename': filename, + 'initial': initial_values, + 'expected': expected} def run_tests(tests): + import eventhandler + failed = 0 for test in tests: + eventhandler.reset_test_state() + try: - payload = get_payload(test['filename']) + payload = get_payload(test['filename'])['payload'] initial = test['initial'] api = TestAPIProvider(payload, 'highfive', initial['new_contributor'], initial['labels'], initial['assignee'], initial['diff']) handle_payload(api, payload) expected = test['expected'] - assert len(api.comments_posted) == expected['comments'] - assert api.labels == expected['labels'] - assert api.assignee == expected['assignee'] + if 'comments' in expected: + assert len(api.comments_posted) == expected['comments'] + if 'labels' in expected: + assert api.labels == expected['labels'] + if 'assignee' in expected: + assert api.assignee == expected['assignee'] except AssertionError: _, _, tb = sys.exc_info() traceback.print_tb(tb) # Fixed format @@ -73,52 +76,19 @@ def run_tests(tests): print('{}: An error occurred on line {} in statement {}'.format(test['filename'], line, text)) failed += 1 - possible_tests = [f for f in os.listdir('.') if f.endswith('.json')] - test_files = set([test['filename'] for test in tests]) - if len(possible_tests) != len(test_files): - print 'Found unused JSON test data: %s' % ', '.join(filter(lambda x: x not in test_files, possible_tests)) - sys.exit(1) print 'Ran %d tests, %d failed' % (len(tests), failed) if failed: sys.exit(1) -add_test('test_new_pr.json', {'new_contributor': True}, - {'labels': ['S-awaiting-review'], 'comments': 1}) - -add_test('test_new_pr.json', {'diff': "+ unsafe fn foo()"}, - {'labels': ['S-awaiting-review'], 'comments': 1}) - -add_test('test_new_pr.json', {'diff': "diff --git components/layout/"}, - {'labels': ['S-awaiting-review'], 'comments': 1}) - -add_test('test_new_pr.json', {'diff': "diff --git components/layout/\ndiff --git tests/wpt"}, - {'labels': ['S-awaiting-review'], 'comments': 0}) - -add_test('test_new_pr.json', {'new_contributor': True}, - {'labels': ['S-awaiting-review'], 'comments': 1}) - -add_test('test_ignored_action.json', {}, {}) - -add_test('test_synchronize.json', {'labels': ['S-needs-code-changes', 'S-tests-failed', 'S-awaiting-merge']}, - {'labels': ['S-awaiting-review']}) - -add_test('test_comment.json', {}, {'assignee': 'jdm'}) - -add_test('test_merge_approved.json', {'labels': ['S-needs-code-changes', 'S-needs-rebase', - 'S-tests-failed', 'S-needs-squash', - 'S-awaiting-review']}, {'labels': ['S-awaiting-merge']}) - -add_test('test_merge_conflict.json', {'labels': ['S-awaiting-merge']}, - {'labels': ['S-needs-rebase']}) - -add_test('test_tests_failed.json', {'labels': ['S-awaiting-merge']}, - {'labels': ['S-tests-failed']}) - -add_test('test_post_retry.json', {'labels': ['S-tests-failed']}, - {'labels': ['S-awaiting-merge']}) - -add_test('test_post_retry.json', {'labels': ['S-awaiting-merge']}, - {'labels': ['S-awaiting-merge']}) - -run_tests(tests) +def setup_tests(): + import eventhandler + (modules, handlers) = eventhandler.get_handlers() + tests = [] + for module, handler in zip(modules, handlers): + tests.extend(handler.register_tests(module[1])) + return tests + +if __name__ == "__main__": + tests = setup_tests() + run_tests(tests)