gitlab-dangerfiles 4.6.0 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +0 -1
- data/.gitlab-ci.yml +11 -21
- data/.rubocop.yml +3 -16
- data/Gemfile.lock +231 -0
- data/lefthook.yml +0 -5
- data/lib/danger/plugins/changelog.rb +8 -4
- data/lib/danger/plugins/internal/helper.rb +17 -6
- data/lib/danger/plugins/roulette.rb +34 -267
- data/lib/danger/rules/commit_messages/Dangerfile +1 -6
- data/lib/danger/rules/simple_roulette/Dangerfile +4 -2
- data/lib/gitlab/dangerfiles/approval.rb +22 -0
- data/lib/gitlab/dangerfiles/base_linter.rb +1 -1
- data/lib/gitlab/dangerfiles/capability.rb +84 -0
- data/lib/gitlab/dangerfiles/commit_linter.rb +2 -2
- data/lib/gitlab/dangerfiles/emoji_checker.rb +1 -1
- data/lib/gitlab/dangerfiles/spec_helper.rb +231 -1
- data/lib/gitlab/dangerfiles/spin.rb +15 -0
- data/lib/gitlab/dangerfiles/spinner.rb +190 -0
- data/lib/gitlab/dangerfiles/teammate.rb +89 -3
- data/lib/gitlab/dangerfiles/type_label_guesser.rb +1 -1
- data/lib/gitlab/dangerfiles/version.rb +1 -1
- metadata +8 -4
- data/lib/gitlab/dangerfiles/category.rb +0 -111
@@ -1,34 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "../../gitlab/dangerfiles/approval"
|
4
|
+
require_relative "../../gitlab/dangerfiles/spinner"
|
3
5
|
require_relative "../../gitlab/dangerfiles/teammate"
|
4
|
-
require_relative "../../gitlab/dangerfiles/weightage/maintainers"
|
5
|
-
require_relative "../../gitlab/dangerfiles/weightage/reviewers"
|
6
6
|
|
7
7
|
module Danger
|
8
8
|
# Common helper functions for our danger scripts. See Danger::Helper
|
9
9
|
# for more details
|
10
10
|
class Roulette < Danger::Plugin
|
11
|
-
ROULETTE_DATA_URL = "https://gitlab-org.gitlab.io/gitlab-roulette/roulette.json"
|
12
11
|
HOURS_WHEN_PERSON_CAN_BE_PICKED = (6..14).freeze
|
13
|
-
|
14
|
-
Spin = Struct.new(:category, :reviewer, :maintainer, :optional_role)
|
15
12
|
HTTPError = Class.new(StandardError)
|
16
13
|
|
17
|
-
Approval = Struct.new(:category, :spin) do
|
18
|
-
def self.from_approval_rule(rule, maintainer)
|
19
|
-
category =
|
20
|
-
if rule["section"] == "codeowners"
|
21
|
-
"`#{rule["name"]}`"
|
22
|
-
else
|
23
|
-
rule["section"]
|
24
|
-
end.to_sym
|
25
|
-
|
26
|
-
spin = Spin.new(category, nil, maintainer, :reviewer)
|
27
|
-
|
28
|
-
new(category, spin)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
14
|
def prepare_categories(changes_keys)
|
33
15
|
categories = Set.new(changes_keys)
|
34
16
|
|
@@ -53,7 +35,7 @@ module Danger
|
|
53
35
|
#
|
54
36
|
# @return [Gitlab::Dangerfiles::Teammate]
|
55
37
|
def team_mr_author
|
56
|
-
@team_mr_author ||= find_member(helper.mr_author)
|
38
|
+
@team_mr_author ||= Gitlab::Dangerfiles::Teammate.find_member(helper.mr_author)
|
57
39
|
end
|
58
40
|
|
59
41
|
# Assigns GitLab team members to be reviewer and maintainer
|
@@ -63,70 +45,22 @@ module Danger
|
|
63
45
|
# @param categories [Array<Symbol>] An array of categories symbols.
|
64
46
|
#
|
65
47
|
# @return [Array<Spin>]
|
66
|
-
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
67
48
|
def spin(project = nil, categories = [:none], ux_fallback_wider_community_reviewer: teammate_pedroms)
|
49
|
+
# TODO: Deprecate the project argument. It prevents us from
|
50
|
+
# memorizing Spinner and can cause unexpected results if it's
|
51
|
+
# passing a different project than the merge request project.
|
68
52
|
project = (project || config_project_name).downcase
|
69
53
|
categories = categories.map { |category| category&.downcase || :none }
|
70
|
-
categories.reject! { |category| import_and_integrate_reject_category?(category, project) }
|
71
|
-
|
72
|
-
spins = categories.sort_by(&:to_s).map do |category|
|
73
|
-
spin_for_category(project, category)
|
74
|
-
end
|
75
|
-
|
76
|
-
backend_spin = spins.find { |spin| spin.category == :backend }
|
77
|
-
frontend_spin = spins.find { |spin| spin.category == :frontend }
|
78
|
-
|
79
|
-
spins.each do |spin|
|
80
|
-
case spin.category
|
81
|
-
when :qa
|
82
|
-
# MR includes QA changes, but also other changes, and author isn't an SET
|
83
|
-
if categories.size > 1 &&
|
84
|
-
!(team_mr_author && team_mr_author.capabilities(project).any? { |capability| capability.end_with?("qa") })
|
85
|
-
spin.optional_role = :maintainer
|
86
|
-
end
|
87
|
-
when :test
|
88
|
-
spin.optional_role = :maintainer
|
89
|
-
|
90
|
-
if spin.reviewer.nil?
|
91
|
-
# Fetch an already picked backend reviewer, or pick one otherwise
|
92
|
-
spin.reviewer = backend_spin&.reviewer || spin_for_category(project, :backend).reviewer
|
93
|
-
end
|
94
|
-
when :tooling
|
95
|
-
if spin.maintainer.nil?
|
96
|
-
# Fetch an already picked backend maintainer, or pick one otherwise
|
97
|
-
spin.maintainer = backend_spin&.maintainer || spin_for_category(project, :backend).maintainer
|
98
|
-
end
|
99
|
-
when :ci_template
|
100
|
-
if spin.maintainer.nil?
|
101
|
-
# Fetch an already picked backend maintainer, or pick one otherwise
|
102
|
-
spin.maintainer = backend_spin&.maintainer || spin_for_category(project, :backend).maintainer
|
103
|
-
end
|
104
|
-
when :analytics_instrumentation
|
105
|
-
spin.optional_role = :maintainer
|
106
|
-
|
107
|
-
if spin.maintainer.nil?
|
108
|
-
# Fetch an already picked maintainer, or pick one otherwise
|
109
|
-
spin.maintainer = backend_spin&.maintainer || frontend_spin&.maintainer || spin_for_category(project, :backend).maintainer
|
110
|
-
end
|
111
|
-
when :import_integrate_be, :import_integrate_fe
|
112
|
-
spin.optional_role = :maintainer
|
113
|
-
when :ux
|
114
|
-
spin.optional_role = :maintainer
|
115
54
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
end
|
124
|
-
|
125
|
-
spins
|
55
|
+
Gitlab::Dangerfiles::Spinner.new(
|
56
|
+
project: project,
|
57
|
+
author: helper.mr_author, team_author: team_mr_author,
|
58
|
+
labels: labels, categories: categories, random: random,
|
59
|
+
ux_fallback_wider_community_reviewer:
|
60
|
+
ux_fallback_wider_community_reviewer)
|
61
|
+
.spin
|
126
62
|
end
|
127
63
|
|
128
|
-
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
129
|
-
|
130
64
|
def codeowners_approvals
|
131
65
|
approval_rules = helper.mr_approval_state["rules"]
|
132
66
|
|
@@ -134,23 +68,31 @@ module Danger
|
|
134
68
|
|
135
69
|
required_approval_rules = unique_approval_rules(approval_rules)
|
136
70
|
required_approval_rules.filter_map do |rule|
|
137
|
-
spin_for_approval_rule?(rule)
|
138
|
-
|
71
|
+
if spin_for_approval_rule?(rule)
|
72
|
+
approver = Gitlab::Dangerfiles::Spinner.new(
|
73
|
+
project: config_project_name.downcase,
|
74
|
+
author: helper.mr_author, team_author: team_mr_author,
|
75
|
+
random: random
|
76
|
+
).spin_for_approver(rule)
|
77
|
+
|
78
|
+
Gitlab::Dangerfiles::Approval.from_approval_rule(rule, approver)
|
79
|
+
end
|
139
80
|
end
|
140
81
|
end
|
141
82
|
|
83
|
+
alias_method :required_approvals, :codeowners_approvals
|
84
|
+
|
85
|
+
# For backward compatibility
|
142
86
|
def warnings
|
143
|
-
|
87
|
+
Gitlab::Dangerfiles::Teammate.warnings
|
144
88
|
end
|
145
89
|
|
90
|
+
private
|
91
|
+
|
146
92
|
def teammate_pedroms
|
147
|
-
@teammate_pedroms ||= find_member("pedroms")
|
93
|
+
@teammate_pedroms ||= Gitlab::Dangerfiles::Teammate.find_member("pedroms")
|
148
94
|
end
|
149
95
|
|
150
|
-
alias_method :required_approvals, :codeowners_approvals
|
151
|
-
|
152
|
-
private
|
153
|
-
|
154
96
|
def spin_for_approval_rule?(rule)
|
155
97
|
rule["rule_type"] == "code_owner" &&
|
156
98
|
should_include_codeowners_rule?(rule) &&
|
@@ -188,179 +130,17 @@ module Danger
|
|
188
130
|
end
|
189
131
|
end
|
190
132
|
|
191
|
-
# @param [Gitlab::Dangerfiles::Teammate] person
|
192
|
-
# @return [Boolean]
|
193
|
-
def valid_person?(person)
|
194
|
-
!mr_author?(person) && person.available
|
195
|
-
end
|
196
|
-
|
197
|
-
# @param [Gitlab::Dangerfiles::Teammate] person
|
198
|
-
# @return [Boolean]
|
199
|
-
def mr_author?(person)
|
200
|
-
person.username == helper.mr_author
|
201
|
-
end
|
202
|
-
|
203
|
-
# @param [String] category name
|
204
|
-
# @return [Boolean]
|
205
|
-
def import_and_integrate_reject_category?(category, project)
|
206
|
-
# Reject Import and Integrate categories if the MR author has reviewing abilities for the category.
|
207
|
-
team_mr_author&.import_integrate_be?(project, category, labels) ||
|
208
|
-
team_mr_author&.import_integrate_fe?(project, category, labels)
|
209
|
-
end
|
210
|
-
|
211
133
|
def random
|
212
134
|
@random ||= Random.new(Digest::MD5.hexdigest(helper.mr_source_branch).to_i(16))
|
213
135
|
end
|
214
136
|
|
215
|
-
def spin_role_for_category(team, role, project, category)
|
216
|
-
team.select do |member|
|
217
|
-
member.public_send("#{role}?", project, category, labels)
|
218
|
-
end
|
219
|
-
end
|
220
|
-
|
221
|
-
# Known issue: If someone is rejected due to OOO, and then becomes not OOO, the
|
222
|
-
# selection will change on next spin.
|
223
|
-
#
|
224
|
-
# @param [Array<Gitlab::Dangerfiles::Teammate>] people
|
225
|
-
#
|
226
|
-
# @return [Gitlab::Dangerfiles::Teammate]
|
227
|
-
def spin_for_person(people)
|
228
|
-
shuffled_people = people.shuffle(random: random)
|
229
|
-
|
230
|
-
shuffled_people.find(&method(:valid_person?))
|
231
|
-
end
|
232
|
-
|
233
|
-
# Spin a reviewer for a particular approval rule
|
234
|
-
#
|
235
|
-
# @param [Hash] rule of approval
|
236
|
-
#
|
237
|
-
# @return [Gitlab::Dangerfiles::Teammate]
|
238
|
-
def spin_for_approver(rule)
|
239
|
-
approvers = rule["eligible_approvers"].filter_map do |approver|
|
240
|
-
find_member(approver["username"], project: config_project_name.downcase)
|
241
|
-
end
|
242
|
-
|
243
|
-
spin_for_person(approvers) || spin_for_approver_fallback(rule)
|
244
|
-
end
|
245
|
-
|
246
|
-
# It can be possible that we don't have a valid reviewer for approval.
|
247
|
-
# In this case, we sample again without considering:
|
248
|
-
#
|
249
|
-
# * If they're available
|
250
|
-
# * If they're an actual reviewer from roulette data
|
251
|
-
#
|
252
|
-
# We do this because we strictly require an approval from the approvers.
|
253
|
-
#
|
254
|
-
# @param [Hash] rule of approval
|
255
|
-
#
|
256
|
-
# @return [Gitlab::Dangerfiles::Teammate]
|
257
|
-
def spin_for_approver_fallback(rule)
|
258
|
-
fallback_approvers = rule["eligible_approvers"].map do |approver|
|
259
|
-
find_member(approver["username"]) ||
|
260
|
-
Gitlab::Dangerfiles::Teammate.new(approver)
|
261
|
-
end
|
262
|
-
|
263
|
-
# Intentionally not using `spin_for_person` to skip `valid_person?`.
|
264
|
-
# This should strictly return someone so we don't filter anything,
|
265
|
-
# and it's a fallback mechanism which should not happen often that
|
266
|
-
# deserves a complex algorithm.
|
267
|
-
fallback_approvers.sample(random: random)
|
268
|
-
end
|
269
|
-
|
270
|
-
def spin_for_category(project, category)
|
271
|
-
team = project_team(project)
|
272
|
-
reviewers, traintainers, maintainers =
|
273
|
-
%i[reviewer traintainer maintainer].map do |role|
|
274
|
-
spin_role_for_category(team, role, project, category)
|
275
|
-
end
|
276
|
-
|
277
|
-
weighted_reviewers = Gitlab::Dangerfiles::Weightage::Reviewers.new(reviewers, traintainers).execute
|
278
|
-
weighted_maintainers = Gitlab::Dangerfiles::Weightage::Maintainers.new(maintainers).execute
|
279
|
-
|
280
|
-
reviewer = spin_for_person(weighted_reviewers)
|
281
|
-
maintainer = spin_for_person(weighted_maintainers)
|
282
|
-
|
283
|
-
# allow projects with small number of reviewers to take from maintainers if possible
|
284
|
-
if reviewer.nil? && weighted_maintainers.uniq.size > 1
|
285
|
-
weighted_maintainers.delete(maintainer)
|
286
|
-
reviewer = spin_for_person(weighted_maintainers)
|
287
|
-
end
|
288
|
-
|
289
|
-
Spin.new(category, reviewer, maintainer, false)
|
290
|
-
end
|
291
|
-
|
292
137
|
def prepare_ux_category!(categories)
|
293
|
-
if labels.include?("Community contribution")
|
294
|
-
categories << :ux
|
295
|
-
else
|
296
|
-
begin
|
138
|
+
if labels.include?("Community contribution") ||
|
297
139
|
# We only want to spin a reviewer for merge requests which has a
|
298
|
-
# designer for the team.
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
labels << "Community contribution"
|
303
|
-
|
304
|
-
# Don't use a fallback reviewer so when a group doesn't have
|
305
|
-
# available reviewers, it'll not give us any reviewers.
|
306
|
-
ux_spin = spin(nil, [:ux], ux_fallback_wider_community_reviewer: nil).first
|
307
|
-
|
308
|
-
categories << :ux if ux_spin.reviewer || ux_spin.maintainer
|
309
|
-
ensure
|
310
|
-
# Make sure we delete the label afterward
|
311
|
-
labels.delete("Community contribution")
|
312
|
-
end
|
313
|
-
end
|
314
|
-
end
|
315
|
-
|
316
|
-
# Fetches the given +url+ and parse its response as JSON.
|
317
|
-
#
|
318
|
-
# @param [String] url
|
319
|
-
#
|
320
|
-
# @return [Hash, Array, NilClass]
|
321
|
-
def http_get_json(url)
|
322
|
-
rsp = Net::HTTP.get_response(URI.parse(url))
|
323
|
-
|
324
|
-
if rsp.is_a?(Net::HTTPRedirection)
|
325
|
-
uri = URI.parse(rsp.header["location"])
|
326
|
-
|
327
|
-
uri.query = nil if uri
|
328
|
-
|
329
|
-
warnings << "Redirection detected: #{uri}."
|
330
|
-
return nil
|
331
|
-
end
|
332
|
-
|
333
|
-
unless rsp.is_a?(Net::HTTPOK)
|
334
|
-
message = rsp.message[0, 30]
|
335
|
-
warnings << "HTTPError: Failed to read #{url}: #{rsp.code} #{message}."
|
336
|
-
return nil
|
337
|
-
end
|
338
|
-
|
339
|
-
JSON.parse(rsp.body)
|
340
|
-
end
|
341
|
-
|
342
|
-
# Looks up the current list of GitLab team members and parses it into a
|
343
|
-
# useful form.
|
344
|
-
#
|
345
|
-
# @return [Array<Gitlab::Dangerfiles::Teammate>]
|
346
|
-
def company_members
|
347
|
-
@company_members ||= begin
|
348
|
-
data = http_get_json(ROULETTE_DATA_URL) || []
|
349
|
-
data.map { |hash| Gitlab::Dangerfiles::Teammate.new(hash) }
|
350
|
-
rescue JSON::ParserError
|
351
|
-
warnings << "Failed to parse JSON response from #{ROULETTE_DATA_URL}"
|
352
|
-
[]
|
353
|
-
end
|
354
|
-
end
|
355
|
-
|
356
|
-
def find_member(username, project: nil)
|
357
|
-
company_members.find do |member|
|
358
|
-
member.username == username &&
|
359
|
-
if project
|
360
|
-
member.in_project?(project)
|
361
|
-
else
|
362
|
-
true
|
363
|
-
end
|
140
|
+
# designer for the team.
|
141
|
+
Gitlab::Dangerfiles::Teammate.has_member_for_the_group?(
|
142
|
+
:ux, project: config_project_name.downcase, labels: labels)
|
143
|
+
categories << :ux
|
364
144
|
end
|
365
145
|
end
|
366
146
|
|
@@ -377,18 +157,5 @@ module Danger
|
|
377
157
|
def labels
|
378
158
|
@labels ||= helper.mr_labels
|
379
159
|
end
|
380
|
-
|
381
|
-
# Like +team+, but only returns teammates in the current project, based on
|
382
|
-
# project_name.
|
383
|
-
#
|
384
|
-
# @return [Array<Gitlab::Dangerfiles::Teammate>]
|
385
|
-
def project_team(project_name)
|
386
|
-
company_members.select do |member|
|
387
|
-
member.in_project?(project_name)
|
388
|
-
end
|
389
|
-
rescue => err
|
390
|
-
warn("Reviewer roulette failed to load team data: #{err.message}")
|
391
|
-
[]
|
392
|
-
end
|
393
160
|
end
|
394
161
|
end
|
@@ -96,11 +96,6 @@ def lint_commits(commits)
|
|
96
96
|
if multi_line_commit_linter && multi_line_commit_linter.failed?
|
97
97
|
warn_or_fail_commits(multi_line_commit_linter)
|
98
98
|
commit_linters.delete(multi_line_commit_linter) # Don't show an error (here) and a warning (below)
|
99
|
-
elsif helper.ci? # We don't have access to the MR title locally
|
100
|
-
title_linter = lint_mr_title(helper.mr_title)
|
101
|
-
if title_linter.failed?
|
102
|
-
warn_or_fail_commits(title_linter)
|
103
|
-
end
|
104
99
|
end
|
105
100
|
else
|
106
101
|
if count_non_fixup_commits(commit_linters) > MAX_COMMITS_COUNT
|
@@ -121,7 +116,7 @@ def warn_or_fail_commits(failed_linters, default_to_fail: true)
|
|
121
116
|
when :subject_too_short, :subject_above_warning, :details_too_many_changes, :details_line_too_long
|
122
117
|
warn_commit(linter.commit, problem_desc)
|
123
118
|
else
|
124
|
-
self.__send__("#{level}_commit", linter.commit, problem_desc)
|
119
|
+
self.__send__(:"#{level}_commit", linter.commit, problem_desc)
|
125
120
|
end
|
126
121
|
end
|
127
122
|
end
|
@@ -16,13 +16,15 @@ To spread load more evenly across eligible reviewers, Danger has picked a candid
|
|
16
16
|
review slot. Feel free to
|
17
17
|
[override these selections](https://about.gitlab.com/handbook/engineering/projects/##{PROJECT_NAME})
|
18
18
|
if you think someone else would be better-suited
|
19
|
-
or use the [GitLab Review Workload Dashboard](https://gitlab-org.gitlab.io/gitlab-roulette
|
19
|
+
or use the [GitLab Review Workload Dashboard](https://gitlab-org.gitlab.io/gitlab-roulette/?currentProject=#{PROJECT_NAME})
|
20
|
+
to find other available reviewers.
|
20
21
|
|
21
22
|
To read more on how to use the reviewer roulette, please take a look at the
|
22
23
|
[Engineering workflow](https://about.gitlab.com/handbook/engineering/workflow/#basics)
|
23
24
|
and [code review guidelines](https://docs.gitlab.com/ee/development/code_review.html).
|
24
25
|
Please consider assigning a reviewer or maintainer who is a
|
25
|
-
[domain expert](https://about.gitlab.com/handbook/engineering/projects
|
26
|
+
[domain expert](https://about.gitlab.com/handbook/engineering/projects/##{PROJECT_NAME})
|
27
|
+
in the area of the merge request.
|
26
28
|
|
27
29
|
Once you've decided who will review this merge request, mention them as you
|
28
30
|
normally would! Danger does not automatically notify them for you.
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "spin"
|
4
|
+
|
5
|
+
module Gitlab
|
6
|
+
module Dangerfiles
|
7
|
+
Approval = Struct.new(:category, :spin) do
|
8
|
+
def self.from_approval_rule(rule, maintainer)
|
9
|
+
category =
|
10
|
+
if rule["section"] == "codeowners"
|
11
|
+
"`#{rule['name']}`"
|
12
|
+
else
|
13
|
+
rule["section"]
|
14
|
+
end.to_sym
|
15
|
+
|
16
|
+
spin = Spin.new(category, nil, maintainer, :reviewer)
|
17
|
+
|
18
|
+
new(category, spin)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -16,7 +16,7 @@ module Gitlab
|
|
16
16
|
subject_too_long: "The %s may not be longer than #{MAX_LINE_LENGTH} characters",
|
17
17
|
subject_starts_with_a_space: "The %s must not start with a space",
|
18
18
|
subject_starts_with_lowercase: "The %s must start with a capital letter",
|
19
|
-
subject_ends_with_a_period: "The %s must not end with a period"
|
19
|
+
subject_ends_with_a_period: "The %s must not end with a period"
|
20
20
|
}
|
21
21
|
end
|
22
22
|
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Dangerfiles
|
5
|
+
CapabilityStruct = Struct.new(:category, :project, :kind, :labels, keyword_init: true)
|
6
|
+
|
7
|
+
class Capability < CapabilityStruct
|
8
|
+
def self.for(category, **arguments)
|
9
|
+
(category_to_class[category] || self)
|
10
|
+
.new(category: category, **arguments)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.category_to_class
|
14
|
+
@category_to_class ||= {
|
15
|
+
none: None,
|
16
|
+
test: Test,
|
17
|
+
tooling: Tooling,
|
18
|
+
import_integrate_be: ImportIntegrateBE,
|
19
|
+
import_integrate_fe: ImportIntegrateFE,
|
20
|
+
ux: UX
|
21
|
+
}.freeze
|
22
|
+
end
|
23
|
+
private_class_method :category_to_class
|
24
|
+
|
25
|
+
def has_capability?(teammate)
|
26
|
+
teammate.capabilities(project).include?(capability)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def capability
|
32
|
+
@capability ||= "#{kind} #{category}"
|
33
|
+
end
|
34
|
+
|
35
|
+
class None < Capability
|
36
|
+
def capability
|
37
|
+
@capability ||= kind.to_s
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
class Test < Capability
|
42
|
+
def has_capability?(teammate)
|
43
|
+
return false if kind != :reviewer
|
44
|
+
|
45
|
+
area = teammate.role[/Software Engineer in Test(?:.*?, (\w+))/, 1]
|
46
|
+
|
47
|
+
!!area && labels.any?("devops::#{area.downcase}")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Tooling < Capability
|
52
|
+
def has_capability?(teammate)
|
53
|
+
if super
|
54
|
+
true
|
55
|
+
elsif %i[trainee_maintainer maintainer].include?(kind)
|
56
|
+
false
|
57
|
+
else # fallback to backend reviewer
|
58
|
+
teammate.capabilities(project).include?("#{kind} backend")
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class ImportIntegrateBE < Capability
|
64
|
+
def has_capability?(teammate)
|
65
|
+
kind == :reviewer &&
|
66
|
+
teammate.role.match?(/Backend Engineer.+Manage:Import and Integrate/)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
class ImportIntegrateFE < Capability
|
71
|
+
def has_capability?(teammate)
|
72
|
+
kind == :reviewer &&
|
73
|
+
teammate.role.match?(/Frontend Engineer.+Manage:Import and Integrate/)
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class UX < Capability
|
78
|
+
def has_capability?(teammate)
|
79
|
+
super && teammate.member_of_the_group?(labels)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -26,7 +26,7 @@ module Gitlab
|
|
26
26
|
message_contains_unicode_emoji: "Avoid the use of Unicode Emoji. These add no value to the commit " \
|
27
27
|
"message, and may not be displayed properly everywhere",
|
28
28
|
message_contains_short_reference: "Use full URLs instead of short references (`gitlab-org/gitlab#123` or " \
|
29
|
-
"`!123`), as short references are displayed as plain text outside of GitLab"
|
29
|
+
"`!123`), as short references are displayed as plain text outside of GitLab"
|
30
30
|
}
|
31
31
|
)
|
32
32
|
end
|
@@ -148,7 +148,7 @@ module Gitlab
|
|
148
148
|
|
149
149
|
def message_contains_short_reference?
|
150
150
|
match_data = commit.message.match(SHORT_REFERENCE_REGEX) ||
|
151
|
-
|
151
|
+
commit.message.match(MS_SHORT_REFERENCE_REGEX)
|
152
152
|
|
153
153
|
return false unless match_data
|
154
154
|
|