2021-11-12 22:04:34 +07:00
|
|
|
import enum
|
|
|
|
import itertools
|
2022-07-11 13:17:04 +07:00
|
|
|
import json
|
2021-11-12 22:04:34 +07:00
|
|
|
import logging
|
2022-12-07 15:25:55 +07:00
|
|
|
from collections import Counter
|
2023-08-23 19:09:32 +07:00
|
|
|
from typing import Dict
|
2021-11-12 22:04:34 +07:00
|
|
|
|
2023-01-24 13:41:14 +07:00
|
|
|
from markupsafe import Markup
|
|
|
|
|
2022-12-07 15:25:55 +07:00
|
|
|
from odoo import models, fields, api, Command
|
[FIX] runbot_merge: make freeze wizard labels lookup not shit
I DECLARE BANKRUPTCY!!!
The previous implementation of labels lookup was really not
intuitive (it was just a char field, and matched labels by equality
including the owner tag), and was also full of broken edge
cases (e.g. traceback if a label matched multiple PRs in the same repo
because people reuse branch names).
Tried messing about with contextual `display_name` and `name_search`
on PRs but the client goes wonky in that case, and there is no clean
autocomplete for non-relational fields.
So created a view which reifies labels, and that can be used as the
basis for our search. It doesn't have to be maintained by hand, can be
searched somewhat flexibly, we can add new view fields in the future
if desirable, and it seems to work fine providing a nice
understandable UX, with the reliability of using a normal Odoo model
the normal way.
Also fixed the handling of bump PRs, clearly clearing the entire field
before trying to update existing records (even with a link_to
inbetween) is not the web client's fancy, re-selecting the current
label would just empty the thing entirely.
So use a two-step process slightly closer to the release PRs instead:
- first update or delete the existing bump PRs
- then add the new ones
The second part is because bump PRs are somewhat less critical than
release, so it can be a bit more DWIM compared to the more deliberate
process of release PRs where first the list of repositories involved
has to be set up just so, then the PRs can be filled in each of them.
Fixes #697
2023-01-24 17:19:18 +07:00
|
|
|
from odoo.exceptions import UserError
|
|
|
|
from odoo.tools import drop_view_if_exists
|
2021-11-12 22:04:34 +07:00
|
|
|
|
2023-08-23 19:09:32 +07:00
|
|
|
from ... import git
|
|
|
|
from ..pull_requests import Repository
|
|
|
|
|
2021-11-12 22:04:34 +07:00
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
class FreezeWizard(models.Model):
|
|
|
|
_name = 'runbot_merge.project.freeze'
|
|
|
|
_description = "Wizard for freezing a project('s master)"
|
|
|
|
|
|
|
|
project_id = fields.Many2one('runbot_merge.project', required=True)
|
2022-07-11 13:17:04 +07:00
|
|
|
errors = fields.Text(compute='_compute_errors')
|
2021-11-12 22:04:34 +07:00
|
|
|
branch_name = fields.Char(required=True, help="Name of the new branches to create")
|
2022-07-11 13:17:04 +07:00
|
|
|
|
2021-11-12 22:04:34 +07:00
|
|
|
required_pr_ids = fields.Many2many(
|
|
|
|
'runbot_merge.pull_requests', string="Required Pull Requests",
|
|
|
|
domain="[('state', 'not in', ('closed', 'merged'))]",
|
|
|
|
help="Pull requests which must have been merged before the freeze is allowed",
|
|
|
|
)
|
2022-07-11 13:17:04 +07:00
|
|
|
|
2023-01-24 13:41:14 +07:00
|
|
|
pr_state_key = fields.Html(string="Color Key", compute='_compute_state_key', readonly=True)
|
|
|
|
def _compute_state_key(self):
|
|
|
|
s = dict(self.env['runbot_merge.pull_requests']._fields['state'].selection)
|
|
|
|
self.pr_state_key = Markup("""
|
|
|
|
<p>%s</p>
|
|
|
|
""") % Markup(" ").join(
|
|
|
|
Markup('<span class="badge border-0 fucking_color_key_{}">{}</span>').format(v, s[k])
|
|
|
|
for k, v in STATE_COLORMAP.items()
|
|
|
|
if v
|
|
|
|
)
|
|
|
|
|
[FIX] runbot_merge: make freeze wizard labels lookup not shit
I DECLARE BANKRUPTCY!!!
The previous implementation of labels lookup was really not
intuitive (it was just a char field, and matched labels by equality
including the owner tag), and was also full of broken edge
cases (e.g. traceback if a label matched multiple PRs in the same repo
because people reuse branch names).
Tried messing about with contextual `display_name` and `name_search`
on PRs but the client goes wonky in that case, and there is no clean
autocomplete for non-relational fields.
So created a view which reifies labels, and that can be used as the
basis for our search. It doesn't have to be maintained by hand, can be
searched somewhat flexibly, we can add new view fields in the future
if desirable, and it seems to work fine providing a nice
understandable UX, with the reliability of using a normal Odoo model
the normal way.
Also fixed the handling of bump PRs, clearly clearing the entire field
before trying to update existing records (even with a link_to
inbetween) is not the web client's fancy, re-selecting the current
label would just empty the thing entirely.
So use a two-step process slightly closer to the release PRs instead:
- first update or delete the existing bump PRs
- then add the new ones
The second part is because bump PRs are somewhat less critical than
release, so it can be a bit more DWIM compared to the more deliberate
process of release PRs where first the list of repositories involved
has to be set up just so, then the PRs can be filled in each of them.
Fixes #697
2023-01-24 17:19:18 +07:00
|
|
|
release_label = fields.Many2one('runbot_merge.freeze.labels', store=False, string="Release label", help="Find release PRs by label")
|
2022-07-11 13:17:04 +07:00
|
|
|
release_pr_ids = fields.One2many(
|
|
|
|
'runbot_merge.project.freeze.prs', 'wizard_id',
|
|
|
|
string="Release pull requests",
|
|
|
|
help="Pull requests used as tips for the freeze branches, "
|
2022-12-07 15:25:55 +07:00
|
|
|
"one per repository",
|
2022-07-11 13:17:04 +07:00
|
|
|
)
|
|
|
|
|
[FIX] runbot_merge: make freeze wizard labels lookup not shit
I DECLARE BANKRUPTCY!!!
The previous implementation of labels lookup was really not
intuitive (it was just a char field, and matched labels by equality
including the owner tag), and was also full of broken edge
cases (e.g. traceback if a label matched multiple PRs in the same repo
because people reuse branch names).
Tried messing about with contextual `display_name` and `name_search`
on PRs but the client goes wonky in that case, and there is no clean
autocomplete for non-relational fields.
So created a view which reifies labels, and that can be used as the
basis for our search. It doesn't have to be maintained by hand, can be
searched somewhat flexibly, we can add new view fields in the future
if desirable, and it seems to work fine providing a nice
understandable UX, with the reliability of using a normal Odoo model
the normal way.
Also fixed the handling of bump PRs, clearly clearing the entire field
before trying to update existing records (even with a link_to
inbetween) is not the web client's fancy, re-selecting the current
label would just empty the thing entirely.
So use a two-step process slightly closer to the release PRs instead:
- first update or delete the existing bump PRs
- then add the new ones
The second part is because bump PRs are somewhat less critical than
release, so it can be a bit more DWIM compared to the more deliberate
process of release PRs where first the list of repositories involved
has to be set up just so, then the PRs can be filled in each of them.
Fixes #697
2023-01-24 17:19:18 +07:00
|
|
|
bump_label = fields.Many2one('runbot_merge.freeze.labels', store=False, string="Bump label", help="Find bump PRs by label")
|
2022-07-11 13:17:04 +07:00
|
|
|
bump_pr_ids = fields.One2many(
|
|
|
|
'runbot_merge.project.freeze.bumps', 'wizard_id',
|
|
|
|
string="Bump pull requests",
|
|
|
|
help="Pull requests used as tips of the frozen-off branches, "
|
2022-12-07 15:25:55 +07:00
|
|
|
"one per repository",
|
2022-07-11 13:17:04 +07:00
|
|
|
)
|
2021-11-12 22:04:34 +07:00
|
|
|
|
|
|
|
_sql_constraints = [
|
|
|
|
('unique_per_project', 'unique (project_id)',
|
|
|
|
"There should be only one ongoing freeze per project"),
|
|
|
|
]
|
|
|
|
|
2022-07-11 13:17:04 +07:00
|
|
|
@api.onchange('release_label')
|
|
|
|
def _onchange_release_label(self):
|
2022-12-07 15:25:55 +07:00
|
|
|
if not self.release_label:
|
|
|
|
return
|
|
|
|
|
2022-07-11 13:17:04 +07:00
|
|
|
prs = self.env['runbot_merge.pull_requests'].search([
|
[FIX] runbot_merge: make freeze wizard labels lookup not shit
I DECLARE BANKRUPTCY!!!
The previous implementation of labels lookup was really not
intuitive (it was just a char field, and matched labels by equality
including the owner tag), and was also full of broken edge
cases (e.g. traceback if a label matched multiple PRs in the same repo
because people reuse branch names).
Tried messing about with contextual `display_name` and `name_search`
on PRs but the client goes wonky in that case, and there is no clean
autocomplete for non-relational fields.
So created a view which reifies labels, and that can be used as the
basis for our search. It doesn't have to be maintained by hand, can be
searched somewhat flexibly, we can add new view fields in the future
if desirable, and it seems to work fine providing a nice
understandable UX, with the reliability of using a normal Odoo model
the normal way.
Also fixed the handling of bump PRs, clearly clearing the entire field
before trying to update existing records (even with a link_to
inbetween) is not the web client's fancy, re-selecting the current
label would just empty the thing entirely.
So use a two-step process slightly closer to the release PRs instead:
- first update or delete the existing bump PRs
- then add the new ones
The second part is because bump PRs are somewhat less critical than
release, so it can be a bit more DWIM compared to the more deliberate
process of release PRs where first the list of repositories involved
has to be set up just so, then the PRs can be filled in each of them.
Fixes #697
2023-01-24 17:19:18 +07:00
|
|
|
('label', '=', self.release_label.label),
|
|
|
|
('state', 'not in', ('merged', 'closed')),
|
2022-07-11 13:17:04 +07:00
|
|
|
])
|
|
|
|
for release_pr in self.release_pr_ids:
|
[FIX] runbot_merge: make freeze wizard labels lookup not shit
I DECLARE BANKRUPTCY!!!
The previous implementation of labels lookup was really not
intuitive (it was just a char field, and matched labels by equality
including the owner tag), and was also full of broken edge
cases (e.g. traceback if a label matched multiple PRs in the same repo
because people reuse branch names).
Tried messing about with contextual `display_name` and `name_search`
on PRs but the client goes wonky in that case, and there is no clean
autocomplete for non-relational fields.
So created a view which reifies labels, and that can be used as the
basis for our search. It doesn't have to be maintained by hand, can be
searched somewhat flexibly, we can add new view fields in the future
if desirable, and it seems to work fine providing a nice
understandable UX, with the reliability of using a normal Odoo model
the normal way.
Also fixed the handling of bump PRs, clearly clearing the entire field
before trying to update existing records (even with a link_to
inbetween) is not the web client's fancy, re-selecting the current
label would just empty the thing entirely.
So use a two-step process slightly closer to the release PRs instead:
- first update or delete the existing bump PRs
- then add the new ones
The second part is because bump PRs are somewhat less critical than
release, so it can be a bit more DWIM compared to the more deliberate
process of release PRs where first the list of repositories involved
has to be set up just so, then the PRs can be filled in each of them.
Fixes #697
2023-01-24 17:19:18 +07:00
|
|
|
p = prs.filtered(lambda p: p.repository == release_pr.repository_id)
|
|
|
|
if len(p) < 2:
|
|
|
|
release_pr.pr_id = p
|
2022-07-11 13:17:04 +07:00
|
|
|
|
|
|
|
@api.onchange('release_pr_ids')
|
|
|
|
def _onchange_release_prs(self):
|
2022-12-07 15:25:55 +07:00
|
|
|
labels = {p.pr_id.label for p in self.release_pr_ids if p.pr_id}
|
[FIX] runbot_merge: make freeze wizard labels lookup not shit
I DECLARE BANKRUPTCY!!!
The previous implementation of labels lookup was really not
intuitive (it was just a char field, and matched labels by equality
including the owner tag), and was also full of broken edge
cases (e.g. traceback if a label matched multiple PRs in the same repo
because people reuse branch names).
Tried messing about with contextual `display_name` and `name_search`
on PRs but the client goes wonky in that case, and there is no clean
autocomplete for non-relational fields.
So created a view which reifies labels, and that can be used as the
basis for our search. It doesn't have to be maintained by hand, can be
searched somewhat flexibly, we can add new view fields in the future
if desirable, and it seems to work fine providing a nice
understandable UX, with the reliability of using a normal Odoo model
the normal way.
Also fixed the handling of bump PRs, clearly clearing the entire field
before trying to update existing records (even with a link_to
inbetween) is not the web client's fancy, re-selecting the current
label would just empty the thing entirely.
So use a two-step process slightly closer to the release PRs instead:
- first update or delete the existing bump PRs
- then add the new ones
The second part is because bump PRs are somewhat less critical than
release, so it can be a bit more DWIM compared to the more deliberate
process of release PRs where first the list of repositories involved
has to be set up just so, then the PRs can be filled in each of them.
Fixes #697
2023-01-24 17:19:18 +07:00
|
|
|
self.release_label = len(labels) == 1 and self.env['runbot_merge.freeze.labels'].search([
|
|
|
|
('label', '=', labels.pop()),
|
|
|
|
])
|
2022-07-11 13:17:04 +07:00
|
|
|
|
|
|
|
@api.onchange('bump_label')
|
|
|
|
def _onchange_bump_label(self):
|
[FIX] runbot_merge: make freeze wizard labels lookup not shit
I DECLARE BANKRUPTCY!!!
The previous implementation of labels lookup was really not
intuitive (it was just a char field, and matched labels by equality
including the owner tag), and was also full of broken edge
cases (e.g. traceback if a label matched multiple PRs in the same repo
because people reuse branch names).
Tried messing about with contextual `display_name` and `name_search`
on PRs but the client goes wonky in that case, and there is no clean
autocomplete for non-relational fields.
So created a view which reifies labels, and that can be used as the
basis for our search. It doesn't have to be maintained by hand, can be
searched somewhat flexibly, we can add new view fields in the future
if desirable, and it seems to work fine providing a nice
understandable UX, with the reliability of using a normal Odoo model
the normal way.
Also fixed the handling of bump PRs, clearly clearing the entire field
before trying to update existing records (even with a link_to
inbetween) is not the web client's fancy, re-selecting the current
label would just empty the thing entirely.
So use a two-step process slightly closer to the release PRs instead:
- first update or delete the existing bump PRs
- then add the new ones
The second part is because bump PRs are somewhat less critical than
release, so it can be a bit more DWIM compared to the more deliberate
process of release PRs where first the list of repositories involved
has to be set up just so, then the PRs can be filled in each of them.
Fixes #697
2023-01-24 17:19:18 +07:00
|
|
|
if not self.bump_label:
|
2022-12-07 15:25:55 +07:00
|
|
|
return
|
2022-07-11 13:17:04 +07:00
|
|
|
prs = self.env['runbot_merge.pull_requests'].search([
|
[FIX] runbot_merge: make freeze wizard labels lookup not shit
I DECLARE BANKRUPTCY!!!
The previous implementation of labels lookup was really not
intuitive (it was just a char field, and matched labels by equality
including the owner tag), and was also full of broken edge
cases (e.g. traceback if a label matched multiple PRs in the same repo
because people reuse branch names).
Tried messing about with contextual `display_name` and `name_search`
on PRs but the client goes wonky in that case, and there is no clean
autocomplete for non-relational fields.
So created a view which reifies labels, and that can be used as the
basis for our search. It doesn't have to be maintained by hand, can be
searched somewhat flexibly, we can add new view fields in the future
if desirable, and it seems to work fine providing a nice
understandable UX, with the reliability of using a normal Odoo model
the normal way.
Also fixed the handling of bump PRs, clearly clearing the entire field
before trying to update existing records (even with a link_to
inbetween) is not the web client's fancy, re-selecting the current
label would just empty the thing entirely.
So use a two-step process slightly closer to the release PRs instead:
- first update or delete the existing bump PRs
- then add the new ones
The second part is because bump PRs are somewhat less critical than
release, so it can be a bit more DWIM compared to the more deliberate
process of release PRs where first the list of repositories involved
has to be set up just so, then the PRs can be filled in each of them.
Fixes #697
2023-01-24 17:19:18 +07:00
|
|
|
('label', '=', self.bump_label.label),
|
|
|
|
('state', 'not in', ('merged', 'closed')),
|
2022-07-11 13:17:04 +07:00
|
|
|
])
|
[FIX] runbot_merge: make freeze wizard labels lookup not shit
I DECLARE BANKRUPTCY!!!
The previous implementation of labels lookup was really not
intuitive (it was just a char field, and matched labels by equality
including the owner tag), and was also full of broken edge
cases (e.g. traceback if a label matched multiple PRs in the same repo
because people reuse branch names).
Tried messing about with contextual `display_name` and `name_search`
on PRs but the client goes wonky in that case, and there is no clean
autocomplete for non-relational fields.
So created a view which reifies labels, and that can be used as the
basis for our search. It doesn't have to be maintained by hand, can be
searched somewhat flexibly, we can add new view fields in the future
if desirable, and it seems to work fine providing a nice
understandable UX, with the reliability of using a normal Odoo model
the normal way.
Also fixed the handling of bump PRs, clearly clearing the entire field
before trying to update existing records (even with a link_to
inbetween) is not the web client's fancy, re-selecting the current
label would just empty the thing entirely.
So use a two-step process slightly closer to the release PRs instead:
- first update or delete the existing bump PRs
- then add the new ones
The second part is because bump PRs are somewhat less critical than
release, so it can be a bit more DWIM compared to the more deliberate
process of release PRs where first the list of repositories involved
has to be set up just so, then the PRs can be filled in each of them.
Fixes #697
2023-01-24 17:19:18 +07:00
|
|
|
commands = []
|
|
|
|
for bump_pr in self.bump_pr_ids:
|
|
|
|
p = prs.filtered(lambda p: p.repository == bump_pr.repository_id)
|
|
|
|
if len(p) == 1:
|
|
|
|
commands.append(Command.update(bump_pr.id, {'pr_id': p.id}))
|
2022-12-07 15:25:55 +07:00
|
|
|
else:
|
[FIX] runbot_merge: make freeze wizard labels lookup not shit
I DECLARE BANKRUPTCY!!!
The previous implementation of labels lookup was really not
intuitive (it was just a char field, and matched labels by equality
including the owner tag), and was also full of broken edge
cases (e.g. traceback if a label matched multiple PRs in the same repo
because people reuse branch names).
Tried messing about with contextual `display_name` and `name_search`
on PRs but the client goes wonky in that case, and there is no clean
autocomplete for non-relational fields.
So created a view which reifies labels, and that can be used as the
basis for our search. It doesn't have to be maintained by hand, can be
searched somewhat flexibly, we can add new view fields in the future
if desirable, and it seems to work fine providing a nice
understandable UX, with the reliability of using a normal Odoo model
the normal way.
Also fixed the handling of bump PRs, clearly clearing the entire field
before trying to update existing records (even with a link_to
inbetween) is not the web client's fancy, re-selecting the current
label would just empty the thing entirely.
So use a two-step process slightly closer to the release PRs instead:
- first update or delete the existing bump PRs
- then add the new ones
The second part is because bump PRs are somewhat less critical than
release, so it can be a bit more DWIM compared to the more deliberate
process of release PRs where first the list of repositories involved
has to be set up just so, then the PRs can be filled in each of them.
Fixes #697
2023-01-24 17:19:18 +07:00
|
|
|
commands.append(Command.delete(bump_pr.id))
|
|
|
|
prs -= p
|
|
|
|
|
|
|
|
commands.extend(
|
|
|
|
Command.create({'repository_id': pr.repository.id, 'pr_id': pr.id})
|
|
|
|
for pr in prs
|
|
|
|
)
|
2022-12-07 15:25:55 +07:00
|
|
|
|
[FIX] runbot_merge: make freeze wizard labels lookup not shit
I DECLARE BANKRUPTCY!!!
The previous implementation of labels lookup was really not
intuitive (it was just a char field, and matched labels by equality
including the owner tag), and was also full of broken edge
cases (e.g. traceback if a label matched multiple PRs in the same repo
because people reuse branch names).
Tried messing about with contextual `display_name` and `name_search`
on PRs but the client goes wonky in that case, and there is no clean
autocomplete for non-relational fields.
So created a view which reifies labels, and that can be used as the
basis for our search. It doesn't have to be maintained by hand, can be
searched somewhat flexibly, we can add new view fields in the future
if desirable, and it seems to work fine providing a nice
understandable UX, with the reliability of using a normal Odoo model
the normal way.
Also fixed the handling of bump PRs, clearly clearing the entire field
before trying to update existing records (even with a link_to
inbetween) is not the web client's fancy, re-selecting the current
label would just empty the thing entirely.
So use a two-step process slightly closer to the release PRs instead:
- first update or delete the existing bump PRs
- then add the new ones
The second part is because bump PRs are somewhat less critical than
release, so it can be a bit more DWIM compared to the more deliberate
process of release PRs where first the list of repositories involved
has to be set up just so, then the PRs can be filled in each of them.
Fixes #697
2023-01-24 17:19:18 +07:00
|
|
|
self.bump_pr_ids = commands
|
2022-07-11 13:17:04 +07:00
|
|
|
|
|
|
|
@api.onchange('bump_pr_ids')
|
|
|
|
def _onchange_bump_prs(self):
|
2022-12-07 15:25:55 +07:00
|
|
|
labels = {p.pr_id.label for p in self.bump_pr_ids if p.pr_id}
|
[FIX] runbot_merge: make freeze wizard labels lookup not shit
I DECLARE BANKRUPTCY!!!
The previous implementation of labels lookup was really not
intuitive (it was just a char field, and matched labels by equality
including the owner tag), and was also full of broken edge
cases (e.g. traceback if a label matched multiple PRs in the same repo
because people reuse branch names).
Tried messing about with contextual `display_name` and `name_search`
on PRs but the client goes wonky in that case, and there is no clean
autocomplete for non-relational fields.
So created a view which reifies labels, and that can be used as the
basis for our search. It doesn't have to be maintained by hand, can be
searched somewhat flexibly, we can add new view fields in the future
if desirable, and it seems to work fine providing a nice
understandable UX, with the reliability of using a normal Odoo model
the normal way.
Also fixed the handling of bump PRs, clearly clearing the entire field
before trying to update existing records (even with a link_to
inbetween) is not the web client's fancy, re-selecting the current
label would just empty the thing entirely.
So use a two-step process slightly closer to the release PRs instead:
- first update or delete the existing bump PRs
- then add the new ones
The second part is because bump PRs are somewhat less critical than
release, so it can be a bit more DWIM compared to the more deliberate
process of release PRs where first the list of repositories involved
has to be set up just so, then the PRs can be filled in each of them.
Fixes #697
2023-01-24 17:19:18 +07:00
|
|
|
self.bump_label = len(labels) == 1 and self.env['runbot_merge.freeze.labels'].search([
|
|
|
|
('label', '=', labels.pop()),
|
|
|
|
])
|
2022-07-11 13:17:04 +07:00
|
|
|
|
2021-11-12 22:04:34 +07:00
|
|
|
@api.depends('release_pr_ids.pr_id.label', 'required_pr_ids.state')
|
|
|
|
def _compute_errors(self):
|
|
|
|
errors = []
|
2022-12-07 15:25:55 +07:00
|
|
|
|
|
|
|
release_repos = Counter(self.mapped('release_pr_ids.repository_id'))
|
|
|
|
release_repos.subtract(self.project_id.repo_ids)
|
|
|
|
excess = {k.name for k, v in release_repos.items() if v > 0}
|
|
|
|
if excess:
|
|
|
|
errors.append("* Every repository must have one release PR, found multiple for %s" % ', '.join(excess))
|
|
|
|
|
2021-11-12 22:04:34 +07:00
|
|
|
without = self.release_pr_ids.filtered(lambda p: not p.pr_id)
|
|
|
|
if without:
|
|
|
|
errors.append("* Every repository must have a release PR, missing release PRs for %s." % ', '.join(
|
2022-07-11 13:17:04 +07:00
|
|
|
without.mapped('repository_id.name')
|
2021-11-12 22:04:34 +07:00
|
|
|
))
|
|
|
|
|
|
|
|
labels = set(self.mapped('release_pr_ids.pr_id.label'))
|
|
|
|
if len(labels) != 1:
|
2022-02-08 17:06:34 +07:00
|
|
|
errors.append("* All release PRs must have the same label, found %r." % ', '.join(sorted(labels)))
|
2022-07-11 13:17:04 +07:00
|
|
|
non_squash = self.mapped('release_pr_ids.pr_id').filtered(lambda p: not p.squash)
|
|
|
|
if non_squash:
|
|
|
|
errors.append("* Release PRs should have a single commit, found more in %s." % ', '.join(p.display_name for p in non_squash))
|
|
|
|
|
2022-12-07 15:25:55 +07:00
|
|
|
bump_repos = Counter(self.mapped('bump_pr_ids.repository_id'))
|
|
|
|
excess = {k.name for k, v in bump_repos.items() if v > 1}
|
|
|
|
if excess:
|
|
|
|
errors.append("* Every repository may have one bump PR, found multiple for %s" % ', '.join(excess))
|
|
|
|
|
2022-07-11 13:17:04 +07:00
|
|
|
bump_labels = set(self.mapped('bump_pr_ids.pr_id.label'))
|
|
|
|
if len(bump_labels) > 1:
|
|
|
|
errors.append("* All bump PRs must have the same label, found %r" % ', '.join(sorted(bump_labels)))
|
|
|
|
non_squash = self.mapped('bump_pr_ids.pr_id').filtered(lambda p: not p.squash)
|
|
|
|
if non_squash:
|
|
|
|
errors.append("* Bump PRs should have a single commit, found more in %s." % ', '.join(p.display_name for p in non_squash))
|
2021-11-12 22:04:34 +07:00
|
|
|
|
|
|
|
unready = sum(p.state not in ('closed', 'merged') for p in self.required_pr_ids)
|
|
|
|
if unready:
|
|
|
|
errors.append(f"* {unready} required PRs not ready.")
|
|
|
|
|
|
|
|
self.errors = '\n'.join(errors) or False
|
|
|
|
|
|
|
|
def action_cancel(self):
|
|
|
|
self.project_id.check_access_rights('write')
|
|
|
|
self.project_id.check_access_rule('write')
|
|
|
|
self.sudo().unlink()
|
|
|
|
|
|
|
|
return {'type': 'ir.actions.act_window_close'}
|
|
|
|
|
2022-02-07 19:23:41 +07:00
|
|
|
def action_open(self):
|
|
|
|
return {
|
|
|
|
'type': 'ir.actions.act_window',
|
|
|
|
'target': 'new',
|
|
|
|
'name': f'Freeze project {self.project_id.name}',
|
|
|
|
'view_mode': 'form',
|
|
|
|
'res_model': self._name,
|
|
|
|
'res_id': self.id,
|
|
|
|
}
|
|
|
|
|
2021-11-12 22:04:34 +07:00
|
|
|
def action_freeze(self):
|
|
|
|
""" Attempts to perform the freeze.
|
|
|
|
"""
|
|
|
|
# if there are still errors, reopen the wizard
|
|
|
|
if self.errors:
|
2022-02-07 19:23:41 +07:00
|
|
|
return self.action_open()
|
2021-11-12 22:04:34 +07:00
|
|
|
|
[FIX] runbot_merge: concurrency error in freeze wizard (hopefully)
During the 16.3 freeze an issue was noticed with the concurrency
safety of the freeze wizard (because it blew up, which caused a few
issues): it is possible for the cancelling of an active staging to the
master branch to fail, which causes the mergebot side of the freeze to
fail, but the github state is completed, which puts the entire thing
in a less than ideal state.
Especially with the additional issue that the branch inserter has its
own concurrency issue (which maybe I should fix): if there are
branches *being* forward-ported across the new branch, it's unable to
see them, and thus can not create the now-missing PRs.
Try to make the freeze wizard more resilient:
1. Take a lock on the master staging (if any) early on, this means if
we can acquire it we should be able to cancel it, and it won't
suffer a concurrency error.
2. Add the `process_updated_commits` cron to the set of locked crons,
trying to read the log timeline it looks like the issue was commits
being impacted on that staging while the action had started:
REPEATABLE READ meant the freeze's transaction was unable to see
the update from the commit statuses, therefore creating a diverging
update when it cancelled the staging, which postgres then reported
as a serialization error.
I'd like to relax the locking of the cron (to just FOR SHARE), but I
think it would work, per postgres:
> SELECT FOR UPDATE, and SELECT FOR SHARE commands behave the same as
> SELECT in terms of searching for target rows: they will only find
> target rows that were committed as of the transaction start
> time. However, such a target row might have already been updated (or
> deleted or locked) by another concurrent transaction by the time it
> is found. In this case, the repeatable read transaction will wait
> for the first updating transaction to commit or roll back (if it is
> still in progress). If the first updater rolls back, then its
> effects are negated and the repeatable read transaction can proceed
> with updating the originally found row. But if the first updater
> commits (and actually updated or deleted the row, not just locked
> it) then the repeatable read transaction will be rolled back with
> the message
This means it would be possible to lock the cron, and then get a
transaction error because the cron modified one of the records we're
going to hit while it was running: as far as the above is concerned
the cron's worker had "just locked" the row so it's fine to
continue. However this makes it more and more likely an error will be
hit when trying to freeze (to no issue, but still). We'll have to see
how that ends up.
Fixes #766 maybe
2023-06-15 20:25:17 +07:00
|
|
|
conflict_crons = self.env.ref('runbot_merge.merge_cron')\
|
|
|
|
| self.env.ref('runbot_merge.staging_cron')\
|
|
|
|
| self.env.ref('runbot_merge.process_updated_commits')
|
2022-07-11 13:17:04 +07:00
|
|
|
# we don't want to run concurrently to the crons above, though we
|
|
|
|
# don't need to prevent read access to them
|
|
|
|
self.env.cr.execute(
|
2024-02-07 16:48:28 +07:00
|
|
|
'SELECT FROM ir_cron WHERE id =ANY(%s) FOR SHARE NOWAIT',
|
2022-07-11 13:17:04 +07:00
|
|
|
[conflict_crons.ids]
|
|
|
|
)
|
|
|
|
|
2022-02-07 19:23:41 +07:00
|
|
|
project_id = self.project_id
|
2021-11-12 22:04:34 +07:00
|
|
|
# need to create the new branch, but at the same time resequence
|
|
|
|
# everything so the new branch is the second one, just after the branch
|
|
|
|
# it "forks"
|
|
|
|
master, rest = project_id.branch_ids[0], project_id.branch_ids[1:]
|
[FIX] runbot_merge: concurrency error in freeze wizard (hopefully)
During the 16.3 freeze an issue was noticed with the concurrency
safety of the freeze wizard (because it blew up, which caused a few
issues): it is possible for the cancelling of an active staging to the
master branch to fail, which causes the mergebot side of the freeze to
fail, but the github state is completed, which puts the entire thing
in a less than ideal state.
Especially with the additional issue that the branch inserter has its
own concurrency issue (which maybe I should fix): if there are
branches *being* forward-ported across the new branch, it's unable to
see them, and thus can not create the now-missing PRs.
Try to make the freeze wizard more resilient:
1. Take a lock on the master staging (if any) early on, this means if
we can acquire it we should be able to cancel it, and it won't
suffer a concurrency error.
2. Add the `process_updated_commits` cron to the set of locked crons,
trying to read the log timeline it looks like the issue was commits
being impacted on that staging while the action had started:
REPEATABLE READ meant the freeze's transaction was unable to see
the update from the commit statuses, therefore creating a diverging
update when it cancelled the staging, which postgres then reported
as a serialization error.
I'd like to relax the locking of the cron (to just FOR SHARE), but I
think it would work, per postgres:
> SELECT FOR UPDATE, and SELECT FOR SHARE commands behave the same as
> SELECT in terms of searching for target rows: they will only find
> target rows that were committed as of the transaction start
> time. However, such a target row might have already been updated (or
> deleted or locked) by another concurrent transaction by the time it
> is found. In this case, the repeatable read transaction will wait
> for the first updating transaction to commit or roll back (if it is
> still in progress). If the first updater rolls back, then its
> effects are negated and the repeatable read transaction can proceed
> with updating the originally found row. But if the first updater
> commits (and actually updated or deleted the row, not just locked
> it) then the repeatable read transaction will be rolled back with
> the message
This means it would be possible to lock the cron, and then get a
transaction error because the cron modified one of the records we're
going to hit while it was running: as far as the above is concerned
the cron's worker had "just locked" the row so it's fine to
continue. However this makes it more and more likely an error will be
hit when trying to freeze (to no issue, but still). We'll have to see
how that ends up.
Fixes #766 maybe
2023-06-15 20:25:17 +07:00
|
|
|
if self.bump_pr_ids and master.active_staging_id:
|
|
|
|
self.env.cr.execute(
|
2024-02-07 16:48:28 +07:00
|
|
|
'SELECT FROM runbot_merge_stagings WHERE id = %s FOR UPDATE NOWAIT',
|
2024-01-16 13:54:43 +07:00
|
|
|
[master.active_staging_id.id]
|
[FIX] runbot_merge: concurrency error in freeze wizard (hopefully)
During the 16.3 freeze an issue was noticed with the concurrency
safety of the freeze wizard (because it blew up, which caused a few
issues): it is possible for the cancelling of an active staging to the
master branch to fail, which causes the mergebot side of the freeze to
fail, but the github state is completed, which puts the entire thing
in a less than ideal state.
Especially with the additional issue that the branch inserter has its
own concurrency issue (which maybe I should fix): if there are
branches *being* forward-ported across the new branch, it's unable to
see them, and thus can not create the now-missing PRs.
Try to make the freeze wizard more resilient:
1. Take a lock on the master staging (if any) early on, this means if
we can acquire it we should be able to cancel it, and it won't
suffer a concurrency error.
2. Add the `process_updated_commits` cron to the set of locked crons,
trying to read the log timeline it looks like the issue was commits
being impacted on that staging while the action had started:
REPEATABLE READ meant the freeze's transaction was unable to see
the update from the commit statuses, therefore creating a diverging
update when it cancelled the staging, which postgres then reported
as a serialization error.
I'd like to relax the locking of the cron (to just FOR SHARE), but I
think it would work, per postgres:
> SELECT FOR UPDATE, and SELECT FOR SHARE commands behave the same as
> SELECT in terms of searching for target rows: they will only find
> target rows that were committed as of the transaction start
> time. However, such a target row might have already been updated (or
> deleted or locked) by another concurrent transaction by the time it
> is found. In this case, the repeatable read transaction will wait
> for the first updating transaction to commit or roll back (if it is
> still in progress). If the first updater rolls back, then its
> effects are negated and the repeatable read transaction can proceed
> with updating the originally found row. But if the first updater
> commits (and actually updated or deleted the row, not just locked
> it) then the repeatable read transaction will be rolled back with
> the message
This means it would be possible to lock the cron, and then get a
transaction error because the cron modified one of the records we're
going to hit while it was running: as far as the above is concerned
the cron's worker had "just locked" the row so it's fine to
continue. However this makes it more and more likely an error will be
hit when trying to freeze (to no issue, but still). We'll have to see
how that ends up.
Fixes #766 maybe
2023-06-15 20:25:17 +07:00
|
|
|
)
|
|
|
|
|
2021-11-12 22:04:34 +07:00
|
|
|
seq = itertools.count(start=1) # start reseq at 1
|
|
|
|
commands = [
|
|
|
|
(1, master.id, {'sequence': next(seq)}),
|
|
|
|
(0, 0, {
|
|
|
|
'name': self.branch_name,
|
|
|
|
'sequence': next(seq),
|
|
|
|
})
|
|
|
|
]
|
2022-07-11 13:17:04 +07:00
|
|
|
commands.extend((1, b.id, {'sequence': s}) for s, b in zip(seq, rest))
|
2021-11-12 22:04:34 +07:00
|
|
|
project_id.branch_ids = commands
|
2022-07-11 13:17:04 +07:00
|
|
|
master_name = master.name
|
2021-11-12 22:04:34 +07:00
|
|
|
|
2022-07-11 13:17:04 +07:00
|
|
|
gh_sessions = {r: r.github() for r in self.project_id.repo_ids}
|
2023-08-23 19:09:32 +07:00
|
|
|
repos: Dict[Repository, git.Repo] = {
|
[CHG] forwardport: perform forward porting without working copies
The goal is to reduce maintenance and odd disk interactions &
concurrency issues, by not creating concurrent clones, not having to
push forks back in the repository, etc... it also removes the need to
cleanup "scratch" working copies though that looks not to have been an
issue in a while.
The work is done on isolated objects without using or mutating refs,
so even concurrent work should not be a problem.
This turns out to not be any more verbose (less so if anything) than
using `cherry-pick`, as that is not really designed for scripted /
non-interactive use, or for squashing commits thereafter. Working
directly with trees and commits is quite a bit cleaner even without a
ton of helpers.
Much of the credit goes to Julia Evans for [their investigation of
3-way merges as the underpinnings of cherry-picking][3-way merge],
this would have been a lot more difficult if I'd had to rediscover the
merge-base trick independently.
A few things have been changed by this:
- The old trace/stderr from cherrypick has disappeared as it's
generated by cherrypick, but for a non-interactive use it's kinda
useless anyway so I probably should have looked into removing it
earlier (I think the main use was investigation of the inflateinit
issue).
- Error on emptied commits has to be hand-rolled as `merge-tree`
couldn't care less, this is not hard but is a bit annoying.
- `merge-tree`'s conflict information only references raw commits,
which makes sense, but requires updating a bunch of tests. Then
again so does the fact that it *usually* doesn't send anything to
stderr, so that's usually disappearing.
Conveniently `merge-tree` merges the conflict marker directly in the
files / tree so we don't have to mess about moving them back out of
the repository and into the working copy as I assume cherry-pick does,
which means we don't have to try and commit them back in ether. That
is a huge part of the gain over faffing about with the working copy.
Fixes #847
[3-way merge]: https://jvns.ca/blog/2023/11/10/how-cherry-pick-and-revert-work/
2024-07-05 18:32:02 +07:00
|
|
|
r: git.get_local(r).check(False)
|
2023-08-23 19:09:32 +07:00
|
|
|
for r in self.project_id.repo_ids
|
|
|
|
}
|
|
|
|
for repo, copy in repos.items():
|
[FIX] *: ensure I don't get bollocked up again by tags
Today (or really a month ago) I learned: when giving git a symbolic
ref (e.g. a ref name), if it's ambiguous then
1. If `$GIT_DIR/$name` exists, that is what you mean (this is usually
useful only for `HEAD`, `FETCH_HEAD`, `ORIG_HEAD`, `MERGE_HEAD`,
`REBASE_HEAD`, `REVERT_HEAD`, `CHERRY_PICK_HEAD`, `BISECT_HEAD` and
`AUTO_MERGE`)
2. otherwise, `refs/$name` if it exists
3. otherwise, `refs/tags/$name` if it exists
4. otherwise, `refs/heads/$name` if it exists
5. otherwise, `refs/remotes/$name` if it exists
6. otherwise, `refs/remotes/$name/HEAD` if it exists
This means if a tag and a branch have the same name and only the name
is provided (not the full ref), git will select the tag, which gets
very confusing for the mergebot as it now tries to rebase onto the tag
(which because that's not fun otherwise was not even on the branch of
the same name).
Fix by providing full refs to `rev-parse` when trying to retrieve the
head of the target branches. And as a defense in depth opportunity,
also exclude tags when fetching refs by spec: apparently fetching a
specific commit does not trigger the retrieval of tags, but any sort
of spec will see the tags come along for the ride even if the tags are
not in any way under the fetched ref e.g. `refs/heads/*` will very
much retrieve the tags despite them being located at `refs/tags/*`.
Fixes #922
2024-09-06 20:09:08 +07:00
|
|
|
copy.fetch(git.source_url(repo), '+refs/heads/*:refs/heads/*', no_tags=True)
|
2024-02-05 22:10:15 +07:00
|
|
|
all_prs = self.release_pr_ids.pr_id | self.bump_pr_ids.pr_id
|
|
|
|
for pr in all_prs:
|
2023-10-27 16:15:05 +07:00
|
|
|
repos[pr.repository].fetch(
|
[CHG] forwardport: perform forward porting without working copies
The goal is to reduce maintenance and odd disk interactions &
concurrency issues, by not creating concurrent clones, not having to
push forks back in the repository, etc... it also removes the need to
cleanup "scratch" working copies though that looks not to have been an
issue in a while.
The work is done on isolated objects without using or mutating refs,
so even concurrent work should not be a problem.
This turns out to not be any more verbose (less so if anything) than
using `cherry-pick`, as that is not really designed for scripted /
non-interactive use, or for squashing commits thereafter. Working
directly with trees and commits is quite a bit cleaner even without a
ton of helpers.
Much of the credit goes to Julia Evans for [their investigation of
3-way merges as the underpinnings of cherry-picking][3-way merge],
this would have been a lot more difficult if I'd had to rediscover the
merge-base trick independently.
A few things have been changed by this:
- The old trace/stderr from cherrypick has disappeared as it's
generated by cherrypick, but for a non-interactive use it's kinda
useless anyway so I probably should have looked into removing it
earlier (I think the main use was investigation of the inflateinit
issue).
- Error on emptied commits has to be hand-rolled as `merge-tree`
couldn't care less, this is not hard but is a bit annoying.
- `merge-tree`'s conflict information only references raw commits,
which makes sense, but requires updating a bunch of tests. Then
again so does the fact that it *usually* doesn't send anything to
stderr, so that's usually disappearing.
Conveniently `merge-tree` merges the conflict marker directly in the
files / tree so we don't have to mess about moving them back out of
the repository and into the working copy as I assume cherry-pick does,
which means we don't have to try and commit them back in ether. That
is a huge part of the gain over faffing about with the working copy.
Fixes #847
[3-way merge]: https://jvns.ca/blog/2023/11/10/how-cherry-pick-and-revert-work/
2024-07-05 18:32:02 +07:00
|
|
|
git.source_url(pr.repository),
|
2023-10-27 16:15:05 +07:00
|
|
|
pr.head,
|
|
|
|
)
|
2021-11-12 22:04:34 +07:00
|
|
|
|
2022-07-11 13:17:04 +07:00
|
|
|
# prep new branch (via tmp refs) on every repo
|
2023-08-23 19:09:32 +07:00
|
|
|
rel_heads: Dict[Repository, str] = {}
|
2022-07-11 13:17:04 +07:00
|
|
|
# store for master heads as odds are high the bump pr(s) will be on the
|
|
|
|
# same repo as one of the release PRs
|
2023-08-23 19:09:32 +07:00
|
|
|
prevs: Dict[Repository, str] = {}
|
2022-02-07 21:15:13 +07:00
|
|
|
for rel in self.release_pr_ids:
|
2022-07-11 13:17:04 +07:00
|
|
|
repo_id = rel.repository_id
|
|
|
|
gh = gh_sessions[repo_id]
|
|
|
|
try:
|
|
|
|
prev = prevs[repo_id] = gh.head(master_name)
|
2023-08-23 19:09:32 +07:00
|
|
|
except Exception as e:
|
|
|
|
raise UserError(f"Unable to resolve branch {master_name} of repository {repo_id.name} to a commit.") from e
|
2022-07-11 13:17:04 +07:00
|
|
|
|
|
|
|
try:
|
2023-08-23 19:09:32 +07:00
|
|
|
commits = gh.commits(rel.pr_id.number)
|
|
|
|
except Exception as e:
|
|
|
|
raise UserError(f"Unable to fetch commits of release PR {rel.pr_id.display_name}.") from e
|
2022-07-11 13:17:04 +07:00
|
|
|
|
2024-01-16 15:45:49 +07:00
|
|
|
_logger.debug("rebasing %s on %s (commits=%s)",
|
|
|
|
rel.pr_id.display_name, prev, len(commits))
|
2023-08-23 19:09:32 +07:00
|
|
|
rel_heads[repo_id] = repos[repo_id].rebase(prev, commits)[0]
|
2021-11-12 22:04:34 +07:00
|
|
|
|
2022-07-11 13:17:04 +07:00
|
|
|
# prep bump
|
2023-08-23 19:09:32 +07:00
|
|
|
bump_heads: Dict[Repository, str] = {}
|
2022-07-11 13:17:04 +07:00
|
|
|
for bump in self.bump_pr_ids:
|
|
|
|
repo_id = bump.repository_id
|
|
|
|
gh = gh_sessions[repo_id]
|
|
|
|
|
|
|
|
try:
|
|
|
|
prev = prevs[repo_id] = prevs.get(repo_id) or gh.head(master_name)
|
2023-08-23 19:09:32 +07:00
|
|
|
except Exception as e:
|
|
|
|
raise UserError(f"Unable to resolve branch {master_name} of repository {repo_id.name} to a commit.") from e
|
2022-07-11 13:17:04 +07:00
|
|
|
|
|
|
|
try:
|
2023-08-23 19:09:32 +07:00
|
|
|
commits = gh.commits(bump.pr_id.number)
|
|
|
|
except Exception as e:
|
|
|
|
raise UserError(f"Unable to fetch commits of bump PR {bump.pr_id.display_name}.") from e
|
2022-07-11 13:17:04 +07:00
|
|
|
|
2024-01-16 15:45:49 +07:00
|
|
|
_logger.debug("rebasing %s on %s (commits=%s)",
|
|
|
|
bump.pr_id.display_name, prev, len(commits))
|
2023-08-23 19:09:32 +07:00
|
|
|
bump_heads[repo_id] = repos[repo_id].rebase(prev, commits)[0]
|
2022-07-11 13:17:04 +07:00
|
|
|
|
2024-04-22 19:11:38 +07:00
|
|
|
# prevent concurrent updates to the commits table so we control the
|
|
|
|
# creation of commit objects from rebasing the release & bump PRs, do it
|
|
|
|
# only just before *pushing*
|
|
|
|
self.env.cr.execute("LOCK runbot_merge_commit IN ACCESS EXCLUSIVE MODE NOWAIT")
|
2022-07-11 13:17:04 +07:00
|
|
|
|
|
|
|
deployed = {}
|
|
|
|
# at this point we've got a bunch of tmp branches with merged release
|
|
|
|
# and bump PRs, it's time to update the corresponding targets
|
|
|
|
to_delete = [] # release prs go on new branches which we try to delete on failure
|
|
|
|
to_revert = [] # bump prs go on new branch which we try to revert on failure
|
|
|
|
failure = None
|
|
|
|
for rel in self.release_pr_ids:
|
|
|
|
repo_id = rel.repository_id
|
2023-08-23 19:09:32 +07:00
|
|
|
|
|
|
|
if repos[repo_id].push(
|
[CHG] forwardport: perform forward porting without working copies
The goal is to reduce maintenance and odd disk interactions &
concurrency issues, by not creating concurrent clones, not having to
push forks back in the repository, etc... it also removes the need to
cleanup "scratch" working copies though that looks not to have been an
issue in a while.
The work is done on isolated objects without using or mutating refs,
so even concurrent work should not be a problem.
This turns out to not be any more verbose (less so if anything) than
using `cherry-pick`, as that is not really designed for scripted /
non-interactive use, or for squashing commits thereafter. Working
directly with trees and commits is quite a bit cleaner even without a
ton of helpers.
Much of the credit goes to Julia Evans for [their investigation of
3-way merges as the underpinnings of cherry-picking][3-way merge],
this would have been a lot more difficult if I'd had to rediscover the
merge-base trick independently.
A few things have been changed by this:
- The old trace/stderr from cherrypick has disappeared as it's
generated by cherrypick, but for a non-interactive use it's kinda
useless anyway so I probably should have looked into removing it
earlier (I think the main use was investigation of the inflateinit
issue).
- Error on emptied commits has to be hand-rolled as `merge-tree`
couldn't care less, this is not hard but is a bit annoying.
- `merge-tree`'s conflict information only references raw commits,
which makes sense, but requires updating a bunch of tests. Then
again so does the fact that it *usually* doesn't send anything to
stderr, so that's usually disappearing.
Conveniently `merge-tree` merges the conflict marker directly in the
files / tree so we don't have to mess about moving them back out of
the repository and into the working copy as I assume cherry-pick does,
which means we don't have to try and commit them back in ether. That
is a huge part of the gain over faffing about with the working copy.
Fixes #847
[3-way merge]: https://jvns.ca/blog/2023/11/10/how-cherry-pick-and-revert-work/
2024-07-05 18:32:02 +07:00
|
|
|
git.source_url(repo_id),
|
2023-08-23 19:09:32 +07:00
|
|
|
f'{rel_heads[repo_id]}:refs/heads/{self.branch_name}',
|
|
|
|
).returncode:
|
2022-07-11 13:17:04 +07:00
|
|
|
failure = ('create', repo_id.name, self.branch_name)
|
|
|
|
break
|
2023-08-23 19:09:32 +07:00
|
|
|
|
|
|
|
deployed[rel.pr_id.id] = rel_heads[repo_id]
|
|
|
|
to_delete.append(repo_id)
|
2022-07-11 13:17:04 +07:00
|
|
|
else: # all release deployments succeeded
|
|
|
|
for bump in self.bump_pr_ids:
|
|
|
|
repo_id = bump.repository_id
|
2023-08-23 19:09:32 +07:00
|
|
|
if repos[repo_id].push(
|
[CHG] forwardport: perform forward porting without working copies
The goal is to reduce maintenance and odd disk interactions &
concurrency issues, by not creating concurrent clones, not having to
push forks back in the repository, etc... it also removes the need to
cleanup "scratch" working copies though that looks not to have been an
issue in a while.
The work is done on isolated objects without using or mutating refs,
so even concurrent work should not be a problem.
This turns out to not be any more verbose (less so if anything) than
using `cherry-pick`, as that is not really designed for scripted /
non-interactive use, or for squashing commits thereafter. Working
directly with trees and commits is quite a bit cleaner even without a
ton of helpers.
Much of the credit goes to Julia Evans for [their investigation of
3-way merges as the underpinnings of cherry-picking][3-way merge],
this would have been a lot more difficult if I'd had to rediscover the
merge-base trick independently.
A few things have been changed by this:
- The old trace/stderr from cherrypick has disappeared as it's
generated by cherrypick, but for a non-interactive use it's kinda
useless anyway so I probably should have looked into removing it
earlier (I think the main use was investigation of the inflateinit
issue).
- Error on emptied commits has to be hand-rolled as `merge-tree`
couldn't care less, this is not hard but is a bit annoying.
- `merge-tree`'s conflict information only references raw commits,
which makes sense, but requires updating a bunch of tests. Then
again so does the fact that it *usually* doesn't send anything to
stderr, so that's usually disappearing.
Conveniently `merge-tree` merges the conflict marker directly in the
files / tree so we don't have to mess about moving them back out of
the repository and into the working copy as I assume cherry-pick does,
which means we don't have to try and commit them back in ether. That
is a huge part of the gain over faffing about with the working copy.
Fixes #847
[3-way merge]: https://jvns.ca/blog/2023/11/10/how-cherry-pick-and-revert-work/
2024-07-05 18:32:02 +07:00
|
|
|
git.source_url(repo_id),
|
2023-08-23 19:09:32 +07:00
|
|
|
f'{bump_heads[repo_id]}:refs/heads/{master_name}'
|
|
|
|
).returncode:
|
2022-07-11 13:17:04 +07:00
|
|
|
failure = ('fast-forward', repo_id.name, master_name)
|
2021-11-12 22:04:34 +07:00
|
|
|
break
|
|
|
|
|
2023-08-23 19:09:32 +07:00
|
|
|
deployed[bump.pr_id.id] = bump_heads[repo_id]
|
|
|
|
to_revert.append(repo_id)
|
|
|
|
|
2022-07-11 13:17:04 +07:00
|
|
|
if failure:
|
|
|
|
addendums = []
|
|
|
|
# creating the branch failed, try to delete all previous branches
|
|
|
|
failures = []
|
|
|
|
for prev_id in to_revert:
|
2023-08-23 19:09:32 +07:00
|
|
|
if repos[prev_id].push(
|
|
|
|
'-f',
|
[CHG] forwardport: perform forward porting without working copies
The goal is to reduce maintenance and odd disk interactions &
concurrency issues, by not creating concurrent clones, not having to
push forks back in the repository, etc... it also removes the need to
cleanup "scratch" working copies though that looks not to have been an
issue in a while.
The work is done on isolated objects without using or mutating refs,
so even concurrent work should not be a problem.
This turns out to not be any more verbose (less so if anything) than
using `cherry-pick`, as that is not really designed for scripted /
non-interactive use, or for squashing commits thereafter. Working
directly with trees and commits is quite a bit cleaner even without a
ton of helpers.
Much of the credit goes to Julia Evans for [their investigation of
3-way merges as the underpinnings of cherry-picking][3-way merge],
this would have been a lot more difficult if I'd had to rediscover the
merge-base trick independently.
A few things have been changed by this:
- The old trace/stderr from cherrypick has disappeared as it's
generated by cherrypick, but for a non-interactive use it's kinda
useless anyway so I probably should have looked into removing it
earlier (I think the main use was investigation of the inflateinit
issue).
- Error on emptied commits has to be hand-rolled as `merge-tree`
couldn't care less, this is not hard but is a bit annoying.
- `merge-tree`'s conflict information only references raw commits,
which makes sense, but requires updating a bunch of tests. Then
again so does the fact that it *usually* doesn't send anything to
stderr, so that's usually disappearing.
Conveniently `merge-tree` merges the conflict marker directly in the
files / tree so we don't have to mess about moving them back out of
the repository and into the working copy as I assume cherry-pick does,
which means we don't have to try and commit them back in ether. That
is a huge part of the gain over faffing about with the working copy.
Fixes #847
[3-way merge]: https://jvns.ca/blog/2023/11/10/how-cherry-pick-and-revert-work/
2024-07-05 18:32:02 +07:00
|
|
|
git.source_url(prev_id),
|
2023-08-23 19:09:32 +07:00
|
|
|
f'{prevs[prev_id]}:refs/heads/{master_name}',
|
|
|
|
).returncode:
|
2022-07-11 13:17:04 +07:00
|
|
|
failures.append(prev_id.name)
|
|
|
|
if failures:
|
|
|
|
addendums.append(
|
|
|
|
"Subsequently unable to revert branches created in %s." % \
|
|
|
|
', '.join(failures)
|
|
|
|
)
|
|
|
|
failures.clear()
|
|
|
|
|
|
|
|
for prev_id in to_delete:
|
2023-08-23 19:09:32 +07:00
|
|
|
if repos[prev_id].push(
|
[CHG] forwardport: perform forward porting without working copies
The goal is to reduce maintenance and odd disk interactions &
concurrency issues, by not creating concurrent clones, not having to
push forks back in the repository, etc... it also removes the need to
cleanup "scratch" working copies though that looks not to have been an
issue in a while.
The work is done on isolated objects without using or mutating refs,
so even concurrent work should not be a problem.
This turns out to not be any more verbose (less so if anything) than
using `cherry-pick`, as that is not really designed for scripted /
non-interactive use, or for squashing commits thereafter. Working
directly with trees and commits is quite a bit cleaner even without a
ton of helpers.
Much of the credit goes to Julia Evans for [their investigation of
3-way merges as the underpinnings of cherry-picking][3-way merge],
this would have been a lot more difficult if I'd had to rediscover the
merge-base trick independently.
A few things have been changed by this:
- The old trace/stderr from cherrypick has disappeared as it's
generated by cherrypick, but for a non-interactive use it's kinda
useless anyway so I probably should have looked into removing it
earlier (I think the main use was investigation of the inflateinit
issue).
- Error on emptied commits has to be hand-rolled as `merge-tree`
couldn't care less, this is not hard but is a bit annoying.
- `merge-tree`'s conflict information only references raw commits,
which makes sense, but requires updating a bunch of tests. Then
again so does the fact that it *usually* doesn't send anything to
stderr, so that's usually disappearing.
Conveniently `merge-tree` merges the conflict marker directly in the
files / tree so we don't have to mess about moving them back out of
the repository and into the working copy as I assume cherry-pick does,
which means we don't have to try and commit them back in ether. That
is a huge part of the gain over faffing about with the working copy.
Fixes #847
[3-way merge]: https://jvns.ca/blog/2023/11/10/how-cherry-pick-and-revert-work/
2024-07-05 18:32:02 +07:00
|
|
|
git.source_url(prev_id),
|
2023-08-23 19:09:32 +07:00
|
|
|
f':refs/heads/{self.branch_name}'
|
|
|
|
).returncode:
|
2022-07-11 13:17:04 +07:00
|
|
|
failures.append(prev_id.name)
|
|
|
|
if failures:
|
|
|
|
addendums.append(
|
|
|
|
"Subsequently unable to delete branches created in %s." % \
|
|
|
|
", ".join(failures)
|
|
|
|
)
|
|
|
|
failures.clear()
|
|
|
|
|
|
|
|
if addendums:
|
|
|
|
addendum = '\n\n' + '\n'.join(addendums)
|
|
|
|
else:
|
|
|
|
addendum = ''
|
|
|
|
|
|
|
|
reason, repo, branch = failure
|
|
|
|
raise UserError(
|
|
|
|
f"Unable to {reason} branch {repo}:{branch}.{addendum}"
|
|
|
|
)
|
|
|
|
|
2024-02-05 22:10:15 +07:00
|
|
|
b = self.env['runbot_merge.branch'].search([('name', '=', self.branch_name)])
|
[MERGE] bot from 16.0 to 17.0
Broken (can't run odoo at all):
- In Odoo 17.0, the `pre_init_hook` takes an env, not a cursor, update
`_check_citext`.
- Odoo 17.0 rejects `@attrs` and doesn't say where they are or how to
update them, fun, hunt down `attrs={'invisible': ...` and try to fix
them.
- Odoo 17.0 warns on non-multi creates, update them, most were very
reasonable, one very wasn't.
Test failures:
- Odoo 17.0 deprecates `name_get` and doesn't use it as a *source*
anymore, replace overrides by overrides to `_compute_display_name`.
- Multiple tracking changes:
- `_track_set_author` takes a `Partner` not an id.
- `_message_compute_author` still requires overriding in order to
handle record creation, which in standard doesn't support author
overriding.
- `mail.tracking.value.field_type` has been removed, the field type
now needs to be retrieved from the `field_id`.
- Some tracking ordering have changed and require adjusting a few
tests.
Also added a few flushes before SQL queries which are not (obviously
at least) at the start of a cron or controller, no test failure
observed but better safe than sorry (probably).
2024-08-12 18:13:03 +07:00
|
|
|
# We specifically don't want to modified() or anything.
|
2024-02-05 22:10:15 +07:00
|
|
|
self.env.cr.execute(
|
|
|
|
"UPDATE runbot_merge_batch SET target=%s WHERE id = %s;"
|
|
|
|
"UPDATE runbot_merge_pull_requests SET target=%s WHERE id = any(%s)",
|
|
|
|
[
|
|
|
|
b.id, self.release_pr_ids.pr_id.batch_id.id,
|
|
|
|
b.id, self.release_pr_ids.pr_id.ids,
|
|
|
|
]
|
|
|
|
)
|
2024-01-29 20:09:22 +07:00
|
|
|
all_prs.batch_id.merge_date = fields.Datetime.now()
|
|
|
|
all_prs.reviewed_by = self.env.user.partner_id.id
|
2024-04-22 19:11:38 +07:00
|
|
|
for p in all_prs:
|
|
|
|
p.commits_map = json.dumps({
|
|
|
|
'': deployed[p.id],
|
|
|
|
p.head: deployed[p.id]
|
|
|
|
})
|
|
|
|
|
|
|
|
# stagings have to be created conditionally as otherwise we might not
|
|
|
|
# have a `target` to set and it's mandatory
|
|
|
|
laster = self.env['runbot_merge.stagings'].search(
|
|
|
|
[('target', '=', master.id), ('state', '=', 'success')],
|
|
|
|
order='id desc',
|
|
|
|
limit=1,
|
|
|
|
).commits.mapped(lambda c: (c.repository_id, c.commit_id))
|
|
|
|
if self.release_pr_ids:
|
|
|
|
rel_items = [(0, 0, {
|
|
|
|
'repository_id': repo.id,
|
|
|
|
'commit_id': self.env['runbot_merge.commit'].create({
|
|
|
|
'sha': sha,
|
|
|
|
'to_check': False,
|
|
|
|
}).id,
|
|
|
|
} if (sha := rel_heads.get(repo)) else {
|
|
|
|
'repository_id': repo.id,
|
|
|
|
'commit_id': commit.id,
|
|
|
|
})
|
|
|
|
for repo, commit in laster
|
|
|
|
]
|
|
|
|
self.env['runbot_merge.stagings'].create([{
|
|
|
|
'state': 'success',
|
|
|
|
'reason': 'release freeze staging',
|
|
|
|
'active': False,
|
|
|
|
'target': b.id,
|
|
|
|
'staging_batch_ids': [
|
|
|
|
(0, 0, {'runbot_merge_batch_id': batch.id})
|
|
|
|
for batch in self.release_pr_ids.pr_id.batch_id
|
|
|
|
],
|
|
|
|
'heads': rel_items,
|
|
|
|
'commits': rel_items,
|
|
|
|
}])
|
|
|
|
|
|
|
|
if self.bump_pr_ids:
|
|
|
|
bump_items = [(0, 0, {
|
|
|
|
'repository_id': repo.id,
|
|
|
|
'commit_id': self.env['runbot_merge.commit'].create({
|
|
|
|
'sha': sha,
|
|
|
|
'to_check': False,
|
|
|
|
}).id,
|
|
|
|
} if (sha := bump_heads.get(repo)) else {
|
|
|
|
'repository_id': repo.id,
|
|
|
|
'commit_id': commit.id,
|
|
|
|
})
|
|
|
|
for repo, commit in laster
|
|
|
|
]
|
|
|
|
self.env['runbot_merge.stagings'].create([{
|
|
|
|
'state': 'success',
|
|
|
|
'reason': 'bump freeze staging',
|
|
|
|
'active': False,
|
|
|
|
'target': master.id,
|
|
|
|
'staging_batch_ids': [
|
|
|
|
(0, 0, {'runbot_merge_batch_id': batch.id})
|
|
|
|
for batch in self.bump_pr_ids.pr_id.batch_id
|
|
|
|
],
|
|
|
|
'heads': bump_items,
|
|
|
|
'commits': bump_items,
|
|
|
|
}])
|
|
|
|
|
2022-07-11 13:17:04 +07:00
|
|
|
self.env['runbot_merge.pull_requests.feedback'].create([{
|
|
|
|
'repository': pr.repository.id,
|
|
|
|
'pull_request': pr.number,
|
|
|
|
'close': True,
|
|
|
|
'message': json.dumps({
|
|
|
|
'sha': deployed[pr.id],
|
|
|
|
'base': self.branch_name if pr in self.release_pr_ids.pr_id else None
|
|
|
|
})
|
|
|
|
} for pr in all_prs])
|
2021-11-12 22:04:34 +07:00
|
|
|
|
2023-01-25 18:14:20 +07:00
|
|
|
if self.bump_pr_ids:
|
|
|
|
master.active_staging_id.cancel("freeze by %s", self.env.user.login)
|
2021-11-12 22:04:34 +07:00
|
|
|
# delete wizard
|
|
|
|
self.sudo().unlink()
|
|
|
|
# managed to create all the things, show reminder text (or close)
|
|
|
|
if project_id.freeze_reminder:
|
|
|
|
return {
|
|
|
|
'type': 'ir.actions.act_window',
|
|
|
|
'target': 'new',
|
|
|
|
'name': f'Freeze reminder {project_id.name}',
|
|
|
|
'view_mode': 'form',
|
|
|
|
'res_model': project_id._name,
|
|
|
|
'res_id': project_id.id,
|
|
|
|
'view_id': self.env.ref('runbot_merge.project_freeze_reminder').id
|
|
|
|
}
|
|
|
|
|
|
|
|
return {'type': 'ir.actions.act_window_close'}
|
|
|
|
|
|
|
|
class ReleasePullRequest(models.Model):
|
|
|
|
_name = 'runbot_merge.project.freeze.prs'
|
|
|
|
_description = "links to pull requests used to \"cap\" freezes"
|
|
|
|
|
|
|
|
wizard_id = fields.Many2one('runbot_merge.project.freeze', required=True, index=True, ondelete='cascade')
|
|
|
|
repository_id = fields.Many2one('runbot_merge.repository', required=True)
|
|
|
|
pr_id = fields.Many2one(
|
|
|
|
'runbot_merge.pull_requests',
|
|
|
|
domain='[("repository", "=", repository_id), ("state", "not in", ("closed", "merged"))]',
|
|
|
|
string="Release Pull Request",
|
|
|
|
)
|
2022-07-11 13:17:04 +07:00
|
|
|
label = fields.Char(related='pr_id.label')
|
|
|
|
|
|
|
|
def write(self, vals):
|
|
|
|
# only the pr should be writeable after initial creation
|
|
|
|
assert 'wizard_id' not in vals
|
|
|
|
assert 'repository_id' not in vals
|
|
|
|
# and if the PR gets set, it should match the requested repository
|
|
|
|
if 'pr_id' in vals:
|
|
|
|
assert self.env['runbot_merge.pull_requests'].browse(vals['pr_id'])\
|
|
|
|
.repository == self.repository_id
|
|
|
|
|
|
|
|
return super().write(vals)
|
|
|
|
|
|
|
|
class BumpPullRequest(models.Model):
|
|
|
|
_name = 'runbot_merge.project.freeze.bumps'
|
|
|
|
_description = "links to pull requests used to \"bump\" the development branches"
|
|
|
|
|
|
|
|
wizard_id = fields.Many2one('runbot_merge.project.freeze', required=True, index=True, ondelete='cascade')
|
2022-12-07 15:25:55 +07:00
|
|
|
# FIXME: repo = wizard.repo?
|
2022-07-11 13:17:04 +07:00
|
|
|
repository_id = fields.Many2one('runbot_merge.repository', required=True)
|
|
|
|
pr_id = fields.Many2one(
|
|
|
|
'runbot_merge.pull_requests',
|
|
|
|
domain='[("repository", "=", repository_id), ("state", "not in", ("closed", "merged"))]',
|
2022-12-07 15:25:55 +07:00
|
|
|
string="Bump Pull Request",
|
2022-07-11 13:17:04 +07:00
|
|
|
)
|
|
|
|
label = fields.Char(related='pr_id.label')
|
2021-11-12 22:04:34 +07:00
|
|
|
|
2022-12-07 15:25:55 +07:00
|
|
|
@api.onchange('repository_id')
|
|
|
|
def _onchange_repository(self):
|
|
|
|
self.pr_id = False
|
|
|
|
|
2021-11-12 22:04:34 +07:00
|
|
|
def write(self, vals):
|
|
|
|
# only the pr should be writeable after initial creation
|
|
|
|
assert 'wizard_id' not in vals
|
|
|
|
# and if the PR gets set, it should match the requested repository
|
2022-12-07 15:25:55 +07:00
|
|
|
if vals.get('pr_id'):
|
2021-11-12 22:04:34 +07:00
|
|
|
assert self.env['runbot_merge.pull_requests'].browse(vals['pr_id'])\
|
|
|
|
.repository == self.repository_id
|
|
|
|
|
|
|
|
return super().write(vals)
|
|
|
|
|
2022-02-07 21:15:13 +07:00
|
|
|
class RepositoryFreeze(models.Model):
|
|
|
|
_inherit = 'runbot_merge.repository'
|
|
|
|
freeze = fields.Boolean(required=True, default=True,
|
|
|
|
help="Freeze this repository by default")
|
|
|
|
|
2021-11-12 22:04:34 +07:00
|
|
|
@enum.unique
|
|
|
|
class Colors(enum.IntEnum):
|
|
|
|
No = 0
|
|
|
|
Red = 1
|
|
|
|
Orange = 2
|
|
|
|
Yellow = 3
|
|
|
|
LightBlue = 4
|
|
|
|
DarkPurple = 5
|
|
|
|
Salmon = 6
|
|
|
|
MediumBlue = 7
|
|
|
|
DarkBlue = 8
|
|
|
|
Fuchsia = 9
|
|
|
|
Green = 10
|
|
|
|
Purple = 11
|
|
|
|
|
|
|
|
STATE_COLORMAP = {
|
|
|
|
'opened': Colors.No,
|
|
|
|
'closed': Colors.Orange,
|
|
|
|
'validated': Colors.No,
|
|
|
|
'approved': Colors.No,
|
|
|
|
'ready': Colors.LightBlue,
|
|
|
|
'merged': Colors.Green,
|
|
|
|
'error': Colors.Red,
|
|
|
|
}
|
2023-01-24 13:41:14 +07:00
|
|
|
class PullRequest(models.Model):
|
2021-11-12 22:04:34 +07:00
|
|
|
_inherit = 'runbot_merge.pull_requests'
|
|
|
|
|
|
|
|
state_color = fields.Integer(compute='_compute_state_color')
|
|
|
|
|
|
|
|
@api.depends('state')
|
|
|
|
def _compute_state_color(self):
|
|
|
|
for p in self:
|
|
|
|
p.state_color = STATE_COLORMAP[p.state]
|
[FIX] runbot_merge: make freeze wizard labels lookup not shit
I DECLARE BANKRUPTCY!!!
The previous implementation of labels lookup was really not
intuitive (it was just a char field, and matched labels by equality
including the owner tag), and was also full of broken edge
cases (e.g. traceback if a label matched multiple PRs in the same repo
because people reuse branch names).
Tried messing about with contextual `display_name` and `name_search`
on PRs but the client goes wonky in that case, and there is no clean
autocomplete for non-relational fields.
So created a view which reifies labels, and that can be used as the
basis for our search. It doesn't have to be maintained by hand, can be
searched somewhat flexibly, we can add new view fields in the future
if desirable, and it seems to work fine providing a nice
understandable UX, with the reliability of using a normal Odoo model
the normal way.
Also fixed the handling of bump PRs, clearly clearing the entire field
before trying to update existing records (even with a link_to
inbetween) is not the web client's fancy, re-selecting the current
label would just empty the thing entirely.
So use a two-step process slightly closer to the release PRs instead:
- first update or delete the existing bump PRs
- then add the new ones
The second part is because bump PRs are somewhat less critical than
release, so it can be a bit more DWIM compared to the more deliberate
process of release PRs where first the list of repositories involved
has to be set up just so, then the PRs can be filled in each of them.
Fixes #697
2023-01-24 17:19:18 +07:00
|
|
|
|
|
|
|
class OpenPRLabels(models.Model):
|
|
|
|
"""Hacking around using contextual display_name to try and autocomplete
|
|
|
|
labels through PRs doesn't work because the client fucks up the display_name
|
|
|
|
(apparently they're not keyed on the context), therefore the behaviour
|
|
|
|
is inconsistent as the label shown in the autocomplete will result in the
|
|
|
|
PR being shown as its label in the o2m, and the other way around (if a PR
|
|
|
|
is selected directly in the o2m, then the PR's base display_name will be
|
|
|
|
shown in the label lookup field).
|
|
|
|
|
|
|
|
Therefore create a dumbshit view of label records.
|
|
|
|
|
|
|
|
Under the assumption that we'll have less than 256 repositories, the id of a
|
|
|
|
label record is the PR's id shifted as the high 24 bits, and the repo id as
|
|
|
|
the low 8.
|
|
|
|
"""
|
|
|
|
_name = 'runbot_merge.freeze.labels'
|
|
|
|
_description = "view representing labels for open PRs so they can autocomplete properly"
|
|
|
|
_rec_name = "label"
|
|
|
|
_auto = False
|
|
|
|
|
|
|
|
def init(self):
|
|
|
|
super().init()
|
2023-08-23 19:09:32 +07:00
|
|
|
drop_view_if_exists(self.env.cr, "runbot_merge_freeze_labels")
|
[FIX] runbot_merge: make freeze wizard labels lookup not shit
I DECLARE BANKRUPTCY!!!
The previous implementation of labels lookup was really not
intuitive (it was just a char field, and matched labels by equality
including the owner tag), and was also full of broken edge
cases (e.g. traceback if a label matched multiple PRs in the same repo
because people reuse branch names).
Tried messing about with contextual `display_name` and `name_search`
on PRs but the client goes wonky in that case, and there is no clean
autocomplete for non-relational fields.
So created a view which reifies labels, and that can be used as the
basis for our search. It doesn't have to be maintained by hand, can be
searched somewhat flexibly, we can add new view fields in the future
if desirable, and it seems to work fine providing a nice
understandable UX, with the reliability of using a normal Odoo model
the normal way.
Also fixed the handling of bump PRs, clearly clearing the entire field
before trying to update existing records (even with a link_to
inbetween) is not the web client's fancy, re-selecting the current
label would just empty the thing entirely.
So use a two-step process slightly closer to the release PRs instead:
- first update or delete the existing bump PRs
- then add the new ones
The second part is because bump PRs are somewhat less critical than
release, so it can be a bit more DWIM compared to the more deliberate
process of release PRs where first the list of repositories involved
has to be set up just so, then the PRs can be filled in each of them.
Fixes #697
2023-01-24 17:19:18 +07:00
|
|
|
self.env.cr.execute("""
|
|
|
|
CREATE VIEW runbot_merge_freeze_labels AS (
|
|
|
|
SELECT DISTINCT ON (label)
|
|
|
|
id << 8 | repository as id,
|
|
|
|
label
|
|
|
|
FROM runbot_merge_pull_requests
|
|
|
|
WHERE state != 'merged' AND state != 'closed'
|
|
|
|
ORDER BY label, repository, id
|
|
|
|
)""")
|
|
|
|
|
|
|
|
label = fields.Char()
|