Fix wildcard branch matching in external status checks
What does this MR do and why?
External Status Checks scoped to a wildcard protected branch (e.g. DEV*) were silently skipped for MRs targeting matching branches (e.g. DEV-main).
Two layers were broken:
MergeRequests::ExternalStatusCheck.applicable_to_branchused a SQL equality predicate (protected_branches.name = ?).ExternalStatusCheck#async_executeusedprotected_branches.by_name(...)which is a fuzzyILIKE, not glob matching.
Neither path supported wildcards, so checks scoped to e.g. DEV* never triggered for MRs targeting DEV-main. The UI already exposes wildcard selection, so users believed it worked. This is a correctness/compliance regression for Ultimate customers (ZD 709115).
This change mirrors the proven pattern used by ApprovalProjectRule and EE::Project::FilterByBranch:
- Adds
MergeRequests::ExternalStatusCheck#applies_to_branch?which delegates toProtectedBranch.matching/RefMatcherfor glob matching, with aloaded?short-circuit that avoids extra SQL. - Reuses the existing
EE::Project::FilterByBranchassociation extension by addingextend: FilterByBranchtohas_many :external_status_checks. This removes the broken SQL-onlyapplicable_to_branchscope on the model and reuses the same Ruby filter thatapproval_rulesalready uses. - Replaces the
async_executeguard withapplies_to_branch?. - Preloads
protected_branchesinProject#execute_external_compliance_hooksso per-check evaluation does not cause N+1 SQL. - Updates the API endpoint (
GET /projects/:id/merge_requests/:iid/status_checks) to paginate the resultingArrayviaKaminari.paginate_array, sorted byidfor deterministic pagination.
Related issue
Closes #596608 (closed)
Screenshots or screen recordings
N/A — backend correctness fix.
How to set up and validate locally
- On any project with an MR feature branch named e.g.
DEV-maintargetingmaster, create an External Status Check (Settings → Merge requests → Status checks) and select a wildcard protected branch likeDEV*. - Open / update an MR targeting
DEV-main. - Before this MR: the check is silently skipped.
- After this MR: the check is enqueued via
ApprovalRules::ExternalApprovalRulePayloadWorkerand the API endpointGET /projects/:id/merge_requests/:iid/status_checksreturns it.
Tests
Adds regression specs for all four cases (exact match / wildcard match / wildcard non-match / "all branches") in both:
MergeRequests::ExternalStatusCheck.applicable_to_branch(collection filter)MergeRequests::ExternalStatusCheck#applies_to_branch?(single-record predicate)#async_execute(the worker enqueue path)
Also asserts an absolute query bound (<= 3) for the collection filter to detect any future N+1 regression, and includes a query_count == 0 test for repeat calls when protected_branches is preloaded.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
/cc @gitlab-org/govern/security-policies-backend