Skip processing empty access levels in BranchRules::UpdateService

What does this MR do and why?

Fixes an issue where toggling Code owner approval on the branch rules page throws an AccessDeniedError when a merge request approval policy with prevent_pushing_and_force_pushing is applied to the branch.

Root Cause

The GraphQL input type BranchProtectionInputType has default_value: [] for push_access_levels and merge_access_levels. When the frontend doesn't explicitly send these fields (e.g., when only toggling code_owner_approval_required), the backend receives an empty array.

Previously, extract_push_access_levels_params! used return unless push_levels_params which only returned early for nil, not for empty arrays. An empty array was processed by access_levels_attributes, which interpreted it as "delete all existing access levels" - marking every existing push access level for destruction.

This triggered the ForcePushCheck in the security policy enforcement, which correctly detected that push access levels were being modified and blocked the request.

Solution

Changed return unless push_levels_params to return if push_levels_params.blank? in both extract_push_access_levels_params! and extract_merge_access_levels_params!. Now empty arrays are treated the same as nil - the method returns early and existing access levels are preserved.

Screenshot

Scenario ff off ff on
branch rule w/ sec policy
branch rule w/o sec policy

MR acceptance checklist

Evaluate this MR against the MR acceptance checklist.

How to set up and validate locally

  1. Create a project with a protected branch (e.g., main)
  2. Navigate to Secure > Policies > New policy > Merge request approval policy
  3. Create a policy with prevent_pushing_and_force_pushing: true targeting the protected branch
  4. Navigate to Settings > Repository > Branch rules > Edit the affected branch
  5. Toggle Code owner approval - should now work without error

Closes #593578

Edited by Alexander Turinske

Merge request reports

Loading