1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
|
# frozen_string_literal: true
class ProtectedBranch < ApplicationRecord
include ProtectedRef
include Gitlab::SQL::Pattern
include FromUnion
include EachBatch
include Presentable
belongs_to :group, foreign_key: :namespace_id, touch: true, inverse_of: :protected_branches
validate :validate_either_project_or_top_group
validates :name, presence: true
validates :name, uniqueness: { scope: [:project_id, :namespace_id] }, if: :name_changed?
scope :requiring_code_owner_approval, -> { where(code_owner_approval_required: true) }
scope :allowing_force_push, -> { where(allow_force_push: true) }
scope :sorted_by_name, -> { order(name: :asc) }
scope :sorted_by_namespace_and_name, -> { order(:namespace_id, :name) }
scope :for_group, ->(group) { where(group: group) }
protected_ref_access_levels :merge, :push
def self.get_ids_by_name(name)
where(name: name).pluck(:id)
end
def self.protected_ref_accessible_to?(ref, user, project:, action:, protected_refs: nil)
if project.empty_repo?
member_access = project.team.max_member_access(user.id)
# Admins are always allowed to create the default branch
return true if user.admin? || user.can?(:admin_project, project)
# Developers can push if it is allowed by default branch protection settings
if member_access == Gitlab::Access::DEVELOPER && project.initial_push_to_default_branch_allowed_for_developer?
return true
end
end
super
end
# Check if branch name is marked as protected in the system
def self.protected?(project, ref_name)
return true if project.empty_repo? && project.default_branch_protected?
return false if ref_name.blank?
ProtectedBranches::CacheService.new(project).fetch(ref_name) do # rubocop: disable CodeReuse/ServiceClass
self.matching(ref_name, protected_refs: protected_refs(project)).present?
end
end
def self.allow_force_push?(project, ref_name)
if allow_protected_branches_for_group?(project.group)
protected_branches = project.all_protected_branches.matching(ref_name)
project_protected_branches, group_protected_branches = protected_branches.partition(&:project_id)
# Group owner can be able to enforce the settings
return group_protected_branches.any?(&:allow_force_push) if group_protected_branches.present?
return project_protected_branches.any?(&:allow_force_push) if project_protected_branches.present?
false
else
project.protected_branches.allowing_force_push.matching(ref_name).any?
end
end
def self.allow_protected_branches_for_group?(group)
Feature.enabled?(:group_protected_branches, group) || Feature.enabled?(:allow_protected_branches_for_group, group)
end
def self.any_protected?(project, ref_names)
protected_refs(project).any? do |protected_ref|
ref_names.any? do |ref_name|
protected_ref.matches?(ref_name)
end
end
end
def self.protected_refs(project)
project.all_protected_branches
end
# overridden in EE
def self.branch_requires_code_owner_approval?(project, branch_name)
false
end
def self.by_name(query)
return none if query.blank?
where(fuzzy_arel_match(:name, query.downcase))
end
def allow_multiple?(type)
type == :push
end
def self.downcase_humanized_name
name.underscore.humanize.downcase
end
def default_branch?
name == project.default_branch
end
def group_level?
entity.is_a?(Group)
end
def project_level?
entity.is_a?(Project)
end
def entity
group || project
end
private
def validate_either_project_or_top_group
if !project && !group
errors.add(:base, _('must be associated with a Group or a Project'))
elsif project && group
errors.add(:base, _('cannot be associated with both a Group and a Project'))
elsif group && group.subgroup?
errors.add(:base, _('cannot be associated with a subgroup'))
end
end
end
ProtectedBranch.prepend_mod_with('ProtectedBranch')
|