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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
|
# frozen_string_literal: true
module Gitlab
module GithubImport
module Importer
class ProtectedBranchImporter
attr_reader :project
# By default on GitHub, both developers and maintainers can merge
# a PR into the protected branch
GITHUB_DEFAULT_MERGE_ACCESS_LEVEL = Gitlab::Access::DEVELOPER
# protected_branch - An instance of
# `Gitlab::GithubImport::Representation::ProtectedBranch`.
# project - An instance of `Project`
# client - An instance of `Gitlab::GithubImport::Client`
def initialize(protected_branch, project, client)
@protected_branch = protected_branch
@project = project
@client = client
@user_finder = GithubImport::UserFinder.new(project, client)
end
def execute
# The creator of the project is always allowed to create protected
# branches, so we skip the authorization check in this service class.
ProtectedBranches::CreateService
.new(project, project.creator, params)
.execute(skip_authorization: true)
update_project_settings if default_branch?
end
private
attr_reader :protected_branch, :user_finder
def params
{
name: protected_branch.id,
push_access_levels_attributes: push_access_levels_attributes,
merge_access_levels_attributes: merge_access_levels_attributes,
allow_force_push: allow_force_push?,
code_owner_approval_required: code_owner_approval_required?
}
end
def allow_force_push?
return false unless protected_branch.allow_force_pushes
if protected_on_gitlab?
ProtectedBranch.allow_force_push?(project, protected_branch.id)
elsif default_branch?
!default_branch_protection.any?
else
true
end
end
def code_owner_approval_required?
return false unless licensed_feature_available?(:code_owner_approval_required)
return protected_branch.require_code_owner_reviews unless protected_on_gitlab?
# Gets the strictest require_code_owner rule between GitHub and GitLab
protected_branch.require_code_owner_reviews ||
ProtectedBranch.branch_requires_code_owner_approval?(
project,
protected_branch.id
)
end
def default_branch?
protected_branch.id == project.default_branch
end
def update_project_settings
update_setting_for_only_allow_merge_if_all_discussions_are_resolved
update_project_push_rule
end
def update_setting_for_only_allow_merge_if_all_discussions_are_resolved
return unless protected_branch.required_conversation_resolution
project.update(only_allow_merge_if_all_discussions_are_resolved: true)
end
def update_project_push_rule
return unless licensed_feature_available?(:push_rules)
return unless protected_branch.required_signatures
push_rule = project.push_rule || project.build_push_rule
push_rule.update!(reject_unsigned_commits: true)
project.project_setting.update!(push_rule_id: push_rule.id)
end
def push_access_levels_attributes
if allowed_to_push_gitlab_user_ids.present?
@allowed_to_push_gitlab_user_ids.map { |user_id| { user_id: user_id } }
elsif protected_branch.required_pull_request_reviews
[{ access_level: Gitlab::Access::NO_ACCESS }]
else
[{ access_level: gitlab_access_level_for(:push) }]
end
end
def merge_access_levels_attributes
[{ access_level: merge_access_level }]
end
def allowed_to_push_gitlab_user_ids
return if protected_branch.allowed_to_push_users.empty? ||
!licensed_feature_available?(:protected_refs_for_users)
@allowed_to_push_gitlab_user_ids = []
protected_branch.allowed_to_push_users.each do |github_user_data|
gitlab_user_id = user_finder.user_id_for(github_user_data)
next unless gitlab_user_id
@allowed_to_push_gitlab_user_ids << gitlab_user_id
end
@allowed_to_push_gitlab_user_ids &= project_member_ids
end
# Gets the strictest merge_access_level between GitHub and GitLab
def merge_access_level
gitlab_access = gitlab_access_level_for(:merge)
return gitlab_access if gitlab_access == Gitlab::Access::NO_ACCESS
[gitlab_access, GITHUB_DEFAULT_MERGE_ACCESS_LEVEL].max
end
# action - :push/:merge
def gitlab_access_level_for(action)
if default_branch?
action == :push ? default_branch_push_access_level : default_branch_merge_access_level
elsif protected_on_gitlab?
non_default_branch_access_level_for(action)
else
gitlab_default_access_level_for(action)
end
end
def default_branch_push_access_level
if default_branch_protection.developer_can_push?
Gitlab::Access::DEVELOPER
else
gitlab_default_access_level_for(:push)
end
end
def default_branch_merge_access_level
if default_branch_protection.developer_can_merge?
Gitlab::Access::DEVELOPER
else
gitlab_default_access_level_for(:merge)
end
end
def default_branch_protection
Gitlab::Access::BranchProtection.new(project.namespace.default_branch_protection)
end
def protected_on_gitlab?
ProtectedBranch.protected?(project, protected_branch.id)
end
def non_default_branch_access_level_for(action)
access_level = ProtectedBranch.access_levels_for_ref(protected_branch.id, action: action)
.find(&:role?)&.access_level
access_level || gitlab_default_access_level_for(action)
end
def gitlab_default_access_level_for(action)
return ProtectedBranch::PushAccessLevel::GITLAB_DEFAULT_ACCESS_LEVEL if action == :push
ProtectedBranch::MergeAccessLevel::GITLAB_DEFAULT_ACCESS_LEVEL
end
def licensed_feature_available?(feature)
project.licensed_feature_available?(feature)
end
def project_member_ids
project.authorized_users.map(&:id)
end
end
end
end
end
|