gitlab-dangerfiles 0.5.0 → 0.8.1
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 +78 -19
- data/lib/danger/roulette.rb +16 -8
- data/lib/gitlab/dangerfiles.rb +3 -0
- data/lib/gitlab/dangerfiles/base_linter.rb +97 -0
- data/lib/gitlab/dangerfiles/changes.rb +53 -0
- data/lib/gitlab/dangerfiles/commit_linter.rb +27 -103
- data/lib/gitlab/dangerfiles/merge_request_linter.rb +30 -0
- data/lib/gitlab/dangerfiles/spec_helper.rb +66 -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 +11 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e8132453b5b4614eaa87813509a18230d9c94d00cdec5b9e6857b032800092b0
|
4
|
+
data.tar.gz: 2e8dfe3056a7da31abbde54e9a8d1cbb96e9c9105d7fde5058903cc2617dbcfe
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8d1020a4fe7e5bcce74d8c03127373ae1a97ecf7ff57a981167cb6b8b733a17d5aef4b4c40fb31b88e996f8fe5830ecd779c49dc89609c3180f8ccf558a99028
|
7
|
+
data.tar.gz: 0d13715735e7bdded2a526575e276f21e11c11fb57f2fdcecdceb9051143f1995e2527cb4a57d9427aa3d8d2ced85d13775d96819d80610ae555f3108b5e508d
|
data/lib/danger/helper.rb
CHANGED
@@ -3,7 +3,9 @@
|
|
3
3
|
require "net/http"
|
4
4
|
require "json"
|
5
5
|
require "danger"
|
6
|
+
require_relative "../gitlab/dangerfiles/changes"
|
6
7
|
require_relative "../gitlab/dangerfiles/teammate"
|
8
|
+
require_relative "../gitlab/dangerfiles/title_linting"
|
7
9
|
|
8
10
|
module Danger
|
9
11
|
# Common helper functions for our danger scripts.
|
@@ -16,6 +18,7 @@ module Danger
|
|
16
18
|
qa: "~QA",
|
17
19
|
test: "~test ~Quality for `spec/features/*`",
|
18
20
|
engineering_productivity: '~"Engineering Productivity" for CI, Danger',
|
21
|
+
ci_template: '~"ci::templates"',
|
19
22
|
}.freeze
|
20
23
|
|
21
24
|
HTTPError = Class.new(StandardError)
|
@@ -104,13 +107,38 @@ module Danger
|
|
104
107
|
end
|
105
108
|
end
|
106
109
|
|
107
|
-
# @return [Hash<
|
110
|
+
# @return [Hash<Symbol,Array<String>>]
|
108
111
|
def changes_by_category(categories)
|
109
112
|
all_changed_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash|
|
110
113
|
categories_for_file(file, categories).each { |category| hash[category] << file }
|
111
114
|
end
|
112
115
|
end
|
113
116
|
|
117
|
+
# @return [Gitlab::Dangerfiles::Changes]
|
118
|
+
def changes(categories)
|
119
|
+
Gitlab::Dangerfiles::Changes.new([]).tap do |changes|
|
120
|
+
git.added_files.each do |file|
|
121
|
+
categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :added, category) }
|
122
|
+
end
|
123
|
+
|
124
|
+
git.modified_files.each do |file|
|
125
|
+
categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :modified, category) }
|
126
|
+
end
|
127
|
+
|
128
|
+
git.deleted_files.each do |file|
|
129
|
+
categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :deleted, category) }
|
130
|
+
end
|
131
|
+
|
132
|
+
git.renamed_files.map { |x| x[:before] }.each do |file|
|
133
|
+
categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :renamed_before, category) }
|
134
|
+
end
|
135
|
+
|
136
|
+
git.renamed_files.map { |x| x[:after] }.each do |file|
|
137
|
+
categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :renamed_after, category) }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
114
142
|
# Determines the categories a file is in, e.g., `[:frontend]`, `[:backend]`, or `%i[frontend engineering_productivity]`
|
115
143
|
# using filename regex and specific change regex if given.
|
116
144
|
#
|
@@ -140,39 +168,64 @@ module Danger
|
|
140
168
|
usernames.map { |u| Gitlab::Dangerfiles::Teammate.new("username" => u) }
|
141
169
|
end
|
142
170
|
|
143
|
-
def
|
144
|
-
|
171
|
+
def mr_iid
|
172
|
+
return "" unless gitlab_helper
|
173
|
+
|
174
|
+
gitlab_helper.mr_json["iid"]
|
145
175
|
end
|
146
176
|
|
147
|
-
def
|
148
|
-
return
|
177
|
+
def mr_title
|
178
|
+
return "" unless gitlab_helper
|
149
179
|
|
150
|
-
|
180
|
+
gitlab_helper.mr_json["title"]
|
151
181
|
end
|
152
182
|
|
153
|
-
def
|
154
|
-
return
|
183
|
+
def mr_web_url
|
184
|
+
return "" unless gitlab_helper
|
185
|
+
|
186
|
+
gitlab_helper.mr_json["web_url"]
|
187
|
+
end
|
188
|
+
|
189
|
+
def mr_labels
|
190
|
+
return [] unless gitlab_helper
|
155
191
|
|
156
|
-
gitlab_helper.
|
192
|
+
gitlab_helper.mr_labels
|
193
|
+
end
|
194
|
+
|
195
|
+
def mr_target_branch
|
196
|
+
return "" unless gitlab_helper
|
197
|
+
|
198
|
+
gitlab_helper.mr_json["target_branch"]
|
199
|
+
end
|
200
|
+
|
201
|
+
def draft_mr?
|
202
|
+
Gitlab::Dangerfiles::TitleLinting.has_draft_flag?(mr_title)
|
203
|
+
end
|
204
|
+
|
205
|
+
def security_mr?
|
206
|
+
mr_web_url.include?("/gitlab-org/security/")
|
157
207
|
end
|
158
208
|
|
159
209
|
def cherry_pick_mr?
|
160
|
-
|
210
|
+
Gitlab::Dangerfiles::TitleLinting.has_cherry_pick_flag?(mr_title)
|
211
|
+
end
|
161
212
|
|
162
|
-
|
213
|
+
def run_all_rspec_mr?
|
214
|
+
Gitlab::Dangerfiles::TitleLinting.has_run_all_rspec_flag?(mr_title)
|
163
215
|
end
|
164
216
|
|
165
|
-
def
|
166
|
-
|
217
|
+
def run_as_if_foss_mr?
|
218
|
+
Gitlab::Dangerfiles::TitleLinting.has_run_as_if_foss_flag?(mr_title)
|
219
|
+
end
|
167
220
|
|
168
|
-
|
221
|
+
def stable_branch?
|
222
|
+
/\A\d+-\d+-stable-ee/i.match?(mr_target_branch)
|
169
223
|
end
|
170
224
|
|
171
225
|
def mr_has_labels?(*labels)
|
172
|
-
return false unless gitlab_helper
|
173
|
-
|
174
226
|
labels = labels.flatten.uniq
|
175
|
-
|
227
|
+
|
228
|
+
(labels & mr_labels) == labels
|
176
229
|
end
|
177
230
|
|
178
231
|
def labels_list(labels, sep: ", ")
|
@@ -189,10 +242,16 @@ module Danger
|
|
189
242
|
all_changed_files.grep(regex)
|
190
243
|
end
|
191
244
|
|
192
|
-
private
|
193
|
-
|
194
245
|
def has_database_scoped_labels?(current_mr_labels)
|
195
246
|
current_mr_labels.any? { |label| label.start_with?("database::") }
|
196
247
|
end
|
248
|
+
|
249
|
+
def has_ci_changes?
|
250
|
+
changed_files(%r{\A(\.gitlab-ci\.yml|\.gitlab/ci/)}).any?
|
251
|
+
end
|
252
|
+
|
253
|
+
def group_label(labels)
|
254
|
+
labels.find { |label| label.start_with?("group::") }
|
255
|
+
end
|
197
256
|
end
|
198
257
|
end
|
data/lib/danger/roulette.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative "helper"
|
3
4
|
require_relative "../gitlab/dangerfiles/teammate"
|
5
|
+
require_relative "../gitlab/dangerfiles/weightage/maintainers"
|
6
|
+
require_relative "../gitlab/dangerfiles/weightage/reviewers"
|
4
7
|
|
5
8
|
module Danger
|
6
9
|
# Common helper functions for our danger scripts. See Danger::Helper
|
@@ -24,7 +27,7 @@ module Danger
|
|
24
27
|
#
|
25
28
|
# @return [Array<Spin>]
|
26
29
|
def spin(project, categories, timezone_experiment: false)
|
27
|
-
spins = categories.map do |category|
|
30
|
+
spins = categories.sort.map do |category|
|
28
31
|
including_timezone = INCLUDE_TIMEZONE_FOR_CATEGORY.fetch(category, timezone_experiment)
|
29
32
|
|
30
33
|
spin_for_category(project, category, timezone_experiment: including_timezone)
|
@@ -37,7 +40,7 @@ module Danger
|
|
37
40
|
case spin.category
|
38
41
|
when :qa
|
39
42
|
# MR includes QA changes, but also other changes, and author isn't an SET
|
40
|
-
if categories.size > 1 && !team_mr_author&.
|
43
|
+
if categories.size > 1 && !team_mr_author&.any_capability?(project, spin.category)
|
41
44
|
spin.optional_role = :maintainer
|
42
45
|
end
|
43
46
|
when :test
|
@@ -52,6 +55,11 @@ module Danger
|
|
52
55
|
# Fetch an already picked backend maintainer, or pick one otherwise
|
53
56
|
spin.maintainer = backend_spin&.maintainer || spin_for_category(project, :backend, timezone_experiment: including_timezone).maintainer
|
54
57
|
end
|
58
|
+
when :ci_template
|
59
|
+
if spin.maintainer.nil?
|
60
|
+
# Fetch an already picked backend maintainer, or pick one otherwise
|
61
|
+
spin.maintainer = backend_spin&.maintainer || spin_for_category(project, :backend, timezone_experiment: including_timezone).maintainer
|
62
|
+
end
|
55
63
|
end
|
56
64
|
end
|
57
65
|
|
@@ -146,13 +154,13 @@ module Danger
|
|
146
154
|
spin_role_for_category(team, role, project, category)
|
147
155
|
end
|
148
156
|
|
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
157
|
random = new_random(mr_source_branch)
|
154
|
-
|
155
|
-
|
158
|
+
|
159
|
+
weighted_reviewers = Gitlab::Dangerfiles::Weightage::Reviewers.new(reviewers, traintainers).execute
|
160
|
+
weighted_maintainers = Gitlab::Dangerfiles::Weightage::Maintainers.new(maintainers).execute
|
161
|
+
|
162
|
+
reviewer = spin_for_person(weighted_reviewers, random: random, timezone_experiment: timezone_experiment)
|
163
|
+
maintainer = spin_for_person(weighted_maintainers, random: random, timezone_experiment: timezone_experiment)
|
156
164
|
|
157
165
|
Spin.new(category, reviewer, maintainer, false, timezone_experiment)
|
158
166
|
end
|
data/lib/gitlab/dangerfiles.rb
CHANGED
@@ -0,0 +1,97 @@
|
|
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
|
+
@subject ||= 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 subject.empty?
|
79
|
+
return false if ("A".."Z").cover?(subject[0])
|
80
|
+
|
81
|
+
first_char = subject.sub(/\A(\[.+\]|\w+:)\s/, "")[0]
|
82
|
+
first_char_downcased = first_char.downcase
|
83
|
+
return true unless ("a".."z").cover?(first_char_downcased)
|
84
|
+
|
85
|
+
first_char.downcase == first_char
|
86
|
+
end
|
87
|
+
|
88
|
+
def subject_ends_with_a_period?
|
89
|
+
subject.end_with?(".")
|
90
|
+
end
|
91
|
+
|
92
|
+
def message_parts
|
93
|
+
@message_parts ||= commit.message.split("\n", 3)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "title_linting"
|
4
|
+
|
5
|
+
module Gitlab
|
6
|
+
module Dangerfiles
|
7
|
+
Change = Struct.new(:file, :change_type, :category)
|
8
|
+
|
9
|
+
class Changes < ::SimpleDelegator
|
10
|
+
def added
|
11
|
+
select_by_change_type(:added)
|
12
|
+
end
|
13
|
+
|
14
|
+
def modified
|
15
|
+
select_by_change_type(:modified)
|
16
|
+
end
|
17
|
+
|
18
|
+
def deleted
|
19
|
+
select_by_change_type(:deleted)
|
20
|
+
end
|
21
|
+
|
22
|
+
def renamed_before
|
23
|
+
select_by_change_type(:renamed_before)
|
24
|
+
end
|
25
|
+
|
26
|
+
def renamed_after
|
27
|
+
select_by_change_type(:renamed_after)
|
28
|
+
end
|
29
|
+
|
30
|
+
def has_category?(category)
|
31
|
+
any? { |change| change.category == category }
|
32
|
+
end
|
33
|
+
|
34
|
+
def by_category(category)
|
35
|
+
Changes.new(select { |change| change.category == category })
|
36
|
+
end
|
37
|
+
|
38
|
+
def categories
|
39
|
+
map(&:category).uniq
|
40
|
+
end
|
41
|
+
|
42
|
+
def files
|
43
|
+
map(&:file)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def select_by_change_type(change_type)
|
49
|
+
Changes.new(select { |change| change.change_type == change_type })
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -1,40 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
require_relative "base_linter"
|
4
|
+
require_relative "emoji_checker"
|
5
5
|
|
6
6
|
module Gitlab
|
7
7
|
module Dangerfiles
|
8
|
-
class CommitLinter
|
9
|
-
MIN_SUBJECT_WORDS_COUNT = 3
|
10
|
-
MAX_LINE_LENGTH = 72
|
8
|
+
class CommitLinter < BaseLinter
|
11
9
|
MAX_CHANGED_FILES_IN_COMMIT = 3
|
12
10
|
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
|
11
|
+
SHORT_REFERENCE_REGEX = %r{([\w\-\/]+)?(?<!`)(#|!|&|%)\d+(?<!`)}.freeze
|
12
|
+
|
13
|
+
def self.problems_mapping
|
14
|
+
super.merge(
|
15
|
+
{
|
16
|
+
separator_missing: "The commit subject and body must be separated by a blank line",
|
17
|
+
details_too_many_changes: "Commits that change #{MAX_CHANGED_LINES_IN_COMMIT} or more lines across " \
|
18
|
+
"at least #{MAX_CHANGED_FILES_IN_COMMIT} files must describe these changes in the commit body",
|
19
|
+
details_line_too_long: "The commit body should not contain more than #{MAX_LINE_LENGTH} characters per line",
|
20
|
+
message_contains_text_emoji: "Avoid the use of Markdown Emoji such as `:+1:`. These add limited value " \
|
21
|
+
"to the commit message, and are displayed as plain text outside of GitLab",
|
22
|
+
message_contains_unicode_emoji: "Avoid the use of Unicode Emoji. These add no value to the commit " \
|
23
|
+
"message, and may not be displayed properly everywhere",
|
24
|
+
message_contains_short_reference: "Use full URLs instead of short references (`gitlab-org/gitlab#123` or " \
|
25
|
+
"`!123`), as short references are displayed as plain text outside of GitLab",
|
26
|
+
}
|
27
|
+
)
|
28
|
+
end
|
34
29
|
|
35
30
|
def initialize(commit)
|
36
|
-
|
37
|
-
|
31
|
+
super
|
32
|
+
|
38
33
|
@linted = false
|
39
34
|
end
|
40
35
|
|
@@ -58,19 +53,11 @@ module Gitlab
|
|
58
53
|
!details.nil? && !details.empty?
|
59
54
|
end
|
60
55
|
|
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")
|
56
|
+
def lint
|
70
57
|
return self if @linted
|
71
58
|
|
72
59
|
@linted = true
|
73
|
-
lint_subject
|
60
|
+
lint_subject
|
74
61
|
lint_separator
|
75
62
|
lint_details
|
76
63
|
lint_message
|
@@ -78,26 +65,6 @@ module Gitlab
|
|
78
65
|
self
|
79
66
|
end
|
80
67
|
|
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
68
|
private
|
102
69
|
|
103
70
|
def lint_separator
|
@@ -114,15 +81,11 @@ module Gitlab
|
|
114
81
|
end
|
115
82
|
|
116
83
|
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
|
84
|
+
line_without_urls = line.strip.gsub(%r{https?://\S+}, "")
|
122
85
|
|
123
86
|
# If the line includes a URL, we'll allow it to exceed MAX_LINE_LENGTH characters, but
|
124
87
|
# only if the line _without_ the URL does not exceed this limit.
|
125
|
-
next unless line_too_long?(
|
88
|
+
next unless line_too_long?(line_without_urls)
|
126
89
|
|
127
90
|
add_problem(:details_line_too_long)
|
128
91
|
break
|
@@ -159,10 +122,6 @@ module Gitlab
|
|
159
122
|
files_changed > MAX_CHANGED_FILES_IN_COMMIT && lines_changed > MAX_CHANGED_LINES_IN_COMMIT
|
160
123
|
end
|
161
124
|
|
162
|
-
def subject
|
163
|
-
message_parts[0].delete_prefix(WIP_PREFIX)
|
164
|
-
end
|
165
|
-
|
166
125
|
def separator
|
167
126
|
message_parts[1]
|
168
127
|
end
|
@@ -171,37 +130,6 @@ module Gitlab
|
|
171
130
|
message_parts[2]&.gsub(/^Signed-off-by.*$/, "")
|
172
131
|
end
|
173
132
|
|
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
133
|
def message_contains_text_emoji?
|
206
134
|
emoji_checker.includes_text_emoji?(commit.message)
|
207
135
|
end
|
@@ -217,10 +145,6 @@ module Gitlab
|
|
217
145
|
def emoji_checker
|
218
146
|
@emoji_checker ||= Gitlab::Dangerfiles::EmojiChecker.new
|
219
147
|
end
|
220
|
-
|
221
|
-
def message_parts
|
222
|
-
@message_parts ||= commit.message.split("\n", 3)
|
223
|
-
end
|
224
148
|
end
|
225
149
|
end
|
226
150
|
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
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require "danger"
|
2
|
+
require "gitlab/dangerfiles/changes"
|
3
|
+
|
4
|
+
module DangerSpecHelper
|
5
|
+
# These functions are a subset of https://github.com/danger/danger/blob/master/spec/spec_helper.rb
|
6
|
+
# If you are expanding these files, see if it's already been done ^.
|
7
|
+
|
8
|
+
# A silent version of the user interface
|
9
|
+
def self.testing_ui
|
10
|
+
Cork::Board.new(silent: true)
|
11
|
+
end
|
12
|
+
|
13
|
+
# Example environment (ENV) that would come from
|
14
|
+
# running a PR on TravisCI
|
15
|
+
def self.testing_env
|
16
|
+
{
|
17
|
+
"GITLAB_CI" => "true",
|
18
|
+
"DANGER_GITLAB_HOST" => "gitlab.example.com",
|
19
|
+
"CI_MERGE_REQUEST_IID" => 28_493,
|
20
|
+
"DANGER_GITLAB_API_TOKEN" => "123sbdq54erfsd3422gdfio",
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
# A stubbed out Dangerfile for use in tests
|
25
|
+
def self.testing_dangerfile
|
26
|
+
env = Danger::EnvironmentManager.new(testing_env)
|
27
|
+
Danger::Dangerfile.new(env, testing_ui)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.fake_danger
|
31
|
+
Class.new do
|
32
|
+
attr_reader :git, :gitlab, :helper
|
33
|
+
|
34
|
+
# rubocop:disable Gitlab/ModuleWithInstanceVariables
|
35
|
+
def initialize(git: nil, gitlab: nil, helper: nil)
|
36
|
+
@git = git
|
37
|
+
@gitlab = gitlab
|
38
|
+
@helper = helper
|
39
|
+
end
|
40
|
+
|
41
|
+
# rubocop:enable Gitlab/ModuleWithInstanceVariables
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
RSpec.shared_context "with dangerfile" do
|
47
|
+
let(:dangerfile) { DangerSpecHelper.testing_dangerfile }
|
48
|
+
let(:added_files) { %w[added1] }
|
49
|
+
let(:modified_files) { %w[modified1] }
|
50
|
+
let(:deleted_files) { %w[deleted1] }
|
51
|
+
let(:renamed_before_file) { "renamed_before" }
|
52
|
+
let(:renamed_after_file) { "renamed_after" }
|
53
|
+
let(:renamed_files) { [{ before: renamed_before_file, after: renamed_after_file }] }
|
54
|
+
let(:change_class) { Gitlab::Dangerfiles::Change }
|
55
|
+
let(:changes_class) { Gitlab::Dangerfiles::Changes }
|
56
|
+
let(:changes) { changes_class.new([]) }
|
57
|
+
let(:mr_title) { "Fake Title" }
|
58
|
+
let(:mr_labels) { [] }
|
59
|
+
|
60
|
+
let(:fake_git) { double("fake-git", added_files: added_files, modified_files: modified_files, deleted_files: deleted_files, renamed_files: renamed_files) }
|
61
|
+
let(:fake_helper) { double("fake-helper", changes: changes, mr_iid: 1234, mr_title: mr_title, mr_labels: mr_labels) }
|
62
|
+
|
63
|
+
before do
|
64
|
+
allow(dangerfile).to receive(:git).and_return(fake_git)
|
65
|
+
end
|
66
|
+
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.8.1
|
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-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: danger
|
@@ -135,10 +135,18 @@ 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
|
139
|
+
- lib/gitlab/dangerfiles/changes.rb
|
138
140
|
- lib/gitlab/dangerfiles/commit_linter.rb
|
139
141
|
- lib/gitlab/dangerfiles/emoji_checker.rb
|
142
|
+
- lib/gitlab/dangerfiles/merge_request_linter.rb
|
143
|
+
- lib/gitlab/dangerfiles/spec_helper.rb
|
140
144
|
- lib/gitlab/dangerfiles/teammate.rb
|
145
|
+
- lib/gitlab/dangerfiles/title_linting.rb
|
141
146
|
- lib/gitlab/dangerfiles/version.rb
|
147
|
+
- lib/gitlab/dangerfiles/weightage.rb
|
148
|
+
- lib/gitlab/dangerfiles/weightage/maintainers.rb
|
149
|
+
- lib/gitlab/dangerfiles/weightage/reviewers.rb
|
142
150
|
homepage: https://gitlab.com/gitlab-org/gitlab-dangerfiles
|
143
151
|
licenses:
|
144
152
|
- MIT
|
@@ -162,7 +170,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
162
170
|
- !ruby/object:Gem::Version
|
163
171
|
version: '0'
|
164
172
|
requirements: []
|
165
|
-
rubygems_version: 3.1.
|
173
|
+
rubygems_version: 3.1.4
|
166
174
|
signing_key:
|
167
175
|
specification_version: 4
|
168
176
|
summary: This gem provides common Dangerfile and plugins for GitLab projects.
|