aboutsummaryrefslogtreecommitdiff
path: root/bot
diff options
context:
space:
mode:
authorFelix Mulder <felix.mulder@gmail.com>2017-02-06 17:18:26 +0100
committerFelix Mulder <felix.mulder@gmail.com>2017-02-13 10:53:45 +0100
commit3f06fe9cc2debaacbb889e33c7339457fc5355cd (patch)
treea9d67a74543e17de42cff6f75cd57020000487f8 /bot
parent8bdc91f7a5fc3efd93b6be255ec1bfb83787c69b (diff)
downloaddotty-3f06fe9cc2debaacbb889e33c7339457fc5355cd.tar.gz
dotty-3f06fe9cc2debaacbb889e33c7339457fc5355cd.tar.bz2
dotty-3f06fe9cc2debaacbb889e33c7339457fc5355cd.zip
Add initial steps to dotty-bot
This PR will add a bot whose first purpose is to check the CLA of contributing PRs. It improves on the old bot in that it checks each commit individually, and doesn't get upset about 100+ commits. It would be fun to do this PR with you @OlivierBlanvillain, feel free to provide feedback/comments and refactor as you like
Diffstat (limited to 'bot')
-rw-r--r--bot/resources/test-pr.json432
-rw-r--r--bot/src/dotty/tools/bot/BotServer.scala18
-rw-r--r--bot/src/dotty/tools/bot/PullRequestService.scala120
3 files changed, 570 insertions, 0 deletions
diff --git a/bot/resources/test-pr.json b/bot/resources/test-pr.json
new file mode 100644
index 000000000..19078ee33
--- /dev/null
+++ b/bot/resources/test-pr.json
@@ -0,0 +1,432 @@
+{
+ "action": "opened",
+ "number": 1943,
+ "pull_request": {
+ "url": "https://api.github.com/repos/lampepfl/dotty/pulls/1943",
+ "id": 104705912,
+ "html_url": "https://github.com/lampepfl/dotty/pull/1943",
+ "diff_url": "https://github.com/lampepfl/dotty/pull/1943.diff",
+ "patch_url": "https://github.com/lampepfl/dotty/pull/1943.patch",
+ "issue_url": "https://api.github.com/repos/lampepfl/dotty/issues/1943",
+ "number": 1943,
+ "state": "open",
+ "locked": false,
+ "title": "Positioned#initialPos: Union the position of every children",
+ "user": {
+ "login": "smarter",
+ "id": 63430,
+ "avatar_url": "https://avatars.githubusercontent.com/u/63430?v=3",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/smarter",
+ "html_url": "https://github.com/smarter",
+ "followers_url": "https://api.github.com/users/smarter/followers",
+ "following_url": "https://api.github.com/users/smarter/following{/other_user}",
+ "gists_url": "https://api.github.com/users/smarter/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/smarter/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/smarter/subscriptions",
+ "organizations_url": "https://api.github.com/users/smarter/orgs",
+ "repos_url": "https://api.github.com/users/smarter/repos",
+ "events_url": "https://api.github.com/users/smarter/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/smarter/received_events",
+ "type": "User",
+ "site_admin": false
+ },
+ "body": "Previously we missed some children, one consequence of this is that the\r\nposition of the typed tree corresponding to the lambda \"z => 1\" did not\r\ncontain the position of \"z\".\r\n\r\nReview by @odersky ",
+ "created_at": "2017-02-05T16:51:01Z",
+ "updated_at": "2017-02-05T16:51:01Z",
+ "closed_at": null,
+ "merged_at": null,
+ "merge_commit_sha": null,
+ "assignee": null,
+ "assignees": [
+
+ ],
+ "milestone": null,
+ "commits_url": "https://api.github.com/repos/lampepfl/dotty/pulls/1943/commits",
+ "review_comments_url": "https://api.github.com/repos/lampepfl/dotty/pulls/1943/comments",
+ "review_comment_url": "https://api.github.com/repos/lampepfl/dotty/pulls/comments{/number}",
+ "comments_url": "https://api.github.com/repos/lampepfl/dotty/issues/1943/comments",
+ "statuses_url": "https://api.github.com/repos/lampepfl/dotty/statuses/9e9206dbf3e1cbd378adcb9f65637d2cf331cc10",
+ "head": {
+ "label": "dotty-staging:fix/lambda-position",
+ "ref": "fix/lambda-position",
+ "sha": "9e9206dbf3e1cbd378adcb9f65637d2cf331cc10",
+ "user": {
+ "login": "dotty-staging",
+ "id": 6998674,
+ "avatar_url": "https://avatars.githubusercontent.com/u/6998674?v=3",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/dotty-staging",
+ "html_url": "https://github.com/dotty-staging",
+ "followers_url": "https://api.github.com/users/dotty-staging/followers",
+ "following_url": "https://api.github.com/users/dotty-staging/following{/other_user}",
+ "gists_url": "https://api.github.com/users/dotty-staging/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/dotty-staging/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/dotty-staging/subscriptions",
+ "organizations_url": "https://api.github.com/users/dotty-staging/orgs",
+ "repos_url": "https://api.github.com/users/dotty-staging/repos",
+ "events_url": "https://api.github.com/users/dotty-staging/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/dotty-staging/received_events",
+ "type": "Organization",
+ "site_admin": false
+ },
+ "repo": {
+ "id": 17904384,
+ "name": "dotty",
+ "full_name": "dotty-staging/dotty",
+ "owner": {
+ "login": "dotty-staging",
+ "id": 6998674,
+ "avatar_url": "https://avatars.githubusercontent.com/u/6998674?v=3",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/dotty-staging",
+ "html_url": "https://github.com/dotty-staging",
+ "followers_url": "https://api.github.com/users/dotty-staging/followers",
+ "following_url": "https://api.github.com/users/dotty-staging/following{/other_user}",
+ "gists_url": "https://api.github.com/users/dotty-staging/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/dotty-staging/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/dotty-staging/subscriptions",
+ "organizations_url": "https://api.github.com/users/dotty-staging/orgs",
+ "repos_url": "https://api.github.com/users/dotty-staging/repos",
+ "events_url": "https://api.github.com/users/dotty-staging/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/dotty-staging/received_events",
+ "type": "Organization",
+ "site_admin": false
+ },
+ "private": false,
+ "html_url": "https://github.com/dotty-staging/dotty",
+ "description": "Research platform for new language concepts and compiler technologies for Scala.",
+ "fork": true,
+ "url": "https://api.github.com/repos/dotty-staging/dotty",
+ "forks_url": "https://api.github.com/repos/dotty-staging/dotty/forks",
+ "keys_url": "https://api.github.com/repos/dotty-staging/dotty/keys{/key_id}",
+ "collaborators_url": "https://api.github.com/repos/dotty-staging/dotty/collaborators{/collaborator}",
+ "teams_url": "https://api.github.com/repos/dotty-staging/dotty/teams",
+ "hooks_url": "https://api.github.com/repos/dotty-staging/dotty/hooks",
+ "issue_events_url": "https://api.github.com/repos/dotty-staging/dotty/issues/events{/number}",
+ "events_url": "https://api.github.com/repos/dotty-staging/dotty/events",
+ "assignees_url": "https://api.github.com/repos/dotty-staging/dotty/assignees{/user}",
+ "branches_url": "https://api.github.com/repos/dotty-staging/dotty/branches{/branch}",
+ "tags_url": "https://api.github.com/repos/dotty-staging/dotty/tags",
+ "blobs_url": "https://api.github.com/repos/dotty-staging/dotty/git/blobs{/sha}",
+ "git_tags_url": "https://api.github.com/repos/dotty-staging/dotty/git/tags{/sha}",
+ "git_refs_url": "https://api.github.com/repos/dotty-staging/dotty/git/refs{/sha}",
+ "trees_url": "https://api.github.com/repos/dotty-staging/dotty/git/trees{/sha}",
+ "statuses_url": "https://api.github.com/repos/dotty-staging/dotty/statuses/{sha}",
+ "languages_url": "https://api.github.com/repos/dotty-staging/dotty/languages",
+ "stargazers_url": "https://api.github.com/repos/dotty-staging/dotty/stargazers",
+ "contributors_url": "https://api.github.com/repos/dotty-staging/dotty/contributors",
+ "subscribers_url": "https://api.github.com/repos/dotty-staging/dotty/subscribers",
+ "subscription_url": "https://api.github.com/repos/dotty-staging/dotty/subscription",
+ "commits_url": "https://api.github.com/repos/dotty-staging/dotty/commits{/sha}",
+ "git_commits_url": "https://api.github.com/repos/dotty-staging/dotty/git/commits{/sha}",
+ "comments_url": "https://api.github.com/repos/dotty-staging/dotty/comments{/number}",
+ "issue_comment_url": "https://api.github.com/repos/dotty-staging/dotty/issues/comments{/number}",
+ "contents_url": "https://api.github.com/repos/dotty-staging/dotty/contents/{+path}",
+ "compare_url": "https://api.github.com/repos/dotty-staging/dotty/compare/{base}...{head}",
+ "merges_url": "https://api.github.com/repos/dotty-staging/dotty/merges",
+ "archive_url": "https://api.github.com/repos/dotty-staging/dotty/{archive_format}{/ref}",
+ "downloads_url": "https://api.github.com/repos/dotty-staging/dotty/downloads",
+ "issues_url": "https://api.github.com/repos/dotty-staging/dotty/issues{/number}",
+ "pulls_url": "https://api.github.com/repos/dotty-staging/dotty/pulls{/number}",
+ "milestones_url": "https://api.github.com/repos/dotty-staging/dotty/milestones{/number}",
+ "notifications_url": "https://api.github.com/repos/dotty-staging/dotty/notifications{?since,all,participating}",
+ "labels_url": "https://api.github.com/repos/dotty-staging/dotty/labels{/name}",
+ "releases_url": "https://api.github.com/repos/dotty-staging/dotty/releases{/id}",
+ "deployments_url": "https://api.github.com/repos/dotty-staging/dotty/deployments",
+ "created_at": "2014-03-19T13:06:15Z",
+ "updated_at": "2016-07-11T14:41:18Z",
+ "pushed_at": "2017-02-05T16:50:41Z",
+ "git_url": "git://github.com/dotty-staging/dotty.git",
+ "ssh_url": "git@github.com:dotty-staging/dotty.git",
+ "clone_url": "https://github.com/dotty-staging/dotty.git",
+ "svn_url": "https://github.com/dotty-staging/dotty",
+ "homepage": "",
+ "size": 27650,
+ "stargazers_count": 4,
+ "watchers_count": 4,
+ "language": "Scala",
+ "has_issues": false,
+ "has_downloads": true,
+ "has_wiki": true,
+ "has_pages": false,
+ "forks_count": 0,
+ "mirror_url": null,
+ "open_issues_count": 0,
+ "forks": 0,
+ "open_issues": 0,
+ "watchers": 4,
+ "default_branch": "master"
+ }
+ },
+ "base": {
+ "label": "lampepfl:master",
+ "ref": "master",
+ "sha": "da7d7231b7f21fe1085abc569eb783590074a359",
+ "user": {
+ "login": "lampepfl",
+ "id": 2684793,
+ "avatar_url": "https://avatars.githubusercontent.com/u/2684793?v=3",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/lampepfl",
+ "html_url": "https://github.com/lampepfl",
+ "followers_url": "https://api.github.com/users/lampepfl/followers",
+ "following_url": "https://api.github.com/users/lampepfl/following{/other_user}",
+ "gists_url": "https://api.github.com/users/lampepfl/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/lampepfl/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/lampepfl/subscriptions",
+ "organizations_url": "https://api.github.com/users/lampepfl/orgs",
+ "repos_url": "https://api.github.com/users/lampepfl/repos",
+ "events_url": "https://api.github.com/users/lampepfl/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/lampepfl/received_events",
+ "type": "Organization",
+ "site_admin": false
+ },
+ "repo": {
+ "id": 7035651,
+ "name": "dotty",
+ "full_name": "lampepfl/dotty",
+ "owner": {
+ "login": "lampepfl",
+ "id": 2684793,
+ "avatar_url": "https://avatars.githubusercontent.com/u/2684793?v=3",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/lampepfl",
+ "html_url": "https://github.com/lampepfl",
+ "followers_url": "https://api.github.com/users/lampepfl/followers",
+ "following_url": "https://api.github.com/users/lampepfl/following{/other_user}",
+ "gists_url": "https://api.github.com/users/lampepfl/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/lampepfl/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/lampepfl/subscriptions",
+ "organizations_url": "https://api.github.com/users/lampepfl/orgs",
+ "repos_url": "https://api.github.com/users/lampepfl/repos",
+ "events_url": "https://api.github.com/users/lampepfl/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/lampepfl/received_events",
+ "type": "Organization",
+ "site_admin": false
+ },
+ "private": false,
+ "html_url": "https://github.com/lampepfl/dotty",
+ "description": "Research platform for new language concepts and compiler technologies for Scala.",
+ "fork": false,
+ "url": "https://api.github.com/repos/lampepfl/dotty",
+ "forks_url": "https://api.github.com/repos/lampepfl/dotty/forks",
+ "keys_url": "https://api.github.com/repos/lampepfl/dotty/keys{/key_id}",
+ "collaborators_url": "https://api.github.com/repos/lampepfl/dotty/collaborators{/collaborator}",
+ "teams_url": "https://api.github.com/repos/lampepfl/dotty/teams",
+ "hooks_url": "https://api.github.com/repos/lampepfl/dotty/hooks",
+ "issue_events_url": "https://api.github.com/repos/lampepfl/dotty/issues/events{/number}",
+ "events_url": "https://api.github.com/repos/lampepfl/dotty/events",
+ "assignees_url": "https://api.github.com/repos/lampepfl/dotty/assignees{/user}",
+ "branches_url": "https://api.github.com/repos/lampepfl/dotty/branches{/branch}",
+ "tags_url": "https://api.github.com/repos/lampepfl/dotty/tags",
+ "blobs_url": "https://api.github.com/repos/lampepfl/dotty/git/blobs{/sha}",
+ "git_tags_url": "https://api.github.com/repos/lampepfl/dotty/git/tags{/sha}",
+ "git_refs_url": "https://api.github.com/repos/lampepfl/dotty/git/refs{/sha}",
+ "trees_url": "https://api.github.com/repos/lampepfl/dotty/git/trees{/sha}",
+ "statuses_url": "https://api.github.com/repos/lampepfl/dotty/statuses/{sha}",
+ "languages_url": "https://api.github.com/repos/lampepfl/dotty/languages",
+ "stargazers_url": "https://api.github.com/repos/lampepfl/dotty/stargazers",
+ "contributors_url": "https://api.github.com/repos/lampepfl/dotty/contributors",
+ "subscribers_url": "https://api.github.com/repos/lampepfl/dotty/subscribers",
+ "subscription_url": "https://api.github.com/repos/lampepfl/dotty/subscription",
+ "commits_url": "https://api.github.com/repos/lampepfl/dotty/commits{/sha}",
+ "git_commits_url": "https://api.github.com/repos/lampepfl/dotty/git/commits{/sha}",
+ "comments_url": "https://api.github.com/repos/lampepfl/dotty/comments{/number}",
+ "issue_comment_url": "https://api.github.com/repos/lampepfl/dotty/issues/comments{/number}",
+ "contents_url": "https://api.github.com/repos/lampepfl/dotty/contents/{+path}",
+ "compare_url": "https://api.github.com/repos/lampepfl/dotty/compare/{base}...{head}",
+ "merges_url": "https://api.github.com/repos/lampepfl/dotty/merges",
+ "archive_url": "https://api.github.com/repos/lampepfl/dotty/{archive_format}{/ref}",
+ "downloads_url": "https://api.github.com/repos/lampepfl/dotty/downloads",
+ "issues_url": "https://api.github.com/repos/lampepfl/dotty/issues{/number}",
+ "pulls_url": "https://api.github.com/repos/lampepfl/dotty/pulls{/number}",
+ "milestones_url": "https://api.github.com/repos/lampepfl/dotty/milestones{/number}",
+ "notifications_url": "https://api.github.com/repos/lampepfl/dotty/notifications{?since,all,participating}",
+ "labels_url": "https://api.github.com/repos/lampepfl/dotty/labels{/name}",
+ "releases_url": "https://api.github.com/repos/lampepfl/dotty/releases{/id}",
+ "deployments_url": "https://api.github.com/repos/lampepfl/dotty/deployments",
+ "created_at": "2012-12-06T12:57:33Z",
+ "updated_at": "2017-02-05T01:04:39Z",
+ "pushed_at": "2017-02-05T16:50:21Z",
+ "git_url": "git://github.com/lampepfl/dotty.git",
+ "ssh_url": "git@github.com:lampepfl/dotty.git",
+ "clone_url": "https://github.com/lampepfl/dotty.git",
+ "svn_url": "https://github.com/lampepfl/dotty",
+ "homepage": "http://dotty.epfl.ch",
+ "size": 28511,
+ "stargazers_count": 1447,
+ "watchers_count": 1447,
+ "language": "Scala",
+ "has_issues": true,
+ "has_downloads": true,
+ "has_wiki": false,
+ "has_pages": true,
+ "forks_count": 212,
+ "mirror_url": null,
+ "open_issues_count": 243,
+ "forks": 212,
+ "open_issues": 243,
+ "watchers": 1447,
+ "default_branch": "master"
+ }
+ },
+ "_links": {
+ "self": {
+ "href": "https://api.github.com/repos/lampepfl/dotty/pulls/1943"
+ },
+ "html": {
+ "href": "https://github.com/lampepfl/dotty/pull/1943"
+ },
+ "issue": {
+ "href": "https://api.github.com/repos/lampepfl/dotty/issues/1943"
+ },
+ "comments": {
+ "href": "https://api.github.com/repos/lampepfl/dotty/issues/1943/comments"
+ },
+ "review_comments": {
+ "href": "https://api.github.com/repos/lampepfl/dotty/pulls/1943/comments"
+ },
+ "review_comment": {
+ "href": "https://api.github.com/repos/lampepfl/dotty/pulls/comments{/number}"
+ },
+ "commits": {
+ "href": "https://api.github.com/repos/lampepfl/dotty/pulls/1943/commits"
+ },
+ "statuses": {
+ "href": "https://api.github.com/repos/lampepfl/dotty/statuses/9e9206dbf3e1cbd378adcb9f65637d2cf331cc10"
+ }
+ },
+ "merged": false,
+ "mergeable": null,
+ "mergeable_state": "unknown",
+ "merged_by": null,
+ "comments": 0,
+ "review_comments": 0,
+ "maintainer_can_modify": true,
+ "commits": 1,
+ "additions": 2,
+ "deletions": 0,
+ "changed_files": 1
+ },
+ "repository": {
+ "id": 7035651,
+ "name": "dotty",
+ "full_name": "lampepfl/dotty",
+ "owner": {
+ "login": "lampepfl",
+ "id": 2684793,
+ "avatar_url": "https://avatars.githubusercontent.com/u/2684793?v=3",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/lampepfl",
+ "html_url": "https://github.com/lampepfl",
+ "followers_url": "https://api.github.com/users/lampepfl/followers",
+ "following_url": "https://api.github.com/users/lampepfl/following{/other_user}",
+ "gists_url": "https://api.github.com/users/lampepfl/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/lampepfl/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/lampepfl/subscriptions",
+ "organizations_url": "https://api.github.com/users/lampepfl/orgs",
+ "repos_url": "https://api.github.com/users/lampepfl/repos",
+ "events_url": "https://api.github.com/users/lampepfl/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/lampepfl/received_events",
+ "type": "Organization",
+ "site_admin": false
+ },
+ "private": false,
+ "html_url": "https://github.com/lampepfl/dotty",
+ "description": "Research platform for new language concepts and compiler technologies for Scala.",
+ "fork": false,
+ "url": "https://api.github.com/repos/lampepfl/dotty",
+ "forks_url": "https://api.github.com/repos/lampepfl/dotty/forks",
+ "keys_url": "https://api.github.com/repos/lampepfl/dotty/keys{/key_id}",
+ "collaborators_url": "https://api.github.com/repos/lampepfl/dotty/collaborators{/collaborator}",
+ "teams_url": "https://api.github.com/repos/lampepfl/dotty/teams",
+ "hooks_url": "https://api.github.com/repos/lampepfl/dotty/hooks",
+ "issue_events_url": "https://api.github.com/repos/lampepfl/dotty/issues/events{/number}",
+ "events_url": "https://api.github.com/repos/lampepfl/dotty/events",
+ "assignees_url": "https://api.github.com/repos/lampepfl/dotty/assignees{/user}",
+ "branches_url": "https://api.github.com/repos/lampepfl/dotty/branches{/branch}",
+ "tags_url": "https://api.github.com/repos/lampepfl/dotty/tags",
+ "blobs_url": "https://api.github.com/repos/lampepfl/dotty/git/blobs{/sha}",
+ "git_tags_url": "https://api.github.com/repos/lampepfl/dotty/git/tags{/sha}",
+ "git_refs_url": "https://api.github.com/repos/lampepfl/dotty/git/refs{/sha}",
+ "trees_url": "https://api.github.com/repos/lampepfl/dotty/git/trees{/sha}",
+ "statuses_url": "https://api.github.com/repos/lampepfl/dotty/statuses/{sha}",
+ "languages_url": "https://api.github.com/repos/lampepfl/dotty/languages",
+ "stargazers_url": "https://api.github.com/repos/lampepfl/dotty/stargazers",
+ "contributors_url": "https://api.github.com/repos/lampepfl/dotty/contributors",
+ "subscribers_url": "https://api.github.com/repos/lampepfl/dotty/subscribers",
+ "subscription_url": "https://api.github.com/repos/lampepfl/dotty/subscription",
+ "commits_url": "https://api.github.com/repos/lampepfl/dotty/commits{/sha}",
+ "git_commits_url": "https://api.github.com/repos/lampepfl/dotty/git/commits{/sha}",
+ "comments_url": "https://api.github.com/repos/lampepfl/dotty/comments{/number}",
+ "issue_comment_url": "https://api.github.com/repos/lampepfl/dotty/issues/comments{/number}",
+ "contents_url": "https://api.github.com/repos/lampepfl/dotty/contents/{+path}",
+ "compare_url": "https://api.github.com/repos/lampepfl/dotty/compare/{base}...{head}",
+ "merges_url": "https://api.github.com/repos/lampepfl/dotty/merges",
+ "archive_url": "https://api.github.com/repos/lampepfl/dotty/{archive_format}{/ref}",
+ "downloads_url": "https://api.github.com/repos/lampepfl/dotty/downloads",
+ "issues_url": "https://api.github.com/repos/lampepfl/dotty/issues{/number}",
+ "pulls_url": "https://api.github.com/repos/lampepfl/dotty/pulls{/number}",
+ "milestones_url": "https://api.github.com/repos/lampepfl/dotty/milestones{/number}",
+ "notifications_url": "https://api.github.com/repos/lampepfl/dotty/notifications{?since,all,participating}",
+ "labels_url": "https://api.github.com/repos/lampepfl/dotty/labels{/name}",
+ "releases_url": "https://api.github.com/repos/lampepfl/dotty/releases{/id}",
+ "deployments_url": "https://api.github.com/repos/lampepfl/dotty/deployments",
+ "created_at": "2012-12-06T12:57:33Z",
+ "updated_at": "2017-02-05T01:04:39Z",
+ "pushed_at": "2017-02-05T16:50:21Z",
+ "git_url": "git://github.com/lampepfl/dotty.git",
+ "ssh_url": "git@github.com:lampepfl/dotty.git",
+ "clone_url": "https://github.com/lampepfl/dotty.git",
+ "svn_url": "https://github.com/lampepfl/dotty",
+ "homepage": "http://dotty.epfl.ch",
+ "size": 28511,
+ "stargazers_count": 1447,
+ "watchers_count": 1447,
+ "language": "Scala",
+ "has_issues": true,
+ "has_downloads": true,
+ "has_wiki": false,
+ "has_pages": true,
+ "forks_count": 212,
+ "mirror_url": null,
+ "open_issues_count": 243,
+ "forks": 212,
+ "open_issues": 243,
+ "watchers": 1447,
+ "default_branch": "master"
+ },
+ "organization": {
+ "login": "lampepfl",
+ "id": 2684793,
+ "url": "https://api.github.com/orgs/lampepfl",
+ "repos_url": "https://api.github.com/orgs/lampepfl/repos",
+ "events_url": "https://api.github.com/orgs/lampepfl/events",
+ "hooks_url": "https://api.github.com/orgs/lampepfl/hooks",
+ "issues_url": "https://api.github.com/orgs/lampepfl/issues",
+ "members_url": "https://api.github.com/orgs/lampepfl/members{/member}",
+ "public_members_url": "https://api.github.com/orgs/lampepfl/public_members{/member}",
+ "avatar_url": "https://avatars.githubusercontent.com/u/2684793?v=3",
+ "description": null
+ },
+ "sender": {
+ "login": "smarter",
+ "id": 63430,
+ "avatar_url": "https://avatars.githubusercontent.com/u/63430?v=3",
+ "gravatar_id": "",
+ "url": "https://api.github.com/users/smarter",
+ "html_url": "https://github.com/smarter",
+ "followers_url": "https://api.github.com/users/smarter/followers",
+ "following_url": "https://api.github.com/users/smarter/following{/other_user}",
+ "gists_url": "https://api.github.com/users/smarter/gists{/gist_id}",
+ "starred_url": "https://api.github.com/users/smarter/starred{/owner}{/repo}",
+ "subscriptions_url": "https://api.github.com/users/smarter/subscriptions",
+ "organizations_url": "https://api.github.com/users/smarter/orgs",
+ "repos_url": "https://api.github.com/users/smarter/repos",
+ "events_url": "https://api.github.com/users/smarter/events{/privacy}",
+ "received_events_url": "https://api.github.com/users/smarter/received_events",
+ "type": "User",
+ "site_admin": false
+ }
+}
diff --git a/bot/src/dotty/tools/bot/BotServer.scala b/bot/src/dotty/tools/bot/BotServer.scala
new file mode 100644
index 000000000..5ce8a83e7
--- /dev/null
+++ b/bot/src/dotty/tools/bot/BotServer.scala
@@ -0,0 +1,18 @@
+package dotty.tools.bot
+
+import org.http4s.server.{ Server, ServerApp }
+import org.http4s.server.blaze._
+
+import scalaz.concurrent.Task
+
+object Main extends ServerApp with PullRequestService {
+
+ /** Services mounted to the server */
+ final val services = prService
+
+ override def server(args: List[String]): Task[Server] =
+ BlazeBuilder
+ .bindHttp(8080, "localhost")
+ .mountService(services, "/api")
+ .start
+}
diff --git a/bot/src/dotty/tools/bot/PullRequestService.scala b/bot/src/dotty/tools/bot/PullRequestService.scala
new file mode 100644
index 000000000..8b568b134
--- /dev/null
+++ b/bot/src/dotty/tools/bot/PullRequestService.scala
@@ -0,0 +1,120 @@
+package dotty.tools.bot
+
+import org.http4s._
+import org.http4s.client.blaze._
+import org.http4s.client.Client
+
+import scalaz.concurrent.Task
+
+import io.circe._
+import io.circe.generic.auto._
+import io.circe.syntax._
+import org.http4s.circe._
+import org.http4s.dsl._
+
+import github4s.Github
+import github4s.jvm.Implicits._
+import github4s.free.domain.{ Commit, Issue }
+
+trait PullRequestService {
+
+ val prService = HttpService {
+ case request @ POST -> Root =>
+ request.as(jsonOf[Issue]).flatMap(checkPullRequest)
+ }
+
+ private case class CLASignature(
+ user: String,
+ signed: Boolean,
+ version: String,
+ currentVersion: String
+ )
+
+ private case class Status(
+ state: String,
+ target_url: String,
+ description: String,
+ context: String = "continuous-integration/CLA"
+ )
+
+ def claUrl(userName: String): String =
+ s"https://www.lightbend.com/contribute/cla/scala/check/$userName"
+
+ def commitsUrl(prNumber: Int): String =
+ s"https://api.github.com/repos/lampepfl/dotty/pulls/$prNumber/commits"
+
+ def toUri(url: String): Task[Uri] =
+ Uri.fromString(url).fold(Task.fail, Task.now)
+
+ def getRequest(endpoint: Uri): Task[Request] = Task.now {
+ Request(uri = endpoint, method = Method.GET)
+ }
+
+ def postRequest(endpoint: Uri): Task[Request] = Task.now {
+ Request(uri = endpoint, method = Method.POST)
+ }
+
+ def shutdownClient(client: Client): Task[Unit] = Task.now {
+ client.shutdownNow()
+ }
+
+ def users(xs: List[Commit]): Task[Set[String]] = Task.now {
+ xs.map(_.login).flatten.toSet
+ }
+
+ sealed trait CommitStatus {
+ def commit: Commit
+ def isValid: Boolean
+ }
+ final case class Valid(commit: Commit) extends CommitStatus { def isValid = true }
+ final case class Invalid(commit: Commit) extends CommitStatus { def isValid = false }
+
+ /** Partitions invalid and valid commits */
+ def checkCLA(xs: List[Commit], httpClient: Client): Task[List[CommitStatus]] = {
+ def checkUser(commit: Commit): Task[CommitStatus] = for {
+ endpoint <- toUri(claUrl(commit.login.get))
+ claReq <- getRequest(endpoint)
+ claRes <- httpClient.expect(claReq)(jsonOf[CLASignature])
+ res = if (claRes.signed) Valid(commit) else Invalid(commit)
+ } yield res
+
+ Task.gatherUnordered(xs.filter(_.login.isDefined).map(checkUser))
+ }
+
+ def sendStatuses(xs: List[CommitStatus], httpClient: Client): Task[Unit] = {
+ def setStatus(cm: CommitStatus): Task[Unit] = for {
+ endpoint <- toUri(cm.commit.url.replaceAll("git\\/commits", "statuses"))
+
+ target = claUrl(cm.commit.login.getOrElse("<invalid-user>"))
+ state = if (cm.isValid) "success" else "failure"
+ desc =
+ if (cm.isValid) "User signed CLA"
+ else "User needs to sign cla: https://www.lightbend.com/contribute/cla/scala"
+
+ statusReq <- postRequest(endpoint).map(_.withBody(Status(state, target, desc).asJson))
+ statusRes <- httpClient.expect(statusReq)(jsonOf[String])
+ print <- Task.now(println(statusRes))
+ } yield print
+
+ Task.gatherUnordered(xs.map(setStatus)).map(_ => ())
+ }
+
+ def checkPullRequest(issue: Issue): Task[Response] = {
+ val httpClient = PooledHttp1Client()
+
+ for {
+ // First get all the commits from the PR
+ endpoint <- toUri(commitsUrl(issue.number))
+ commitsReq <- getRequest(endpoint)
+ commitsRes <- httpClient.expect(commitsReq)(jsonOf[List[Commit]])
+
+ // Then get check the CLA of each commit
+ statuses <- checkCLA(commitsRes, httpClient)
+
+ // Send statuses to Github and exit
+ _ <- sendStatuses(statuses, httpClient)
+ _ <- shutdownClient(httpClient)
+ resp <- Ok("All statuses checked")
+ } yield resp
+ }
+}