mirror of
https://github.com/odoo/runbot.git
synced 2025-03-27 13:25:47 +07:00
[IMP] runbot_merge: small reorg of main models file
To prep for the addition of the freeze wizard: * move projects out of `pull_requests.py` * then realize half the methods there have no relation to projects and move them to more relevant places in `pull_requests.py` * update corresponding crons (and tests using those crons) as the methods have changed model, and the cron definitions thus need to be updated * split update to labels out of sending feedback comments while at it: labels are not used much during tests so their manipulation can be avoided; and labels are not as urgent as feedback so the crons can be quite a bit slower * move the project view out of `mergebot.xml` as well
This commit is contained in:
parent
9f7cfcc7ac
commit
f13c60a018
@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
'data/merge_cron.xml',
|
'data/merge_cron.xml',
|
||||||
'views/res_partner.xml',
|
'views/res_partner.xml',
|
||||||
|
'views/runbot_merge_project.xml',
|
||||||
'views/mergebot.xml',
|
'views/mergebot.xml',
|
||||||
'views/templates.xml',
|
'views/templates.xml',
|
||||||
],
|
],
|
||||||
|
@ -21,19 +21,29 @@
|
|||||||
</record>
|
</record>
|
||||||
<record model="ir.cron" id="feedback_cron">
|
<record model="ir.cron" id="feedback_cron">
|
||||||
<field name="name">Send feedback to PR</field>
|
<field name="name">Send feedback to PR</field>
|
||||||
<field name="model_id" ref="model_runbot_merge_project"/>
|
<field name="model_id" ref="model_runbot_merge_pull_requests_feedback"/>
|
||||||
<field name="state">code</field>
|
<field name="state">code</field>
|
||||||
<field name="code">model._send_feedback()</field>
|
<field name="code">model._send()</field>
|
||||||
<field name="interval_number">1</field>
|
<field name="interval_number">1</field>
|
||||||
<field name="interval_type">minutes</field>
|
<field name="interval_type">minutes</field>
|
||||||
<field name="numbercall">-1</field>
|
<field name="numbercall">-1</field>
|
||||||
<field name="doall" eval="False"/>
|
<field name="doall" eval="False"/>
|
||||||
</record>
|
</record>
|
||||||
|
<record model="ir.cron" id="labels_cron">
|
||||||
|
<field name="name">Update labels on PR</field>
|
||||||
|
<field name="model_id" ref="model_runbot_merge_pull_requests_tagging"/>
|
||||||
|
<field name="state">code</field>
|
||||||
|
<field name="code">model._send()</field>
|
||||||
|
<field name="interval_number">10</field>
|
||||||
|
<field name="interval_type">minutes</field>
|
||||||
|
<field name="numbercall">-1</field>
|
||||||
|
<field name="doall" eval="False"/>
|
||||||
|
</record>
|
||||||
<record model="ir.cron" id="fetch_prs_cron">
|
<record model="ir.cron" id="fetch_prs_cron">
|
||||||
<field name="name">Check for PRs to fetch</field>
|
<field name="name">Check for PRs to fetch</field>
|
||||||
<field name="model_id" ref="model_runbot_merge_project"/>
|
<field name="model_id" ref="model_runbot_merge_fetch_job"/>
|
||||||
<field name="state">code</field>
|
<field name="state">code</field>
|
||||||
<field name="code">model._check_fetch(True)</field>
|
<field name="code">model._check(True)</field>
|
||||||
<field name="interval_number">1</field>
|
<field name="interval_number">1</field>
|
||||||
<field name="interval_type">minutes</field>
|
<field name="interval_type">minutes</field>
|
||||||
<field name="numbercall">-1</field>
|
<field name="numbercall">-1</field>
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
from . import res_partner
|
from . import res_partner
|
||||||
|
from . import project
|
||||||
from . import pull_requests
|
from . import pull_requests
|
||||||
|
83
runbot_merge/models/project.py
Normal file
83
runbot_merge/models/project.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import logging
|
||||||
|
import re
|
||||||
|
|
||||||
|
from odoo import models, fields
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
class Project(models.Model):
|
||||||
|
_name = _description = 'runbot_merge.project'
|
||||||
|
|
||||||
|
name = fields.Char(required=True, index=True)
|
||||||
|
repo_ids = fields.One2many(
|
||||||
|
'runbot_merge.repository', 'project_id',
|
||||||
|
help="Repos included in that project, they'll be staged together. "\
|
||||||
|
"*Not* to be used for cross-repo dependencies (that is to be handled by the CI)"
|
||||||
|
)
|
||||||
|
branch_ids = fields.One2many(
|
||||||
|
'runbot_merge.branch', 'project_id',
|
||||||
|
context={'active_test': False},
|
||||||
|
help="Branches of all project's repos which are managed by the merge bot. Also "\
|
||||||
|
"target branches of PR this project handles."
|
||||||
|
)
|
||||||
|
|
||||||
|
ci_timeout = fields.Integer(
|
||||||
|
default=60, required=True,
|
||||||
|
help="Delay (in minutes) before a staging is considered timed out and failed"
|
||||||
|
)
|
||||||
|
|
||||||
|
github_token = fields.Char("Github Token", required=True)
|
||||||
|
github_prefix = fields.Char(
|
||||||
|
required=True,
|
||||||
|
default="hanson", # mergebot du bot du bot du~
|
||||||
|
help="Prefix (~bot name) used when sending commands from PR "
|
||||||
|
"comments e.g. [hanson retry] or [hanson r+ p=1]"
|
||||||
|
)
|
||||||
|
|
||||||
|
batch_limit = fields.Integer(
|
||||||
|
default=8, help="Maximum number of PRs staged together")
|
||||||
|
|
||||||
|
secret = fields.Char(
|
||||||
|
help="Webhook secret. If set, will be checked against the signature "
|
||||||
|
"of (valid) incoming webhook signatures, failing signatures "
|
||||||
|
"will lead to webhook rejection. Should only use ASCII."
|
||||||
|
)
|
||||||
|
|
||||||
|
def _check_stagings(self, commit=False):
|
||||||
|
for branch in self.search([]).mapped('branch_ids').filtered('active'):
|
||||||
|
staging = branch.active_staging_id
|
||||||
|
if not staging:
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
with self.env.cr.savepoint():
|
||||||
|
staging.check_status()
|
||||||
|
except Exception:
|
||||||
|
_logger.exception("Failed to check staging for branch %r (staging %s)",
|
||||||
|
branch.name, staging)
|
||||||
|
else:
|
||||||
|
if commit:
|
||||||
|
self.env.cr.commit()
|
||||||
|
|
||||||
|
def _create_stagings(self, commit=False):
|
||||||
|
for branch in self.search([]).mapped('branch_ids').filtered('active'):
|
||||||
|
if not branch.active_staging_id:
|
||||||
|
try:
|
||||||
|
with self.env.cr.savepoint():
|
||||||
|
branch.try_staging()
|
||||||
|
except Exception:
|
||||||
|
_logger.exception("Failed to create staging for branch %r", branch.name)
|
||||||
|
else:
|
||||||
|
if commit:
|
||||||
|
self.env.cr.commit()
|
||||||
|
|
||||||
|
def _find_commands(self, comment):
|
||||||
|
return re.findall(
|
||||||
|
'^\s*[@|#]?{}:? (.*)$'.format(self.github_prefix),
|
||||||
|
comment, re.MULTILINE | re.IGNORECASE)
|
||||||
|
|
||||||
|
def _has_branch(self, name):
|
||||||
|
self.env.cr.execute("""
|
||||||
|
SELECT 1 FROM runbot_merge_branch
|
||||||
|
WHERE project_id = %s AND name = %s
|
||||||
|
LIMIT 1
|
||||||
|
""", (self.id, name))
|
||||||
|
return bool(self.env.cr.rowcount)
|
@ -29,207 +29,7 @@ from .. import github, exceptions, controllers, utils
|
|||||||
WAIT_FOR_VISIBILITY = [10, 10, 10, 10]
|
WAIT_FOR_VISIBILITY = [10, 10, 10, 10]
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
class Project(models.Model):
|
|
||||||
_name = _description = 'runbot_merge.project'
|
|
||||||
|
|
||||||
name = fields.Char(required=True, index=True)
|
|
||||||
repo_ids = fields.One2many(
|
|
||||||
'runbot_merge.repository', 'project_id',
|
|
||||||
help="Repos included in that project, they'll be staged together. "\
|
|
||||||
"*Not* to be used for cross-repo dependencies (that is to be handled by the CI)"
|
|
||||||
)
|
|
||||||
branch_ids = fields.One2many(
|
|
||||||
'runbot_merge.branch', 'project_id',
|
|
||||||
context={'active_test': False},
|
|
||||||
help="Branches of all project's repos which are managed by the merge bot. Also "\
|
|
||||||
"target branches of PR this project handles."
|
|
||||||
)
|
|
||||||
|
|
||||||
ci_timeout = fields.Integer(
|
|
||||||
default=60, required=True,
|
|
||||||
help="Delay (in minutes) before a staging is considered timed out and failed"
|
|
||||||
)
|
|
||||||
|
|
||||||
github_token = fields.Char("Github Token", required=True)
|
|
||||||
github_prefix = fields.Char(
|
|
||||||
required=True,
|
|
||||||
default="hanson", # mergebot du bot du bot du~
|
|
||||||
help="Prefix (~bot name) used when sending commands from PR "
|
|
||||||
"comments e.g. [hanson retry] or [hanson r+ p=1]"
|
|
||||||
)
|
|
||||||
|
|
||||||
batch_limit = fields.Integer(
|
|
||||||
default=8, help="Maximum number of PRs staged together")
|
|
||||||
|
|
||||||
secret = fields.Char(
|
|
||||||
help="Webhook secret. If set, will be checked against the signature "
|
|
||||||
"of (valid) incoming webhook signatures, failing signatures "
|
|
||||||
"will lead to webhook rejection. Should only use ASCII."
|
|
||||||
)
|
|
||||||
|
|
||||||
def _check_stagings(self, commit=False):
|
|
||||||
for branch in self.search([]).mapped('branch_ids').filtered('active'):
|
|
||||||
staging = branch.active_staging_id
|
|
||||||
if not staging:
|
|
||||||
continue
|
|
||||||
try:
|
|
||||||
with self.env.cr.savepoint():
|
|
||||||
staging.check_status()
|
|
||||||
except Exception:
|
|
||||||
_logger.exception("Failed to check staging for branch %r (staging %s)",
|
|
||||||
branch.name, staging)
|
|
||||||
else:
|
|
||||||
if commit:
|
|
||||||
self.env.cr.commit()
|
|
||||||
|
|
||||||
def _create_stagings(self, commit=False):
|
|
||||||
for branch in self.search([]).mapped('branch_ids').filtered('active'):
|
|
||||||
if not branch.active_staging_id:
|
|
||||||
try:
|
|
||||||
with self.env.cr.savepoint():
|
|
||||||
branch.try_staging()
|
|
||||||
except Exception:
|
|
||||||
_logger.exception("Failed to create staging for branch %r", branch.name)
|
|
||||||
else:
|
|
||||||
if commit:
|
|
||||||
self.env.cr.commit()
|
|
||||||
|
|
||||||
def _send_feedback(self):
|
|
||||||
Repos = self.env['runbot_merge.repository']
|
|
||||||
ghs = {}
|
|
||||||
# noinspection SqlResolve
|
|
||||||
self.env.cr.execute("""
|
|
||||||
SELECT
|
|
||||||
t.repository as repo_id,
|
|
||||||
t.pull_request as pr_number,
|
|
||||||
array_agg(t.id) as ids,
|
|
||||||
array_agg(t.tags_remove::json) as to_remove,
|
|
||||||
array_agg(t.tags_add::json) as to_add
|
|
||||||
FROM runbot_merge_pull_requests_tagging t
|
|
||||||
GROUP BY t.repository, t.pull_request
|
|
||||||
""")
|
|
||||||
to_remove = []
|
|
||||||
for repo_id, pr, ids, remove, add in self.env.cr.fetchall():
|
|
||||||
repo = Repos.browse(repo_id)
|
|
||||||
|
|
||||||
gh = ghs.get(repo)
|
|
||||||
if not gh:
|
|
||||||
gh = ghs[repo] = repo.github()
|
|
||||||
|
|
||||||
# fold all grouped PRs'
|
|
||||||
tags_remove, tags_add = set(), set()
|
|
||||||
for minus, plus in zip(remove, add):
|
|
||||||
tags_remove.update(minus)
|
|
||||||
# need to remove minuses from to_add in case we get e.g.
|
|
||||||
# -foo +bar; -bar +baz, if we don't remove the minus, we'll end
|
|
||||||
# up with -foo +bar +baz instead of -foo +baz
|
|
||||||
tags_add.difference_update(minus)
|
|
||||||
tags_add.update(plus)
|
|
||||||
|
|
||||||
try:
|
|
||||||
gh.change_tags(pr, tags_remove, tags_add)
|
|
||||||
except Exception:
|
|
||||||
_logger.exception(
|
|
||||||
"Error while trying to change the tags of %s#%s from %s to %s",
|
|
||||||
repo.name, pr, remove, add,
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
to_remove.extend(ids)
|
|
||||||
self.env['runbot_merge.pull_requests.tagging'].browse(to_remove).unlink()
|
|
||||||
|
|
||||||
to_remove = []
|
|
||||||
for f in self.env['runbot_merge.pull_requests.feedback'].search([]):
|
|
||||||
repo = f.repository
|
|
||||||
gh = ghs.get((repo, f.token_field))
|
|
||||||
if not gh:
|
|
||||||
gh = ghs[(repo, f.token_field)] = repo.github(f.token_field)
|
|
||||||
|
|
||||||
try:
|
|
||||||
message = f.message
|
|
||||||
if f.close:
|
|
||||||
gh.close(f.pull_request)
|
|
||||||
try:
|
|
||||||
data = json.loads(message or '')
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
pr_to_notify = self.env['runbot_merge.pull_requests'].search([
|
|
||||||
('repository', '=', repo.id),
|
|
||||||
('number', '=', f.pull_request),
|
|
||||||
])
|
|
||||||
if pr_to_notify:
|
|
||||||
self._notify_pr_merged(gh, pr_to_notify, data)
|
|
||||||
message = None
|
|
||||||
if message:
|
|
||||||
gh.comment(f.pull_request, message)
|
|
||||||
except Exception:
|
|
||||||
_logger.exception(
|
|
||||||
"Error while trying to %s %s#%s (%s)",
|
|
||||||
'close' if f.close else 'send a comment to',
|
|
||||||
repo.name, f.pull_request,
|
|
||||||
utils.shorten(f.message, 200)
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
to_remove.append(f.id)
|
|
||||||
self.env['runbot_merge.pull_requests.feedback'].browse(to_remove).unlink()
|
|
||||||
|
|
||||||
def _notify_pr_merged(self, gh, pr, payload):
|
|
||||||
deployment = gh('POST', 'deployments', json={
|
|
||||||
'ref': pr.head, 'environment': 'merge',
|
|
||||||
'description': "Merge %s into %s" % (pr, pr.target.name),
|
|
||||||
'task': 'merge',
|
|
||||||
'auto_merge': False,
|
|
||||||
'required_contexts': [],
|
|
||||||
}).json()
|
|
||||||
gh('POST', 'deployments/{}/statuses'.format(deployment['id']), json={
|
|
||||||
'state': 'success',
|
|
||||||
'target_url': 'https://github.com/{}/commit/{}'.format(
|
|
||||||
pr.repository.name,
|
|
||||||
payload['sha'],
|
|
||||||
),
|
|
||||||
'description': "Merged %s in %s at %s" % (
|
|
||||||
pr, pr.target.name, payload['sha']
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
def is_timed_out(self, staging):
|
|
||||||
return fields.Datetime.from_string(staging.timeout_limit) < datetime.datetime.now()
|
|
||||||
|
|
||||||
def _check_fetch(self, commit=False):
|
|
||||||
"""
|
|
||||||
:param bool commit: commit after each fetch has been executed
|
|
||||||
"""
|
|
||||||
while True:
|
|
||||||
f = self.env['runbot_merge.fetch_job'].search([], limit=1)
|
|
||||||
if not f:
|
|
||||||
return
|
|
||||||
|
|
||||||
self.env.cr.execute("SAVEPOINT runbot_merge_before_fetch")
|
|
||||||
try:
|
|
||||||
f.repository._load_pr(f.number)
|
|
||||||
except Exception:
|
|
||||||
self.env.cr.execute("ROLLBACK TO SAVEPOINT runbot_merge_before_fetch")
|
|
||||||
_logger.exception("Failed to load pr %s, skipping it", f.number)
|
|
||||||
finally:
|
|
||||||
self.env.cr.execute("RELEASE SAVEPOINT runbot_merge_before_fetch")
|
|
||||||
|
|
||||||
# commit after each fetched PR
|
|
||||||
f.active = False
|
|
||||||
if commit:
|
|
||||||
self.env.cr.commit()
|
|
||||||
|
|
||||||
def _find_commands(self, comment):
|
|
||||||
return re.findall(
|
|
||||||
'^\s*[@|#]?{}:? (.*)$'.format(self.github_prefix),
|
|
||||||
comment, re.MULTILINE | re.IGNORECASE)
|
|
||||||
|
|
||||||
def _has_branch(self, name):
|
|
||||||
self.env.cr.execute("""
|
|
||||||
SELECT 1 FROM runbot_merge_branch
|
|
||||||
WHERE project_id = %s AND name = %s
|
|
||||||
LIMIT 1
|
|
||||||
""", (self.id, name))
|
|
||||||
return bool(self.env.cr.rowcount)
|
|
||||||
|
|
||||||
class StatusConfiguration(models.Model):
|
class StatusConfiguration(models.Model):
|
||||||
_name = 'runbot_merge.repository.status'
|
_name = 'runbot_merge.repository.status'
|
||||||
@ -1179,6 +979,25 @@ class PullRequests(models.Model):
|
|||||||
self.previous_failure = json.dumps(prev)
|
self.previous_failure = json.dumps(prev)
|
||||||
self._notify_ci_failed(ci)
|
self._notify_ci_failed(ci)
|
||||||
|
|
||||||
|
def _notify_merged(self, gh, payload):
|
||||||
|
deployment = gh('POST', 'deployments', json={
|
||||||
|
'ref': self.head, 'environment': 'merge',
|
||||||
|
'description': "Merge %s into %s" % (self.display_name, self.target.name),
|
||||||
|
'task': 'merge',
|
||||||
|
'auto_merge': False,
|
||||||
|
'required_contexts': [],
|
||||||
|
}).json()
|
||||||
|
gh('POST', 'deployments/{}/statuses'.format(deployment['id']), json={
|
||||||
|
'state': 'success',
|
||||||
|
'target_url': 'https://github.com/{}/commit/{}'.format(
|
||||||
|
self.repository.name,
|
||||||
|
payload['sha'],
|
||||||
|
),
|
||||||
|
'description': "Merged %s in %s at %s" % (
|
||||||
|
self.display_name, self.target.name, payload['sha']
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
def _statuses_equivalent(self, a, b):
|
def _statuses_equivalent(self, a, b):
|
||||||
""" Check if two statuses are *equivalent* meaning the description field
|
""" Check if two statuses are *equivalent* meaning the description field
|
||||||
is ignored (check only state and target_url). This is because the
|
is ignored (check only state and target_url). This is because the
|
||||||
@ -1613,6 +1432,49 @@ class Tagging(models.Model):
|
|||||||
values['tags_add'] = json.dumps(list(values['tags_add']))
|
values['tags_add'] = json.dumps(list(values['tags_add']))
|
||||||
return super().create(values)
|
return super().create(values)
|
||||||
|
|
||||||
|
def _send(self):
|
||||||
|
# noinspection SqlResolve
|
||||||
|
self.env.cr.execute("""
|
||||||
|
SELECT
|
||||||
|
t.repository as repo_id,
|
||||||
|
t.pull_request as pr_number,
|
||||||
|
array_agg(t.id) as ids,
|
||||||
|
array_agg(t.tags_remove::json) as to_remove,
|
||||||
|
array_agg(t.tags_add::json) as to_add
|
||||||
|
FROM runbot_merge_pull_requests_tagging t
|
||||||
|
GROUP BY t.repository, t.pull_request
|
||||||
|
""")
|
||||||
|
Repos = self.env['runbot_merge.repository']
|
||||||
|
ghs = {}
|
||||||
|
to_remove = []
|
||||||
|
for repo_id, pr, ids, remove, add in self.env.cr.fetchall():
|
||||||
|
repo = Repos.browse(repo_id)
|
||||||
|
|
||||||
|
gh = ghs.get(repo)
|
||||||
|
if not gh:
|
||||||
|
gh = ghs[repo] = repo.github()
|
||||||
|
|
||||||
|
# fold all grouped PRs'
|
||||||
|
tags_remove, tags_add = set(), set()
|
||||||
|
for minus, plus in zip(remove, add):
|
||||||
|
tags_remove.update(minus)
|
||||||
|
# need to remove minuses from to_add in case we get e.g.
|
||||||
|
# -foo +bar; -bar +baz, if we don't remove the minus, we'll end
|
||||||
|
# up with -foo +bar +baz instead of -foo +baz
|
||||||
|
tags_add.difference_update(minus)
|
||||||
|
tags_add.update(plus)
|
||||||
|
|
||||||
|
try:
|
||||||
|
gh.change_tags(pr, tags_remove, tags_add)
|
||||||
|
except Exception:
|
||||||
|
_logger.exception(
|
||||||
|
"Error while trying to change the tags of %s#%s from %s to %s",
|
||||||
|
repo.name, pr, remove, add,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
to_remove.extend(ids)
|
||||||
|
self.browse(to_remove).unlink()
|
||||||
|
|
||||||
class Feedback(models.Model):
|
class Feedback(models.Model):
|
||||||
""" Queue of feedback comments to send to PR users
|
""" Queue of feedback comments to send to PR users
|
||||||
"""
|
"""
|
||||||
@ -1631,6 +1493,44 @@ class Feedback(models.Model):
|
|||||||
help="Token field (from repo's project) to use to post messages"
|
help="Token field (from repo's project) to use to post messages"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _send(self):
|
||||||
|
ghs = {}
|
||||||
|
to_remove = []
|
||||||
|
for f in self.search([]):
|
||||||
|
repo = f.repository
|
||||||
|
gh = ghs.get((repo, f.token_field))
|
||||||
|
if not gh:
|
||||||
|
gh = ghs[(repo, f.token_field)] = repo.github(f.token_field)
|
||||||
|
|
||||||
|
try:
|
||||||
|
message = f.message
|
||||||
|
if f.close:
|
||||||
|
gh.close(f.pull_request)
|
||||||
|
try:
|
||||||
|
data = json.loads(message or '')
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
pr_to_notify = self.env['runbot_merge.pull_requests'].search([
|
||||||
|
('repository', '=', repo.id),
|
||||||
|
('number', '=', f.pull_request),
|
||||||
|
])
|
||||||
|
if pr_to_notify:
|
||||||
|
pr_to_notify._notify_merged(gh, data)
|
||||||
|
message = None
|
||||||
|
if message:
|
||||||
|
gh.comment(f.pull_request, message)
|
||||||
|
except Exception:
|
||||||
|
_logger.exception(
|
||||||
|
"Error while trying to %s %s#%s (%s)",
|
||||||
|
'close' if f.close else 'send a comment to',
|
||||||
|
repo.name, f.pull_request,
|
||||||
|
utils.shorten(f.message, 200)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
to_remove.append(f.id)
|
||||||
|
self.browse(to_remove).unlink()
|
||||||
|
|
||||||
class Commit(models.Model):
|
class Commit(models.Model):
|
||||||
"""Represents a commit onto which statuses might be posted,
|
"""Represents a commit onto which statuses might be posted,
|
||||||
independent of everything else as commits can be created by
|
independent of everything else as commits can be created by
|
||||||
@ -1981,9 +1881,12 @@ class Stagings(models.Model):
|
|||||||
finally:
|
finally:
|
||||||
self.batch_ids.write({'active': False})
|
self.batch_ids.write({'active': False})
|
||||||
self.write({'active': False})
|
self.write({'active': False})
|
||||||
elif self.state == 'failure' or project.is_timed_out(self):
|
elif self.state == 'failure' or self.is_timed_out():
|
||||||
self.try_splitting()
|
self.try_splitting()
|
||||||
|
|
||||||
|
def is_timed_out(self):
|
||||||
|
return fields.Datetime.from_string(self.timeout_limit) < datetime.datetime.now()
|
||||||
|
|
||||||
def _safety_dance(self, gh, staging_heads):
|
def _safety_dance(self, gh, staging_heads):
|
||||||
""" Reverting updates doesn't work if the branches are protected
|
""" Reverting updates doesn't work if the branches are protected
|
||||||
(because a revert is basically a force push). So we can update
|
(because a revert is basically a force push). So we can update
|
||||||
@ -2154,6 +2057,28 @@ class FetchJob(models.Model):
|
|||||||
repository = fields.Many2one('runbot_merge.repository', required=True)
|
repository = fields.Many2one('runbot_merge.repository', required=True)
|
||||||
number = fields.Integer(required=True)
|
number = fields.Integer(required=True)
|
||||||
|
|
||||||
|
def _check(self, commit=False):
|
||||||
|
"""
|
||||||
|
:param bool commit: commit after each fetch has been executed
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
f = self.search([], limit=1)
|
||||||
|
if not f:
|
||||||
|
return
|
||||||
|
|
||||||
|
self.env.cr.execute("SAVEPOINT runbot_merge_before_fetch")
|
||||||
|
try:
|
||||||
|
f.repository._load_pr(f.number)
|
||||||
|
except Exception:
|
||||||
|
self.env.cr.execute("ROLLBACK TO SAVEPOINT runbot_merge_before_fetch")
|
||||||
|
_logger.exception("Failed to load pr %s, skipping it", f.number)
|
||||||
|
finally:
|
||||||
|
self.env.cr.execute("RELEASE SAVEPOINT runbot_merge_before_fetch")
|
||||||
|
|
||||||
|
f.active = False
|
||||||
|
if commit:
|
||||||
|
self.env.cr.commit()
|
||||||
|
|
||||||
# The commit (and PR) statuses was originally a map of ``{context:state}``
|
# The commit (and PR) statuses was originally a map of ``{context:state}``
|
||||||
# however it turns out to clarify error messages it'd be useful to have
|
# however it turns out to clarify error messages it'd be useful to have
|
||||||
# a bit more information e.g. a link to the CI's build info on failure and
|
# a bit more information e.g. a link to the CI's build info on failure and
|
||||||
|
@ -27,7 +27,7 @@ def default_crons():
|
|||||||
'runbot_merge.staging_cron',
|
'runbot_merge.staging_cron',
|
||||||
# env['runbot_merge.pull_requests']._check_linked_prs_statuses()
|
# env['runbot_merge.pull_requests']._check_linked_prs_statuses()
|
||||||
'runbot_merge.check_linked_prs_status',
|
'runbot_merge.check_linked_prs_status',
|
||||||
# env['runbot_merge.project']._send_feedback()
|
# env['runbot_merge.pull_requests.feedback']._send()
|
||||||
'runbot_merge.feedback_cron',
|
'runbot_merge.feedback_cron',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -907,7 +907,7 @@ def test_rebase_failure(env, repo, users, config):
|
|||||||
with mock.patch.object(GH, 'set_ref', autospec=True, side_effect=wrapper):
|
with mock.patch.object(GH, 'set_ref', autospec=True, side_effect=wrapper):
|
||||||
env['runbot_merge.project']._check_progress()
|
env['runbot_merge.project']._check_progress()
|
||||||
|
|
||||||
env['runbot_merge.project']._send_feedback()
|
env['runbot_merge.pull_requests.feedback']._send()
|
||||||
|
|
||||||
assert pr_a.comments == [
|
assert pr_a.comments == [
|
||||||
(users['reviewer'], 'hansen r+'),
|
(users['reviewer'], 'hansen r+'),
|
||||||
|
@ -154,6 +154,7 @@ def test_pseudo_version_tag(env, project, make_repo, setreviewers, config):
|
|||||||
with repo:
|
with repo:
|
||||||
repo.post_status('staging.master', 'success', 'ci')
|
repo.post_status('staging.master', 'success', 'ci')
|
||||||
env.run_crons() # should merge staging
|
env.run_crons() # should merge staging
|
||||||
|
env.run_crons('runbot_merge.labels_cron') # update labels
|
||||||
assert pr_id.state == 'merged'
|
assert pr_id.state == 'merged'
|
||||||
assert pr.labels >= {'2.1'}
|
assert pr.labels >= {'2.1'}
|
||||||
|
|
||||||
@ -174,5 +175,6 @@ def test_pseudo_version_tag(env, project, make_repo, setreviewers, config):
|
|||||||
with repo:
|
with repo:
|
||||||
repo.post_status('staging.master', 'success', 'ci')
|
repo.post_status('staging.master', 'success', 'ci')
|
||||||
env.run_crons() # should merge staging
|
env.run_crons() # should merge staging
|
||||||
|
env.run_crons('runbot_merge.labels_cron') # update labels
|
||||||
assert pr_id.state == 'merged'
|
assert pr_id.state == 'merged'
|
||||||
assert pr.labels >= {'post-bonk'}
|
assert pr.labels >= {'post-bonk'}
|
||||||
|
@ -1,50 +1,4 @@
|
|||||||
<odoo>
|
<odoo>
|
||||||
<record id="runbot_merge_form_project" model="ir.ui.view">
|
|
||||||
<field name="name">Project Form</field>
|
|
||||||
<field name="model">runbot_merge.project</field>
|
|
||||||
<field name="arch" type="xml">
|
|
||||||
<form>
|
|
||||||
<sheet>
|
|
||||||
<div class="oe_title">
|
|
||||||
<h1><field name="name" placeholder="Name"/></h1>
|
|
||||||
</div>
|
|
||||||
<group>
|
|
||||||
<group>
|
|
||||||
<field name="github_prefix" string="bot name"/>
|
|
||||||
</group>
|
|
||||||
</group>
|
|
||||||
<group>
|
|
||||||
<group>
|
|
||||||
<field name="github_token"/>
|
|
||||||
<field name="secret"/>
|
|
||||||
</group>
|
|
||||||
<group>
|
|
||||||
<field name="ci_timeout"/>
|
|
||||||
<field name="batch_limit"/>
|
|
||||||
</group>
|
|
||||||
</group>
|
|
||||||
|
|
||||||
<separator string="Repositories"/>
|
|
||||||
<field name="repo_ids">
|
|
||||||
<tree>
|
|
||||||
<field name="sequence" widget="handle"/>
|
|
||||||
<field name="name"/>
|
|
||||||
<field name="branch_filter"/>
|
|
||||||
<field name="status_ids" widget="many2many_tags"/>
|
|
||||||
</tree>
|
|
||||||
</field>
|
|
||||||
<separator string="Branches"/>
|
|
||||||
<field name="branch_ids">
|
|
||||||
<tree editable="bottom" decoration-muted="not active">
|
|
||||||
<field name="sequence" widget="handle" />
|
|
||||||
<field name="name"/>
|
|
||||||
<field name="active"/>
|
|
||||||
</tree>
|
|
||||||
</field>
|
|
||||||
</sheet>
|
|
||||||
</form>
|
|
||||||
</field>
|
|
||||||
</record>
|
|
||||||
|
|
||||||
<record id="form_repository" model="ir.ui.view">
|
<record id="form_repository" model="ir.ui.view">
|
||||||
<field name="name">Repository form</field>
|
<field name="name">Repository form</field>
|
||||||
|
51
runbot_merge/views/runbot_merge_project.xml
Normal file
51
runbot_merge/views/runbot_merge_project.xml
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
<odoo>
|
||||||
|
<record id="runbot_merge_form_project" model="ir.ui.view">
|
||||||
|
<field name="name">Project Form</field>
|
||||||
|
<field name="model">runbot_merge.project</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<header>
|
||||||
|
<button type="object" name="action_freeze" string="Freeze" class="oe_highlight"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<div class="oe_title">
|
||||||
|
<h1><field name="name" placeholder="Name"/></h1>
|
||||||
|
</div>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="github_prefix" string="bot name"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="github_token"/>
|
||||||
|
<field name="secret"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="ci_timeout"/>
|
||||||
|
<field name="batch_limit"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<separator string="Repositories"/>
|
||||||
|
<field name="repo_ids">
|
||||||
|
<tree>
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="branch_filter"/>
|
||||||
|
<field name="status_ids" widget="many2many_tags"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
<separator string="Branches"/>
|
||||||
|
<field name="branch_ids">
|
||||||
|
<tree editable="bottom" decoration-muted="not active">
|
||||||
|
<field name="sequence" widget="handle" />
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="active"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
Loading…
Reference in New Issue
Block a user