Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Add towncrier changelog entries to a PR. #95

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 9,4 @@ build/
dist/
doc/api/
doc/_build/
.tox/
6 changes: 5 additions & 1 deletion baldrick/blueprints/github.py
Original file line number Diff line number Diff line change
@@ -1,6 1,7 @@
import json

from flask import Blueprint, request
from loguru import logger

from baldrick.github.github_api import RepoHandler

Expand Down Expand Up @@ -29,13 30,16 @@ def github_webhook_handler(func):
def github_webhook():

if not request.data:
logger.trace("No payload received")
return "No payload received"

# Parse the JSON sent by GitHub
payload = json.loads(request.data)

if 'installation' not in payload:
return "No installation key found in payload"
msg = "No installation key found in payload"
logger.trace(msg)
return msg
else:
installation = payload['installation']['id']

Expand Down
56 changes: 56 additions & 0 deletions baldrick/github/github_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 322,62 @@ def get_all_labels(self):
return [label['name'] for label in result]


def create_file(self, path, message, content, branch, *, committer=None, author=None):
"""
Create a new file in the repo.

Parameters
----------
path : `str`
The path to create the file at, from the base of the repo.

message : `str`
The commit message to be used with this commit.

content : `str` or `bytes`
The base64 encoded content to be added to the commit. If the type
`bytes` it is assumed to be already base64 encoded. If the type is
string it will be assumed to be a utf-8 string and encoded to bytes
and then base64.

branch : `str`
The branch to add the file to.

committer : `dict`, optional
The committer for the commit, should be a dict of ``{'name': str,
'email': str}``. Will default to the bot user.

author : `dict`, optional
The author for the commit, should be a dict of ``{'name': str,
'email': str}``. Will default to the bot user.

Returns
-------
sha : `str`
The commit hash that was created.

"""
if isinstance(content, str):
content = base64.b64encode(content.encode("utf-8"))

url = f"{HOST}/repos/{self.repo}/contents/{path}"
data = {
"message": message,
"content": content,
"branch": branch,
}

if committer:
data["committer"] = committer

if author:
data["author"] = author

resp = requests.put(url, data=data)

return resp["commit"]


class IssueHandler(GitHubHandler):

def __init__(self, repo, number, installation=None):
Expand Down
71 changes: 71 additions & 0 deletions baldrick/plugins/github_comment_matcher.py
Original file line number Diff line number Diff line change
@@ -0,0 1,71 @@
"""
A plugin which reacts to comments on PRs and can add a changelog.
"""
import re
from loguru import logger

from baldrick.github.github_api import PullRequestHandler, IssueHandler
from baldrick.blueprints.github import github_webhook_handler

ISSUE_COMMENT_MATCHERS = {}
PR_COMMENT_MATCHERS = {}


@github_webhook_handler
def handle_issue_comments(repo_handler, payload, headers):

event = headers['X-GitHub-Event']
if event != 'issue_comment':
return "Not a pull_request or issues event"

number = payload['issue']['number']

issue_handler = IssueHandler(repo_handler.repo, number, repo_handler.installation)
if 'pull_request' in payload['issue']:
issue_handler = PullRequestHandler(repo_handler.repo, number, repo_handler.installation)

process_issue_comment(issue_handler, repo_handler, payload['comment']['body'], payload)


def match_issue_comment(regex, issue_type="both"):
"""
A decorator used to call a plugin on a issue comment.

Parameters
----------
regex : `str`
The regex to apply to the comment.

issue_type : `str` {'issue', 'pull', 'both'}
The type of issue to match on. Defaults to 'both' to match comments on issues and PRs.
"""
def wrapper(func):
if issue_type in ('issue', 'both'):
ISSUE_COMMENT_MATCHERS[regex] = func
return func
if issue_type in ('pull', 'both'):
PR_COMMENT_MATCHERS[regex] = func
return func

raise ValueError("issue_type must be one one 'issue', 'pull' or 'both'")

return wrapper


def process_issue_comment(issue_handler, repo_handler, comment, payload):
logger.trace(f"Processing comment {comment} on {issue_handler.repo}#{issue_handler.number}.")

if 'pull_request' in payload['issue']:
for expression, func in PR_COMMENT_MATCHERS.items():
logger.trace(f"Testing {expression} for pull_request.")
match = re.search(expression, comment, False)
if match:
logger.trace(f"{expression} matched calling {func}")
func(issue_handler, repo_handler, comment, payload)
else:
for expression, func in ISSUE_COMMENT_MATCHERS.items():
logger.trace(f"Testing {expression} for issue.")
match = re.search(expression, comment, False)
if match:
logger.trace(f"{expression} matched calling {func}")
func(issue_handler, repo_handler, comment, payload)
43 changes: 1 addition & 42 deletions baldrick/plugins/github_towncrier_changelog.py
Original file line number Diff line number Diff line change
@@ -1,53 1,12 @@
import os
import re
from collections import OrderedDict

from loguru import logger
from toml import loads
from towncrier._settings import parse_toml

from .github_pull_requests import pull_request_handler

try:
from towncrier._settings import parse_toml
except ImportError: # pragma: nocover
from towncrier._settings import _template_fname, _start_string, _title_format, _underlines, _default_types

def parse_toml(config): # pragma: nocover
if 'tool' not in config:
raise ValueError("No [tool.towncrier] section.")

config = config['tool']['towncrier']

sections = OrderedDict()
types = OrderedDict()

if "section" in config:
for x in config["section"]:
sections[x.get('name', '')] = x['path']
else:
sections[''] = ''

if "type" in config:
for x in config["type"]:
types[x["directory"]] = {"name": x["name"],
"showcontent": x["showcontent"]}
else:
types = _default_types

return {
'package': config.get('package', ''),
'package_dir': config.get('package_dir', '.'),
'filename': config.get('filename', 'NEWS.rst'),
'directory': config.get('directory'),
'sections': sections,
'types': types,
'template': config.get('template', _template_fname),
'start_line': config.get('start_string', _start_string),
'title_format': config.get('title_format', _title_format),
'issue_format': config.get('issue_format'),
'underlines': config.get('underlines', _underlines)
}


def calculate_fragment_paths(config):

Expand Down
1 change: 1 addition & 0 deletions baldrick/plugins/tests/issue_comment_payload.json
Original file line number Diff line number Diff line change
@@ -0,0 1 @@
{"action": "created", "issue": {"url": "https://api.github.com/repos/Cadair/testgiles/issues/15", "repository_url": "https://api.github.com/repos/Cadair/testgiles", "labels_url": "https://api.github.com/repos/Cadair/testgiles/issues/15/labels{/name}", "comments_url": "https://api.github.com/repos/Cadair/testgiles/issues/15/comments", "events_url": "https://api.github.com/repos/Cadair/testgiles/issues/15/events", "html_url": "https://github.com/Cadair/testgiles/pull/15", "id": 549197525, "node_id": "MDExOlB1bGxSZXF1ZXN0MzYyMzM0ODkw", "number": 15, "title": "Update README.md", "user": {"login": "Cadair", "id": 1391051, "node_id": "MDQ6VXNlcjEzOTEwNTE=", "avatar_url": "https://avatars2.githubusercontent.com/u/1391051?v=4", "gravatar_id": "", "url": "https://api.github.com/users/Cadair", "html_url": "https://github.com/Cadair", "followers_url": "https://api.github.com/users/Cadair/followers", "following_url": "https://api.github.com/users/Cadair/following{/other_user}", "gists_url": "https://api.github.com/users/Cadair/gists{/gist_id}", "starred_url": "https://api.github.com/users/Cadair/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/Cadair/subscriptions", "organizations_url": "https://api.github.com/users/Cadair/orgs", "repos_url": "https://api.github.com/users/Cadair/repos", "events_url": "https://api.github.com/users/Cadair/events{/privacy}", "received_events_url": "https://api.github.com/users/Cadair/received_events", "type": "User", "site_admin": false}, "labels": [], "state": "open", "locked": false, "assignee": null, "assignees": [], "milestone": null, "comments": 11, "created_at": "2020-01-13T21:40:45Z", "updated_at": "2020-01-14T17:43:03Z", "closed_at": null, "author_association": "OWNER", "pull_request": {"url": "https://api.github.com/repos/Cadair/testgiles/pulls/15", "html_url": "https://github.com/Cadair/testgiles/pull/15", "diff_url": "https://github.com/Cadair/testgiles/pull/15.diff", "patch_url": "https://github.com/Cadair/testgiles/pull/15.patch"}, "body": ""}, "comment": {"url": "https://api.github.com/repos/Cadair/testgiles/issues/comments/574291511", "html_url": "https://github.com/Cadair/testgiles/pull/15#issuecomment-574291511", "issue_url": "https://api.github.com/repos/Cadair/testgiles/issues/15", "id": 574291511, "node_id": "MDEyOklzc3VlQ29tbWVudDU3NDI5MTUxMQ==", "user": {"login": "Cadair", "id": 1391051, "node_id": "MDQ6VXNlcjEzOTEwNTE=", "avatar_url": "https://avatars2.githubusercontent.com/u/1391051?v=4", "gravatar_id": "", "url": "https://api.github.com/users/Cadair", "html_url": "https://github.com/Cadair", "followers_url": "https://api.github.com/users/Cadair/followers", "following_url": "https://api.github.com/users/Cadair/following{/other_user}", "gists_url": "https://api.github.com/users/Cadair/gists{/gist_id}", "starred_url": "https://api.github.com/users/Cadair/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/Cadair/subscriptions", "organizations_url": "https://api.github.com/users/Cadair/orgs", "repos_url": "https://api.github.com/users/Cadair/repos", "events_url": "https://api.github.com/users/Cadair/events{/privacy}", "received_events_url": "https://api.github.com/users/Cadair/received_events", "type": "User", "site_admin": false}, "created_at": "2020-01-14T17:43:03Z", "updated_at": "2020-01-14T17:43:03Z", "author_association": "OWNER", "body": "pr?"}, "repository": {"id": 160889443, "node_id": "MDEwOlJlcG9zaXRvcnkxNjA4ODk0NDM=", "name": "testgiles", "full_name": "Cadair/testgiles", "private": false, "owner": {"login": "Cadair", "id": 1391051, "node_id": "MDQ6VXNlcjEzOTEwNTE=", "avatar_url": "https://avatars2.githubusercontent.com/u/1391051?v=4", "gravatar_id": "", "url": "https://api.github.com/users/Cadair", "html_url": "https://github.com/Cadair", "followers_url": "https://api.github.com/users/Cadair/followers", "following_url": "https://api.github.com/users/Cadair/following{/other_user}", "gists_url": "https://api.github.com/users/Cadair/gists{/gist_id}", "starred_url": "https://api.github.com/users/Cadair/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/Cadair/subscriptions", "organizations_url": "https://api.github.com/users/Cadair/orgs", "repos_url": "https://api.github.com/users/Cadair/repos", "events_url": "https://api.github.com/users/Cadair/events{/privacy}", "received_events_url": "https://api.github.com/users/Cadair/received_events", "type": "User", "site_admin": false}, "html_url": "https://github.com/Cadair/testgiles", "description": "A repo for testing giles", "fork": false, "url": "https://api.github.com/repos/Cadair/testgiles", "forks_url": "https://api.github.com/repos/Cadair/testgiles/forks", "keys_url": "https://api.github.com/repos/Cadair/testgiles/keys{/key_id}", "collaborators_url": "https://api.github.com/repos/Cadair/testgiles/collaborators{/collaborator}", "teams_url": "https://api.github.com/repos/Cadair/testgiles/teams", "hooks_url": "https://api.github.com/repos/Cadair/testgiles/hooks", "issue_events_url": "https://api.github.com/repos/Cadair/testgiles/issues/events{/number}", "events_url": "https://api.github.com/repos/Cadair/testgiles/events", "assignees_url": "https://api.github.com/repos/Cadair/testgiles/assignees{/user}", "branches_url": "https://api.github.com/repos/Cadair/testgiles/branches{/branch}", "tags_url": "https://api.github.com/repos/Cadair/testgiles/tags", "blobs_url": "https://api.github.com/repos/Cadair/testgiles/git/blobs{/sha}", "git_tags_url": "https://api.github.com/repos/Cadair/testgiles/git/tags{/sha}", "git_refs_url": "https://api.github.com/repos/Cadair/testgiles/git/refs{/sha}", "trees_url": "https://api.github.com/repos/Cadair/testgiles/git/trees{/sha}", "statuses_url": "https://api.github.com/repos/Cadair/testgiles/statuses/{sha}", "languages_url": "https://api.github.com/repos/Cadair/testgiles/languages", "stargazers_url": "https://api.github.com/repos/Cadair/testgiles/stargazers", "contributors_url": "https://api.github.com/repos/Cadair/testgiles/contributors", "subscribers_url": "https://api.github.com/repos/Cadair/testgiles/subscribers", "subscription_url": "https://api.github.com/repos/Cadair/testgiles/subscription", "commits_url": "https://api.github.com/repos/Cadair/testgiles/commits{/sha}", "git_commits_url": "https://api.github.com/repos/Cadair/testgiles/git/commits{/sha}", "comments_url": "https://api.github.com/repos/Cadair/testgiles/comments{/number}", "issue_comment_url": "https://api.github.com/repos/Cadair/testgiles/issues/comments{/number}", "contents_url": "https://api.github.com/repos/Cadair/testgiles/contents/{ path}", "compare_url": "https://api.github.com/repos/Cadair/testgiles/compare/{base}...{head}", "merges_url": "https://api.github.com/repos/Cadair/testgiles/merges", "archive_url": "https://api.github.com/repos/Cadair/testgiles/{archive_format}{/ref}", "downloads_url": "https://api.github.com/repos/Cadair/testgiles/downloads", "issues_url": "https://api.github.com/repos/Cadair/testgiles/issues{/number}", "pulls_url": "https://api.github.com/repos/Cadair/testgiles/pulls{/number}", "milestones_url": "https://api.github.com/repos/Cadair/testgiles/milestones{/number}", "notifications_url": "https://api.github.com/repos/Cadair/testgiles/notifications{?since,all,participating}", "labels_url": "https://api.github.com/repos/Cadair/testgiles/labels{/name}", "releases_url": "https://api.github.com/repos/Cadair/testgiles/releases{/id}", "deployments_url": "https://api.github.com/repos/Cadair/testgiles/deployments", "created_at": "2018-12-08T00:33:47Z", "updated_at": "2020-01-13T21:44:09Z", "pushed_at": "2020-01-13T21:44:07Z", "git_url": "git://github.com/Cadair/testgiles.git", "ssh_url": "[email protected]:Cadair/testgiles.git", "clone_url": "https://github.com/Cadair/testgiles.git", "svn_url": "https://github.com/Cadair/testgiles", "homepage": null, "size": 28, "stargazers_count": 0, "watchers_count": 0, "language": null, "has_issues": true, "has_projects": true, "has_downloads": true, "has_wiki": true, "has_pages": false, "forks_count": 0, "mirror_url": null, "archived": false, "disabled": false, "open_issues_count": 6, "license": null, "forks": 0, "open_issues": 6, "watchers": 0, "default_branch": "master"}, "sender": {"login": "Cadair", "id": 1391051, "node_id": "MDQ6VXNlcjEzOTEwNTE=", "avatar_url": "https://avatars2.githubusercontent.com/u/1391051?v=4", "gravatar_id": "", "url": "https://api.github.com/users/Cadair", "html_url": "https://github.com/Cadair", "followers_url": "https://api.github.com/users/Cadair/followers", "following_url": "https://api.github.com/users/Cadair/following{/other_user}", "gists_url": "https://api.github.com/users/Cadair/gists{/gist_id}", "starred_url": "https://api.github.com/users/Cadair/starred{/owner}{/repo}", "subscriptions_url": "https://api.github.com/users/Cadair/subscriptions", "organizations_url": "https://api.github.com/users/Cadair/orgs", "repos_url": "https://api.github.com/users/Cadair/repos", "events_url": "https://api.github.com/users/Cadair/events{/privacy}", "received_events_url": "https://api.github.com/users/Cadair/received_events", "type": "User", "site_admin": false}, "installation": {"id": 101055, "node_id": "MDIzOkludGVncmF0aW9uSW5zdGFsbGF0aW9uMTAxMDU1"}}
52 changes: 52 additions & 0 deletions baldrick/plugins/tests/test_add_changelog_towncrier.py
Original file line number Diff line number Diff line change
@@ -0,0 1,52 @@
import json
from pathlib import Path
from unittest.mock import patch, PropertyMock

from baldrick.github.github_api import FILE_CACHE
from baldrick.github.github_api import RepoHandler, PullRequestHandler
from baldrick.plugins.github_comment_matcher import handle_issue_comments
from baldrick.plugins.towncrier_add_entry import add_changelog_entry


CONFIG_TEMPLATE = """
[ tool.testbot ]
[ tool.testbot.issue_comments ]
enabled=true
"""


class TestAddChangelogPlugin:

def setup_method(self, method):

self.get_file_contents_mock = patch('baldrick.github.github_api.PullRequestHandler.get_file_contents')
self.get_base_branch_mock = patch('baldrick.github.github_api.PullRequestHandler.base_branch')
a = self.get_base_branch_mock.start()
a.return_value = "master"

self.repo_handler = RepoHandler("nota/repo", "1234")
self.pr_handler = PullRequestHandler("nota/repo", "1234")

self.get_file_contents = self.get_file_contents_mock.start()
FILE_CACHE.clear()

def teardown_method(self, method):
self.get_file_contents_mock.stop()
self.get_base_branch_mock.stop()

@property
def issue_comment_payload(self):
with open(Path(__file__).parent / 'issue_comment_payload.json') as fobj:
return json.loads(fobj.read())

def test_handle_issue_comments(self, app):
handle_issue_comments(self.repo_handler, self.issue_comment_payload,
{'X-GitHub-Event': 'issue_comment'})

def test_milestone_present(self, app):

self.get_file_contents.return_value = CONFIG_TEMPLATE.format(missing="missing milestone",
present="milestone present")
with app.app_context():
ret = add_changelog_entry(self.pr_handler, self.repo_handler, "hello", None)

8 changes: 8 additions & 0 deletions baldrick/plugins/towncrier_add_entry.py
Original file line number Diff line number Diff line change
@@ -0,0 1,8 @@
from loguru import logger

from baldrick.plugins.github_comment_matcher import match_issue_comment


@match_issue_comment(".*", issue_type="pull")
def add_changelog_entry(pr_handler, repo_handler, comment, payload):
logger.debug(f"Adding changelog entry to {pr_handler.repo}#{pr_handler.number}")
14 changes: 14 additions & 0 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -0,0 1,14 @@
[tox]
envlist = py{36,37,38},build_docs

[testenv]
extras = test
setenv =
HOME = {envtmpdir}
commands =
pytest . {posargs}

[testenv:build_docs]
changedir = {toxinidir}
extras = docs
commands = sphinx-build docs docs/_build/html -W -b html -d docs/_build/.doctrees {posargs}