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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: d044eef9bd28cb411b658eb1211c49a4f0ec769a93121c2fd7ce698fcd10a95c
4
- data.tar.gz: 56ab60d3e31b399f314a1653b03c81f36efc84493585f00502e36dbc38d0554b
3
+ metadata.gz: a41237cf8c1b948162962ffd5712cac36034d6e3f6ddbd551f7b7f5c64315ac6
4
+ data.tar.gz: 9416beb3ed3688db58d83bfafb103a3f21b15c7a1e1dae83fe08dae33b13d329
5
5
  SHA512:
6
- metadata.gz: 9ffef7ad5e6f4d1ba093f254f210ed70391a395123b7e7d372b4cd947145d9a4958d88454e414686bf292822434ab54087a14c912cf7c16e832ff3b3aeb0d340
7
- data.tar.gz: 804db12887f8acf6bcd4cded8d420537c508bf66a684d53522fcaa96b6f7c60f959ad0b86484bded1c9aac19abcf6aa6be38c90e1ba9e47b759670b9a0eaeb0a
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<String,Array<String>>]
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 sanitize_mr_title(title)
144
- title.gsub(/^WIP: */, "").gsub(/`/, '\\\`')
216
+ def mr_iid
217
+ return "" unless gitlab_helper
218
+
219
+ gitlab_helper.mr_json["iid"]
145
220
  end
146
221
 
147
- def draft_mr?
148
- return false unless gitlab_helper
222
+ def mr_title
223
+ return "" unless gitlab_helper
149
224
 
150
- DRAFT_REGEX.match?(gitlab_helper.mr_json["title"])
225
+ gitlab_helper.mr_json["title"]
151
226
  end
152
227
 
153
- def security_mr?
154
- return false unless gitlab_helper
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
- gitlab_helper.mr_json["web_url"].include?("/gitlab-org/security/")
250
+ def security_mr?
251
+ mr_web_url.include?("/gitlab-org/security/")
157
252
  end
158
253
 
159
254
  def cherry_pick_mr?
160
- return false unless gitlab_helper
255
+ Gitlab::Dangerfiles::TitleLinting.has_cherry_pick_flag?(mr_title)
256
+ end
161
257
 
162
- /cherry[\s-]*pick/i.match?(gitlab_helper.mr_json["title"])
258
+ def run_all_rspec_mr?
259
+ Gitlab::Dangerfiles::TitleLinting.has_run_all_rspec_flag?(mr_title)
163
260
  end
164
261
 
165
- def stable_branch?
166
- return false unless gitlab_helper
262
+ def run_as_if_foss_mr?
263
+ Gitlab::Dangerfiles::TitleLinting.has_run_as_if_foss_flag?(mr_title)
264
+ end
167
265
 
168
- /\A\d+-\d+-stable-ee/i.match?(gitlab_helper.mr_json["target_branch"])
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
- (labels & gitlab_helper.mr_labels) == labels
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
@@ -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&.reviewer?(project, spin.category, [])
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
- reviewer = spin_for_person(reviewers + traintainers + traintainers, random: random, timezone_experiment: timezone_experiment)
155
- maintainer = spin_for_person(maintainers, random: random, timezone_experiment: timezone_experiment)
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
- emoji_checker_path = File.expand_path("emoji_checker", __dir__)
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+\b}.freeze
14
- DEFAULT_SUBJECT_DESCRIPTION = "commit subject"
15
- WIP_PREFIX = "WIP: "
16
- PROBLEMS = {
17
- subject_too_short: "The %s must contain at least #{MIN_SUBJECT_WORDS_COUNT} words",
18
- subject_too_long: "The %s may not be longer than #{MAX_LINE_LENGTH} characters",
19
- subject_starts_with_lowercase: "The %s must start with a capital letter",
20
- subject_ends_with_a_period: "The %s must not end with a period",
21
- separator_missing: "The commit subject and body must be separated by a blank line",
22
- details_too_many_changes: "Commits that change #{MAX_CHANGED_LINES_IN_COMMIT} or more lines across " \
23
- "at least #{MAX_CHANGED_FILES_IN_COMMIT} files must describe these changes in the commit body",
24
- details_line_too_long: "The commit body should not contain more than #{MAX_LINE_LENGTH} characters per line",
25
- message_contains_text_emoji: "Avoid the use of Markdown Emoji such as `:+1:`. These add limited value " \
26
- "to the commit message, and are displayed as plain text outside of GitLab",
27
- message_contains_unicode_emoji: "Avoid the use of Unicode Emoji. These add no value to the commit " \
28
- "message, and may not be displayed properly everywhere",
29
- message_contains_short_reference: "Use full URLs instead of short references (`gitlab-org/gitlab#123` or " \
30
- "`!123`), as short references are displayed as plain text outside of GitLab",
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
- @commit = commit
37
- @problems = {}
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 failed?
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(subject_description)
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
- line = line.strip
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?(line.length - url_size)
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
@@ -1,5 +1,5 @@
1
1
  module Gitlab
2
2
  module Dangerfiles
3
- VERSION = "0.5.0"
3
+ VERSION = "0.6.0"
4
4
  end
5
5
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ module Dangerfiles
5
+ module Weightage
6
+ CAPACITY_MULTIPLIER = 2 # change this number to change what it means to be a reduced capacity reviewer 1/this number
7
+ BASE_REVIEWER_WEIGHT = 1
8
+ end
9
+ end
10
+ 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.5.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: 2020-09-29 00:00:00.000000000 Z
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.2
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.