gitlab-dangerfiles 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml 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.