[MERGE] v13 migration of mergebot/forwardbot

This commit is contained in:
Xavier Morel 2020-01-23 16:35:10 +01:00
commit 4bd65a4dff
6 changed files with 119 additions and 66 deletions

View File

@ -17,7 +17,33 @@
<field name="perm_write">1</field> <field name="perm_write">1</field>
<field name="perm_unlink">1</field> <field name="perm_unlink">1</field>
</record> </record>
<record id="access_forwardport_tagging_admin" model="ir.model.access">
<field name="name">Admin access to tagging</field>
<field name="model_id" ref="model_forwardport_tagging"/>
<field name="group_id" ref="runbot_merge.group_admin"/>
<field name="perm_read">1</field>
<field name="perm_create">1</field>
<field name="perm_write">1</field>
<field name="perm_unlink">1</field>
</record>
<record id="access_forwardport_branch_remover_admin" model="ir.model.access">
<field name="name">Admin access to branch remover</field>
<field name="model_id" ref="model_forwardport_branch_remover"/>
<field name="group_id" ref="runbot_merge.group_admin"/>
<field name="perm_read">1</field>
<field name="perm_create">1</field>
<field name="perm_write">1</field>
<field name="perm_unlink">1</field>
</record>
<record id="access_forwardport_tagging" model="ir.model.access">
<field name="name">No normal access to tagging</field>
<field name="model_id" ref="model_forwardport_tagging"/>
<field name="perm_read">1</field>
<field name="perm_create">0</field>
<field name="perm_write">0</field>
<field name="perm_unlink">0</field>
</record>
<record id="access_forwardport_batches" model="ir.model.access"> <record id="access_forwardport_batches" model="ir.model.access">
<field name="name">No normal access to batches</field> <field name="name">No normal access to batches</field>
<field name="model_id" ref="model_forwardport_batches"/> <field name="model_id" ref="model_forwardport_batches"/>

View File

@ -886,6 +886,7 @@ class Feedback(models.Model):
class Tagging(models.Model): class Tagging(models.Model):
_name = 'forwardport.tagging' _name = 'forwardport.tagging'
_description = "ad-hoc forwardport tagging commands"
token_field = fields.Selection([ token_field = fields.Selection([
('github_token', 'Mergebot'), ('github_token', 'Mergebot'),

View File

@ -26,7 +26,7 @@ WAIT_FOR_VISIBILITY = [10, 10, 10, 10]
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
class Project(models.Model): class Project(models.Model):
_name = 'runbot_merge.project' _name = _description = 'runbot_merge.project'
name = fields.Char(required=True, index=True) name = fields.Char(required=True, index=True)
repo_ids = fields.One2many( repo_ids = fields.One2many(
@ -213,7 +213,7 @@ class Project(models.Model):
return bool(self.env.cr.rowcount) return bool(self.env.cr.rowcount)
class Repository(models.Model): class Repository(models.Model):
_name = 'runbot_merge.repository' _name = _description = 'runbot_merge.repository'
name = fields.Char(required=True) name = fields.Char(required=True)
project_id = fields.Many2one('runbot_merge.project', required=True) project_id = fields.Many2one('runbot_merge.project', required=True)
@ -285,7 +285,7 @@ class Repository(models.Model):
}) })
class Branch(models.Model): class Branch(models.Model):
_name = 'runbot_merge.branch' _name = _description = 'runbot_merge.branch'
_order = 'sequence, name' _order = 'sequence, name'
name = fields.Char(required=True) name = fields.Char(required=True)
@ -507,7 +507,7 @@ class Branch(models.Model):
ACL = collections.namedtuple('ACL', 'is_admin is_reviewer is_author') ACL = collections.namedtuple('ACL', 'is_admin is_reviewer is_author')
class PullRequests(models.Model): class PullRequests(models.Model):
_name = 'runbot_merge.pull_requests' _name = _description = 'runbot_merge.pull_requests'
_order = 'number desc' _order = 'number desc'
target = fields.Many2one('runbot_merge.branch', required=True, index=True) target = fields.Many2one('runbot_merge.branch', required=True, index=True)
@ -544,18 +544,14 @@ class PullRequests(models.Model):
reviewed_by = fields.Many2one('res.partner') reviewed_by = fields.Many2one('res.partner')
delegates = fields.Many2many('res.partner', help="Delegate reviewers, not intrinsically reviewers but can review this PR") delegates = fields.Many2many('res.partner', help="Delegate reviewers, not intrinsically reviewers but can review this PR")
priority = fields.Selection([ priority = fields.Integer(default=2, index=True)
(0, 'Urgent'),
(1, 'Pressing'),
(2, 'Normal'),
], default=2, index=True)
statuses = fields.Text(compute='_compute_statuses') statuses = fields.Text(compute='_compute_statuses')
status = fields.Char(compute='_compute_statuses') status = fields.Char(compute='_compute_statuses')
previous_failure = fields.Char(default='{}') previous_failure = fields.Char(default='{}')
batch_id = fields.Many2one('runbot_merge.batch',compute='_compute_active_batch', store=True) batch_id = fields.Many2one('runbot_merge.batch', string="Active Batch", compute='_compute_active_batch', store=True)
batch_ids = fields.Many2many('runbot_merge.batch') batch_ids = fields.Many2many('runbot_merge.batch', string="Batches")
staging_id = fields.Many2one(related='batch_id.staging_id', store=True) staging_id = fields.Many2one(related='batch_id.staging_id', store=True)
commits_map = fields.Char(help="JSON-encoded mapping of PR commits to actually integrated commits. The integration head (either a merge commit or the PR's topmost) is mapped from the 'empty' pr commit (the key is an empty string, because you can't put a null key in json maps).", default='{}') commits_map = fields.Char(help="JSON-encoded mapping of PR commits to actually integrated commits. The integration head (either a merge commit or the PR's topmost) is mapped from the 'empty' pr commit (the key is an empty string, because you can't put a null key in json maps).", default='{}')
@ -574,22 +570,10 @@ class PullRequests(models.Model):
return super(PullRequests, self)._compute_display_name() return super(PullRequests, self)._compute_display_name()
def name_get(self): def name_get(self):
return { return [
p.id: '%s#%s' % (p.repository.name, p.number) (p.id, '%s#%d' % (p.repository.name, p.number))
for p in self for p in self
} ]
def __str__(self):
if len(self) == 0:
separator = ''
elif len(self) == 1:
separator = ' '
else:
separator = 's '
return '<pull_request%s%s>' % (separator, ' '.join(
'{0.id} ({0.display_name})'.format(p)
for p in self
))
# missing link to other PRs # missing link to other PRs
@api.depends('priority', 'state', 'squash', 'merge_method', 'batch_id.active', 'label') @api.depends('priority', 'state', 'squash', 'merge_method', 'batch_id.active', 'label')
@ -608,6 +592,7 @@ class PullRequests(models.Model):
for s in self: for s in self:
c = Commits.search([('sha', '=', s.head)]) c = Commits.search([('sha', '=', s.head)])
if not (c and c.statuses): if not (c and c.statuses):
s.status = s.statuses = False
continue continue
statuses = json.loads(c.statuses) statuses = json.loads(c.statuses)
@ -965,7 +950,6 @@ class PullRequests(models.Model):
'message': message, 'message': message,
}) })
@api.multi
def write(self, vals): def write(self, vals):
oldstate = { pr: pr._tagstate for pr in self } oldstate = { pr: pr._tagstate for pr in self }
@ -988,7 +972,6 @@ class PullRequests(models.Model):
}) })
return w return w
@api.multi
def unlink(self): def unlink(self):
for pr in self: for pr in self:
self.env['runbot_merge.pull_requests.tagging'].create({ self.env['runbot_merge.pull_requests.tagging'].create({
@ -1212,7 +1195,7 @@ class PullRequests(models.Model):
WHERE id = %s AND state != 'merged' WHERE id = %s AND state != 'merged'
''', [self.id]) ''', [self.id])
self.env.cr.commit() self.env.cr.commit()
self.invalidate_cache(fnames=['state'], ids=[self.id]) self.modified(['state'])
if self.env.cr.rowcount: if self.env.cr.rowcount:
self.env['runbot_merge.pull_requests.tagging'].create({ self.env['runbot_merge.pull_requests.tagging'].create({
'pull_request': self.number, 'pull_request': self.number,
@ -1259,7 +1242,7 @@ class Tagging(models.Model):
way of that. Instead, queue tagging changes into this table whose way of that. Instead, queue tagging changes into this table whose
execution can be cron-driven. execution can be cron-driven.
""" """
_name = 'runbot_merge.pull_requests.tagging' _name = _description = 'runbot_merge.pull_requests.tagging'
repository = fields.Many2one('runbot_merge.repository', required=True) repository = fields.Many2one('runbot_merge.repository', required=True)
# store the PR number (not id) as we need a Tagging for PR objects # store the PR number (not id) as we need a Tagging for PR objects
@ -1290,7 +1273,7 @@ class Tagging(models.Model):
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
""" """
_name = 'runbot_merge.pull_requests.feedback' _name = _description = 'runbot_merge.pull_requests.feedback'
repository = fields.Many2one('runbot_merge.repository', required=True) repository = fields.Many2one('runbot_merge.repository', required=True)
# store the PR number (not id) as we may want to send feedback to PR # store the PR number (not id) as we may want to send feedback to PR
@ -1310,7 +1293,7 @@ class Commit(models.Model):
independent of everything else as commits can be created by independent of everything else as commits can be created by
statuses only, by PR pushes, by branch updates, ... statuses only, by PR pushes, by branch updates, ...
""" """
_name = 'runbot_merge.commit' _name = _description = 'runbot_merge.commit'
sha = fields.Char(required=True) sha = fields.Char(required=True)
statuses = fields.Char(help="json-encoded mapping of status contexts to states", default="{}") statuses = fields.Char(help="json-encoded mapping of status contexts to states", default="{}")
@ -1366,7 +1349,7 @@ class Commit(models.Model):
return res return res
class Stagings(models.Model): class Stagings(models.Model):
_name = 'runbot_merge.stagings' _name = _description = 'runbot_merge.stagings'
target = fields.Many2one('runbot_merge.branch', required=True) target = fields.Many2one('runbot_merge.branch', required=True)
@ -1466,7 +1449,6 @@ class Stagings(models.Model):
vals['timeout_limit'] = fields.Datetime.to_string(datetime.datetime.now() + datetime.timedelta(minutes=s.target.project_id.ci_timeout)) vals['timeout_limit'] = fields.Datetime.to_string(datetime.datetime.now() + datetime.timedelta(minutes=s.target.project_id.ci_timeout))
s.write(vals) s.write(vals)
@api.multi
def action_cancel(self): def action_cancel(self):
self.cancel("explicitly cancelled by %s", self.env.user.display_name) self.cancel("explicitly cancelled by %s", self.env.user.display_name)
return { 'type': 'ir.actions.act_window_close' } return { 'type': 'ir.actions.act_window_close' }
@ -1691,7 +1673,7 @@ class Stagings(models.Model):
return repo_name return repo_name
class Split(models.Model): class Split(models.Model):
_name = 'runbot_merge.split' _name = _description = 'runbot_merge.split'
target = fields.Many2one('runbot_merge.branch', required=True) target = fields.Many2one('runbot_merge.branch', required=True)
batch_ids = fields.One2many('runbot_merge.batch', 'split_id', context={'active_test': False}) batch_ids = fields.One2many('runbot_merge.batch', 'split_id', context={'active_test': False})
@ -1703,7 +1685,7 @@ class Batch(models.Model):
repositories e.g. change an API in repo1, this breaks use of that API repositories e.g. change an API in repo1, this breaks use of that API
in repo2 which now needs to be updated. in repo2 which now needs to be updated.
""" """
_name = 'runbot_merge.batch' _name = _description = 'runbot_merge.batch'
target = fields.Many2one('runbot_merge.branch', required=True) target = fields.Many2one('runbot_merge.branch', required=True)
staging_id = fields.Many2one('runbot_merge.stagings') staging_id = fields.Many2one('runbot_merge.stagings')
@ -1796,7 +1778,7 @@ class Batch(models.Model):
}) })
class FetchJob(models.Model): class FetchJob(models.Model):
_name = 'runbot_merge.fetch_job' _name = _description = 'runbot_merge.fetch_job'
active = fields.Boolean(default=True) active = fields.Boolean(default=True)
repository = fields.Many2one('runbot_merge.repository', required=True) repository = fields.Many2one('runbot_merge.repository', required=True)

View File

@ -8,7 +8,7 @@ class Partner(models.Model):
reviewer = fields.Boolean(default=False, help="Can review PRs (maybe m2m to repos/branches?)") reviewer = fields.Boolean(default=False, help="Can review PRs (maybe m2m to repos/branches?)")
self_reviewer = fields.Boolean(default=False, help="Can review own PRs (independent from reviewer)") self_reviewer = fields.Boolean(default=False, help="Can review own PRs (independent from reviewer)")
delegate_reviewer = fields.Many2many('runbot_merge.pull_requests') delegate_reviewer = fields.Many2many('runbot_merge.pull_requests')
formatted_email = fields.Char(compute='_rfc5322_formatted') formatted_email = fields.Char(string="commit email", compute='_rfc5322_formatted')
def _auto_init(self): def _auto_init(self):
res = super(Partner, self)._auto_init() res = super(Partner, self)._auto_init()

View File

@ -0,0 +1,61 @@
// FIX: bs4 shit-heap colors and styles
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
color: #666666;
}
h1, h2, h3, h4, h5, h6{
color: inherit;
margin-top: 0.66em;
margin-bottom: 0.33em;
}
h5 { font-size: 1em; }
.bg-success, .bg-info, .bg-warning, .bg-danger, .bg-gray-lighter {
color: inherit;
}
.dropdown-item, .dropdown-menu, .dropdown-menu a {
color: inherit;
}
.bg-success {
background-color: #dff0d8 !important;
}
.bg-info {
background-color: #d9edf7 !important;
}
.bg-warning {
background-color: #fcf8e3 !important;
}
.bg-danger {
background-color: #f2dede !important;
}
.list-inline {
margin-bottom: 10px;
}
.list-inline > li {
padding: 0 5px;
margin-right: 0;
}
// mergebot layouting
.stagings {
display: flex;
align-items: stretch;
}
.stagings > li {
flex: 1;
padding: 0.1em;
padding-left: 0.5em;
}
.stagings > li:not(:last-child) {
border-right: 1px solid lightgray;
}
.batch:not(:last-child) {
border-bottom: 1px solid lightgray;
}
.batch a:not(:last-of-type) a:after {
content: ",";
}
.pr-listing > * { display: inline-block; }
.pr-awaiting { opacity: 0.8; }
.pr-blocked { opacity: 0.6; }
.pr-failed { opacity: 0.9; }

View File

@ -1,34 +1,17 @@
<odoo> <odoo>
<template id="dashboard" name="mergebot dashboard"> <function model="website.page" name="write">
<t t-set="styles"> <value eval="ref('website.homepage_page')"/>
<style> <value eval="{'active': False}"/>
.stagings { </function>
display: flex;
align-items: stretch;
}
.stagings > li {
flex: 1;
padding: 0.1em; <template id="assets_frontend" inherit_id="web.assets_frontend">
padding-left: 0.5em; <xpath expr="link[last()]" position="after">
} <link rel="stylesheet" type="text/scss" href="/runbot_merge/static/scss/runbot_merge.scss"/>
.stagings > li:not(:last-child) { </xpath>
border-right: 1px solid lightgray; </template>
}
.batch:not(:last-child) { <template id="dashboard" name="mergebot dashboard">
border-bottom: 1px solid lightgray;
}
.batch a:not(:last-of-type) a:after {
content: ",";
}
.pr-listing > * { display: inline-block; }
.pr-awaiting { opacity: 0.8; }
.pr-blocked { opacity: 0.6; }
.pr-failed { opacity: 0.9; }
</style>
</t>
<t t-call="website.layout"> <t t-call="website.layout">
<t t-set="head" t-value="(head or '') + styles"/>
<div id="wrap"><div class="container-fluid"> <div id="wrap"><div class="container-fluid">
<section t-foreach="projects.with_context(active_test=False)" t-as="project" class="row"> <section t-foreach="projects.with_context(active_test=False)" t-as="project" class="row">
<h1 class="col-md-12"><t t-esc="project.name"/></h1> <h1 class="col-md-12"><t t-esc="project.name"/></h1>