gitlab-dangerfiles 4.8.1 → 4.12.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 +1 -0
- data/.gitlab/merge_request_templates/Release.md +1 -1
- data/.gitlab-ci.yml +3 -2
- data/.rubocop.yml +11 -2
- data/.rubocop_todo.yml +55 -128
- data/.tool-versions +1 -0
- data/Gemfile.lock +4 -1
- data/README.md +53 -2
- data/gitlab-dangerfiles.gemspec +1 -0
- data/lefthook.yml +5 -0
- data/lib/danger/plugins/commit_messages.rb +140 -0
- data/lib/danger/plugins/duo_code.rb +12 -0
- data/lib/danger/plugins/internal/helper.rb +16 -5
- data/lib/danger/plugins/roulette.rb +77 -3
- data/lib/danger/rules/commit_messages/Dangerfile +1 -123
- data/lib/danger/rules/duo_code_review/Dangerfile +10 -0
- data/lib/danger/rules/simple_roulette/Dangerfile +10 -5
- data/lib/gitlab/dangerfiles/approval.rb +3 -3
- data/lib/gitlab/dangerfiles/capability.rb +0 -16
- data/lib/gitlab/dangerfiles/commit_linter.rb +4 -4
- data/lib/gitlab/dangerfiles/config.rb +29 -3
- data/lib/gitlab/dangerfiles/spec_helper.rb +2 -50
- data/lib/gitlab/dangerfiles/spinner.rb +1 -13
- data/lib/gitlab/dangerfiles/teammate.rb +0 -12
- data/lib/gitlab/dangerfiles/version.rb +1 -1
- metadata +21 -3
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../../gitlab/dangerfiles/commit_linter"
|
|
4
|
+
require_relative "../../gitlab/dangerfiles/merge_request_linter"
|
|
5
|
+
|
|
6
|
+
module Danger
|
|
7
|
+
# Common helper functions for our danger scripts. See Danger::Helper
|
|
8
|
+
# for more details
|
|
9
|
+
class CommitMessages < Danger::Plugin
|
|
10
|
+
COMMIT_MSG_DOC = "https://docs.gitlab.com/ee/development/contributing/merge_request_workflow.html#commit-messages-guidelines"
|
|
11
|
+
MORE_INFO = "For more information, take a look at our [Commit message guidelines](#{COMMIT_MSG_DOC}).".freeze
|
|
12
|
+
THE_DANGER_JOB_TEXT = "the `%<job_name>s` job"
|
|
13
|
+
MAX_COMMITS_COUNT_EXCEEDED_MESSAGE = <<~MSG
|
|
14
|
+
This merge request includes more than %<max_commits_count>d commits. Each commit should meet the following criteria:
|
|
15
|
+
|
|
16
|
+
1. Have a well-written commit message.
|
|
17
|
+
1. Has all tests passing when used on its own (e.g. when using git checkout SHA).
|
|
18
|
+
1. Can be reverted on its own without also requiring the revert of commit that came before it.
|
|
19
|
+
1. Is small enough that it can be reviewed in isolation in under 30 minutes or so.
|
|
20
|
+
|
|
21
|
+
If this merge request contains commits that do not meet this criteria and/or contains intermediate work, please rebase these commits into a smaller number of commits or split this merge request into multiple smaller merge requests.
|
|
22
|
+
MSG
|
|
23
|
+
|
|
24
|
+
def build_message(commit, message, more_info: true)
|
|
25
|
+
[message].tap do |full_message|
|
|
26
|
+
full_message << ". #{MORE_INFO}" if more_info
|
|
27
|
+
full_message.unshift("#{commit.sha}: ") if commit.sha
|
|
28
|
+
end.join
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def fail_commit(commit, message, more_info: true)
|
|
32
|
+
self.fail(build_message(commit, message, more_info: more_info))
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def warn_commit(commit, message, more_info: true)
|
|
36
|
+
self.warn(build_message(commit, message, more_info: more_info))
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def danger_job_link
|
|
40
|
+
if helper.ci?
|
|
41
|
+
"[#{format(THE_DANGER_JOB_TEXT, job_name: ENV.fetch('CI_JOB_NAME', nil))}](#{ENV.fetch('CI_JOB_URL', nil)})"
|
|
42
|
+
else
|
|
43
|
+
THE_DANGER_JOB_TEXT
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Perform various checks against commits. We're not using
|
|
48
|
+
# https://github.com/jonallured/danger-commit_lint because its output is not
|
|
49
|
+
# very helpful, and it doesn't offer the means of ignoring merge commits.
|
|
50
|
+
def lint_commit(commit)
|
|
51
|
+
linter = Gitlab::Dangerfiles::CommitLinter.new(commit)
|
|
52
|
+
|
|
53
|
+
# For now we'll ignore merge commits, as getting rid of those is a problem
|
|
54
|
+
# separate from enforcing good commit messages.
|
|
55
|
+
return linter if linter.merge?
|
|
56
|
+
|
|
57
|
+
# We ignore revert commits as they are well structured by Git already
|
|
58
|
+
return linter if linter.revert?
|
|
59
|
+
|
|
60
|
+
# If MR is set to squash, we ignore fixup commits
|
|
61
|
+
return linter if linter.fixup? && helper.squash_mr?
|
|
62
|
+
|
|
63
|
+
if linter.fixup?
|
|
64
|
+
msg = "Squash or fixup commits must be squashed before merge, or **edit** the merge request, " \
|
|
65
|
+
"enable **Squash commits when merge request is accepted** and re-run #{danger_job_link}."
|
|
66
|
+
|
|
67
|
+
if helper.draft_mr? || helper.squash_mr?
|
|
68
|
+
warn_commit(commit, msg, more_info: false)
|
|
69
|
+
else
|
|
70
|
+
fail_commit(commit, msg, more_info: false)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# Makes no sense to process other rules for fixup commits, they trigger just more noise
|
|
74
|
+
return linter
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Fail if a suggestion commit is used and squash is not enabled
|
|
78
|
+
if linter.suggestion?
|
|
79
|
+
unless helper.squash_mr?
|
|
80
|
+
msg = "If you are applying suggestions, **edit** the merge request, enable **Squash commits when " \
|
|
81
|
+
"merge request is accepted** and re-run #{danger_job_link}."
|
|
82
|
+
fail_commit(commit, msg, more_info: false)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
return linter
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
linter.lint
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def lint_mr_title(mr_title)
|
|
92
|
+
commit = Struct.new(:message, :sha).new(mr_title)
|
|
93
|
+
|
|
94
|
+
Gitlab::Dangerfiles::MergeRequestLinter.new(commit).lint
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def count_non_fixup_commits(commit_linters)
|
|
98
|
+
commit_linters.count { |commit_linter| !commit_linter.fixup? }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def lint_commits(commits)
|
|
102
|
+
commit_linters = commits.map { |commit| lint_commit(commit) }
|
|
103
|
+
|
|
104
|
+
if helper.squash_mr?
|
|
105
|
+
multi_line_commit_linter = commit_linters.detect do |commit_linter|
|
|
106
|
+
!commit_linter.merge? && commit_linter.multi_line?
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
if multi_line_commit_linter&.failed?
|
|
110
|
+
warn_or_fail_commits(multi_line_commit_linter)
|
|
111
|
+
commit_linters.delete(multi_line_commit_linter) # Don't show an error (here) and a warning (below)
|
|
112
|
+
end
|
|
113
|
+
elsif count_non_fixup_commits(commit_linters) > max_commits_count
|
|
114
|
+
self.warn(format(MAX_COMMITS_COUNT_EXCEEDED_MESSAGE, max_commits_count: max_commits_count))
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
failed_commit_linters = commit_linters.select(&:failed?)
|
|
118
|
+
warn_or_fail_commits(failed_commit_linters, default_to_fail: !helper.squash_mr?)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def max_commits_count
|
|
122
|
+
helper.config.max_commits_count
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def warn_or_fail_commits(failed_linters, default_to_fail: true)
|
|
126
|
+
level = default_to_fail ? :fail : :warn
|
|
127
|
+
|
|
128
|
+
Array(failed_linters).each do |linter|
|
|
129
|
+
linter.problems.each do |problem_key, problem_desc|
|
|
130
|
+
case problem_key
|
|
131
|
+
when :subject_too_short, :details_too_many_changes, :details_line_too_long
|
|
132
|
+
warn_commit(linter.commit, problem_desc)
|
|
133
|
+
else
|
|
134
|
+
__send__(:"#{level}_commit", linter.commit, problem_desc)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Danger
|
|
4
|
+
# Contains method to check if Duo code suggestion added to MR as a reviewer.
|
|
5
|
+
class DuoCode < Danger::Plugin
|
|
6
|
+
def suggestion_added?
|
|
7
|
+
return false unless helper.ci?
|
|
8
|
+
|
|
9
|
+
helper.mr_reviewers.include?("GitLabDuo")
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -25,12 +25,14 @@ module Danger
|
|
|
25
25
|
pipeline: '~"maintenance::pipelines" for CI',
|
|
26
26
|
ci_template: '~"ci::templates"',
|
|
27
27
|
analytics_instrumentation: '~"analytics instrumentation"',
|
|
28
|
-
import_integrate_be: '~"group::import and integrate" (backend)',
|
|
29
|
-
import_integrate_fe: '~"group::import and integrate" (frontend)',
|
|
30
28
|
Authentication: '~"group::authentication"',
|
|
31
29
|
Authorization: '~"group::authorization"',
|
|
32
30
|
Compliance: '~"group::compliance"',
|
|
33
|
-
Verify: '~"devops::verify"'
|
|
31
|
+
Verify: '~"devops::verify"',
|
|
32
|
+
"AST:Composition Analysis": '~"group::composition analysis"',
|
|
33
|
+
"AST:Dynamic Analysis": '~"group::dynamic analysis"',
|
|
34
|
+
"AST:Secret Detection": '~"group::secret detection"',
|
|
35
|
+
"AST:Static Analysis": '~"group::static analysis"'
|
|
34
36
|
}.freeze
|
|
35
37
|
# rubocop:enable Style/HashSyntax
|
|
36
38
|
|
|
@@ -181,7 +183,9 @@ module Danger
|
|
|
181
183
|
# @return [{Symbol => Array<String>}] a hash of the type +{ category1: ["file1", "file2"], category2: ["file3", "file4"] }+
|
|
182
184
|
# using filename regex (+filename_regex+) and specific change regex (+changes_regex+) from the given +categories+ hash.
|
|
183
185
|
def changes_by_category(categories = [])
|
|
184
|
-
|
|
186
|
+
changed_and_deleted_files = all_changed_files + changes.deleted.files
|
|
187
|
+
|
|
188
|
+
changed_and_deleted_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash|
|
|
185
189
|
categories_for_file(file, categories).each { |category| hash[category] << file }
|
|
186
190
|
end
|
|
187
191
|
end
|
|
@@ -244,7 +248,7 @@ module Danger
|
|
|
244
248
|
# @return [String] the GFM for a category label, making its best guess if it's not
|
|
245
249
|
# a category we know about.
|
|
246
250
|
def label_for_category(category)
|
|
247
|
-
CATEGORY_LABELS[category] ||
|
|
251
|
+
helper.config.custom_labels_for_categories[category] || CATEGORY_LABELS[category] ||
|
|
248
252
|
|
|
249
253
|
if category.start_with?("`")
|
|
250
254
|
category.to_s
|
|
@@ -288,6 +292,13 @@ module Danger
|
|
|
288
292
|
gitlab_helper.mr_json["assignees"]
|
|
289
293
|
end
|
|
290
294
|
|
|
295
|
+
# @return [Array<Hash>] +[]+ when not in the CI context, and the MR reviewers otherwise.
|
|
296
|
+
def mr_reviewers
|
|
297
|
+
return [] unless ci?
|
|
298
|
+
|
|
299
|
+
gitlab_helper.mr_json["reviewers"]
|
|
300
|
+
end
|
|
301
|
+
|
|
291
302
|
# @return [String] +""+ when not in the CI context, and the MR title otherwise.
|
|
292
303
|
def mr_title
|
|
293
304
|
return "" unless ci?
|
|
@@ -9,6 +9,7 @@ module Danger
|
|
|
9
9
|
# for more details
|
|
10
10
|
class Roulette < Danger::Plugin
|
|
11
11
|
HOURS_WHEN_PERSON_CAN_BE_PICKED = (6..14).freeze
|
|
12
|
+
BOT_REVIEWERS = %w[GitLabDuo].freeze
|
|
12
13
|
HTTPError = Class.new(StandardError)
|
|
13
14
|
|
|
14
15
|
def prepare_categories(changes_keys)
|
|
@@ -45,7 +46,7 @@ module Danger
|
|
|
45
46
|
# @param categories [Array<Symbol>] An array of categories symbols.
|
|
46
47
|
#
|
|
47
48
|
# @return [Array<Spin>]
|
|
48
|
-
def spin(project = nil, categories = [:none], ux_fallback_wider_community_reviewer:
|
|
49
|
+
def spin(project = nil, categories = [:none], ux_fallback_wider_community_reviewer: ux_fallback_reviewer)
|
|
49
50
|
# TODO: Deprecate the project argument. It prevents us from
|
|
50
51
|
# memorizing Spinner and can cause unexpected results if it's
|
|
51
52
|
# passing a different project than the merge request project.
|
|
@@ -87,10 +88,40 @@ module Danger
|
|
|
87
88
|
Gitlab::Dangerfiles::Teammate.warnings
|
|
88
89
|
end
|
|
89
90
|
|
|
91
|
+
# Automatically assigns reviewers from roulette spins if configured to do so
|
|
92
|
+
#
|
|
93
|
+
# @param spins [Array<Spin>] The roulette spins to potentially assign from
|
|
94
|
+
def assign_reviewers_from_roulette(spins)
|
|
95
|
+
return if human_reviewers?
|
|
96
|
+
|
|
97
|
+
reviewers_to_assign = find_reviewers_to_assign(spins)
|
|
98
|
+
|
|
99
|
+
if reviewers_to_assign.any?
|
|
100
|
+
post_assignment_message(reviewers_to_assign)
|
|
101
|
+
else
|
|
102
|
+
warn("No reviewers available for assignment")
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Determines if auto-assignment should happen based on configuration
|
|
107
|
+
#
|
|
108
|
+
# @return [Boolean]
|
|
109
|
+
def auto_assign_reviewers?
|
|
110
|
+
return false if helper.config.auto_assign_for_roulette_roles.empty?
|
|
111
|
+
|
|
112
|
+
configured_labels = helper.config.auto_assign_for_roulette_labels
|
|
113
|
+
|
|
114
|
+
return true if configured_labels.empty?
|
|
115
|
+
|
|
116
|
+
mr_labels = helper.mr_labels
|
|
117
|
+
configured_labels.any? { |label| mr_labels.include?(label) }
|
|
118
|
+
end
|
|
119
|
+
|
|
90
120
|
private
|
|
91
121
|
|
|
92
|
-
def
|
|
93
|
-
|
|
122
|
+
def ux_fallback_reviewer
|
|
123
|
+
teammates = %w[pedroms annabeldunstone seggenberger jmiocene clavimoniere]
|
|
124
|
+
Gitlab::Dangerfiles::Teammate.find_member(teammates.sample)
|
|
94
125
|
end
|
|
95
126
|
|
|
96
127
|
def spin_for_approval_rule?(rule)
|
|
@@ -157,5 +188,48 @@ module Danger
|
|
|
157
188
|
def labels
|
|
158
189
|
@labels ||= helper.mr_labels
|
|
159
190
|
end
|
|
191
|
+
|
|
192
|
+
# Checks if any non-bot reviewers are already assigned to the merge request
|
|
193
|
+
#
|
|
194
|
+
# @return [Boolean]
|
|
195
|
+
def human_reviewers?
|
|
196
|
+
helper.mr_reviewers.any? { |reviewer| !BOT_REVIEWERS.include?(reviewer["username"]) }
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# Find reviewers to assign based on configured roles
|
|
200
|
+
#
|
|
201
|
+
# @param spins [Array<Spin>] The roulette spins
|
|
202
|
+
# @return [Array<String>] Array of usernames to assign
|
|
203
|
+
def find_reviewers_to_assign(spins)
|
|
204
|
+
roles_to_assign = helper.config.auto_assign_for_roulette_roles
|
|
205
|
+
reviewers_to_assign = []
|
|
206
|
+
|
|
207
|
+
spins.each do |spin|
|
|
208
|
+
if roles_to_assign.include?(:reviewer) && spin.reviewer&.username
|
|
209
|
+
reviewers_to_assign << spin.reviewer.username
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
if roles_to_assign.include?(:maintainer) && spin.maintainer&.username
|
|
213
|
+
reviewers_to_assign << spin.maintainer.username
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
if reviewers_to_assign.any?
|
|
217
|
+
break
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
reviewers_to_assign
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Posts the assignment message with the selected reviewers
|
|
225
|
+
#
|
|
226
|
+
# @param reviewers_to_assign [Array<String>] Array of usernames to assign
|
|
227
|
+
def post_assignment_message(reviewers_to_assign)
|
|
228
|
+
role_text = helper.config.auto_assign_for_roulette_roles.map(&:to_s).join(' and ')
|
|
229
|
+
message = "🎲 Assigned #{role_text}s based on reviewer roulette.\n/assign_reviewer #{reviewers_to_assign.map { |u| "@#{u}" }.join(' ')}"
|
|
230
|
+
markdown(message)
|
|
231
|
+
rescue StandardError => e
|
|
232
|
+
warn("Failed to assign reviewers: #{e.message}")
|
|
233
|
+
end
|
|
160
234
|
end
|
|
161
235
|
end
|
|
@@ -1,127 +1,5 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative "../../../gitlab/dangerfiles/commit_linter"
|
|
4
|
-
require_relative "../../../gitlab/dangerfiles/merge_request_linter"
|
|
5
|
-
|
|
6
|
-
COMMIT_MESSAGE_GUIDELINES = "https://docs.gitlab.com/ee/development/contributing/merge_request_workflow.html#commit-messages-guidelines"
|
|
7
|
-
MORE_INFO = "For more information, take a look at our [Commit message guidelines](#{COMMIT_MESSAGE_GUIDELINES})."
|
|
8
|
-
THE_DANGER_JOB_TEXT = "the `%<job_name>s` job"
|
|
9
|
-
MAX_COMMITS_COUNT = helper.config.max_commits_count
|
|
10
|
-
MAX_COMMITS_COUNT_EXCEEDED_MESSAGE = <<~MSG
|
|
11
|
-
This merge request includes more than %<max_commits_count>d commits. Each commit should meet the following criteria:
|
|
12
|
-
|
|
13
|
-
1. Have a well-written commit message.
|
|
14
|
-
1. Has all tests passing when used on its own (e.g. when using git checkout SHA).
|
|
15
|
-
1. Can be reverted on its own without also requiring the revert of commit that came before it.
|
|
16
|
-
1. Is small enough that it can be reviewed in isolation in under 30 minutes or so.
|
|
17
|
-
|
|
18
|
-
If this merge request contains commits that do not meet this criteria and/or contains intermediate work, please rebase these commits into a smaller number of commits or split this merge request into multiple smaller merge requests.
|
|
19
|
-
MSG
|
|
20
|
-
|
|
21
|
-
def fail_commit(commit, message, more_info: true)
|
|
22
|
-
self.fail(build_message(commit, message, more_info: more_info))
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def warn_commit(commit, message, more_info: true)
|
|
26
|
-
self.warn(build_message(commit, message, more_info: more_info))
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def build_message(commit, message, more_info: true)
|
|
30
|
-
[message].tap do |full_message|
|
|
31
|
-
full_message << ". #{MORE_INFO}" if more_info
|
|
32
|
-
full_message.unshift("#{commit.sha}: ") if commit.sha
|
|
33
|
-
end.join
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def danger_job_link
|
|
37
|
-
helper.ci? ? "[#{format(THE_DANGER_JOB_TEXT, job_name: ENV["CI_JOB_NAME"])}](#{ENV['CI_JOB_URL']})" : THE_DANGER_JOB_TEXT
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
# Perform various checks against commits. We're not using
|
|
41
|
-
# https://github.com/jonallured/danger-commit_lint because its output is not
|
|
42
|
-
# very helpful, and it doesn't offer the means of ignoring merge commits.
|
|
43
|
-
def lint_commit(commit)
|
|
44
|
-
linter = Gitlab::Dangerfiles::CommitLinter.new(commit)
|
|
45
|
-
|
|
46
|
-
# For now we'll ignore merge commits, as getting rid of those is a problem
|
|
47
|
-
# separate from enforcing good commit messages.
|
|
48
|
-
return linter if linter.merge?
|
|
49
|
-
|
|
50
|
-
# We ignore revert commits as they are well structured by Git already
|
|
51
|
-
return linter if linter.revert?
|
|
52
|
-
|
|
53
|
-
# If MR is set to squash, we ignore fixup commits
|
|
54
|
-
return linter if linter.fixup? && helper.squash_mr?
|
|
55
|
-
|
|
56
|
-
if linter.fixup?
|
|
57
|
-
msg = "Squash or fixup commits must be squashed before merge, or **edit** the merge request, enable **Squash commits when merge request is accepted** and re-run #{danger_job_link}."
|
|
58
|
-
if helper.draft_mr? || helper.squash_mr?
|
|
59
|
-
warn_commit(commit, msg, more_info: false)
|
|
60
|
-
else
|
|
61
|
-
fail_commit(commit, msg, more_info: false)
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
# Makes no sense to process other rules for fixup commits, they trigger just more noise
|
|
65
|
-
return linter
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
# Fail if a suggestion commit is used and squash is not enabled
|
|
69
|
-
if linter.suggestion?
|
|
70
|
-
unless helper.squash_mr?
|
|
71
|
-
fail_commit(commit, "If you are applying suggestions, **edit** the merge request, enable **Squash commits when merge request is accepted** and re-run #{danger_job_link}.", more_info: false)
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
return linter
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
linter.lint
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def lint_mr_title(mr_title)
|
|
81
|
-
commit = Struct.new(:message, :sha).new(mr_title)
|
|
82
|
-
|
|
83
|
-
Gitlab::Dangerfiles::MergeRequestLinter.new(commit).lint
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def count_non_fixup_commits(commit_linters)
|
|
87
|
-
commit_linters.count { |commit_linter| !commit_linter.fixup? }
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def lint_commits(commits)
|
|
91
|
-
commit_linters = commits.map { |commit| lint_commit(commit) }
|
|
92
|
-
|
|
93
|
-
if helper.squash_mr?
|
|
94
|
-
multi_line_commit_linter = commit_linters.detect { |commit_linter| !commit_linter.merge? && commit_linter.multi_line? }
|
|
95
|
-
|
|
96
|
-
if multi_line_commit_linter && multi_line_commit_linter.failed?
|
|
97
|
-
warn_or_fail_commits(multi_line_commit_linter)
|
|
98
|
-
commit_linters.delete(multi_line_commit_linter) # Don't show an error (here) and a warning (below)
|
|
99
|
-
end
|
|
100
|
-
else
|
|
101
|
-
if count_non_fixup_commits(commit_linters) > MAX_COMMITS_COUNT
|
|
102
|
-
self.warn(format(MAX_COMMITS_COUNT_EXCEEDED_MESSAGE, max_commits_count: MAX_COMMITS_COUNT))
|
|
103
|
-
end
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
failed_commit_linters = commit_linters.select { |commit_linter| commit_linter.failed? }
|
|
107
|
-
warn_or_fail_commits(failed_commit_linters, default_to_fail: !helper.squash_mr?)
|
|
108
|
-
end
|
|
109
|
-
|
|
110
|
-
def warn_or_fail_commits(failed_linters, default_to_fail: true)
|
|
111
|
-
level = default_to_fail ? :fail : :warn
|
|
112
|
-
|
|
113
|
-
Array(failed_linters).each do |linter|
|
|
114
|
-
linter.problems.each do |problem_key, problem_desc|
|
|
115
|
-
case problem_key
|
|
116
|
-
when :subject_too_short, :subject_above_warning, :details_too_many_changes, :details_line_too_long
|
|
117
|
-
warn_commit(linter.commit, problem_desc)
|
|
118
|
-
else
|
|
119
|
-
self.__send__(:"#{level}_commit", linter.commit, problem_desc)
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
|
|
125
3
|
# As part of https://gitlab.com/groups/gitlab-org/-/epics/4826 we are
|
|
126
4
|
# vendoring workhorse commits from the stand-alone gitlab-workhorse
|
|
127
5
|
# repo. There is no point in linting commits that we want to vendor as
|
|
@@ -130,4 +8,4 @@ def workhorse_changes?
|
|
|
130
8
|
git.diff.any? { |file| file.path.start_with?('workhorse/') }
|
|
131
9
|
end
|
|
132
10
|
|
|
133
|
-
lint_commits(git.commits) unless workhorse_changes?
|
|
11
|
+
commit_messages.lint_commits(git.commits) unless workhorse_changes?
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
return if duo_code.suggestion_added?
|
|
4
|
+
|
|
5
|
+
case helper.config.duo_code_review
|
|
6
|
+
when :optional
|
|
7
|
+
markdown("We advise getting a review from GitLab Duo Code. You can assign `@GitLabDuo` as a reviewer to this merge request.")
|
|
8
|
+
when :mandatory
|
|
9
|
+
failure("A review from GitLab Duo Code is mandatory. Please assign `@GitLabDuo` as a reviewer to this merge request.")
|
|
10
|
+
end
|
|
@@ -8,9 +8,6 @@ MESSAGE = <<MARKDOWN
|
|
|
8
8
|
Changes that require review have been detected! A merge request is normally
|
|
9
9
|
reviewed by both a reviewer and a maintainer in its primary category and by a
|
|
10
10
|
maintainer in all other categories.
|
|
11
|
-
MARKDOWN
|
|
12
|
-
|
|
13
|
-
TABLE_MARKDOWN = <<MARKDOWN
|
|
14
11
|
|
|
15
12
|
To spread load more evenly across eligible reviewers, Danger has picked a candidate for each
|
|
16
13
|
review slot. Feel free to
|
|
@@ -26,9 +23,11 @@ Please consider assigning a reviewer or maintainer who is a
|
|
|
26
23
|
[domain expert](https://about.gitlab.com/handbook/engineering/projects/##{PROJECT_NAME})
|
|
27
24
|
in the area of the merge request.
|
|
28
25
|
|
|
26
|
+
MARKDOWN
|
|
27
|
+
|
|
28
|
+
NO_AUTO_ASSIGN = <<MARKDOWN
|
|
29
29
|
Once you've decided who will review this merge request, mention them as you
|
|
30
30
|
normally would! Danger does not automatically notify them for you.
|
|
31
|
-
|
|
32
31
|
MARKDOWN
|
|
33
32
|
|
|
34
33
|
TABLE_HEADER_WITH_CATEGORIES = <<MARKDOWN
|
|
@@ -101,5 +100,11 @@ if changes.any?
|
|
|
101
100
|
|
|
102
101
|
markdown(format(WARNING_MESSAGE, warnings: warnings)) if warnings
|
|
103
102
|
|
|
104
|
-
markdown(
|
|
103
|
+
markdown(table_header + rows.join("\n")) unless rows.empty?
|
|
104
|
+
|
|
105
|
+
if roulette.auto_assign_reviewers?
|
|
106
|
+
roulette.assign_reviewers_from_roulette(random_roulette_spins)
|
|
107
|
+
else
|
|
108
|
+
markdown(NO_AUTO_ASSIGN)
|
|
109
|
+
end
|
|
105
110
|
end
|
|
@@ -4,18 +4,18 @@ require_relative "spin"
|
|
|
4
4
|
|
|
5
5
|
module Gitlab
|
|
6
6
|
module Dangerfiles
|
|
7
|
-
Approval = Struct.new(:category, :spin) do
|
|
7
|
+
Approval = Struct.new(:category, :spin, :name) do
|
|
8
8
|
def self.from_approval_rule(rule, maintainer)
|
|
9
9
|
category =
|
|
10
10
|
if rule["section"] == "codeowners"
|
|
11
11
|
"`#{rule['name']}`"
|
|
12
12
|
else
|
|
13
|
-
rule["section"]
|
|
13
|
+
rule["section"]&.downcase
|
|
14
14
|
end.to_sym
|
|
15
15
|
|
|
16
16
|
spin = Spin.new(category, nil, maintainer, :reviewer)
|
|
17
17
|
|
|
18
|
-
new(category, spin)
|
|
18
|
+
new(category, spin, rule["name"])
|
|
19
19
|
end
|
|
20
20
|
end
|
|
21
21
|
end
|
|
@@ -15,8 +15,6 @@ module Gitlab
|
|
|
15
15
|
none: None,
|
|
16
16
|
test: Test,
|
|
17
17
|
tooling: Tooling,
|
|
18
|
-
import_integrate_be: ImportIntegrateBE,
|
|
19
|
-
import_integrate_fe: ImportIntegrateFE,
|
|
20
18
|
ux: UX
|
|
21
19
|
}.freeze
|
|
22
20
|
end
|
|
@@ -60,20 +58,6 @@ module Gitlab
|
|
|
60
58
|
end
|
|
61
59
|
end
|
|
62
60
|
|
|
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
61
|
class UX < Capability
|
|
78
62
|
def has_capability?(teammate)
|
|
79
63
|
super && teammate.member_of_the_group?(labels)
|
|
@@ -9,9 +9,9 @@ module Gitlab
|
|
|
9
9
|
MAX_CHANGED_FILES_IN_COMMIT = 3
|
|
10
10
|
MAX_CHANGED_LINES_IN_COMMIT = 30
|
|
11
11
|
# Issue, MR, Epic
|
|
12
|
-
SHORT_REFERENCE_REGEX = %r{(\S*([\w\-\/]+)?(?<!`)(#|!|&)
|
|
12
|
+
SHORT_REFERENCE_REGEX = %r{(\S*([\w\-\/]+)?(?<!`)(#|!|&)(?>\d+)(?!`))}.freeze
|
|
13
13
|
# Milestone
|
|
14
|
-
MS_SHORT_REFERENCE_REGEX = %r{(\S*([\w\-\/]+)?(?<!`)%"?\d{1,3}\.\d{1,3}"?(
|
|
14
|
+
MS_SHORT_REFERENCE_REGEX = %r{(\S*([\w\-\/]+)?(?<!`)%(?>"?\d{1,3}\.\d{1,3}"?)(?!`))}.freeze
|
|
15
15
|
SUGGESTIONS_APPLIED_COMMIT_REGEX = /Apply \d+ suggestion\(s\) to \d+ file\(s\)/.freeze
|
|
16
16
|
|
|
17
17
|
def self.problems_mapping
|
|
@@ -25,8 +25,8 @@ module Gitlab
|
|
|
25
25
|
"to the commit message, and are displayed as plain text outside of 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
|
-
message_contains_short_reference: "Use full URLs instead of short references (`gitlab-org/gitlab#123
|
|
29
|
-
"`!123`), as short references are displayed as plain text outside of GitLab"
|
|
28
|
+
message_contains_short_reference: "Use full URLs instead of short references (`gitlab-org/gitlab#123`, " \
|
|
29
|
+
"`!123`, or `%%12.3`), as short references are displayed as plain text outside of GitLab"
|
|
30
30
|
}
|
|
31
31
|
)
|
|
32
32
|
end
|
|
@@ -21,31 +21,54 @@ module Gitlab
|
|
|
21
21
|
# match changed lines in files that match +filename_regex+. Used in `helper.changes_by_category`, `helper.changes`, and `helper.categories_for_file`.
|
|
22
22
|
attr_accessor :files_to_category
|
|
23
23
|
|
|
24
|
+
# @!attribute custom_labels_for_categories
|
|
25
|
+
# @return [{String => String}] A hash of the form +{ category_name => label }+.
|
|
26
|
+
# Used in `helper.custom_labels_for_categories`.
|
|
27
|
+
attr_accessor :custom_labels_for_categories
|
|
28
|
+
|
|
29
|
+
# @!attribute disabled_roulette_categories
|
|
30
|
+
# @return [Array] indicating which categories would be disabled for the simple roulette. Default to `[]` (all categories are enabled)
|
|
31
|
+
attr_accessor :disabled_roulette_categories
|
|
32
|
+
|
|
33
|
+
# Rule: changes_size
|
|
24
34
|
# @!attribute code_size_thresholds
|
|
25
35
|
# @return [{ high: Integer, medium: Integer }] a hash of the form +{ high: 42, medium: 12 }+ where +:high+ is the lines changed threshold which triggers an error, and +:medium+ is the lines changed threshold which triggers a warning. Also, see +DEFAULT_CHANGES_SIZE_THRESHOLDS+ for the format of the hash.
|
|
26
36
|
attr_accessor :code_size_thresholds
|
|
27
37
|
|
|
38
|
+
# Rule: commit_messages
|
|
28
39
|
# @!attribute max_commits_count
|
|
29
40
|
# @return [Integer] the maximum number of allowed non-squashed/non-fixup commits for a given MR. A warning is triggered if the MR has more commits.
|
|
30
41
|
attr_accessor :max_commits_count
|
|
31
42
|
|
|
32
|
-
#
|
|
33
|
-
#
|
|
34
|
-
|
|
43
|
+
# Rule: duo_code_review
|
|
44
|
+
# @!attribute duo_code_review
|
|
45
|
+
# @return [Symbol] whether a review from GitLab Duo Code is `:mandatory` or `:optional`. Default to `:optional`.
|
|
46
|
+
attr_accessor :duo_code_review
|
|
35
47
|
|
|
48
|
+
# Rule: simple_roulette
|
|
36
49
|
# @!attribute included_optional_codeowners_sections_for_roulette
|
|
37
50
|
# @return [Array] indicating which optional codeowners sections should be included in roulette. Default to `[]`.
|
|
38
51
|
attr_accessor :included_optional_codeowners_sections_for_roulette
|
|
39
52
|
|
|
53
|
+
# Rule: simple_roulette
|
|
40
54
|
# @!attribute excluded_required_codeowners_sections_for_roulette
|
|
41
55
|
# @return [Array] indicating which required codeowners sections should be excluded from roulette. Default to `[]`.
|
|
42
56
|
attr_accessor :excluded_required_codeowners_sections_for_roulette
|
|
43
57
|
|
|
58
|
+
# @!attribute auto_assign_for_roulette_roles
|
|
59
|
+
# @return [Array<Symbol>] which roles to auto assign as reviewers, given roulette recommendations (:reviewer, :maintainer, or both). If empty, auto-assignment is disabled. Default to `[]`.
|
|
60
|
+
attr_accessor :auto_assign_for_roulette_roles
|
|
61
|
+
|
|
62
|
+
# @!attribute auto_assign_for_roulette_labels
|
|
63
|
+
# @return [Array<String>] MR labels that allow auto reviewer assignment. If empty, applies to all MRs, provided :auto_assign_for_roulette_roles is not empty. Default to `[]`.
|
|
64
|
+
attr_accessor :auto_assign_for_roulette_labels
|
|
65
|
+
|
|
44
66
|
DEFAULT_CHANGES_SIZE_THRESHOLDS = { high: 2_000, medium: 500 }.freeze
|
|
45
67
|
DEFAULT_COMMIT_MESSAGES_MAX_COMMITS_COUNT = 10
|
|
46
68
|
|
|
47
69
|
def initialize
|
|
48
70
|
@files_to_category = {}
|
|
71
|
+
@custom_labels_for_categories = {}
|
|
49
72
|
@project_root = nil
|
|
50
73
|
@project_name = ENV["CI_PROJECT_NAME"]
|
|
51
74
|
@ci_only_rules = []
|
|
@@ -54,6 +77,9 @@ module Gitlab
|
|
|
54
77
|
@disabled_roulette_categories = []
|
|
55
78
|
@included_optional_codeowners_sections_for_roulette = []
|
|
56
79
|
@excluded_required_codeowners_sections_for_roulette = []
|
|
80
|
+
@auto_assign_for_roulette_roles = []
|
|
81
|
+
@auto_assign_for_roulette_labels = []
|
|
82
|
+
@duo_code_review = :optional
|
|
57
83
|
end
|
|
58
84
|
end
|
|
59
85
|
end
|