mirror of
https://github.com/odoo/runbot.git
synced 2025-03-27 13:25:47 +07:00
[IMP] *: review mergebot & forwardbot messages for pinging
Old messages were quite inconsistent in their pinging of the PR author and reviewer. Reviewed messages (probably missed some but...) and try to more consistently ping when the feedback requires some sort of action in order to proceed. Fixes #592
This commit is contained in:
parent
4a3cde2faa
commit
f430c014c1
11
conftest.py
11
conftest.py
@ -582,17 +582,6 @@ class Repo:
|
|||||||
parents=[p['sha'] for p in gh_commit['parents']],
|
parents=[p['sha'] for p in gh_commit['parents']],
|
||||||
)
|
)
|
||||||
|
|
||||||
def log(self, ref_or_sha):
|
|
||||||
for page in itertools.count(1):
|
|
||||||
r = self._session.get(
|
|
||||||
'https://api.github.com/repos/{}/commits'.format(self.name),
|
|
||||||
params={'sha': ref_or_sha, 'page': page}
|
|
||||||
)
|
|
||||||
assert 200 <= r.status_code < 300, r.json()
|
|
||||||
yield from map(self._commit_from_gh, r.json())
|
|
||||||
if not r.links.get('next'):
|
|
||||||
return
|
|
||||||
|
|
||||||
def read_tree(self, commit):
|
def read_tree(self, commit):
|
||||||
""" read tree object from commit
|
""" read tree object from commit
|
||||||
|
|
||||||
|
@ -79,12 +79,12 @@ class ForwardPortTasks(models.Model, Queue):
|
|||||||
batch.active = False
|
batch.active = False
|
||||||
|
|
||||||
|
|
||||||
CONFLICT_TEMPLATE = "WARNING: the latest change ({previous.head}) triggered " \
|
CONFLICT_TEMPLATE = "{ping}WARNING: the latest change ({previous.head}) triggered " \
|
||||||
"a conflict when updating the next forward-port " \
|
"a conflict when updating the next forward-port " \
|
||||||
"({next.display_name}), and has been ignored.\n\n" \
|
"({next.display_name}), and has been ignored.\n\n" \
|
||||||
"You will need to update this pull request differently, " \
|
"You will need to update this pull request differently, " \
|
||||||
"or fix the issue by hand on {next.display_name}."
|
"or fix the issue by hand on {next.display_name}."
|
||||||
CHILD_CONFLICT = "WARNING: the update of {previous.display_name} to " \
|
CHILD_CONFLICT = "{ping}WARNING: the update of {previous.display_name} to " \
|
||||||
"{previous.head} has caused a conflict in this pull request, " \
|
"{previous.head} has caused a conflict in this pull request, " \
|
||||||
"data may have been lost."
|
"data may have been lost."
|
||||||
class UpdateQueue(models.Model, Queue):
|
class UpdateQueue(models.Model, Queue):
|
||||||
@ -118,14 +118,15 @@ class UpdateQueue(models.Model, Queue):
|
|||||||
Feedback.create({
|
Feedback.create({
|
||||||
'repository': child.repository.id,
|
'repository': child.repository.id,
|
||||||
'pull_request': child.number,
|
'pull_request': child.number,
|
||||||
'message': "Ancestor PR %s has been updated but this PR"
|
'message': "%sancestor PR %s has been updated but this PR"
|
||||||
" is %s and can't be updated to match."
|
" is %s and can't be updated to match."
|
||||||
"\n\n"
|
"\n\n"
|
||||||
"You may want or need to manually update any"
|
"You may want or need to manually update any"
|
||||||
" followup PR." % (
|
" followup PR." % (
|
||||||
self.new_root.display_name,
|
child.ping(),
|
||||||
child.state,
|
self.new_root.display_name,
|
||||||
)
|
child.state,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -137,6 +138,7 @@ class UpdateQueue(models.Model, Queue):
|
|||||||
'repository': previous.repository.id,
|
'repository': previous.repository.id,
|
||||||
'pull_request': previous.number,
|
'pull_request': previous.number,
|
||||||
'message': CONFLICT_TEMPLATE.format(
|
'message': CONFLICT_TEMPLATE.format(
|
||||||
|
ping=previous.ping(),
|
||||||
previous=previous,
|
previous=previous,
|
||||||
next=child
|
next=child
|
||||||
)
|
)
|
||||||
@ -144,7 +146,7 @@ class UpdateQueue(models.Model, Queue):
|
|||||||
Feedback.create({
|
Feedback.create({
|
||||||
'repository': child.repository.id,
|
'repository': child.repository.id,
|
||||||
'pull_request': child.number,
|
'pull_request': child.number,
|
||||||
'message': CHILD_CONFLICT.format(previous=previous, next=child)\
|
'message': CHILD_CONFLICT.format(ping=child.ping(), previous=previous, next=child)\
|
||||||
+ (f'\n\nstdout:\n```\n{out.strip()}\n```' if out.strip() else '')
|
+ (f'\n\nstdout:\n```\n{out.strip()}\n```' if out.strip() else '')
|
||||||
+ (f'\n\nstderr:\n```\n{err.strip()}\n```' if err.strip() else '')
|
+ (f'\n\nstderr:\n```\n{err.strip()}\n```' if err.strip() else '')
|
||||||
})
|
})
|
||||||
|
@ -261,8 +261,11 @@ class PullRequests(models.Model):
|
|||||||
self.env['runbot_merge.pull_requests.feedback'].create({
|
self.env['runbot_merge.pull_requests.feedback'].create({
|
||||||
'repository': p.repository.id,
|
'repository': p.repository.id,
|
||||||
'pull_request': p.number,
|
'pull_request': p.number,
|
||||||
'message': "This PR was modified / updated and has become a normal PR. "
|
'message': "%sthis PR was modified / updated and has become a normal PR. "
|
||||||
"It should be merged the normal way (via @%s)" % p.repository.project_id.github_prefix,
|
"It should be merged the normal way (via @%s)" % (
|
||||||
|
p.source_id.ping(),
|
||||||
|
p.repository.project_id.github_prefix,
|
||||||
|
),
|
||||||
'token_field': 'fp_github_token',
|
'token_field': 'fp_github_token',
|
||||||
})
|
})
|
||||||
if vals.get('state') == 'merged':
|
if vals.get('state') == 'merged':
|
||||||
@ -298,7 +301,6 @@ class PullRequests(models.Model):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
Feedback = self.env['runbot_merge.pull_requests.feedback']
|
|
||||||
# TODO: don't use a mutable tokens iterator
|
# TODO: don't use a mutable tokens iterator
|
||||||
tokens = iter(tokens)
|
tokens = iter(tokens)
|
||||||
while True:
|
while True:
|
||||||
@ -306,6 +308,7 @@ class PullRequests(models.Model):
|
|||||||
if token is None:
|
if token is None:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
ping = False
|
||||||
close = False
|
close = False
|
||||||
msg = None
|
msg = None
|
||||||
if token in ('ci', 'skipci'):
|
if token in ('ci', 'skipci'):
|
||||||
@ -314,7 +317,8 @@ class PullRequests(models.Model):
|
|||||||
pr.fw_policy = token
|
pr.fw_policy = token
|
||||||
msg = "Not waiting for CI to create followup forward-ports." if token == 'skipci' else "Waiting for CI to create followup forward-ports."
|
msg = "Not waiting for CI to create followup forward-ports." if token == 'skipci' else "Waiting for CI to create followup forward-ports."
|
||||||
else:
|
else:
|
||||||
msg = "I don't trust you enough to do that @{}.".format(login)
|
ping = True
|
||||||
|
msg = "you can't configure ci."
|
||||||
|
|
||||||
if token == 'ignore': # replace 'ignore' by 'up to <pr_branch>'
|
if token == 'ignore': # replace 'ignore' by 'up to <pr_branch>'
|
||||||
token = 'up'
|
token = 'up'
|
||||||
@ -322,55 +326,51 @@ class PullRequests(models.Model):
|
|||||||
|
|
||||||
if token in ('r+', 'review+'):
|
if token in ('r+', 'review+'):
|
||||||
if not self.source_id:
|
if not self.source_id:
|
||||||
Feedback.create({
|
ping = True
|
||||||
'repository': self.repository.id,
|
msg = "I can only do this on forward-port PRs and this is not one, see {}.".format(
|
||||||
'pull_request': self.number,
|
self.repository.project_id.github_prefix
|
||||||
'message': "I'm sorry, @{}. I can only do this on forward-port PRs and this ain't one.".format(login),
|
)
|
||||||
'token_field': 'fp_github_token',
|
else:
|
||||||
})
|
merge_bot = self.repository.project_id.github_prefix
|
||||||
continue
|
# don't update the root ever
|
||||||
merge_bot = self.repository.project_id.github_prefix
|
for pr in (p for p in self._iter_ancestors() if p.parent_id if p.state in RPLUS):
|
||||||
# don't update the root ever
|
# only the author is delegated explicitely on the
|
||||||
for pr in (p for p in self._iter_ancestors() if p.parent_id if p.state in RPLUS):
|
pr._parse_commands(author, {**comment, 'body': merge_bot + ' r+'}, login)
|
||||||
# only the author is delegated explicitely on the
|
|
||||||
pr._parse_commands(author, {**comment, 'body': merge_bot + ' r+'}, login)
|
|
||||||
elif token == 'close':
|
elif token == 'close':
|
||||||
msg = "I'm sorry, @{}. I can't close this PR for you.".format(
|
|
||||||
login)
|
|
||||||
if self.source_id._pr_acl(author).is_reviewer:
|
if self.source_id._pr_acl(author).is_reviewer:
|
||||||
close = True
|
close = True
|
||||||
msg = None
|
else:
|
||||||
|
ping = True
|
||||||
|
msg = "you can't close PRs."
|
||||||
|
|
||||||
elif token == 'up' and next(tokens, None) == 'to':
|
elif token == 'up' and next(tokens, None) == 'to':
|
||||||
limit = next(tokens, None)
|
limit = next(tokens, None)
|
||||||
|
ping = True
|
||||||
if not self._pr_acl(author).is_author:
|
if not self._pr_acl(author).is_author:
|
||||||
Feedback.create({
|
msg = "you can't set a forward-port limit.".format(login)
|
||||||
'repository': self.repository.id,
|
elif not limit:
|
||||||
'pull_request': self.number,
|
msg = "please provide a branch to forward-port to."
|
||||||
'message': "I'm sorry, @{}. You can't set a forward-port limit.".format(login),
|
|
||||||
'token_field': 'fp_github_token',
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
if not limit:
|
|
||||||
msg = "Please provide a branch to forward-port to."
|
|
||||||
else:
|
else:
|
||||||
limit_id = self.env['runbot_merge.branch'].with_context(active_test=False).search([
|
limit_id = self.env['runbot_merge.branch'].with_context(active_test=False).search([
|
||||||
('project_id', '=', self.repository.project_id.id),
|
('project_id', '=', self.repository.project_id.id),
|
||||||
('name', '=', limit),
|
('name', '=', limit),
|
||||||
])
|
])
|
||||||
if self.source_id:
|
if self.source_id:
|
||||||
msg = "Sorry, forward-port limit can only be set on " \
|
msg = "forward-port limit can only be set on " \
|
||||||
f"an origin PR ({self.source_id.display_name} " \
|
f"an origin PR ({self.source_id.display_name} " \
|
||||||
"here) before it's merged and forward-ported."
|
"here) before it's merged and forward-ported."
|
||||||
elif self.state in ['merged', 'closed']:
|
elif self.state in ['merged', 'closed']:
|
||||||
msg = "Sorry, forward-port limit can only be set before the PR is merged."
|
msg = "forward-port limit can only be set before the PR is merged."
|
||||||
elif not limit_id:
|
elif not limit_id:
|
||||||
msg = "There is no branch %r, it can't be used as a forward port target." % limit
|
msg = "there is no branch %r, it can't be used as a forward port target." % limit
|
||||||
elif limit_id == self.target:
|
elif limit_id == self.target:
|
||||||
|
ping = False
|
||||||
msg = "Forward-port disabled."
|
msg = "Forward-port disabled."
|
||||||
self.limit_id = limit_id
|
self.limit_id = limit_id
|
||||||
elif not limit_id.fp_enabled:
|
elif not limit_id.fp_enabled:
|
||||||
msg = "Branch %r is disabled, it can't be used as a forward port target." % limit_id.name
|
msg = "branch %r is disabled, it can't be used as a forward port target." % limit_id.name
|
||||||
else:
|
else:
|
||||||
|
ping = False
|
||||||
msg = "Forward-porting to %r." % limit_id.name
|
msg = "Forward-porting to %r." % limit_id.name
|
||||||
self.limit_id = limit_id
|
self.limit_id = limit_id
|
||||||
|
|
||||||
@ -382,7 +382,7 @@ class PullRequests(models.Model):
|
|||||||
self.env['runbot_merge.pull_requests.feedback'].create({
|
self.env['runbot_merge.pull_requests.feedback'].create({
|
||||||
'repository': self.repository.id,
|
'repository': self.repository.id,
|
||||||
'pull_request': self.number,
|
'pull_request': self.number,
|
||||||
'message': msg,
|
'message': f'@{author.github_login} {msg}' if msg and ping else msg,
|
||||||
'close': close,
|
'close': close,
|
||||||
'token_field': 'fp_github_token',
|
'token_field': 'fp_github_token',
|
||||||
})
|
})
|
||||||
@ -397,8 +397,8 @@ class PullRequests(models.Model):
|
|||||||
'repository': self.repository.id,
|
'repository': self.repository.id,
|
||||||
'pull_request': self.number,
|
'pull_request': self.number,
|
||||||
'token_field': 'fp_github_token',
|
'token_field': 'fp_github_token',
|
||||||
'message': '%s\n\n%s failed on this forward-port PR' % (
|
'message': '%s%s failed on this forward-port PR' % (
|
||||||
self.source_id._pingline(),
|
self.source_id.ping(),
|
||||||
ci,
|
ci,
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
@ -578,10 +578,10 @@ class PullRequests(models.Model):
|
|||||||
'repository': pr.repository.id,
|
'repository': pr.repository.id,
|
||||||
'pull_request': pr.number,
|
'pull_request': pr.number,
|
||||||
'token_field': 'fp_github_token',
|
'token_field': 'fp_github_token',
|
||||||
'message': "This pull request can not be forward ported: "
|
'message': "%sthis pull request can not be forward ported: "
|
||||||
"next branch is %r but linked pull request %s "
|
"next branch is %r but linked pull request %s "
|
||||||
"has a next branch %r." % (
|
"has a next branch %r." % (
|
||||||
t.name, linked.display_name, other.name
|
pr.ping(), t.name, linked.display_name, other.name
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
_logger.warning(
|
_logger.warning(
|
||||||
@ -678,8 +678,8 @@ class PullRequests(models.Model):
|
|||||||
'delegates': [(6, False, (source.delegates | pr.delegates).ids)]
|
'delegates': [(6, False, (source.delegates | pr.delegates).ids)]
|
||||||
})
|
})
|
||||||
if has_conflicts and pr.parent_id and pr.state not in ('merged', 'closed'):
|
if has_conflicts and pr.parent_id and pr.state not in ('merged', 'closed'):
|
||||||
message = source._pingline() + """
|
message = source.ping() + """\
|
||||||
The next pull request (%s) is in conflict. You can merge the chain up to here by saying
|
the next pull request (%s) is in conflict. You can merge the chain up to here by saying
|
||||||
> @%s r+
|
> @%s r+
|
||||||
%s""" % (new_pr.display_name, pr.repository.project_id.fp_github_name, footer)
|
%s""" % (new_pr.display_name, pr.repository.project_id.fp_github_name, footer)
|
||||||
self.env['runbot_merge.pull_requests.feedback'].create({
|
self.env['runbot_merge.pull_requests.feedback'].create({
|
||||||
@ -710,19 +710,21 @@ The next pull request (%s) is in conflict. You can merge the chain up to here by
|
|||||||
'* %s%s\n' % (sha, ' <- on this commit' if sha == h else '')
|
'* %s%s\n' % (sha, ' <- on this commit' if sha == h else '')
|
||||||
for sha in hh
|
for sha in hh
|
||||||
)
|
)
|
||||||
message = f"""{source._pingline()} cherrypicking of pull request {source.display_name} failed.
|
message = f"""{source.ping()}cherrypicking of pull request {source.display_name} failed.
|
||||||
{lines}{sout}{serr}
|
{lines}{sout}{serr}
|
||||||
Either perform the forward-port manually (and push to this branch, proceeding as usual) or close this PR (maybe?).
|
Either perform the forward-port manually (and push to this branch, proceeding as usual) or close this PR (maybe?).
|
||||||
|
|
||||||
In the former case, you may want to edit this PR message as well.
|
In the former case, you may want to edit this PR message as well.
|
||||||
"""
|
"""
|
||||||
elif has_conflicts:
|
elif has_conflicts:
|
||||||
message = """%s
|
message = """%s\
|
||||||
While this was properly forward-ported, at least one co-dependent PR (%s) did not succeed. You will need to fix it before this can be merged.
|
while this was properly forward-ported, at least one co-dependent PR (%s) did \
|
||||||
|
not succeed. You will need to fix it before this can be merged.
|
||||||
|
|
||||||
Both this PR and the others will need to be approved via `@%s r+` as they are all considered "in conflict".
|
Both this PR and the others will need to be approved via `@%s r+` as they are \
|
||||||
|
all considered "in conflict".
|
||||||
%s""" % (
|
%s""" % (
|
||||||
source._pingline(),
|
source.ping(),
|
||||||
', '.join(p.display_name for p in (new_batch - new_pr)),
|
', '.join(p.display_name for p in (new_batch - new_pr)),
|
||||||
proj.github_prefix,
|
proj.github_prefix,
|
||||||
footer
|
footer
|
||||||
@ -733,8 +735,8 @@ Both this PR and the others will need to be approved via `@%s r+` as they are al
|
|||||||
for p in pr._iter_ancestors()
|
for p in pr._iter_ancestors()
|
||||||
if p.parent_id
|
if p.parent_id
|
||||||
)
|
)
|
||||||
message = source._pingline() + """
|
message = source.ping() + """\
|
||||||
This PR targets %s and is the last of the forward-port chain%s
|
this PR targets %s and is the last of the forward-port chain%s
|
||||||
%s
|
%s
|
||||||
To merge the full chain, say
|
To merge the full chain, say
|
||||||
> @%s r+
|
> @%s r+
|
||||||
@ -772,14 +774,6 @@ This PR targets %s and is part of the forward-port chain. Further PRs will be cr
|
|||||||
b.prs[0]._schedule_fp_followup()
|
b.prs[0]._schedule_fp_followup()
|
||||||
return b
|
return b
|
||||||
|
|
||||||
def _pingline(self):
|
|
||||||
assignees = (self.author | self.reviewed_by).mapped('github_login')
|
|
||||||
return "Ping %s" % ', '.join(
|
|
||||||
'@' + login
|
|
||||||
for login in assignees
|
|
||||||
if login
|
|
||||||
)
|
|
||||||
|
|
||||||
def _create_fp_branch(self, target_branch, fp_branch_name, cleanup):
|
def _create_fp_branch(self, target_branch, fp_branch_name, cleanup):
|
||||||
""" Creates a forward-port for the current PR to ``target_branch`` under
|
""" Creates a forward-port for the current PR to ``target_branch`` under
|
||||||
``fp_branch_name``.
|
``fp_branch_name``.
|
||||||
@ -1084,8 +1078,9 @@ stderr:
|
|||||||
self.env['runbot_merge.pull_requests.feedback'].create({
|
self.env['runbot_merge.pull_requests.feedback'].create({
|
||||||
'repository': source.repository.id,
|
'repository': source.repository.id,
|
||||||
'pull_request': source.number,
|
'pull_request': source.number,
|
||||||
'message': "This pull request has forward-port PRs awaiting action (not merged or closed): %s" % ', '.join(
|
'message': "%sthis pull request has forward-port PRs awaiting action (not merged or closed):\n%s" % (
|
||||||
pr.display_name for pr in sorted(prs, key=lambda p: p.number)
|
source.ping(),
|
||||||
|
'\n- '.join(pr.display_name for pr in sorted(prs, key=lambda p: p.number))
|
||||||
),
|
),
|
||||||
'token_field': 'fp_github_token',
|
'token_field': 'fp_github_token',
|
||||||
})
|
})
|
||||||
|
@ -73,8 +73,8 @@ This PR targets b and is part of the forward-port chain. Further PRs will be cre
|
|||||||
|
|
||||||
More info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port
|
More info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port
|
||||||
'''),
|
'''),
|
||||||
(users['user'], """Ping @%s, @%s
|
(users['user'], """@%s @%s the next pull request (%s) is in conflict. \
|
||||||
The next pull request (%s) is in conflict. You can merge the chain up to here by saying
|
You can merge the chain up to here by saying
|
||||||
> @%s r+
|
> @%s r+
|
||||||
|
|
||||||
More info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port
|
More info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port
|
||||||
@ -343,11 +343,12 @@ b
|
|||||||
|
|
||||||
assert pr2.comments == [
|
assert pr2.comments == [
|
||||||
seen(env, pr2, users),
|
seen(env, pr2, users),
|
||||||
(users['user'], re_matches(r'Ping.*CONFLICT', re.DOTALL)),
|
(users['user'], re_matches(r'@%s @%s .*CONFLICT' % (users['user'], users['reviewer']), re.DOTALL)),
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
(users['user'], f"All commits must have author and committer email, "
|
(users['user'], f"@{users['user']} @{users['reviewer']} unable to stage: "
|
||||||
|
"All commits must have author and committer email, "
|
||||||
f"missing email on {pr2_id.head} indicates the "
|
f"missing email on {pr2_id.head} indicates the "
|
||||||
f"authorship is most likely incorrect."),
|
"authorship is most likely incorrect."),
|
||||||
]
|
]
|
||||||
assert pr2_id.state == 'error'
|
assert pr2_id.state == 'error'
|
||||||
assert not pr2_id.staging_id, "staging should have been rejected"
|
assert not pr2_id.staging_id, "staging should have been rejected"
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import collections
|
import collections
|
||||||
|
import time
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
@ -157,12 +158,12 @@ def test_disable(env, config, make_repo, users, enabled):
|
|||||||
# responses and we don't care that much
|
# responses and we don't care that much
|
||||||
assert set(pr.comments) == {
|
assert set(pr.comments) == {
|
||||||
(users['reviewer'], "hansen r+\n%s up to" % bot_name),
|
(users['reviewer'], "hansen r+\n%s up to" % bot_name),
|
||||||
|
(users['other'], "@%s please provide a branch to forward-port to." % users['reviewer']),
|
||||||
(users['reviewer'], "%s up to b" % bot_name),
|
(users['reviewer'], "%s up to b" % bot_name),
|
||||||
|
(users['other'], "@%s branch 'b' is disabled, it can't be used as a forward port target." % users['reviewer']),
|
||||||
(users['reviewer'], "%s up to foo" % bot_name),
|
(users['reviewer'], "%s up to foo" % bot_name),
|
||||||
|
(users['other'], "@%s there is no branch 'foo', it can't be used as a forward port target." % users['reviewer']),
|
||||||
(users['reviewer'], "%s up to c" % bot_name),
|
(users['reviewer'], "%s up to c" % bot_name),
|
||||||
(users['other'], "Please provide a branch to forward-port to."),
|
|
||||||
(users['other'], "Branch 'b' is disabled, it can't be used as a forward port target."),
|
|
||||||
(users['other'], "There is no branch 'foo', it can't be used as a forward port target."),
|
|
||||||
(users['other'], "Forward-porting to 'c'."),
|
(users['other'], "Forward-porting to 'c'."),
|
||||||
seen(env, pr, users),
|
seen(env, pr, users),
|
||||||
}
|
}
|
||||||
@ -201,14 +202,13 @@ def test_default_disabled(env, config, make_repo, users):
|
|||||||
assert pr2.comments == [
|
assert pr2.comments == [
|
||||||
seen(env, pr2, users),
|
seen(env, pr2, users),
|
||||||
(users['user'], """\
|
(users['user'], """\
|
||||||
Ping @%s, @%s
|
@%(user)s @%(reviewer)s this PR targets b and is the last of the forward-port chain.
|
||||||
This PR targets b and is the last of the forward-port chain.
|
|
||||||
|
|
||||||
To merge the full chain, say
|
To merge the full chain, say
|
||||||
> @%s r+
|
> @%(user)s r+
|
||||||
|
|
||||||
More info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port
|
More info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port
|
||||||
""" % (users['user'], users['reviewer'], users['user'])),
|
""" % users)
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_limit_after_merge(env, config, make_repo, users):
|
def test_limit_after_merge(env, config, make_repo, users):
|
||||||
@ -247,7 +247,7 @@ def test_limit_after_merge(env, config, make_repo, users):
|
|||||||
(users['reviewer'], "hansen r+"),
|
(users['reviewer'], "hansen r+"),
|
||||||
seen(env, pr1, users),
|
seen(env, pr1, users),
|
||||||
(users['reviewer'], bot_name + ' up to b'),
|
(users['reviewer'], bot_name + ' up to b'),
|
||||||
(bot_name, "Sorry, forward-port limit can only be set before the PR is merged."),
|
(bot_name, "@%s forward-port limit can only be set before the PR is merged." % users['reviewer']),
|
||||||
]
|
]
|
||||||
assert pr2.comments == [
|
assert pr2.comments == [
|
||||||
seen(env, pr2, users),
|
seen(env, pr2, users),
|
||||||
@ -257,9 +257,11 @@ This PR targets b and is part of the forward-port chain. Further PRs will be cre
|
|||||||
More info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port
|
More info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port
|
||||||
"""),
|
"""),
|
||||||
(users['reviewer'], bot_name + ' up to b'),
|
(users['reviewer'], bot_name + ' up to b'),
|
||||||
(bot_name, "Sorry, forward-port limit can only be set on an origin PR"
|
(bot_name, "@%s forward-port limit can only be set on an origin PR"
|
||||||
" (%s here) before it's merged and forward-ported." % p1.display_name
|
" (%s here) before it's merged and forward-ported." % (
|
||||||
),
|
users['reviewer'],
|
||||||
|
p1.display_name,
|
||||||
|
)),
|
||||||
]
|
]
|
||||||
|
|
||||||
# update pr2 to detach it from pr1
|
# update pr2 to detach it from pr1
|
||||||
@ -279,10 +281,13 @@ More info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port
|
|||||||
env.run_crons()
|
env.run_crons()
|
||||||
|
|
||||||
assert pr2.comments[4:] == [
|
assert pr2.comments[4:] == [
|
||||||
(bot_name, "This PR was modified / updated and has become a normal PR. "
|
(bot_name, "@%s @%s this PR was modified / updated and has become a normal PR. "
|
||||||
"It should be merged the normal way (via @hansen)"),
|
"It should be merged the normal way (via @%s)" % (
|
||||||
|
users['user'], users['reviewer'],
|
||||||
|
p2.repository.project_id.github_prefix
|
||||||
|
)),
|
||||||
(users['reviewer'], bot_name + ' up to b'),
|
(users['reviewer'], bot_name + ' up to b'),
|
||||||
(bot_name, "Sorry, forward-port limit can only be set on an origin PR "
|
(bot_name, f"@{users['reviewer']} forward-port limit can only be set on an origin PR "
|
||||||
f"({p1.display_name} here) before it's merged and forward-ported."
|
f"({p1.display_name} here) before it's merged and forward-ported."
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -125,8 +125,11 @@ def test_straightforward_flow(env, config, make_repo, users):
|
|||||||
assert pr.comments == [
|
assert pr.comments == [
|
||||||
(users['reviewer'], 'hansen r+ rebase-ff'),
|
(users['reviewer'], 'hansen r+ rebase-ff'),
|
||||||
seen(env, pr, users),
|
seen(env, pr, users),
|
||||||
(users['user'], 'Merge method set to rebase and fast-forward'),
|
(users['user'], 'Merge method set to rebase and fast-forward.'),
|
||||||
(users['user'], 'This pull request has forward-port PRs awaiting action (not merged or closed): ' + ', '.join((pr1 | pr2).mapped('display_name'))),
|
(users['user'], '@%s @%s this pull request has forward-port PRs awaiting action (not merged or closed):\n%s' % (
|
||||||
|
users['other'], users['reviewer'],
|
||||||
|
'\n- '.join((pr1 | pr2).mapped('display_name'))
|
||||||
|
)),
|
||||||
]
|
]
|
||||||
|
|
||||||
assert pr0_ == pr0
|
assert pr0_ == pr0
|
||||||
@ -148,8 +151,7 @@ def test_straightforward_flow(env, config, make_repo, users):
|
|||||||
assert pr2_remote.comments == [
|
assert pr2_remote.comments == [
|
||||||
seen(env, pr2_remote, users),
|
seen(env, pr2_remote, users),
|
||||||
(users['user'], """\
|
(users['user'], """\
|
||||||
Ping @%s, @%s
|
@%s @%s this PR targets c and is the last of the forward-port chain containing:
|
||||||
This PR targets c and is the last of the forward-port chain containing:
|
|
||||||
* %s
|
* %s
|
||||||
|
|
||||||
To merge the full chain, say
|
To merge the full chain, say
|
||||||
@ -314,11 +316,18 @@ def test_empty(env, config, make_repo, users):
|
|||||||
env.run_crons('forwardport.reminder', 'runbot_merge.feedback_cron', context={'forwardport_updated_before': FAKE_PREV_WEEK})
|
env.run_crons('forwardport.reminder', 'runbot_merge.feedback_cron', context={'forwardport_updated_before': FAKE_PREV_WEEK})
|
||||||
env.run_crons('forwardport.reminder', 'runbot_merge.feedback_cron', context={'forwardport_updated_before': FAKE_PREV_WEEK})
|
env.run_crons('forwardport.reminder', 'runbot_merge.feedback_cron', context={'forwardport_updated_before': FAKE_PREV_WEEK})
|
||||||
|
|
||||||
|
awaiting = (
|
||||||
|
users['other'],
|
||||||
|
'@%s @%s this pull request has forward-port PRs awaiting action (not merged or closed):\n%s' % (
|
||||||
|
users['user'], users['reviewer'],
|
||||||
|
fail_id.display_name
|
||||||
|
)
|
||||||
|
)
|
||||||
assert pr1.comments == [
|
assert pr1.comments == [
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
seen(env, pr1, users),
|
seen(env, pr1, users),
|
||||||
(users['other'], 'This pull request has forward-port PRs awaiting action (not merged or closed): ' + fail_id.display_name),
|
awaiting,
|
||||||
(users['other'], 'This pull request has forward-port PRs awaiting action (not merged or closed): ' + fail_id.display_name),
|
awaiting,
|
||||||
], "each cron run should trigger a new message on the ancestor"
|
], "each cron run should trigger a new message on the ancestor"
|
||||||
# check that this stops if we close the PR
|
# check that this stops if we close the PR
|
||||||
with prod:
|
with prod:
|
||||||
@ -327,8 +336,8 @@ def test_empty(env, config, make_repo, users):
|
|||||||
assert pr1.comments == [
|
assert pr1.comments == [
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
seen(env, pr1, users),
|
seen(env, pr1, users),
|
||||||
(users['other'], 'This pull request has forward-port PRs awaiting action (not merged or closed): ' + fail_id.display_name),
|
awaiting,
|
||||||
(users['other'], 'This pull request has forward-port PRs awaiting action (not merged or closed): ' + fail_id.display_name),
|
awaiting,
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_partially_empty(env, config, make_repo):
|
def test_partially_empty(env, config, make_repo):
|
||||||
@ -562,8 +571,7 @@ def test_delegate_fw(env, config, make_repo, users):
|
|||||||
|
|
||||||
assert pr2.comments == [
|
assert pr2.comments == [
|
||||||
seen(env, pr2, users),
|
seen(env, pr2, users),
|
||||||
(users['user'], '''Ping @{self_reviewer}, @{reviewer}
|
(users['user'], '''@{self_reviewer} @{reviewer} this PR targets c and is the last of the forward-port chain.
|
||||||
This PR targets c and is the last of the forward-port chain.
|
|
||||||
|
|
||||||
To merge the full chain, say
|
To merge the full chain, say
|
||||||
> @{user} r+
|
> @{user} r+
|
||||||
|
@ -44,7 +44,7 @@ This PR targets b and is part of the forward-port chain. Further PRs will be cre
|
|||||||
|
|
||||||
More info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port
|
More info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port
|
||||||
''')
|
''')
|
||||||
ci_warning = (users['user'], 'Ping @%(user)s, @%(reviewer)s\n\nci/runbot failed on this forward-port PR' % users)
|
ci_warning = (users['user'], '@%(user)s @%(reviewer)s ci/runbot failed on this forward-port PR' % users)
|
||||||
|
|
||||||
# oh no CI of the first FP PR failed!
|
# oh no CI of the first FP PR failed!
|
||||||
# simulate status being sent multiple times (e.g. on multiple repos) with
|
# simulate status being sent multiple times (e.g. on multiple repos) with
|
||||||
@ -103,7 +103,11 @@ More info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port
|
|||||||
assert pr1_remote.comments == [
|
assert pr1_remote.comments == [
|
||||||
seen(env, pr1_remote, users),
|
seen(env, pr1_remote, users),
|
||||||
fp_intermediate, ci_warning, ci_warning,
|
fp_intermediate, ci_warning, ci_warning,
|
||||||
(users['user'], "This PR was modified / updated and has become a normal PR. It should be merged the normal way (via @%s)" % pr1_id.repository.project_id.github_prefix),
|
(users['user'], "@%s @%s this PR was modified / updated and has become a normal PR. "
|
||||||
|
"It should be merged the normal way (via @%s)" % (
|
||||||
|
users['user'], users['reviewer'],
|
||||||
|
pr1_id.repository.project_id.github_prefix
|
||||||
|
)),
|
||||||
], "users should be warned that the PR has become non-FP"
|
], "users should be warned that the PR has become non-FP"
|
||||||
# NOTE: should the followup PR wait for pr1 CI or not?
|
# NOTE: should the followup PR wait for pr1 CI or not?
|
||||||
assert pr2_id.head != pr2_head
|
assert pr2_id.head != pr2_head
|
||||||
@ -210,9 +214,13 @@ def test_update_merged(env, make_repo, config, users):
|
|||||||
More info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port
|
More info at https://github.com/odoo/odoo/wiki/Mergebot#forward-port
|
||||||
'''),
|
'''),
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
(users['user'], """Ancestor PR %s has been updated but this PR is merged and can't be updated to match.
|
(users['user'], """@%s @%s ancestor PR %s has been updated but this PR is merged and can't be updated to match.
|
||||||
|
|
||||||
You may want or need to manually update any followup PR.""" % pr1_id.display_name)
|
You may want or need to manually update any followup PR.""" % (
|
||||||
|
users['user'],
|
||||||
|
users['reviewer'],
|
||||||
|
pr1_id.display_name,
|
||||||
|
))
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_duplicate_fw(env, make_repo, setreviewers, config, users):
|
def test_duplicate_fw(env, make_repo, setreviewers, config, users):
|
||||||
@ -377,7 +385,7 @@ conflict!
|
|||||||
# 2. "forward port chain" bit
|
# 2. "forward port chain" bit
|
||||||
# 3. updated / modified & got detached
|
# 3. updated / modified & got detached
|
||||||
assert pr2.comments[3:] == [
|
assert pr2.comments[3:] == [
|
||||||
(users['user'], f"WARNING: the latest change ({pr2_id.head}) triggered "
|
(users['user'], f"@{users['user']} WARNING: the latest change ({pr2_id.head}) triggered "
|
||||||
f"a conflict when updating the next forward-port "
|
f"a conflict when updating the next forward-port "
|
||||||
f"({pr3_id.display_name}), and has been ignored.\n\n"
|
f"({pr3_id.display_name}), and has been ignored.\n\n"
|
||||||
f"You will need to update this pull request "
|
f"You will need to update this pull request "
|
||||||
@ -389,7 +397,7 @@ conflict!
|
|||||||
# 2. forward-port chain thing
|
# 2. forward-port chain thing
|
||||||
assert repo.get_pr(pr3_id.number).comments[2:] == [
|
assert repo.get_pr(pr3_id.number).comments[2:] == [
|
||||||
(users['user'], re_matches(f'''\
|
(users['user'], re_matches(f'''\
|
||||||
WARNING: the update of {pr2_id.display_name} to {pr2_id.head} has caused a \
|
@{users['user']} WARNING: the update of {pr2_id.display_name} to {pr2_id.head} has caused a \
|
||||||
conflict in this pull request, data may have been lost.
|
conflict in this pull request, data may have been lost.
|
||||||
|
|
||||||
stdout:
|
stdout:
|
||||||
|
@ -399,18 +399,20 @@ class TestNotAllBranches:
|
|||||||
assert pr_a.comments == [
|
assert pr_a.comments == [
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
seen(env, pr_a, users),
|
seen(env, pr_a, users),
|
||||||
(users['user'], "This pull request can not be forward ported: next "
|
(users['user'], "@%s @%s this pull request can not be forward ported:"
|
||||||
"branch is 'b' but linked pull request %s#%d has a"
|
" next branch is 'b' but linked pull request %s "
|
||||||
" next branch 'c'." % (b.name, pr_b.number)
|
"has a next branch 'c'." % (
|
||||||
)
|
users['user'], users['reviewer'], pr_b_id.display_name,
|
||||||
|
)),
|
||||||
]
|
]
|
||||||
assert pr_b.comments == [
|
assert pr_b.comments == [
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
seen(env, pr_b, users),
|
seen(env, pr_b, users),
|
||||||
(users['user'], "This pull request can not be forward ported: next "
|
(users['user'], "@%s @%s this pull request can not be forward ported:"
|
||||||
"branch is 'c' but linked pull request %s#%d has a"
|
" next branch is 'c' but linked pull request %s "
|
||||||
" next branch 'b'." % (a.name, pr_a.number)
|
"has a next branch 'b'." % (
|
||||||
)
|
users['user'], users['reviewer'], pr_a_id.display_name,
|
||||||
|
)),
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_new_intermediate_branch(env, config, make_repo):
|
def test_new_intermediate_branch(env, config, make_repo):
|
||||||
@ -755,7 +757,7 @@ def test_approve_draft(env, config, make_repo, users):
|
|||||||
assert pr.comments == [
|
assert pr.comments == [
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
seen(env, pr, users),
|
seen(env, pr, users),
|
||||||
(users['user'], f"I'm sorry, @{users['reviewer']}. Draft PRs can not be approved."),
|
(users['user'], f"I'm sorry, @{users['reviewer']}: draft PRs can not be approved."),
|
||||||
]
|
]
|
||||||
|
|
||||||
with prod:
|
with prod:
|
||||||
|
3
runbot_merge/changelog/2022-06/pinging.md
Normal file
3
runbot_merge/changelog/2022-06/pinging.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
IMP: review pinging (`@`-notification) of users by the mergebot and forwardbot
|
||||||
|
|
||||||
|
The bots should more consistently ping users when they need some sort of action to proceed.
|
@ -218,7 +218,7 @@ def handle_pr(env, event):
|
|||||||
if pr_obj.state == 'merged':
|
if pr_obj.state == 'merged':
|
||||||
feedback(
|
feedback(
|
||||||
close=True,
|
close=True,
|
||||||
message="@%s ya silly goose you can't reopen a PR that's been merged PR." % event['sender']['login']
|
message="@%s ya silly goose you can't reopen a merged PR." % event['sender']['login']
|
||||||
)
|
)
|
||||||
|
|
||||||
if pr_obj.state == 'closed':
|
if pr_obj.state == 'closed':
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import ast
|
import ast
|
||||||
import base64
|
import base64
|
||||||
import collections
|
import collections
|
||||||
|
import contextlib
|
||||||
import datetime
|
import datetime
|
||||||
import io
|
import io
|
||||||
import itertools
|
import itertools
|
||||||
@ -116,7 +117,7 @@ All substitutions are tentatively applied sequentially to the input.
|
|||||||
feedback({
|
feedback({
|
||||||
'repository': self.id,
|
'repository': self.id,
|
||||||
'pull_request': number,
|
'pull_request': number,
|
||||||
'message': "I'm sorry. Branch `{}` is not within my remit.".format(pr['base']['ref']),
|
'message': "Branch `{}` is not within my remit, imma just ignore it.".format(pr['base']['ref']),
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
|
||||||
@ -142,8 +143,9 @@ All substitutions are tentatively applied sequentially to the input.
|
|||||||
feedback({
|
feedback({
|
||||||
'repository': self.id,
|
'repository': self.id,
|
||||||
'pull_request': number,
|
'pull_request': number,
|
||||||
'message': "Sorry, I didn't know about this PR and had to retrieve "
|
'message': "%sI didn't know about this PR and had to retrieve "
|
||||||
"its information, you may have to re-approve it."
|
"its information, you may have to re-approve it as "
|
||||||
|
"I didn't see previous commands." % pr_id.ping()
|
||||||
})
|
})
|
||||||
# init the PR to the null commit so we can later synchronise it back
|
# init the PR to the null commit so we can later synchronise it back
|
||||||
# back to the "proper" head while resetting reviews
|
# back to the "proper" head while resetting reviews
|
||||||
@ -338,14 +340,20 @@ class Branch(models.Model):
|
|||||||
_logger.exception("Failed to merge %s into staging branch", pr.display_name)
|
_logger.exception("Failed to merge %s into staging branch", pr.display_name)
|
||||||
if first or isinstance(e, exceptions.Unmergeable):
|
if first or isinstance(e, exceptions.Unmergeable):
|
||||||
if len(e.args) > 1 and e.args[1]:
|
if len(e.args) > 1 and e.args[1]:
|
||||||
message = e.args[1]
|
reason = e.args[1]
|
||||||
else:
|
else:
|
||||||
message = "Unable to stage PR (%s)" % e.__context__
|
reason = e.__context__
|
||||||
|
# if the reason is a json document, assume it's a github
|
||||||
|
# error and try to extract the error message to give it to
|
||||||
|
# the user
|
||||||
|
with contextlib.suppress(Exception):
|
||||||
|
reason = json.loads(str(reason))['message'].lower()
|
||||||
|
|
||||||
pr.state = 'error'
|
pr.state = 'error'
|
||||||
self.env['runbot_merge.pull_requests.feedback'].create({
|
self.env['runbot_merge.pull_requests.feedback'].create({
|
||||||
'repository': pr.repository.id,
|
'repository': pr.repository.id,
|
||||||
'pull_request': pr.number,
|
'pull_request': pr.number,
|
||||||
'message': message,
|
'message': f'{pr.ping()}unable to stage: {reason}',
|
||||||
})
|
})
|
||||||
else:
|
else:
|
||||||
first = False
|
first = False
|
||||||
@ -534,6 +542,17 @@ class PullRequests(models.Model):
|
|||||||
repo_name = fields.Char(related='repository.name')
|
repo_name = fields.Char(related='repository.name')
|
||||||
message_title = fields.Char(compute='_compute_message_title')
|
message_title = fields.Char(compute='_compute_message_title')
|
||||||
|
|
||||||
|
def ping(self, author=True, reviewer=True):
|
||||||
|
P = self.env['res.partner']
|
||||||
|
s = ' '.join(
|
||||||
|
f'@{p.github_login}'
|
||||||
|
for p in (self.author if author else P) | (self.reviewed_by if reviewer else P)
|
||||||
|
if p
|
||||||
|
)
|
||||||
|
if s:
|
||||||
|
s += ' '
|
||||||
|
return s
|
||||||
|
|
||||||
@api.depends('repository.name', 'number')
|
@api.depends('repository.name', 'number')
|
||||||
def _compute_url(self):
|
def _compute_url(self):
|
||||||
base = werkzeug.urls.url_parse(self.env['ir.config_parameter'].sudo().get_param('web.base.url', 'http://localhost:8069'))
|
base = werkzeug.urls.url_parse(self.env['ir.config_parameter'].sudo().get_param('web.base.url', 'http://localhost:8069'))
|
||||||
@ -791,14 +810,14 @@ class PullRequests(models.Model):
|
|||||||
msgs = []
|
msgs = []
|
||||||
for command, param in commands:
|
for command, param in commands:
|
||||||
ok = False
|
ok = False
|
||||||
msg = []
|
msg = None
|
||||||
if command == 'retry':
|
if command == 'retry':
|
||||||
if is_author:
|
if is_author:
|
||||||
if self.state == 'error':
|
if self.state == 'error':
|
||||||
ok = True
|
ok = True
|
||||||
self.state = 'ready'
|
self.state = 'ready'
|
||||||
else:
|
else:
|
||||||
msg = "Retry makes no sense when the PR is not in error."
|
msg = "retry makes no sense when the PR is not in error."
|
||||||
elif command == 'check':
|
elif command == 'check':
|
||||||
if is_author:
|
if is_author:
|
||||||
self.env['runbot_merge.fetch_job'].create({
|
self.env['runbot_merge.fetch_job'].create({
|
||||||
@ -808,14 +827,14 @@ class PullRequests(models.Model):
|
|||||||
ok = True
|
ok = True
|
||||||
elif command == 'review':
|
elif command == 'review':
|
||||||
if self.draft:
|
if self.draft:
|
||||||
msg = "Draft PRs can not be approved."
|
msg = "draft PRs can not be approved."
|
||||||
elif param and is_reviewer:
|
elif param and is_reviewer:
|
||||||
oldstate = self.state
|
oldstate = self.state
|
||||||
newstate = RPLUS.get(self.state)
|
newstate = RPLUS.get(self.state)
|
||||||
if not author.email:
|
if not author.email:
|
||||||
msg = "I must know your email before you can review PRs. Please contact an administrator."
|
msg = "I must know your email before you can review PRs. Please contact an administrator."
|
||||||
elif not newstate:
|
elif not newstate:
|
||||||
msg = "This PR is already reviewed, reviewing it again is useless."
|
msg = "this PR is already reviewed, reviewing it again is useless."
|
||||||
else:
|
else:
|
||||||
self.state = newstate
|
self.state = newstate
|
||||||
self.reviewed_by = author
|
self.reviewed_by = author
|
||||||
@ -832,7 +851,7 @@ class PullRequests(models.Model):
|
|||||||
Feedback.create({
|
Feedback.create({
|
||||||
'repository': self.repository.id,
|
'repository': self.repository.id,
|
||||||
'pull_request': self.number,
|
'pull_request': self.number,
|
||||||
'message': "@{}, you may want to rebuild or fix this PR as it has failed CI.".format(author.github_login),
|
'message': "@{} you may want to rebuild or fix this PR as it has failed CI.".format(login),
|
||||||
})
|
})
|
||||||
elif not param and is_author:
|
elif not param and is_author:
|
||||||
newstate = RMINUS.get(self.state)
|
newstate = RMINUS.get(self.state)
|
||||||
@ -846,7 +865,7 @@ class PullRequests(models.Model):
|
|||||||
'pull_request': self.number,
|
'pull_request': self.number,
|
||||||
'message': "PR priority reset to 1, as pull requests with priority 0 ignore review state.",
|
'message': "PR priority reset to 1, as pull requests with priority 0 ignore review state.",
|
||||||
})
|
})
|
||||||
self.unstage("unreviewed (r-) by %s", author.github_login)
|
self.unstage("unreviewed (r-) by %s", login)
|
||||||
ok = True
|
ok = True
|
||||||
else:
|
else:
|
||||||
msg = "r- makes no sense in the current PR state."
|
msg = "r- makes no sense in the current PR state."
|
||||||
@ -875,7 +894,7 @@ class PullRequests(models.Model):
|
|||||||
elif command == 'method':
|
elif command == 'method':
|
||||||
if is_reviewer:
|
if is_reviewer:
|
||||||
if param == 'squash' and not self.squash:
|
if param == 'squash' and not self.squash:
|
||||||
msg = "Squash can only be used with a single commit at this time."
|
msg = "squash can only be used with a single commit at this time."
|
||||||
else:
|
else:
|
||||||
self.merge_method = param
|
self.merge_method = param
|
||||||
ok = True
|
ok = True
|
||||||
@ -883,7 +902,7 @@ class PullRequests(models.Model):
|
|||||||
Feedback.create({
|
Feedback.create({
|
||||||
'repository': self.repository.id,
|
'repository': self.repository.id,
|
||||||
'pull_request': self.number,
|
'pull_request': self.number,
|
||||||
'message':"Merge method set to %s" % explanation
|
'message':"Merge method set to %s." % explanation
|
||||||
})
|
})
|
||||||
elif command == 'override':
|
elif command == 'override':
|
||||||
overridable = author.override_rights\
|
overridable = author.override_rights\
|
||||||
@ -905,7 +924,7 @@ class PullRequests(models.Model):
|
|||||||
c.create({'sha': self.head, 'statuses': '{}'})
|
c.create({'sha': self.head, 'statuses': '{}'})
|
||||||
ok = True
|
ok = True
|
||||||
else:
|
else:
|
||||||
msg = f"You are not allowed to override this status."
|
msg = "you are not allowed to override this status."
|
||||||
else:
|
else:
|
||||||
# ignore unknown commands
|
# ignore unknown commands
|
||||||
continue
|
continue
|
||||||
@ -920,21 +939,23 @@ class PullRequests(models.Model):
|
|||||||
applied.append(reformat(command, param))
|
applied.append(reformat(command, param))
|
||||||
else:
|
else:
|
||||||
ignored.append(reformat(command, param))
|
ignored.append(reformat(command, param))
|
||||||
msgs.append(msg or "You can't {}.".format(reformat(command, param)))
|
msgs.append(msg or "you can't {}.".format(reformat(command, param)))
|
||||||
|
|
||||||
|
if msgs:
|
||||||
|
joiner = ' ' if len(msgs) == 1 else '\n- '
|
||||||
|
msgs.insert(0, "I'm sorry, @{}:".format(login))
|
||||||
|
Feedback.create({
|
||||||
|
'repository': self.repository.id,
|
||||||
|
'pull_request': self.number,
|
||||||
|
'message': joiner.join(msgs),
|
||||||
|
})
|
||||||
|
|
||||||
msg = []
|
msg = []
|
||||||
if applied:
|
if applied:
|
||||||
msg.append('applied ' + ' '.join(applied))
|
msg.append('applied ' + ' '.join(applied))
|
||||||
if ignored:
|
if ignored:
|
||||||
ignoredstr = ' '.join(ignored)
|
ignoredstr = ' '.join(ignored)
|
||||||
msg.append('ignored ' + ignoredstr)
|
msg.append('ignored ' + ignoredstr)
|
||||||
|
|
||||||
if msgs:
|
|
||||||
msgs.insert(0, "I'm sorry, @{}.".format(login))
|
|
||||||
Feedback.create({
|
|
||||||
'repository': self.repository.id,
|
|
||||||
'pull_request': self.number,
|
|
||||||
'message': ' '.join(msgs),
|
|
||||||
})
|
|
||||||
return '\n'.join(msg)
|
return '\n'.join(msg)
|
||||||
|
|
||||||
def _pr_acl(self, user):
|
def _pr_acl(self, user):
|
||||||
@ -1021,7 +1042,7 @@ class PullRequests(models.Model):
|
|||||||
self.env['runbot_merge.pull_requests.feedback'].create({
|
self.env['runbot_merge.pull_requests.feedback'].create({
|
||||||
'repository': self.repository.id,
|
'repository': self.repository.id,
|
||||||
'pull_request': self.number,
|
'pull_request': self.number,
|
||||||
'message': "%r failed on this reviewed PR." % ci,
|
'message': "%s%r failed on this reviewed PR." % (self.ping(), ci),
|
||||||
})
|
})
|
||||||
|
|
||||||
def _auto_init(self):
|
def _auto_init(self):
|
||||||
@ -1158,11 +1179,9 @@ class PullRequests(models.Model):
|
|||||||
self.env['runbot_merge.pull_requests.feedback'].create({
|
self.env['runbot_merge.pull_requests.feedback'].create({
|
||||||
'repository': r.repository.id,
|
'repository': r.repository.id,
|
||||||
'pull_request': r.number,
|
'pull_request': r.number,
|
||||||
'message': "Linked pull request(s) {} not ready. Linked PRs are not staged until all of them are ready.".format(
|
'message': "{}linked pull request(s) {} not ready. Linked PRs are not staged until all of them are ready.".format(
|
||||||
', '.join(map(
|
r.ping(),
|
||||||
'{0.display_name}'.format,
|
', '.join(map('{0.display_name}'.format, unready))
|
||||||
unready
|
|
||||||
))
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
r.link_warned = True
|
r.link_warned = True
|
||||||
@ -1171,6 +1190,11 @@ class PullRequests(models.Model):
|
|||||||
|
|
||||||
# send feedback for multi-commit PRs without a merge_method (which
|
# send feedback for multi-commit PRs without a merge_method (which
|
||||||
# we've not warned yet)
|
# we've not warned yet)
|
||||||
|
methods = ''.join(
|
||||||
|
'* `%s` to %s\n' % pair
|
||||||
|
for pair in type(self).merge_method.selection
|
||||||
|
if pair[0] != 'squash'
|
||||||
|
)
|
||||||
for r in self.search([
|
for r in self.search([
|
||||||
('state', '=', 'ready'),
|
('state', '=', 'ready'),
|
||||||
('squash', '=', False),
|
('squash', '=', False),
|
||||||
@ -1180,10 +1204,9 @@ class PullRequests(models.Model):
|
|||||||
self.env['runbot_merge.pull_requests.feedback'].create({
|
self.env['runbot_merge.pull_requests.feedback'].create({
|
||||||
'repository': r.repository.id,
|
'repository': r.repository.id,
|
||||||
'pull_request': r.number,
|
'pull_request': r.number,
|
||||||
'message': "Because this PR has multiple commits, I need to know how to merge it:\n\n" + ''.join(
|
'message': "%sbecause this PR has multiple commits, I need to know how to merge it:\n\n%s" % (
|
||||||
'* `%s` to %s\n' % pair
|
r.ping(),
|
||||||
for pair in type(self).merge_method.selection
|
methods,
|
||||||
if pair[0] != 'squash'
|
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
r.method_warned = True
|
r.method_warned = True
|
||||||
@ -1740,7 +1763,7 @@ class Stagings(models.Model):
|
|||||||
self.env['runbot_merge.pull_requests.feedback'].create({
|
self.env['runbot_merge.pull_requests.feedback'].create({
|
||||||
'repository': pr.repository.id,
|
'repository': pr.repository.id,
|
||||||
'pull_request': pr.number,
|
'pull_request': pr.number,
|
||||||
'message':"Staging failed: %s" % message
|
'message': "%sstaging failed: %s" % (pr.ping(), message),
|
||||||
})
|
})
|
||||||
|
|
||||||
self.batch_ids.write({'active': False})
|
self.batch_ids.write({'active': False})
|
||||||
@ -2042,11 +2065,11 @@ class Batch(models.Model):
|
|||||||
self.env['runbot_merge.pull_requests.feedback'].create({
|
self.env['runbot_merge.pull_requests.feedback'].create({
|
||||||
'repository': pr.repository.id,
|
'repository': pr.repository.id,
|
||||||
'pull_request': pr.number,
|
'pull_request': pr.number,
|
||||||
'message': "We apparently missed an update to this PR "
|
'message': "%swe apparently missed an update to this PR "
|
||||||
"and tried to stage it in a state which "
|
"and tried to stage it in a state which "
|
||||||
"might not have been approved. PR has been "
|
"might not have been approved. PR has been "
|
||||||
"updated to %s, please check and approve or "
|
"updated to %s, please check and approve or "
|
||||||
"re-approve." % new_head
|
"re-approve." % (pr.ping(), new_head)
|
||||||
})
|
})
|
||||||
return self.env['runbot_merge.batch']
|
return self.env['runbot_merge.batch']
|
||||||
|
|
||||||
|
@ -63,7 +63,6 @@ def test_trivial_flow(env, repo, page, users, config):
|
|||||||
]
|
]
|
||||||
assert statuses == [('legal/cla', 'ok'), ('ci/runbot', 'ok')]
|
assert statuses == [('legal/cla', 'ok'), ('ci/runbot', 'ok')]
|
||||||
|
|
||||||
|
|
||||||
with repo:
|
with repo:
|
||||||
pr.post_comment('hansen r+ rebase-merge', config['role_reviewer']['token'])
|
pr.post_comment('hansen r+ rebase-merge', config['role_reviewer']['token'])
|
||||||
assert pr_id.state == 'ready'
|
assert pr_id.state == 'ready'
|
||||||
@ -443,8 +442,8 @@ def test_staging_conflict_first(env, repo, users, config, page):
|
|||||||
assert pr.comments == [
|
assert pr.comments == [
|
||||||
(users['reviewer'], 'hansen r+ rebase-merge'),
|
(users['reviewer'], 'hansen r+ rebase-merge'),
|
||||||
seen(env, pr, users),
|
seen(env, pr, users),
|
||||||
(users['user'], 'Merge method set to rebase and merge, using the PR as merge commit message'),
|
(users['user'], 'Merge method set to rebase and merge, using the PR as merge commit message.'),
|
||||||
(users['user'], re_matches('^Unable to stage PR')),
|
(users['user'], '@%(user)s @%(reviewer)s unable to stage: merge conflict' % users),
|
||||||
]
|
]
|
||||||
|
|
||||||
dangerbox = pr_page(page, pr).cssselect('.alert-danger span')
|
dangerbox = pr_page(page, pr).cssselect('.alert-danger span')
|
||||||
@ -566,8 +565,8 @@ def test_staging_ci_failure_single(env, repo, users, config, page):
|
|||||||
assert pr.comments == [
|
assert pr.comments == [
|
||||||
(users['reviewer'], 'hansen r+ rebase-merge'),
|
(users['reviewer'], 'hansen r+ rebase-merge'),
|
||||||
seen(env, pr, users),
|
seen(env, pr, users),
|
||||||
(users['user'], "Merge method set to rebase and merge, using the PR as merge commit message"),
|
(users['user'], "Merge method set to rebase and merge, using the PR as merge commit message."),
|
||||||
(users['user'], 'Staging failed: ci/runbot')
|
(users['user'], '@%(user)s @%(reviewer)s staging failed: ci/runbot' % users)
|
||||||
]
|
]
|
||||||
|
|
||||||
dangerbox = pr_page(page, pr).cssselect('.alert-danger span')
|
dangerbox = pr_page(page, pr).cssselect('.alert-danger span')
|
||||||
@ -970,9 +969,9 @@ def test_ci_failure_after_review(env, repo, users, config):
|
|||||||
assert prx.comments == [
|
assert prx.comments == [
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
seen(env, prx, users),
|
seen(env, prx, users),
|
||||||
(users['user'], "'ci/runbot' failed on this reviewed PR.".format_map(users)),
|
(users['user'], "@{user} @{reviewer} 'ci/runbot' failed on this reviewed PR.".format_map(users)),
|
||||||
(users['user'], "'legal/cla' failed on this reviewed PR.".format_map(users)),
|
(users['user'], "@{user} @{reviewer} 'legal/cla' failed on this reviewed PR.".format_map(users)),
|
||||||
(users['user'], "'legal/cla' failed on this reviewed PR.".format_map(users)),
|
(users['user'], "@{user} @{reviewer} 'legal/cla' failed on this reviewed PR.".format_map(users)),
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_reopen_merged_pr(env, repo, config, users):
|
def test_reopen_merged_pr(env, repo, config, users):
|
||||||
@ -1016,7 +1015,7 @@ def test_reopen_merged_pr(env, repo, config, users):
|
|||||||
assert prx.comments == [
|
assert prx.comments == [
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
seen(env, prx, users),
|
seen(env, prx, users),
|
||||||
(users['user'], "@%s ya silly goose you can't reopen a PR that's been merged PR." % users['other'])
|
(users['user'], "@%s ya silly goose you can't reopen a merged PR." % users['other'])
|
||||||
]
|
]
|
||||||
|
|
||||||
class TestNoRequiredStatus:
|
class TestNoRequiredStatus:
|
||||||
@ -1212,7 +1211,7 @@ class TestRetry:
|
|||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
(users['reviewer'], 'hansen retry'),
|
(users['reviewer'], 'hansen retry'),
|
||||||
seen(env, prx, users),
|
seen(env, prx, users),
|
||||||
(users['user'], "I'm sorry, @{}. Retry makes no sense when the PR is not in error.".format(users['reviewer'])),
|
(users['user'], "I'm sorry, @{reviewer}: retry makes no sense when the PR is not in error.".format_map(users)),
|
||||||
]
|
]
|
||||||
|
|
||||||
@pytest.mark.parametrize('disabler', ['user', 'other', 'reviewer'])
|
@pytest.mark.parametrize('disabler', ['user', 'other', 'reviewer'])
|
||||||
@ -1408,12 +1407,13 @@ class TestMergeMethod:
|
|||||||
assert prx.comments == [
|
assert prx.comments == [
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
seen(env, prx, users),
|
seen(env, prx, users),
|
||||||
(users['user'], """Because this PR has multiple commits, I need to know how to merge it:
|
(users['user'], """@{user} @{reviewer} because this PR has multiple \
|
||||||
|
commits, I need to know how to merge it:
|
||||||
|
|
||||||
* `merge` to merge directly, using the PR as merge commit message
|
* `merge` to merge directly, using the PR as merge commit message
|
||||||
* `rebase-merge` to rebase and merge, using the PR as merge commit message
|
* `rebase-merge` to rebase and merge, using the PR as merge commit message
|
||||||
* `rebase-ff` to rebase and fast-forward
|
* `rebase-ff` to rebase and fast-forward
|
||||||
"""),
|
""".format_map(users)),
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_pr_method_no_review(self, repo, env, users, config):
|
def test_pr_method_no_review(self, repo, env, users, config):
|
||||||
@ -1453,11 +1453,11 @@ class TestMergeMethod:
|
|||||||
assert prx.comments == [
|
assert prx.comments == [
|
||||||
(users['reviewer'], 'hansen rebase-merge'),
|
(users['reviewer'], 'hansen rebase-merge'),
|
||||||
seen(env, prx, users),
|
seen(env, prx, users),
|
||||||
(users['user'], "Merge method set to rebase and merge, using the PR as merge commit message"),
|
(users['user'], "Merge method set to rebase and merge, using the PR as merge commit message."),
|
||||||
(users['reviewer'], 'hansen merge'),
|
(users['reviewer'], 'hansen merge'),
|
||||||
(users['user'], "Merge method set to merge directly, using the PR as merge commit message"),
|
(users['user'], "Merge method set to merge directly, using the PR as merge commit message."),
|
||||||
(users['reviewer'], 'hansen rebase-ff'),
|
(users['reviewer'], 'hansen rebase-ff'),
|
||||||
(users['user'], "Merge method set to rebase and fast-forward"),
|
(users['user'], "Merge method set to rebase and fast-forward."),
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_pr_rebase_merge(self, repo, env, users, config):
|
def test_pr_rebase_merge(self, repo, env, users, config):
|
||||||
@ -2025,7 +2025,7 @@ Part-of: {pr_id.display_name}"""
|
|||||||
assert pr1.comments == [
|
assert pr1.comments == [
|
||||||
seen(env, pr1, users),
|
seen(env, pr1, users),
|
||||||
(users['reviewer'], 'hansen r+ squash'),
|
(users['reviewer'], 'hansen r+ squash'),
|
||||||
(users['user'], 'Merge method set to squash')
|
(users['user'], 'Merge method set to squash.')
|
||||||
]
|
]
|
||||||
merged_head = repo.commit('master')
|
merged_head = repo.commit('master')
|
||||||
assert merged_head.message == f"""first pr
|
assert merged_head.message == f"""first pr
|
||||||
@ -2049,13 +2049,13 @@ Signed-off-by: {get_partner(env, users["reviewer"]).formatted_email}\
|
|||||||
assert pr2.comments == [
|
assert pr2.comments == [
|
||||||
seen(env, pr2, users),
|
seen(env, pr2, users),
|
||||||
(users['reviewer'], 'hansen r+ squash'),
|
(users['reviewer'], 'hansen r+ squash'),
|
||||||
(users['user'], f"I'm sorry, @{users['reviewer']}. Squash can only be used with a single commit at this time."),
|
(users['user'], f"I'm sorry, @{users['reviewer']}: squash can only be used with a single commit at this time."),
|
||||||
(users['user'], """Because this PR has multiple commits, I need to know how to merge it:
|
(users['user'], """@{user} @{reviewer} because this PR has multiple commits, I need to know how to merge it:
|
||||||
|
|
||||||
* `merge` to merge directly, using the PR as merge commit message
|
* `merge` to merge directly, using the PR as merge commit message
|
||||||
* `rebase-merge` to rebase and merge, using the PR as merge commit message
|
* `rebase-merge` to rebase and merge, using the PR as merge commit message
|
||||||
* `rebase-ff` to rebase and fast-forward
|
* `rebase-ff` to rebase and fast-forward
|
||||||
""")
|
""".format_map(users))
|
||||||
]
|
]
|
||||||
|
|
||||||
@pytest.mark.xfail(reason="removed support for squash- command")
|
@pytest.mark.xfail(reason="removed support for squash- command")
|
||||||
@ -2859,7 +2859,7 @@ class TestReviewing(object):
|
|||||||
(users['user'], "I'm sorry, @{}. I'm afraid I can't do that.".format(users['other'])),
|
(users['user'], "I'm sorry, @{}. I'm afraid I can't do that.".format(users['other'])),
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
(users['user'], "I'm sorry, @{}. This PR is already reviewed, reviewing it again is useless.".format(
|
(users['user'], "I'm sorry, @{}: this PR is already reviewed, reviewing it again is useless.".format(
|
||||||
users['reviewer'])),
|
users['reviewer'])),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -2888,7 +2888,7 @@ class TestReviewing(object):
|
|||||||
assert prx.comments == [
|
assert prx.comments == [
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
seen(env, prx, users),
|
seen(env, prx, users),
|
||||||
(users['user'], "I'm sorry, @{}. You can't review+.".format(users['reviewer'])),
|
(users['user'], "I'm sorry, @{}: you can't review+.".format(users['reviewer'])),
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_self_review_success(self, env, repo, users, config):
|
def test_self_review_success(self, env, repo, users, config):
|
||||||
@ -3044,7 +3044,7 @@ class TestReviewing(object):
|
|||||||
seen(env, pr, users),
|
seen(env, pr, users),
|
||||||
(users['reviewer'], 'hansen delegate+'),
|
(users['reviewer'], 'hansen delegate+'),
|
||||||
(users['user'], 'hansen r+'),
|
(users['user'], 'hansen r+'),
|
||||||
(users['user'], f"I'm sorry, @{users['user']}. I must know your email before you can review PRs. Please contact an administrator."),
|
(users['user'], f"I'm sorry, @{users['user']}: I must know your email before you can review PRs. Please contact an administrator."),
|
||||||
]
|
]
|
||||||
user_partner.fetch_github_email()
|
user_partner.fetch_github_email()
|
||||||
assert user_partner.email
|
assert user_partner.email
|
||||||
@ -3108,9 +3108,9 @@ class TestUnknownPR:
|
|||||||
seen(env, prx, users),
|
seen(env, prx, users),
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
(users['user'], "Sorry, I didn't know about this PR and had to "
|
(users['user'], "I didn't know about this PR and had to "
|
||||||
"retrieve its information, you may have to "
|
"retrieve its information, you may have to "
|
||||||
"re-approve it."),
|
"re-approve it as I didn't see previous commands."),
|
||||||
seen(env, prx, users),
|
seen(env, prx, users),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3161,9 +3161,9 @@ class TestUnknownPR:
|
|||||||
assert pr.comments == [
|
assert pr.comments == [
|
||||||
seen(env, pr, users),
|
seen(env, pr, users),
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
(users['user'], "Sorry, I didn't know about this PR and had to "
|
(users['user'], "I didn't know about this PR and had to retrieve "
|
||||||
"retrieve its information, you may have to "
|
"its information, you may have to re-approve it "
|
||||||
"re-approve it."),
|
"as I didn't see previous commands."),
|
||||||
seen(env, pr, users),
|
seen(env, pr, users),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -3189,7 +3189,7 @@ class TestUnknownPR:
|
|||||||
assert prx.comments == [
|
assert prx.comments == [
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
(users['user'], "This PR targets the un-managed branch %s:branch, it can not be merged." % repo.name),
|
(users['user'], "This PR targets the un-managed branch %s:branch, it can not be merged." % repo.name),
|
||||||
(users['user'], "I'm sorry. Branch `branch` is not within my remit."),
|
(users['user'], "Branch `branch` is not within my remit, imma just ignore it."),
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_rplus_review_unmanaged(self, env, repo, users, config):
|
def test_rplus_review_unmanaged(self, env, repo, users, config):
|
||||||
@ -3586,7 +3586,7 @@ class TestFeedback:
|
|||||||
assert prx.comments == [
|
assert prx.comments == [
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
seen(env, prx, users),
|
seen(env, prx, users),
|
||||||
(users['user'], "'ci/runbot' failed on this reviewed PR.")
|
(users['user'], "@%(user)s @%(reviewer)s 'ci/runbot' failed on this reviewed PR." % users)
|
||||||
]
|
]
|
||||||
|
|
||||||
def test_review_failed(self, repo, env, users, config):
|
def test_review_failed(self, repo, env, users, config):
|
||||||
@ -3616,8 +3616,9 @@ class TestFeedback:
|
|||||||
assert prx.comments == [
|
assert prx.comments == [
|
||||||
seen(env, prx, users),
|
seen(env, prx, users),
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
(users['user'], "@%s, you may want to rebuild or fix this PR as it has failed CI." % users['reviewer'])
|
(users['user'], "@%s you may want to rebuild or fix this PR as it has failed CI." % users['reviewer'])
|
||||||
]
|
]
|
||||||
|
|
||||||
class TestInfrastructure:
|
class TestInfrastructure:
|
||||||
def test_protection(self, repo):
|
def test_protection(self, repo):
|
||||||
""" force-pushing on a protected ref should fail
|
""" force-pushing on a protected ref should fail
|
||||||
|
@ -429,7 +429,7 @@ def test_merge_fail(env, project, repo_a, repo_b, users, config):
|
|||||||
assert pr1b.comments == [
|
assert pr1b.comments == [
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
seen(env, pr1b, users),
|
seen(env, pr1b, users),
|
||||||
(users['user'], re_matches('^Unable to stage PR')),
|
(users['user'], '@%(user)s @%(reviewer)s unable to stage: merge conflict' % users),
|
||||||
]
|
]
|
||||||
other = to_pr(env, pr1a)
|
other = to_pr(env, pr1a)
|
||||||
reviewer = get_partner(env, users["reviewer"]).formatted_email
|
reviewer = get_partner(env, users["reviewer"]).formatted_email
|
||||||
@ -527,14 +527,22 @@ class TestCompanionsNotReady:
|
|||||||
assert p_a.comments == [
|
assert p_a.comments == [
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
seen(env, p_a, users),
|
seen(env, p_a, users),
|
||||||
(users['user'], "Linked pull request(s) %s#%d not ready. Linked PRs are not staged until all of them are ready." % (repo_b.name, p_b.number)),
|
(users['user'], "@%s @%s linked pull request(s) %s not ready. Linked PRs are not staged until all of them are ready." % (
|
||||||
|
users['user'],
|
||||||
|
users['reviewer'],
|
||||||
|
pr_b.display_name,
|
||||||
|
)),
|
||||||
]
|
]
|
||||||
# ensure the message is only sent once per PR
|
# ensure the message is only sent once per PR
|
||||||
env.run_crons('runbot_merge.check_linked_prs_status')
|
env.run_crons('runbot_merge.check_linked_prs_status')
|
||||||
assert p_a.comments == [
|
assert p_a.comments == [
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
seen(env, p_a, users),
|
seen(env, p_a, users),
|
||||||
(users['user'], "Linked pull request(s) %s#%d not ready. Linked PRs are not staged until all of them are ready." % (repo_b.name, p_b.number)),
|
(users['user'], "@%s @%s linked pull request(s) %s not ready. Linked PRs are not staged until all of them are ready." % (
|
||||||
|
users['user'],
|
||||||
|
users['reviewer'],
|
||||||
|
pr_b.display_name,
|
||||||
|
)),
|
||||||
]
|
]
|
||||||
assert p_b.comments == [seen(env, p_b, users)]
|
assert p_b.comments == [seen(env, p_b, users)]
|
||||||
|
|
||||||
@ -570,7 +578,8 @@ class TestCompanionsNotReady:
|
|||||||
assert pr_b.comments == [
|
assert pr_b.comments == [
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
seen(env, pr_b, users),
|
seen(env, pr_b, users),
|
||||||
(users['user'], "Linked pull request(s) %s#%d, %s#%d not ready. Linked PRs are not staged until all of them are ready." % (
|
(users['user'], "@%s @%s linked pull request(s) %s#%d, %s#%d not ready. Linked PRs are not staged until all of them are ready." % (
|
||||||
|
users['user'], users['reviewer'],
|
||||||
repo_a.name, pr_a.number,
|
repo_a.name, pr_a.number,
|
||||||
repo_c.name, pr_c.number
|
repo_c.name, pr_c.number
|
||||||
))
|
))
|
||||||
@ -609,7 +618,8 @@ class TestCompanionsNotReady:
|
|||||||
assert pr_b.comments == [
|
assert pr_b.comments == [
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
seen(env, pr_b, users),
|
seen(env, pr_b, users),
|
||||||
(users['user'], "Linked pull request(s) %s#%d not ready. Linked PRs are not staged until all of them are ready." % (
|
(users['user'], "@%s @%s linked pull request(s) %s#%d not ready. Linked PRs are not staged until all of them are ready." % (
|
||||||
|
users['user'], users['reviewer'],
|
||||||
repo_a.name, pr_a.number
|
repo_a.name, pr_a.number
|
||||||
))
|
))
|
||||||
]
|
]
|
||||||
@ -617,7 +627,8 @@ class TestCompanionsNotReady:
|
|||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
seen(env, pr_c, users),
|
seen(env, pr_c, users),
|
||||||
(users['user'],
|
(users['user'],
|
||||||
"Linked pull request(s) %s#%d not ready. Linked PRs are not staged until all of them are ready." % (
|
"@%s @%s linked pull request(s) %s#%d not ready. Linked PRs are not staged until all of them are ready." % (
|
||||||
|
users['user'], users['reviewer'],
|
||||||
repo_a.name, pr_a.number
|
repo_a.name, pr_a.number
|
||||||
))
|
))
|
||||||
]
|
]
|
||||||
@ -655,7 +666,10 @@ def test_other_failed(env, project, repo_a, repo_b, users, config):
|
|||||||
assert pr_a.comments == [
|
assert pr_a.comments == [
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
seen(env, pr_a, users),
|
seen(env, pr_a, users),
|
||||||
(users['user'], 'Staging failed: ci/runbot on %s (view more at http://example.org/b)' % sth)
|
(users['user'], '@%s @%s staging failed: ci/runbot on %s (view more at http://example.org/b)' % (
|
||||||
|
users['user'], users['reviewer'],
|
||||||
|
sth
|
||||||
|
))
|
||||||
]
|
]
|
||||||
|
|
||||||
class TestMultiBatches:
|
class TestMultiBatches:
|
||||||
|
@ -89,7 +89,7 @@ def test_basic(env, project, make_repo, users, setreviewers, config):
|
|||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
seen(env, pr, users),
|
seen(env, pr, users),
|
||||||
(users['reviewer'], 'hansen override=l/int'),
|
(users['reviewer'], 'hansen override=l/int'),
|
||||||
(users['user'], "I'm sorry, @{}. You are not allowed to override this status.".format(users['reviewer'])),
|
(users['user'], "I'm sorry, @{}: you are not allowed to override this status.".format(users['reviewer'])),
|
||||||
(users['other'], "hansen override=l/int"),
|
(users['other'], "hansen override=l/int"),
|
||||||
]
|
]
|
||||||
assert pr_id.statuses == '{}'
|
assert pr_id.statuses == '{}'
|
||||||
|
Loading…
Reference in New Issue
Block a user