gitlab-dangerfiles 0.5.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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.
|