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
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
|
# frozen_string_literal: true
class ProtectedBranch < ApplicationRecord
include ProtectedRef
include Gitlab::SQL::Pattern
include FromUnion
belongs_to :group, foreign_key: :namespace_id, touch: true, inverse_of: :protected_branches
validate :validate_either_project_or_top_group
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)
# Maintainers, owners and admins are allowed to create the default branch
if project.empty_repo? && project.default_branch_protected?
return true if user.admin? || project.team.max_member_access(user.id) > Gitlab::Access::DEVELOPER
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?
dry_run = Feature.disabled?(:rely_on_protected_branches_cache, project)
new_cache_result = new_cache(project, ref_name, dry_run: dry_run)
return new_cache_result unless new_cache_result.nil?
deprecated_cache(project, ref_name)
end
def self.new_cache(project, ref_name, dry_run: true)
ProtectedBranches::CacheService.new(project).fetch(ref_name, dry_run: dry_run) do # rubocop: disable CodeReuse/ServiceClass
self.matching(ref_name, protected_refs: protected_refs(project)).present?
end
end
# Deprecated: https://gitlab.com/gitlab-org/gitlab/-/issues/370608
# ----------------------------------------------------------------
CACHE_EXPIRE_IN = 1.hour
def self.deprecated_cache(project, ref_name)
Rails.cache.fetch(protected_ref_cache_key(project, ref_name), expires_in: CACHE_EXPIRE_IN) do
self.matching(ref_name, protected_refs: protected_refs(project)).present?
end
end
def self.protected_ref_cache_key(project, ref_name)
"protected_ref-#{project.cache_key}-#{Digest::SHA1.hexdigest(ref_name)}"
end
# End of deprecation --------------------------------------------
def self.allow_force_push?(project, ref_name)
if Feature.enabled?(:group_protected_branches)
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.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)
if Feature.enabled?(:group_protected_branches)
project.all_protected_branches
else
project.protected_branches
end
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')
|