Skip to content

Commit

Permalink
[IMP] mergebot: staging endpoints
Browse files Browse the repository at this point in the history
- use lingo `staged` and `merged` for clarity (instead of `heads` and
  `commits`)
- convert from json-rpc to regular HTTP to make easier to call
- add the staged and merged heads when returning staging data
- return more complete objects (rather than a list of ids) when
  matching stagings based on commits / heads

Fixes #1018
  • Loading branch information
xmo-odoo committed Jan 14, 2025
1 parent 89411f9 commit 7d5c3dc
Showing 1 changed file with 42 additions and 38 deletions.
80 changes: 42 additions & 38 deletions runbot_merge/controllers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from typing import Callable

import sentry_sdk
from werkzeug.exceptions import NotFound, UnprocessableEntity

from odoo.api import Environment
from odoo.http import Controller, request, route, Response
Expand All @@ -18,40 +19,56 @@

_logger = logging.getLogger(__name__)

def staging_dict(staging):
return {
'id': staging.id,
'pull_requests': staging.batch_ids.prs.mapped(lambda p: {
'name': p.display_name,
'repository': p.repository.name,
'number': p.number,
}),
'merged': staging.commit_ids.mapped('sha'),
'staged': staging.head_ids.mapped('sha'),
}

class MergebotController(Controller):
@route('/runbot_merge/stagings', auth='none', type='json')
def stagings_for_commits(self, commits=None, heads=None):
Stagings = request.env(user=1)['runbot_merge.stagings'].sudo()
if commits:
stagings = Stagings.for_commits(*commits)
elif heads:
stagings = Stagings.for_heads(*heads)
else:
raise ValueError('Must receive one of "commits" or "heads" kwarg')
@route('/runbot_merge/stagings', auth='none', type='http', methods=['GET'])
def stagings_for_commits(self, **kw):
commits = request.httprequest.args.getlist('merged')
heads = request.httprequest.args.getlist('staged')
if bool(commits) ^ bool(heads):
Stagings = request.env(user=1)['runbot_merge.stagings'].sudo()
if commits:
stagings = Stagings.for_commits(*commits)
else:
stagings = Stagings.for_heads(*heads)

return stagings.ids
# can pass `?state=` to get stagings in any state
if state := request.httprequest.args.get('state', 'success'):
stagings = stagings.filtered(lambda s: s.state == state)
return request.make_json_response(stagings.mapped(staging_dict))
else:
raise UnprocessableEntity("Must receive either `merged` or `staged` query parameters (can receive multiple of either)")

@route('/runbot_merge/stagings/<int:staging>', auth='none', type='json')
@route('/runbot_merge/stagings/<int:staging>', auth='none', type='http', methods=['GET'])
def prs_for_staging(self, staging):
staging = request.env(user=1)['runbot_merge.stagings'].browse(staging)
return [
batch.prs.mapped(lambda p: {
'name': p.display_name,
'repository': p.repository.name,
'number': p.number,
})
for batch in staging.sudo().batch_ids
]
staging = request.env(user=1)['runbot_merge.stagings'].browse(staging).sudo()
if not staging.exists:
raise NotFound()

return request.make_json_response(staging_dict(staging))

@route('/runbot_merge/stagings/<int:from_staging>/<int:to_staging>', auth='none', type='json')
def prs_for_stagings(self, from_staging, to_staging, include_from=True, include_to=True):
@route('/runbot_merge/stagings/<int:from_staging>/<int:to_staging>', auth='none', type='http', methods=['GET'])
def prs_for_stagings(self, from_staging, to_staging, include_from='1', include_to='1'):
Stagings = request.env(user=1, context={"active_test": False})['runbot_merge.stagings']
from_staging = Stagings.browse(from_staging)
to_staging = Stagings.browse(to_staging)
if not (from_staging.exists() and to_staging.exists()):
raise NotFound()
if from_staging.target != to_staging.target:
raise ValueError(f"Stagings must have the same target branch, found {from_staging.target.name} and {to_staging.target.name}")
raise UnprocessableEntity(f"Stagings must have the same target branch, found {from_staging.target.name} and {to_staging.target.name}")
if from_staging.id >= to_staging.id:
raise ValueError("first staging must be older than second staging")
raise UnprocessableEntity("first staging must be older than second staging")

stagings = Stagings.search([
('target', '=', to_staging.target.id),
Expand All @@ -60,20 +77,7 @@ def prs_for_stagings(self, from_staging, to_staging, include_from=True, include_
('id', '<=' if include_to else '<', to_staging.id),
], order="id asc")

return [
{
'staging': staging.id,
'prs': [
batch.prs.mapped(lambda p: {
'name': p.display_name,
'repository': p.repository.name,
'number': p.number,
})
for batch in staging.batch_ids
]
}
for staging in stagings
]
return request.make_json_response([staging_dict(staging) for staging in stagings])

@route('/runbot_merge/hooks', auth='none', type='http', csrf=False, methods=['POST'])
def index(self) -> Response:
Expand Down

0 comments on commit 7d5c3dc

Please sign in to comment.