gitlab-dangerfiles 0.5.0 → 0.6.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/lib/danger/helper.rb +123 -19
- data/lib/danger/roulette.rb +15 -8
- data/lib/gitlab/dangerfiles/base_linter.rb +96 -0
- data/lib/gitlab/dangerfiles/commit_linter.rb +25 -103
- data/lib/gitlab/dangerfiles/merge_request_linter.rb +30 -0
- data/lib/gitlab/dangerfiles/teammate.rb +7 -1
- data/lib/gitlab/dangerfiles/title_linting.rb +38 -0
- data/lib/gitlab/dangerfiles/version.rb +1 -1
- data/lib/gitlab/dangerfiles/weightage.rb +10 -0
- data/lib/gitlab/dangerfiles/weightage/maintainers.rb +33 -0
- data/lib/gitlab/dangerfiles/weightage/reviewers.rb +65 -0
- metadata +9 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a41237cf8c1b948162962ffd5712cac36034d6e3f6ddbd551f7b7f5c64315ac6
|
4
|
+
data.tar.gz: 9416beb3ed3688db58d83bfafb103a3f21b15c7a1e1dae83fe08dae33b13d329
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 294c63c0dad1685a71e4110ab5248d96e7efa97ef448dc861c6a443a8123fd490b1fbdf303b87bf7c18802074b24b3b371a433b3ef06c831cc368559c4c832a0
|
7
|
+
data.tar.gz: aa3bc70fe5068018df9dd1eee9010732b4df231033434ba02c3872b0902e3850adde1b97b6228dbda9f58e10c032b7d4b6467c96f18392d3e74dfab497638835
|
data/lib/danger/helper.rb
CHANGED
@@ -4,6 +4,7 @@ require "net/http"
|
|
4
4
|
require "json"
|
5
5
|
require "danger"
|
6
6
|
require_relative "../gitlab/dangerfiles/teammate"
|
7
|
+
require_relative "../gitlab/dangerfiles/title_linting"
|
7
8
|
|
8
9
|
module Danger
|
9
10
|
# Common helper functions for our danger scripts.
|
@@ -16,10 +17,57 @@ module Danger
|
|
16
17
|
qa: "~QA",
|
17
18
|
test: "~test ~Quality for `spec/features/*`",
|
18
19
|
engineering_productivity: '~"Engineering Productivity" for CI, Danger',
|
20
|
+
ci_template: '~"ci::templates"',
|
19
21
|
}.freeze
|
20
22
|
|
21
23
|
HTTPError = Class.new(StandardError)
|
22
24
|
|
25
|
+
Change = Struct.new(:file, :change_type, :category)
|
26
|
+
|
27
|
+
class Changes < ::SimpleDelegator
|
28
|
+
def added
|
29
|
+
select_by_change_type(:added)
|
30
|
+
end
|
31
|
+
|
32
|
+
def modified
|
33
|
+
select_by_change_type(:modified)
|
34
|
+
end
|
35
|
+
|
36
|
+
def deleted
|
37
|
+
select_by_change_type(:deleted)
|
38
|
+
end
|
39
|
+
|
40
|
+
def renamed_before
|
41
|
+
select_by_change_type(:renamed_before)
|
42
|
+
end
|
43
|
+
|
44
|
+
def renamed_after
|
45
|
+
select_by_change_type(:renamed_after)
|
46
|
+
end
|
47
|
+
|
48
|
+
def has_category?(category)
|
49
|
+
any? { |change| change.category == category }
|
50
|
+
end
|
51
|
+
|
52
|
+
def by_category(category)
|
53
|
+
Changes.new(select { |change| change.category == category })
|
54
|
+
end
|
55
|
+
|
56
|
+
def categories
|
57
|
+
map(&:category).uniq
|
58
|
+
end
|
59
|
+
|
60
|
+
def files
|
61
|
+
map(&:file)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def select_by_change_type(change_type)
|
67
|
+
Changes.new(select { |change| change.change_type == change_type })
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
23
71
|
def gitlab_helper
|
24
72
|
# Unfortunately the following does not work:
|
25
73
|
# - respond_to?(:gitlab)
|
@@ -104,13 +152,38 @@ module Danger
|
|
104
152
|
end
|
105
153
|
end
|
106
154
|
|
107
|
-
# @return [Hash<
|
155
|
+
# @return [Hash<Symbol,Array<String>>]
|
108
156
|
def changes_by_category(categories)
|
109
157
|
all_changed_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash|
|
110
158
|
categories_for_file(file, categories).each { |category| hash[category] << file }
|
111
159
|
end
|
112
160
|
end
|
113
161
|
|
162
|
+
# @return [Changes]
|
163
|
+
def changes(categories)
|
164
|
+
Changes.new([]).tap do |changes|
|
165
|
+
git.added_files.each do |file|
|
166
|
+
categories_for_file(file, categories).each { |category| changes << Change.new(file, :added, category) }
|
167
|
+
end
|
168
|
+
|
169
|
+
git.modified_files.each do |file|
|
170
|
+
categories_for_file(file, categories).each { |category| changes << Change.new(file, :modified, category) }
|
171
|
+
end
|
172
|
+
|
173
|
+
git.deleted_files.each do |file|
|
174
|
+
categories_for_file(file, categories).each { |category| changes << Change.new(file, :deleted, category) }
|
175
|
+
end
|
176
|
+
|
177
|
+
git.renamed_files.map { |x| x[:before] }.each do |file|
|
178
|
+
categories_for_file(file, categories).each { |category| changes << Change.new(file, :renamed_before, category) }
|
179
|
+
end
|
180
|
+
|
181
|
+
git.renamed_files.map { |x| x[:after] }.each do |file|
|
182
|
+
categories_for_file(file, categories).each { |category| changes << Change.new(file, :renamed_after, category) }
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
114
187
|
# Determines the categories a file is in, e.g., `[:frontend]`, `[:backend]`, or `%i[frontend engineering_productivity]`
|
115
188
|
# using filename regex and specific change regex if given.
|
116
189
|
#
|
@@ -140,39 +213,64 @@ module Danger
|
|
140
213
|
usernames.map { |u| Gitlab::Dangerfiles::Teammate.new("username" => u) }
|
141
214
|
end
|
142
215
|
|
143
|
-
def
|
144
|
-
|
216
|
+
def mr_iid
|
217
|
+
return "" unless gitlab_helper
|
218
|
+
|
219
|
+
gitlab_helper.mr_json["iid"]
|
145
220
|
end
|
146
221
|
|
147
|
-
def
|
148
|
-
return
|
222
|
+
def mr_title
|
223
|
+
return "" unless gitlab_helper
|
149
224
|
|
150
|
-
|
225
|
+
gitlab_helper.mr_json["title"]
|
151
226
|
end
|
152
227
|
|
153
|
-
def
|
154
|
-
return
|
228
|
+
def mr_web_url
|
229
|
+
return "" unless gitlab_helper
|
230
|
+
|
231
|
+
gitlab_helper.mr_json["web_url"]
|
232
|
+
end
|
233
|
+
|
234
|
+
def mr_labels
|
235
|
+
return [] unless gitlab_helper
|
236
|
+
|
237
|
+
gitlab_helper.mr_labels
|
238
|
+
end
|
239
|
+
|
240
|
+
def mr_target_branch
|
241
|
+
return "" unless gitlab_helper
|
242
|
+
|
243
|
+
gitlab_helper.mr_json["target_branch"]
|
244
|
+
end
|
245
|
+
|
246
|
+
def draft_mr?
|
247
|
+
Gitlab::Dangerfiles::TitleLinting.has_draft_flag?(mr_title)
|
248
|
+
end
|
155
249
|
|
156
|
-
|
250
|
+
def security_mr?
|
251
|
+
mr_web_url.include?("/gitlab-org/security/")
|
157
252
|
end
|
158
253
|
|
159
254
|
def cherry_pick_mr?
|
160
|
-
|
255
|
+
Gitlab::Dangerfiles::TitleLinting.has_cherry_pick_flag?(mr_title)
|
256
|
+
end
|
161
257
|
|
162
|
-
|
258
|
+
def run_all_rspec_mr?
|
259
|
+
Gitlab::Dangerfiles::TitleLinting.has_run_all_rspec_flag?(mr_title)
|
163
260
|
end
|
164
261
|
|
165
|
-
def
|
166
|
-
|
262
|
+
def run_as_if_foss_mr?
|
263
|
+
Gitlab::Dangerfiles::TitleLinting.has_run_as_if_foss_flag?(mr_title)
|
264
|
+
end
|
167
265
|
|
168
|
-
|
266
|
+
def stable_branch?
|
267
|
+
/\A\d+-\d+-stable-ee/i.match?(mr_target_branch)
|
169
268
|
end
|
170
269
|
|
171
270
|
def mr_has_labels?(*labels)
|
172
|
-
return false unless gitlab_helper
|
173
|
-
|
174
271
|
labels = labels.flatten.uniq
|
175
|
-
|
272
|
+
|
273
|
+
(labels & mr_labels) == labels
|
176
274
|
end
|
177
275
|
|
178
276
|
def labels_list(labels, sep: ", ")
|
@@ -189,10 +287,16 @@ module Danger
|
|
189
287
|
all_changed_files.grep(regex)
|
190
288
|
end
|
191
289
|
|
192
|
-
private
|
193
|
-
|
194
290
|
def has_database_scoped_labels?(current_mr_labels)
|
195
291
|
current_mr_labels.any? { |label| label.start_with?("database::") }
|
196
292
|
end
|
293
|
+
|
294
|
+
def has_ci_changes?
|
295
|
+
changed_files(%r{\A(\.gitlab-ci\.yml|\.gitlab/ci/)}).any?
|
296
|
+
end
|
297
|
+
|
298
|
+
def group_label(labels)
|
299
|
+
labels.find { |label| label.start_with?("group::") }
|
300
|
+
end
|
197
301
|
end
|
198
302
|
end
|
data/lib/danger/roulette.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "../gitlab/dangerfiles/teammate"
|
4
|
+
require_relative "../gitlab/dangerfiles/weightage/maintainers"
|
5
|
+
require_relative "../gitlab/dangerfiles/weightage/reviewers"
|
4
6
|
|
5
7
|
module Danger
|
6
8
|
# Common helper functions for our danger scripts. See Danger::Helper
|
@@ -24,7 +26,7 @@ module Danger
|
|
24
26
|
#
|
25
27
|
# @return [Array<Spin>]
|
26
28
|
def spin(project, categories, timezone_experiment: false)
|
27
|
-
spins = categories.map do |category|
|
29
|
+
spins = categories.sort.map do |category|
|
28
30
|
including_timezone = INCLUDE_TIMEZONE_FOR_CATEGORY.fetch(category, timezone_experiment)
|
29
31
|
|
30
32
|
spin_for_category(project, category, timezone_experiment: including_timezone)
|
@@ -37,7 +39,7 @@ module Danger
|
|
37
39
|
case spin.category
|
38
40
|
when :qa
|
39
41
|
# MR includes QA changes, but also other changes, and author isn't an SET
|
40
|
-
if categories.size > 1 && !team_mr_author&.
|
42
|
+
if categories.size > 1 && !team_mr_author&.any_capability?(project, spin.category)
|
41
43
|
spin.optional_role = :maintainer
|
42
44
|
end
|
43
45
|
when :test
|
@@ -52,6 +54,11 @@ module Danger
|
|
52
54
|
# Fetch an already picked backend maintainer, or pick one otherwise
|
53
55
|
spin.maintainer = backend_spin&.maintainer || spin_for_category(project, :backend, timezone_experiment: including_timezone).maintainer
|
54
56
|
end
|
57
|
+
when :ci_template
|
58
|
+
if spin.maintainer.nil?
|
59
|
+
# Fetch an already picked backend maintainer, or pick one otherwise
|
60
|
+
spin.maintainer = backend_spin&.maintainer || spin_for_category(project, :backend, timezone_experiment: including_timezone).maintainer
|
61
|
+
end
|
55
62
|
end
|
56
63
|
end
|
57
64
|
|
@@ -146,13 +153,13 @@ module Danger
|
|
146
153
|
spin_role_for_category(team, role, project, category)
|
147
154
|
end
|
148
155
|
|
149
|
-
# TODO: take CODEOWNERS into account?
|
150
|
-
# https://gitlab.com/gitlab-org/gitlab/issues/26723
|
151
|
-
|
152
|
-
# Make traintainers have triple the chance to be picked as a reviewer
|
153
156
|
random = new_random(mr_source_branch)
|
154
|
-
|
155
|
-
|
157
|
+
|
158
|
+
weighted_reviewers = Gitlab::Dangerfiles::Weightage::Reviewers.new(reviewers, traintainers).execute
|
159
|
+
weighted_maintainers = Gitlab::Dangerfiles::Weightage::Maintainers.new(maintainers).execute
|
160
|
+
|
161
|
+
reviewer = spin_for_person(weighted_reviewers, random: random, timezone_experiment: timezone_experiment)
|
162
|
+
maintainer = spin_for_person(weighted_maintainers, random: random, timezone_experiment: timezone_experiment)
|
156
163
|
|
157
164
|
Spin.new(category, reviewer, maintainer, false, timezone_experiment)
|
158
165
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "title_linting"
|
4
|
+
|
5
|
+
module Gitlab
|
6
|
+
module Dangerfiles
|
7
|
+
class BaseLinter
|
8
|
+
MIN_SUBJECT_WORDS_COUNT = 3
|
9
|
+
MAX_LINE_LENGTH = 72
|
10
|
+
|
11
|
+
attr_reader :commit, :problems
|
12
|
+
|
13
|
+
def self.problems_mapping
|
14
|
+
{
|
15
|
+
subject_too_short: "The %s must contain at least #{MIN_SUBJECT_WORDS_COUNT} words",
|
16
|
+
subject_too_long: "The %s may not be longer than #{MAX_LINE_LENGTH} characters",
|
17
|
+
subject_starts_with_lowercase: "The %s must start with a capital letter",
|
18
|
+
subject_ends_with_a_period: "The %s must not end with a period",
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.subject_description
|
23
|
+
"commit subject"
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(commit)
|
27
|
+
@commit = commit
|
28
|
+
@problems = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def failed?
|
32
|
+
problems.any?
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_problem(problem_key, *args)
|
36
|
+
@problems[problem_key] = sprintf(self.class.problems_mapping[problem_key], *args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def lint_subject
|
40
|
+
if subject_too_short?
|
41
|
+
add_problem(:subject_too_short, self.class.subject_description)
|
42
|
+
end
|
43
|
+
|
44
|
+
if subject_too_long?
|
45
|
+
add_problem(:subject_too_long, self.class.subject_description)
|
46
|
+
end
|
47
|
+
|
48
|
+
if subject_starts_with_lowercase?
|
49
|
+
add_problem(:subject_starts_with_lowercase, self.class.subject_description)
|
50
|
+
end
|
51
|
+
|
52
|
+
if subject_ends_with_a_period?
|
53
|
+
add_problem(:subject_ends_with_a_period, self.class.subject_description)
|
54
|
+
end
|
55
|
+
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def subject
|
62
|
+
TitleLinting.remove_draft_flag(message_parts[0])
|
63
|
+
end
|
64
|
+
|
65
|
+
def subject_too_short?
|
66
|
+
subject.split(" ").length < MIN_SUBJECT_WORDS_COUNT
|
67
|
+
end
|
68
|
+
|
69
|
+
def subject_too_long?
|
70
|
+
line_too_long?(subject)
|
71
|
+
end
|
72
|
+
|
73
|
+
def line_too_long?(line)
|
74
|
+
line.length > MAX_LINE_LENGTH
|
75
|
+
end
|
76
|
+
|
77
|
+
def subject_starts_with_lowercase?
|
78
|
+
return false if ("A".."Z").cover?(subject[0])
|
79
|
+
|
80
|
+
first_char = subject.sub(/\A(\[.+\]|\w+:)\s/, "")[0]
|
81
|
+
first_char_downcased = first_char.downcase
|
82
|
+
return true unless ("a".."z").cover?(first_char_downcased)
|
83
|
+
|
84
|
+
first_char.downcase == first_char
|
85
|
+
end
|
86
|
+
|
87
|
+
def subject_ends_with_a_period?
|
88
|
+
subject.end_with?(".")
|
89
|
+
end
|
90
|
+
|
91
|
+
def message_parts
|
92
|
+
@message_parts ||= commit.message.split("\n", 3)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -1,40 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
defined?(Rails) ? require_dependency(emoji_checker_path) : require_relative(emoji_checker_path)
|
3
|
+
require_relative "emoji_checker"
|
5
4
|
|
6
5
|
module Gitlab
|
7
6
|
module Dangerfiles
|
8
7
|
class CommitLinter
|
9
|
-
MIN_SUBJECT_WORDS_COUNT = 3
|
10
|
-
MAX_LINE_LENGTH = 72
|
11
|
-
MAX_CHANGED_FILES_IN_COMMIT = 3
|
12
8
|
MAX_CHANGED_LINES_IN_COMMIT = 30
|
13
|
-
SHORT_REFERENCE_REGEX = %r{([\w\-\/]+)?(#|!|&|%)\d
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
}.freeze
|
32
|
-
|
33
|
-
attr_reader :commit, :problems
|
9
|
+
SHORT_REFERENCE_REGEX = %r{([\w\-\/]+)?(?<!`)(#|!|&|%)\d+(?<!`)}.freeze
|
10
|
+
|
11
|
+
def self.problems_mapping
|
12
|
+
super.merge(
|
13
|
+
{
|
14
|
+
separator_missing: "The commit subject and body must be separated by a blank line",
|
15
|
+
details_too_many_changes: "Commits that change #{MAX_CHANGED_LINES_IN_COMMIT} or more lines across " \
|
16
|
+
"at least #{MAX_CHANGED_FILES_IN_COMMIT} files must describe these changes in the commit body",
|
17
|
+
details_line_too_long: "The commit body should not contain more than #{MAX_LINE_LENGTH} characters per line",
|
18
|
+
message_contains_text_emoji: "Avoid the use of Markdown Emoji such as `:+1:`. These add limited value " \
|
19
|
+
"to the commit message, and are displayed as plain text outside of GitLab",
|
20
|
+
message_contains_unicode_emoji: "Avoid the use of Unicode Emoji. These add no value to the commit " \
|
21
|
+
"message, and may not be displayed properly everywhere",
|
22
|
+
message_contains_short_reference: "Use full URLs instead of short references (`gitlab-org/gitlab#123` or " \
|
23
|
+
"`!123`), as short references are displayed as plain text outside of GitLab",
|
24
|
+
}
|
25
|
+
)
|
26
|
+
end
|
34
27
|
|
35
28
|
def initialize(commit)
|
36
|
-
|
37
|
-
|
29
|
+
super
|
30
|
+
|
38
31
|
@linted = false
|
39
32
|
end
|
40
33
|
|
@@ -58,19 +51,11 @@ module Gitlab
|
|
58
51
|
!details.nil? && !details.empty?
|
59
52
|
end
|
60
53
|
|
61
|
-
def
|
62
|
-
problems.any?
|
63
|
-
end
|
64
|
-
|
65
|
-
def add_problem(problem_key, *args)
|
66
|
-
@problems[problem_key] = sprintf(PROBLEMS[problem_key], *args)
|
67
|
-
end
|
68
|
-
|
69
|
-
def lint(subject_description = "commit subject")
|
54
|
+
def lint
|
70
55
|
return self if @linted
|
71
56
|
|
72
57
|
@linted = true
|
73
|
-
lint_subject
|
58
|
+
lint_subject
|
74
59
|
lint_separator
|
75
60
|
lint_details
|
76
61
|
lint_message
|
@@ -78,26 +63,6 @@ module Gitlab
|
|
78
63
|
self
|
79
64
|
end
|
80
65
|
|
81
|
-
def lint_subject(subject_description)
|
82
|
-
if subject_too_short?
|
83
|
-
add_problem(:subject_too_short, subject_description)
|
84
|
-
end
|
85
|
-
|
86
|
-
if subject_too_long?
|
87
|
-
add_problem(:subject_too_long, subject_description)
|
88
|
-
end
|
89
|
-
|
90
|
-
if subject_starts_with_lowercase?
|
91
|
-
add_problem(:subject_starts_with_lowercase, subject_description)
|
92
|
-
end
|
93
|
-
|
94
|
-
if subject_ends_with_a_period?
|
95
|
-
add_problem(:subject_ends_with_a_period, subject_description)
|
96
|
-
end
|
97
|
-
|
98
|
-
self
|
99
|
-
end
|
100
|
-
|
101
66
|
private
|
102
67
|
|
103
68
|
def lint_separator
|
@@ -114,15 +79,11 @@ module Gitlab
|
|
114
79
|
end
|
115
80
|
|
116
81
|
details&.each_line do |line|
|
117
|
-
|
118
|
-
|
119
|
-
next unless line_too_long?(line)
|
120
|
-
|
121
|
-
url_size = line.scan(%r((https?://\S+))).sum { |(url)| url.length } # rubocop:disable CodeReuse/ActiveRecord
|
82
|
+
line_without_urls = line.strip.gsub(%r{https?://\S+}, "")
|
122
83
|
|
123
84
|
# If the line includes a URL, we'll allow it to exceed MAX_LINE_LENGTH characters, but
|
124
85
|
# only if the line _without_ the URL does not exceed this limit.
|
125
|
-
next unless line_too_long?(
|
86
|
+
next unless line_too_long?(line_without_urls)
|
126
87
|
|
127
88
|
add_problem(:details_line_too_long)
|
128
89
|
break
|
@@ -159,10 +120,6 @@ module Gitlab
|
|
159
120
|
files_changed > MAX_CHANGED_FILES_IN_COMMIT && lines_changed > MAX_CHANGED_LINES_IN_COMMIT
|
160
121
|
end
|
161
122
|
|
162
|
-
def subject
|
163
|
-
message_parts[0].delete_prefix(WIP_PREFIX)
|
164
|
-
end
|
165
|
-
|
166
123
|
def separator
|
167
124
|
message_parts[1]
|
168
125
|
end
|
@@ -171,37 +128,6 @@ module Gitlab
|
|
171
128
|
message_parts[2]&.gsub(/^Signed-off-by.*$/, "")
|
172
129
|
end
|
173
130
|
|
174
|
-
def line_too_long?(line)
|
175
|
-
case line
|
176
|
-
when String
|
177
|
-
line.length > MAX_LINE_LENGTH
|
178
|
-
when Integer
|
179
|
-
line > MAX_LINE_LENGTH
|
180
|
-
else
|
181
|
-
raise ArgumentError, "The line argument (#{line}) should be a String or an Integer! #{line.class} given."
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
def subject_too_short?
|
186
|
-
subject.split(" ").length < MIN_SUBJECT_WORDS_COUNT
|
187
|
-
end
|
188
|
-
|
189
|
-
def subject_too_long?
|
190
|
-
line_too_long?(subject)
|
191
|
-
end
|
192
|
-
|
193
|
-
def subject_starts_with_lowercase?
|
194
|
-
first_char = subject.sub(/\A\[.+\]\s/, "")[0]
|
195
|
-
first_char_downcased = first_char.downcase
|
196
|
-
return true unless ("a".."z").cover?(first_char_downcased)
|
197
|
-
|
198
|
-
first_char.downcase == first_char
|
199
|
-
end
|
200
|
-
|
201
|
-
def subject_ends_with_a_period?
|
202
|
-
subject.end_with?(".")
|
203
|
-
end
|
204
|
-
|
205
131
|
def message_contains_text_emoji?
|
206
132
|
emoji_checker.includes_text_emoji?(commit.message)
|
207
133
|
end
|
@@ -217,10 +143,6 @@ module Gitlab
|
|
217
143
|
def emoji_checker
|
218
144
|
@emoji_checker ||= Gitlab::Dangerfiles::EmojiChecker.new
|
219
145
|
end
|
220
|
-
|
221
|
-
def message_parts
|
222
|
-
@message_parts ||= commit.message.split("\n", 3)
|
223
|
-
end
|
224
146
|
end
|
225
147
|
end
|
226
148
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base_linter"
|
4
|
+
|
5
|
+
module Gitlab
|
6
|
+
module Dangerfiles
|
7
|
+
class MergeRequestLinter < BaseLinter
|
8
|
+
alias_method :lint, :lint_subject
|
9
|
+
|
10
|
+
def self.subject_description
|
11
|
+
"merge request title"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.mr_run_options_regex
|
15
|
+
[
|
16
|
+
"RUN AS-IF-FOSS",
|
17
|
+
"UPDATE CACHE",
|
18
|
+
"RUN ALL RSPEC",
|
19
|
+
"SKIP RSPEC FAIL-FAST",
|
20
|
+
].join("|")
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def subject
|
26
|
+
super.gsub(/\[?(#{self.class.mr_run_options_regex})\]?/, "").strip
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -3,7 +3,7 @@
|
|
3
3
|
module Gitlab
|
4
4
|
module Dangerfiles
|
5
5
|
class Teammate
|
6
|
-
attr_reader :options, :username, :name, :role, :projects, :available, :hungry, :tz_offset_hours
|
6
|
+
attr_reader :options, :username, :name, :role, :projects, :available, :hungry, :reduced_capacity, :tz_offset_hours
|
7
7
|
|
8
8
|
# The options data are produced by https://gitlab.com/gitlab-org/gitlab-roulette/-/blob/master/lib/team_member.rb
|
9
9
|
def initialize(options = {})
|
@@ -15,6 +15,7 @@ module Gitlab
|
|
15
15
|
@projects = options["projects"]
|
16
16
|
@available = options["available"]
|
17
17
|
@hungry = options["hungry"]
|
18
|
+
@reduced_capacity = options["reduced_capacity"]
|
18
19
|
@tz_offset_hours = options["tz_offset_hours"]
|
19
20
|
end
|
20
21
|
|
@@ -32,6 +33,10 @@ module Gitlab
|
|
32
33
|
projects&.has_key?(name)
|
33
34
|
end
|
34
35
|
|
36
|
+
def any_capability?(project, category)
|
37
|
+
capabilities(project).any? { |capability| capability.end_with?(category.to_s) }
|
38
|
+
end
|
39
|
+
|
35
40
|
def reviewer?(project, category, labels)
|
36
41
|
has_capability?(project, category, :reviewer, labels)
|
37
42
|
end
|
@@ -93,6 +98,7 @@ module Gitlab
|
|
93
98
|
when :engineering_productivity
|
94
99
|
return false unless role[/Engineering Productivity/]
|
95
100
|
return true if kind == :reviewer
|
101
|
+
return true if capabilities(project).include?("#{kind} engineering_productivity")
|
96
102
|
|
97
103
|
capabilities(project).include?("#{kind} backend")
|
98
104
|
else
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Dangerfiles
|
5
|
+
module TitleLinting
|
6
|
+
DRAFT_REGEX = /\A*#{Regexp.union(/(?i)(\[WIP\]\s*|WIP:\s*|WIP$)/, /(?i)(\[draft\]|\(draft\)|draft:|draft\s\-\s|draft$)/)}+\s*/i.freeze
|
7
|
+
CHERRY_PICK_REGEX = /cherry[\s-]*pick/i.freeze
|
8
|
+
RUN_ALL_RSPEC_REGEX = /RUN ALL RSPEC/i.freeze
|
9
|
+
RUN_AS_IF_FOSS_REGEX = /RUN AS-IF-FOSS/i.freeze
|
10
|
+
|
11
|
+
module_function
|
12
|
+
|
13
|
+
def sanitize_mr_title(title)
|
14
|
+
remove_draft_flag(title).gsub(/`/, '\\\`')
|
15
|
+
end
|
16
|
+
|
17
|
+
def remove_draft_flag(title)
|
18
|
+
title.gsub(DRAFT_REGEX, "")
|
19
|
+
end
|
20
|
+
|
21
|
+
def has_draft_flag?(title)
|
22
|
+
DRAFT_REGEX.match?(title)
|
23
|
+
end
|
24
|
+
|
25
|
+
def has_cherry_pick_flag?(title)
|
26
|
+
CHERRY_PICK_REGEX.match?(title)
|
27
|
+
end
|
28
|
+
|
29
|
+
def has_run_all_rspec_flag?(title)
|
30
|
+
RUN_ALL_RSPEC_REGEX.match?(title)
|
31
|
+
end
|
32
|
+
|
33
|
+
def has_run_as_if_foss_flag?(title)
|
34
|
+
RUN_AS_IF_FOSS_REGEX.match?(title)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../weightage"
|
4
|
+
|
5
|
+
module Gitlab
|
6
|
+
module Dangerfiles
|
7
|
+
module Weightage
|
8
|
+
class Maintainers
|
9
|
+
def initialize(maintainers)
|
10
|
+
@maintainers = maintainers
|
11
|
+
end
|
12
|
+
|
13
|
+
def execute
|
14
|
+
maintainers.each_with_object([]) do |maintainer, weighted_maintainers|
|
15
|
+
add_weighted_reviewer(weighted_maintainers, maintainer, Gitlab::Dangerfiles::Weightage::BASE_REVIEWER_WEIGHT)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
attr_reader :maintainers
|
22
|
+
|
23
|
+
def add_weighted_reviewer(reviewers, reviewer, weight)
|
24
|
+
if reviewer.reduced_capacity
|
25
|
+
reviewers.fill(reviewer, reviewers.size, weight)
|
26
|
+
else
|
27
|
+
reviewers.fill(reviewer, reviewers.size, weight * Gitlab::Dangerfiles::Weightage::CAPACITY_MULTIPLIER)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../weightage"
|
4
|
+
|
5
|
+
module Gitlab
|
6
|
+
module Dangerfiles
|
7
|
+
module Weightage
|
8
|
+
# Weights after (current multiplier of 2)
|
9
|
+
#
|
10
|
+
# +------------------------------+--------------------------------+
|
11
|
+
# | reviewer type | weight(times in reviewer pool) |
|
12
|
+
# +------------------------------+--------------------------------+
|
13
|
+
# | reduced capacity reviewer | 1 |
|
14
|
+
# | reviewer | 2 |
|
15
|
+
# | hungry reviewer | 4 |
|
16
|
+
# | reduced capacity traintainer | 3 |
|
17
|
+
# | traintainer | 6 |
|
18
|
+
# | hungry traintainer | 8 |
|
19
|
+
# +------------------------------+--------------------------------+
|
20
|
+
#
|
21
|
+
class Reviewers
|
22
|
+
DEFAULT_REVIEWER_WEIGHT = Gitlab::Dangerfiles::Weightage::CAPACITY_MULTIPLIER * Gitlab::Dangerfiles::Weightage::BASE_REVIEWER_WEIGHT
|
23
|
+
TRAINTAINER_WEIGHT = 3
|
24
|
+
|
25
|
+
def initialize(reviewers, traintainers)
|
26
|
+
@reviewers = reviewers
|
27
|
+
@traintainers = traintainers
|
28
|
+
end
|
29
|
+
|
30
|
+
def execute
|
31
|
+
# TODO: take CODEOWNERS into account?
|
32
|
+
# https://gitlab.com/gitlab-org/gitlab/issues/26723
|
33
|
+
|
34
|
+
weighted_reviewers + weighted_traintainers
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
attr_reader :reviewers, :traintainers
|
40
|
+
|
41
|
+
def weighted_reviewers
|
42
|
+
reviewers.each_with_object([]) do |reviewer, total_reviewers|
|
43
|
+
add_weighted_reviewer(total_reviewers, reviewer, Gitlab::Dangerfiles::Weightage::BASE_REVIEWER_WEIGHT)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def weighted_traintainers
|
48
|
+
traintainers.each_with_object([]) do |reviewer, total_traintainers|
|
49
|
+
add_weighted_reviewer(total_traintainers, reviewer, TRAINTAINER_WEIGHT)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def add_weighted_reviewer(reviewers, reviewer, weight)
|
54
|
+
if reviewer.reduced_capacity
|
55
|
+
reviewers.fill(reviewer, reviewers.size, weight)
|
56
|
+
elsif reviewer.hungry
|
57
|
+
reviewers.fill(reviewer, reviewers.size, weight * Gitlab::Dangerfiles::Weightage::CAPACITY_MULTIPLIER + DEFAULT_REVIEWER_WEIGHT)
|
58
|
+
else
|
59
|
+
reviewers.fill(reviewer, reviewers.size, weight * Gitlab::Dangerfiles::Weightage::CAPACITY_MULTIPLIER)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: gitlab-dangerfiles
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Rémy Coutable
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-03-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: danger
|
@@ -135,10 +135,16 @@ files:
|
|
135
135
|
- lib/gitlab-dangerfiles.rb
|
136
136
|
- lib/gitlab/Dangerfile
|
137
137
|
- lib/gitlab/dangerfiles.rb
|
138
|
+
- lib/gitlab/dangerfiles/base_linter.rb
|
138
139
|
- lib/gitlab/dangerfiles/commit_linter.rb
|
139
140
|
- lib/gitlab/dangerfiles/emoji_checker.rb
|
141
|
+
- lib/gitlab/dangerfiles/merge_request_linter.rb
|
140
142
|
- lib/gitlab/dangerfiles/teammate.rb
|
143
|
+
- lib/gitlab/dangerfiles/title_linting.rb
|
141
144
|
- lib/gitlab/dangerfiles/version.rb
|
145
|
+
- lib/gitlab/dangerfiles/weightage.rb
|
146
|
+
- lib/gitlab/dangerfiles/weightage/maintainers.rb
|
147
|
+
- lib/gitlab/dangerfiles/weightage/reviewers.rb
|
142
148
|
homepage: https://gitlab.com/gitlab-org/gitlab-dangerfiles
|
143
149
|
licenses:
|
144
150
|
- MIT
|
@@ -162,7 +168,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
168
|
- !ruby/object:Gem::Version
|
163
169
|
version: '0'
|
164
170
|
requirements: []
|
165
|
-
rubygems_version: 3.1.
|
171
|
+
rubygems_version: 3.1.4
|
166
172
|
signing_key:
|
167
173
|
specification_version: 4
|
168
174
|
summary: This gem provides common Dangerfile and plugins for GitLab projects.
|