mirror of
https://github.com/odoo/runbot.git
synced 2025-03-15 23:45:44 +07:00
[ADD] runbot_merge: reviews support
Reviews are interpreted like comments and can contain any number of commands, with the difference that APPROVED and REQUEST_CHANGES are interpreted as (respectively) r+ and r- prefixes.
This commit is contained in:
parent
49c8fdbed2
commit
7033952913
@ -10,7 +10,11 @@ class MergebotController(Controller):
|
||||
def index(self):
|
||||
event = request.httprequest.headers['X-Github-Event']
|
||||
|
||||
return EVENTS.get(event, lambda _: "Unknown event {}".format(event))(request.jsonrequest)
|
||||
c = EVENTS.get(event)
|
||||
if c:
|
||||
return c(request.jsonrequest)
|
||||
_logger.warn('Unknown event %s', event)
|
||||
return 'Unknown event {}'.format(event)
|
||||
|
||||
def handle_pr(event):
|
||||
if event['action'] in [
|
||||
@ -191,14 +195,36 @@ def handle_comment(event):
|
||||
|
||||
return pr._parse_commands(partner, event['comment']['body'])
|
||||
|
||||
def handle_review(event):
|
||||
env = request.env(user=1)
|
||||
|
||||
partner = env['res.partner'].search([('github_login', '=', event['review']['user']['login'])])
|
||||
if not partner:
|
||||
_logger.info('ignoring comment from %s: not in system', event['review']['user']['login'])
|
||||
return 'ignored'
|
||||
|
||||
pr = env['runbot_merge.pull_requests'].search([
|
||||
('number', '=', event['pull_request']['number']),
|
||||
('repository.name', '=', event['repository']['full_name'])
|
||||
])
|
||||
|
||||
firstline = ''
|
||||
state = event['review']['state'].lower()
|
||||
if state == 'approved':
|
||||
firstline = pr.repository.project_id.github_prefix + ' r+\n'
|
||||
elif state == 'request_changes':
|
||||
firstline = pr.repository.project_id.github_prefix + ' r-\n'
|
||||
|
||||
return pr._parse_commands(partner, firstline + event['review']['body'])
|
||||
|
||||
def handle_ping(event):
|
||||
print("Got ping! {}".format(event['zen']))
|
||||
return "pong"
|
||||
|
||||
EVENTS = {
|
||||
# TODO: add review?
|
||||
'pull_request': handle_pr,
|
||||
'status': handle_status,
|
||||
'issue_comment': handle_comment,
|
||||
'pull_request_review': handle_review,
|
||||
'ping': handle_ping,
|
||||
}
|
||||
|
@ -327,9 +327,9 @@ class Repo(object):
|
||||
pr.base = body.get('base')
|
||||
|
||||
if body.get('state') == 'open':
|
||||
self.notify('pull_request', 'reopened', self.name, pr)
|
||||
self.notify('pull_request', 'reopened', pr)
|
||||
elif body.get('state') == 'closed':
|
||||
self.notify('pull_request', 'closed', self.name, pr)
|
||||
self.notify('pull_request', 'closed', pr)
|
||||
|
||||
return (200, {})
|
||||
|
||||
@ -434,20 +434,20 @@ class PR(Issue):
|
||||
self.label = label
|
||||
self.state = 'open'
|
||||
|
||||
repo.notify('pull_request', 'opened', repo.name, self)
|
||||
repo.notify('pull_request', 'opened', self)
|
||||
|
||||
@Issue.title.setter
|
||||
def title(self, value):
|
||||
old = self.title
|
||||
Issue.title.fset(self, value)
|
||||
self.repo.notify('pull_request', 'edited', self.repo.name, self, {
|
||||
self.repo.notify('pull_request', 'edited', self, {
|
||||
'title': {'from': old}
|
||||
})
|
||||
@Issue.body.setter
|
||||
def body(self, value):
|
||||
old = self.body
|
||||
Issue.body.fset(self, value)
|
||||
self.repo.notify('pull_request', 'edited', self.repo.name, self, {
|
||||
self.repo.notify('pull_request', 'edited', self, {
|
||||
'body': {'from': old}
|
||||
})
|
||||
@property
|
||||
@ -456,22 +456,22 @@ class PR(Issue):
|
||||
@base.setter
|
||||
def base(self, value):
|
||||
old, self._base = self._base, value
|
||||
self.repo.notify('pull_request', 'edited', self.repo.name, self, {
|
||||
self.repo.notify('pull_request', 'edited', self, {
|
||||
'base': {'from': {'ref': old}}
|
||||
})
|
||||
|
||||
def push(self, sha):
|
||||
self.head = sha
|
||||
self.repo.notify('pull_request', 'synchronize', self.repo.name, self)
|
||||
self.repo.notify('pull_request', 'synchronize', self)
|
||||
|
||||
def open(self):
|
||||
assert self.state == 'closed'
|
||||
self.state = 'open'
|
||||
self.repo.notify('pull_request', 'reopened', self.repo.name, self)
|
||||
self.repo.notify('pull_request', 'reopened', self)
|
||||
|
||||
def close(self):
|
||||
self.state = 'closed'
|
||||
self.repo.notify('pull_request', 'closed', self.repo.name, self)
|
||||
self.repo.notify('pull_request', 'closed', self)
|
||||
|
||||
@property
|
||||
def commits(self):
|
||||
@ -480,6 +480,10 @@ class PR(Issue):
|
||||
return len({h for h, _ in git.walk_ancestors(store, self.head, False)}
|
||||
- {h for h, _ in git.walk_ancestors(store, target, False)})
|
||||
|
||||
def post_review(self, state, user, body):
|
||||
self.comments.append((user, "REVIEW %s\n\n%s " % (state, body)))
|
||||
self.repo.notify('pull_request_review', state, self, user, body)
|
||||
|
||||
class Commit(object):
|
||||
__slots__ = ['tree', 'message', 'author', 'committer', 'parents', 'statuses']
|
||||
def __init__(self, tree, message, author, committer, parents):
|
||||
@ -524,33 +528,39 @@ class Client(werkzeug.test.Client):
|
||||
data=json.dumps(data),
|
||||
)
|
||||
|
||||
def pull_request(self, action, repository, pr, changes=None):
|
||||
def pull_request(self, action, pr, changes=None):
|
||||
assert action in ('opened', 'reopened', 'closed', 'synchronize', 'edited')
|
||||
return self.open(self._make_env(
|
||||
'pull_request', {
|
||||
'action': action,
|
||||
'pull_request': {
|
||||
'number': pr.number,
|
||||
'head': {
|
||||
'sha': pr.head,
|
||||
'label': pr.label,
|
||||
},
|
||||
'base': {
|
||||
'ref': pr.base,
|
||||
'repo': {
|
||||
'name': repository.split('/')[1],
|
||||
'full_name': repository,
|
||||
},
|
||||
},
|
||||
'title': pr.title,
|
||||
'body': pr.body,
|
||||
'commits': pr.commits,
|
||||
'user': { 'login': pr.user },
|
||||
},
|
||||
'pull_request': self._pr(pr),
|
||||
**({'changes': changes} if changes else {})
|
||||
}
|
||||
))
|
||||
|
||||
def pull_request_review(self, action, pr, user, body):
|
||||
"""
|
||||
:type action: 'APPROVED' | 'REQUEST_CHANGES' | 'COMMENT'
|
||||
:type pr: PR
|
||||
:type user: str
|
||||
:type body: str
|
||||
"""
|
||||
assert action in ('APPROVED', 'REQUEST_CHANGES', 'COMMENT')
|
||||
return self.open(self._make_env(
|
||||
'pull_request_review', {
|
||||
'review': {
|
||||
'state': action,
|
||||
'body': body,
|
||||
'user': {'login': user},
|
||||
},
|
||||
'pull_request': self._pr(pr),
|
||||
'repository': {
|
||||
'name': pr.repo.name.split('/')[1],
|
||||
'full_name': pr.repo.name,
|
||||
}
|
||||
}
|
||||
))
|
||||
|
||||
def status(self, repository, context, state, sha):
|
||||
assert state in ('success', 'failure', 'pending')
|
||||
return self.open(self._make_env(
|
||||
@ -573,3 +583,26 @@ class Client(werkzeug.test.Client):
|
||||
if isinstance(issue, PR):
|
||||
contents['issue']['pull_request'] = { 'url': 'fake' }
|
||||
return self.open(self._make_env('issue_comment', contents))
|
||||
|
||||
def _pr(self, pr):
|
||||
"""
|
||||
:type pr: PR
|
||||
"""
|
||||
return {
|
||||
'number': pr.number,
|
||||
'head': {
|
||||
'sha': pr.head,
|
||||
'label': pr.label,
|
||||
},
|
||||
'base': {
|
||||
'ref': pr.base,
|
||||
'repo': {
|
||||
'name': pr.repo.name.split('/')[1],
|
||||
'full_name': pr.repo.name,
|
||||
},
|
||||
},
|
||||
'title': pr.title,
|
||||
'body': pr.body,
|
||||
'commits': pr.commits,
|
||||
'user': {'login': pr.user},
|
||||
}
|
||||
|
@ -32,7 +32,9 @@ def repo(gh, env):
|
||||
})
|
||||
# need to create repo & branch in env so hook will allow processing them
|
||||
return gh.repo('odoo/odoo', hooks=[
|
||||
((odoo.http.root, '/runbot_merge/hooks'), ['pull_request', 'issue_comment', 'status'])
|
||||
((odoo.http.root, '/runbot_merge/hooks'), [
|
||||
'pull_request', 'issue_comment', 'status', 'pull_request_review',
|
||||
])
|
||||
])
|
||||
|
||||
def test_trivial_flow(env, repo):
|
||||
@ -1120,3 +1122,23 @@ class TestReviewing(object):
|
||||
('repository.name', '=', 'odoo/odoo'),
|
||||
('number', '=', prx.number)
|
||||
]).state == 'ready'
|
||||
|
||||
def test_actual_review(self, env, repo):
|
||||
m = repo.make_commit(None, 'initial', None, tree={'m': 'm'})
|
||||
m2 = repo.make_commit(m, 'second', None, tree={'m': 'm', 'm2': 'm2'})
|
||||
repo.make_ref('heads/master', m2)
|
||||
|
||||
c1 = repo.make_commit(m, 'first', None, tree={'m': 'c1'})
|
||||
prx = repo.make_pr('title', 'body', target='master', ctid=c1, user='user')
|
||||
pr = env['runbot_merge.pull_requests'].search([
|
||||
('repository.name', '=', 'odoo/odoo'),
|
||||
('number', '=', prx.number)
|
||||
])
|
||||
|
||||
prx.post_review('COMMENT', 'reviewer', "hansen priority=1")
|
||||
assert pr.priority == 1
|
||||
assert pr.state == 'opened'
|
||||
|
||||
prx.post_review('APPROVED', 'reviewer', "hansen priority=2")
|
||||
assert pr.priority == 2
|
||||
assert pr.state == 'approved'
|
||||
|
@ -35,7 +35,7 @@ def project(env):
|
||||
def repo_a(gh, project):
|
||||
project.write({'repo_ids': [(0, 0, {'name': "odoo/a"})]})
|
||||
return gh.repo('odoo/a', hooks=[
|
||||
((odoo.http.root, '/runbot_merge/hooks'), ['pull_request', 'issue_comment', 'status'])
|
||||
((odoo.http.root, '/runbot_merge/hooks'), ['pull_request', 'issue_comment', 'status', 'pull_request_review'])
|
||||
])
|
||||
|
||||
@pytest.fixture
|
||||
|
Loading…
Reference in New Issue
Block a user