Skip to content

Commit 3a0b8ec

Browse files
[IMP] runbot_merge: recombine remaining batches after culprit PR
When a multi-batch staging fails and a culprit PR is later identified in a single-batch staging, cancel pending sibling stagings/splits from the same split root and create a new split with all remaining active batches. This lets the scheduler stage them together again, reducing CI cycles and accelerating merges while preserving safety (bisect still applies if more culprits/interactions remain).
1 parent 8e03691 commit 3a0b8ec

File tree

1 file changed

+41
-1
lines changed

1 file changed

+41
-1
lines changed

runbot_merge/models/pull_requests.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2304,6 +2304,41 @@ def fail(self, message, prs=None):
23042304
})
23052305
return True
23062306

2307+
def recombine_remaining_after_culprit(self, culprit_pr):
2308+
"""Combines all the remaining batches of the same split group
2309+
(same source_id) except for the batch of the culprit PR and the closed ones,
2310+
creating a single Split with the rest in order to test them together.
2311+
"""
2312+
culprit_batch = culprit_pr.batch_id
2313+
if not culprit_batch:
2314+
_logger.info("No culprit batch found for PR %s; skipping recombine", culprit_pr)
2315+
return False
2316+
2317+
Split = self.env['runbot_merge.split']
2318+
source = (self.parent_id or self)
2319+
2320+
sibling_splits = Split.search([('source_id', '=', source.id)])
2321+
if not sibling_splits:
2322+
_logger.info("No sibling splits for root staging %s; skipping recombine", source)
2323+
return False
2324+
2325+
candidate_batches = sibling_splits.mapped('batch_ids').filtered(lambda b: b.id != culprit_batch.id and b.active)
2326+
if len(candidate_batches) <= 1:
2327+
_logger.info("Nothing to recombine (remaining=%s) for source %s after culprit %s",
2328+
len(candidate_batches), source, culprit_pr)
2329+
return False
2330+
2331+
new_split = Split.create({
2332+
'target': self.target.id,
2333+
'source_id': source.id,
2334+
'batch_ids': [Command.link(b.id) for b in candidate_batches],
2335+
})
2336+
_logger.info(
2337+
"Recombined %d batches after culprit PR#%s into %s (source=%s)",
2338+
len(candidate_batches), culprit_pr.number, new_split, source
2339+
)
2340+
return True
2341+
23072342
def try_splitting(self):
23082343
batches = len(self.batch_ids)
23092344
if batches > 1:
@@ -2357,11 +2392,15 @@ def try_splitting(self):
23572392
viewmore = ' (view more at %(target_url)s)' % status
23582393
if pr:
23592394
self.fail("%s%s" % (reason, viewmore), pr)
2395+
try:
2396+
self.recombine_remaining_after_culprit(pr)
2397+
except Exception:
2398+
_logger.exception("Failed to recombine after culprit PR %s", pr)
23602399
else:
23612400
self.fail('%s on %s%s' % (reason, head.commit_id.sha, viewmore))
23622401
return False
23632402

2364-
# the staging failed but we don't have a specific culprit, fail
2403+
# the staging failed, but we don't have a specific culprit, fail
23652404
# everything
23662405
self.fail("unknown reason")
23672406

@@ -2527,6 +2566,7 @@ def for_commits(self, *heads):
25272566

25282567
class Split(models.Model):
25292568
_name = _description = 'runbot_merge.split'
2569+
_order = 'id desc'
25302570

25312571
target = fields.Many2one('runbot_merge.branch', required=True)
25322572
source_id = fields.Many2one('runbot_merge.stagings', required=True)

0 commit comments

Comments
 (0)