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:

  1. MergeRequests::ExternalStatusCheck.applicable_to_branch used a SQL equality predicate (protected_branches.name = ?).
  2. ExternalStatusCheck#async_execute used protected_branches.by_name(...) which is a fuzzy ILIKE, 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 to ProtectedBranch.matching / RefMatcher for glob matching, with a loaded? short-circuit that avoids extra SQL.
  • Reuses the existing EE::Project::FilterByBranch association extension by adding extend: FilterByBranch to has_many :external_status_checks. This removes the broken SQL-only applicable_to_branch scope on the model and reuses the same Ruby filter that approval_rules already uses.
  • Replaces the async_execute guard with applies_to_branch?.
  • Preloads protected_branches in Project#execute_external_compliance_hooks so per-check evaluation does not cause N+1 SQL.
  • Updates the API endpoint (GET /projects/:id/merge_requests/:iid/status_checks) to paginate the resulting Array via Kaminari.paginate_array, sorted by id for deterministic pagination.

Closes #596608 (closed)

Screenshots or screen recordings

N/A — backend correctness fix.

How to set up and validate locally

  1. On any project with an MR feature branch named e.g. DEV-main targeting master, create an External Status Check (Settings → Merge requests → Status checks) and select a wildcard protected branch like DEV*.
  2. Open / update an MR targeting DEV-main.
  3. Before this MR: the check is silently skipped.
  4. After this MR: the check is enqueued via ApprovalRules::ExternalApprovalRulePayloadWorker and the API endpoint GET /projects/:id/merge_requests/:iid/status_checks returns 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

Merge request reports

Loading
Loading