diff --git a/runbot_merge/controllers/misc.py b/runbot_merge/controllers/misc.py
index bbd27c1c0..8f18973c5 100644
--- a/runbot_merge/controllers/misc.py
+++ b/runbot_merge/controllers/misc.py
@@ -6,9 +6,9 @@ class MergebotController(Controller):
@from_role('tx', signed=True)
@route('/i18n/merge_commit', type='json', auth='public')
- def merge_commit(self, commit_hash, repository, branch, project="RD"):
+ def merge_commit(self, commit_hash, repository, branch, project="RD", callback_url=None):
"""Merge a specific commit hash in a repository
-
+
The commit_hash must be known by mergebot (in the git network)
Used for translation synchronisation from transifex
"""
@@ -25,9 +25,12 @@ def merge_commit(self, commit_hash, repository, branch, project="RD"):
if not target:
return {"error": f"Target branch {project}:{branch} not found"}
- patch = request.env["runbot_merge.patch"].sudo().create({
+ vals = {
"repository": repository_id.id,
"target": target.id,
- "commit": commit_hash
- })
+ "commit": commit_hash,
+ }
+ if callback_url:
+ vals["callback_url"] = callback_url
+ patch = request.env["runbot_merge.patch"].sudo().create(vals)
return {"patch": patch.id}
diff --git a/runbot_merge/data/merge_cron.xml b/runbot_merge/data/merge_cron.xml
index d0b6ba910..37135fb77 100644
--- a/runbot_merge/data/merge_cron.xml
+++ b/runbot_merge/data/merge_cron.xml
@@ -21,6 +21,15 @@
40
+
+ Fire pending patch callbacks
+
+ code
+ model._cron_fire_pending_callbacks()
+ 6
+ hours
+ -1
+
Warn on linked PRs where only one is ready
diff --git a/runbot_merge/models/patcher.py b/runbot_merge/models/patcher.py
index 699a4cf41..0573e19f1 100644
--- a/runbot_merge/models/patcher.py
+++ b/runbot_merge/models/patcher.py
@@ -21,6 +21,7 @@
from typing import Union
from markupsafe import Markup
+import requests
from odoo import models, fields, api
from odoo.exceptions import ValidationError
@@ -144,6 +145,16 @@ class Patch(models.Model):
repository = fields.Many2one('runbot_merge.repository', required=True, tracking=True)
target = fields.Many2one('runbot_merge.branch', required=True, tracking=True)
commit = fields.Char(size=40, string="commit to cherry-pick, must be in-network", tracking=True)
+ callback_url = fields.Char(
+ help="If set, a POST request is made to this URL after the patch was either applied or failed.",
+ tracking=True,
+ )
+ callback_retries_left = fields.Integer(
+ help="Number of times the callback can still be retried.",
+ default=5,
+ tracking=True,
+ )
+ success = fields.Boolean(help="Indicates whether the patch was successfully applied.", default=False, tracking=True)
patch = fields.Text(string="unified diff to apply", tracking=True)
format = fields.Selection([
@@ -215,7 +226,11 @@ def _auto_init(self):
super()._auto_init()
self.env.cr.execute("""
CREATE INDEX IF NOT EXISTS runbot_merge_patch_active
- ON runbot_merge_patch (target) WHERE active
+ ON runbot_merge_patch (target) WHERE active;
+ CREATE INDEX IF NOT EXISTS runbot_merge_patch_callback
+ ON runbot_merge_patch (callback_url, active)
+ WHERE callback_url IS NOT NULL
+ AND active IS FALSE;
""")
@api.model_create_multi
@@ -354,7 +369,9 @@ def _apply_patches(self, target: Branch) -> bool:
)
else:
info['target_head'] = c
+ patch.success = True
+ self.env.ref('runbot_merge.ir_cron_fire_patch_callbacks')._trigger()
return True
def _apply_commit(self, r: git.Repo, parent: str) -> str:
@@ -441,3 +458,32 @@ def reader(_r, f):
author=p.author,
committer=p.committer,
).stdout.strip()
+
+ @api.model
+ def _cron_fire_pending_callbacks(self) -> None:
+ pending_patches = self.search([('active', '=', False), ('callback_url', '!=', False)])
+ if not pending_patches:
+ return
+
+ session = requests.Session()
+ for patch in pending_patches:
+ if patch.callback_retries_left <= 0:
+ patch.callback_url = False
+ _logger.warning("Patch callback to %s failed too many times, giving up.", patch.callback_url)
+ self.env.cr.commit()
+ continue
+
+ try:
+ res = session.post(
+ patch.callback_url,
+ params={'success': patch.success},
+ timeout=10,
+ )
+ res.raise_for_status()
+ patch.callback_url = False
+ _logger.info("Patch callback to %s succeeded.", patch.callback_url)
+ except requests.RequestException:
+ _logger.warning("Patch callback to %s failed.", patch.callback_url)
+ finally:
+ patch.callback_retries_left -= 1
+ self.env.cr.commit()