mirror of
https://github.com/odoo/runbot.git
synced 2025-03-27 13:25:47 +07:00
[FIX] runbot_merge: handling of staging cancellation
Move staging cancellation to the batch, remove its (complicated) handling from the PRs. This loses some precision in the cancellation message, but that could likely be recovered (in part) by adding more precise checks & diagnostic extractions in the compute.
This commit is contained in:
parent
ad1d590d9c
commit
a6a37f8896
@ -1175,6 +1175,9 @@ class Model:
|
|||||||
def __len__(self):
|
def __len__(self):
|
||||||
return len(self._ids)
|
return len(self._ids)
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash((self._model, frozenset(self._ids)))
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
if not isinstance(other, Model):
|
if not isinstance(other, Model):
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
@ -92,4 +92,11 @@ class Batch(models.Model):
|
|||||||
p.display_name for p in unready
|
p.display_name for p in unready
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
if batch.blocked and batch.cancel_staging:
|
||||||
|
batch.target.active_staging_id.cancel(
|
||||||
|
'unstaged by %s on %s (%s)',
|
||||||
|
self.env.user.login,
|
||||||
|
batch,
|
||||||
|
', '.join(batch.prs.mapped('display_name')),
|
||||||
|
)
|
||||||
batch.blocked = False
|
batch.blocked = False
|
||||||
|
@ -281,6 +281,8 @@ class Parser:
|
|||||||
return Priority.ALONE
|
return Priority.ALONE
|
||||||
|
|
||||||
def parse_cancel(self) -> CancelStaging:
|
def parse_cancel(self) -> CancelStaging:
|
||||||
|
self.assert_next('=')
|
||||||
|
self.assert_next('staging')
|
||||||
return CancelStaging()
|
return CancelStaging()
|
||||||
|
|
||||||
def parse_skipchecks(self) -> SkipChecks:
|
def parse_skipchecks(self) -> SkipChecks:
|
||||||
|
@ -700,7 +700,7 @@ class PullRequests(models.Model):
|
|||||||
else:
|
else:
|
||||||
msg = self._approve(author, login)
|
msg = self._approve(author, login)
|
||||||
case commands.Reject() if is_author:
|
case commands.Reject() if is_author:
|
||||||
if cancellers := self.batch_id.cancel_staging:
|
if self.batch_id.cancel_staging:
|
||||||
self.batch_id.cancel_staging = False
|
self.batch_id.cancel_staging = False
|
||||||
if self.batch_id.skipchecks or self.reviewed_by:
|
if self.batch_id.skipchecks or self.reviewed_by:
|
||||||
if self.error:
|
if self.error:
|
||||||
@ -756,14 +756,11 @@ class PullRequests(models.Model):
|
|||||||
p.reviewed_by = author
|
p.reviewed_by = author
|
||||||
case commands.CancelStaging() if is_admin:
|
case commands.CancelStaging() if is_admin:
|
||||||
self.batch_id.cancel_staging = True
|
self.batch_id.cancel_staging = True
|
||||||
# FIXME: remove this when skipchecks properly affects state,
|
if not self.batch_id.blocked:
|
||||||
# maybe: staging cancellation should then only occur
|
self.target.active_staging_id.cancel(
|
||||||
# when a cancel_staging PR transitions to ready, or
|
"Unstaged by %s on %s",
|
||||||
# a ready PR is flagged as cancelling staging
|
author.github_login, self.display_name,
|
||||||
self.target.active_staging_id.cancel(
|
)
|
||||||
"Unstaged by %s on %s",
|
|
||||||
author.github_login, self.display_name,
|
|
||||||
)
|
|
||||||
case commands.Override(statuses):
|
case commands.Override(statuses):
|
||||||
for status in statuses:
|
for status in statuses:
|
||||||
overridable = author.override_rights\
|
overridable = author.override_rights\
|
||||||
@ -1081,12 +1078,6 @@ class PullRequests(models.Model):
|
|||||||
def write(self, vals):
|
def write(self, vals):
|
||||||
if vals.get('squash'):
|
if vals.get('squash'):
|
||||||
vals['merge_method'] = False
|
vals['merge_method'] = False
|
||||||
fields = []
|
|
||||||
canceler = vals.get('cancel_staging') or any(p.cancel_staging for p in self)
|
|
||||||
if canceler:
|
|
||||||
fields.append('state')
|
|
||||||
fields.append('skipchecks')
|
|
||||||
prev = {pr.id: {field: pr[field] for field in fields} for pr in self}
|
|
||||||
|
|
||||||
# when explicitly marking a PR as ready
|
# when explicitly marking a PR as ready
|
||||||
if vals.get('state') == 'ready':
|
if vals.get('state') == 'ready':
|
||||||
@ -1144,21 +1135,6 @@ class PullRequests(models.Model):
|
|||||||
if newhead:
|
if newhead:
|
||||||
c = self.env['runbot_merge.commit'].search([('sha', '=', newhead)])
|
c = self.env['runbot_merge.commit'].search([('sha', '=', newhead)])
|
||||||
self._validate(c.statuses or '{}')
|
self._validate(c.statuses or '{}')
|
||||||
|
|
||||||
if canceler:
|
|
||||||
for pr in self:
|
|
||||||
if not pr.cancel_staging:
|
|
||||||
continue
|
|
||||||
|
|
||||||
old = prev[pr.id]
|
|
||||||
def ready(pr):
|
|
||||||
return pr['state'] == 'ready'\
|
|
||||||
or (pr['skipchecks'] and pr['state'] != 'error')
|
|
||||||
if ready(pr) and not ready(old):
|
|
||||||
if old['state'] == 'error': # error -> ready gets a bespoke message
|
|
||||||
pr.target.active_staging_id.cancel(f"retrying {pr.display_name}")
|
|
||||||
else:
|
|
||||||
pr.target.active_staging_id.cancel(f"{pr.display_name} became ready")
|
|
||||||
return w
|
return w
|
||||||
|
|
||||||
def _check_linked_prs_statuses(self, commit=False):
|
def _check_linked_prs_statuses(self, commit=False):
|
||||||
|
@ -9,6 +9,7 @@ import functools
|
|||||||
import operator
|
import operator
|
||||||
import time
|
import time
|
||||||
import xmlrpc.client
|
import xmlrpc.client
|
||||||
|
from itertools import repeat
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import requests
|
import requests
|
||||||
@ -1468,3 +1469,65 @@ def test_freeze_conflict(env, project, repo_a, repo_b, repo_c, users, config):
|
|||||||
with pytest.raises(AssertionError) as e:
|
with pytest.raises(AssertionError) as e:
|
||||||
repo_b.get_ref('heads/1.1')
|
repo_b.get_ref('heads/1.1')
|
||||||
assert e.value.args[0].startswith("Not Found")
|
assert e.value.args[0].startswith("Not Found")
|
||||||
|
|
||||||
|
def test_cancel_staging(env, project, repo_a, repo_b, users, config):
|
||||||
|
"""If a batch is flagged as staging cancelling (from any PR), the staging
|
||||||
|
should get cancelled if and when the batch transitions to unblocked
|
||||||
|
"""
|
||||||
|
with repo_a, repo_b:
|
||||||
|
make_branch(repo_a, 'master', 'initial', {'a': '1'})
|
||||||
|
make_branch(repo_b, 'master', 'initial', {'b': '1'})
|
||||||
|
|
||||||
|
pr_a = make_pr(repo_a, 'batch', [{'a': '2'}], user=config['role_user']['token'], statuses=[], reviewer=None)
|
||||||
|
pr_b = make_pr(repo_b, 'batch', [{'b': '2'}], user=config['role_user']['token'], statuses=[], reviewer=None)
|
||||||
|
pr_lone = make_pr(
|
||||||
|
repo_a,
|
||||||
|
"C",
|
||||||
|
[{'c': '1'}],
|
||||||
|
user=config['role_user']['token'],
|
||||||
|
reviewer=config['role_reviewer']['token'],
|
||||||
|
)
|
||||||
|
env.run_crons()
|
||||||
|
|
||||||
|
a_id, b_id, lone_id = map(to_pr, repeat(env), [pr_a, pr_b, pr_lone])
|
||||||
|
assert lone_id.staging_id
|
||||||
|
st = lone_id.staging_id
|
||||||
|
|
||||||
|
with repo_a:
|
||||||
|
pr_a.post_comment("hansen cancel=staging", config['role_reviewer']['token'])
|
||||||
|
assert a_id.state == 'opened'
|
||||||
|
assert a_id.cancel_staging
|
||||||
|
assert b_id.cancel_staging
|
||||||
|
assert lone_id.staging_id == st
|
||||||
|
with repo_a:
|
||||||
|
pr_a.post_comment('hansen r+', config['role_reviewer']['token'])
|
||||||
|
assert a_id.state == 'approved'
|
||||||
|
assert lone_id.staging_id == st
|
||||||
|
with repo_a:
|
||||||
|
repo_a.post_status(a_id.head, 'success', 'ci/runbot')
|
||||||
|
repo_a.post_status(a_id.head, 'success', 'legal/cla')
|
||||||
|
env.run_crons()
|
||||||
|
assert a_id.state == 'ready'
|
||||||
|
assert lone_id.staging_id == st
|
||||||
|
|
||||||
|
assert b_id.state == 'opened'
|
||||||
|
with repo_b:
|
||||||
|
pr_b.post_comment('hansen r+', config['role_reviewer']['token'])
|
||||||
|
assert b_id.state == 'approved'
|
||||||
|
assert lone_id.staging_id == st
|
||||||
|
with repo_b:
|
||||||
|
repo_b.post_status(b_id.head, 'success', 'ci/runbot')
|
||||||
|
repo_b.post_status(b_id.head, 'success', 'legal/cla')
|
||||||
|
assert b_id.state == 'approved'
|
||||||
|
assert lone_id.staging_id == st
|
||||||
|
env.run_crons()
|
||||||
|
assert b_id.state == 'ready'
|
||||||
|
# should have cancelled the staging, picked a and b, and re-staged the
|
||||||
|
# entire thing
|
||||||
|
assert lone_id.staging_id != st
|
||||||
|
|
||||||
|
assert len({
|
||||||
|
lone_id.staging_id.id,
|
||||||
|
a_id.staging_id.id,
|
||||||
|
b_id.staging_id.id,
|
||||||
|
}) == 1
|
||||||
|
Loading…
Reference in New Issue
Block a user