From 3f06fe9cc2debaacbb889e33c7339457fc5355cd Mon Sep 17 00:00:00 2001 From: Felix Mulder Date: Mon, 6 Feb 2017 17:18:26 +0100 Subject: 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 --- bot/resources/test-pr.json | 432 +++++++++++++++++++++++ bot/src/dotty/tools/bot/BotServer.scala | 18 + bot/src/dotty/tools/bot/PullRequestService.scala | 120 +++++++ 3 files changed, 570 insertions(+) create mode 100644 bot/resources/test-pr.json create mode 100644 bot/src/dotty/tools/bot/BotServer.scala create mode 100644 bot/src/dotty/tools/bot/PullRequestService.scala (limited to 'bot') 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("")) + 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 + } +} -- cgit v1.2.3