gitlab-dangerfiles 1.1.0 → 2.1.2

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.
@@ -4,41 +4,63 @@ require_relative "title_linting"
4
4
 
5
5
  module Gitlab
6
6
  module Dangerfiles
7
+ # @!attribute file
8
+ # @return [String] the file name that's changed.
9
+ # @!attribute change_type
10
+ # @return [Symbol] the type of change (+:added+, +:modified+, +:deleted+, +:renamed_before+, +:renamed_after+).
11
+ # @!attribute category
12
+ # @return [Symbol] the category of the change.
13
+ # This is defined by consumers of the gem through +helper.changes_by_category+ or +helper.changes+.
7
14
  Change = Struct.new(:file, :change_type, :category)
8
15
 
9
16
  class Changes < ::SimpleDelegator
17
+ # Return an +Gitlab::Dangerfiles::Changes+ object with only the changes for the added files.
18
+ #
19
+ # @return [Gitlab::Dangerfiles::Changes]
10
20
  def added
11
21
  select_by_change_type(:added)
12
22
  end
13
23
 
24
+ # @return [Gitlab::Dangerfiles::Changes] the changes for the modified files.
14
25
  def modified
15
26
  select_by_change_type(:modified)
16
27
  end
17
28
 
29
+ # @return [Gitlab::Dangerfiles::Changes] the changes for the deleted files.
18
30
  def deleted
19
31
  select_by_change_type(:deleted)
20
32
  end
21
33
 
34
+ # @return [Gitlab::Dangerfiles::Changes] the changes for the renamed files (before the rename).
22
35
  def renamed_before
23
36
  select_by_change_type(:renamed_before)
24
37
  end
25
38
 
39
+ # @return [Gitlab::Dangerfiles::Changes] the changes for the renamed files (after the rename).
26
40
  def renamed_after
27
41
  select_by_change_type(:renamed_after)
28
42
  end
29
43
 
44
+ # @param category [Symbol] A category of change.
45
+ #
46
+ # @return [Boolean] whether there are any change for the given +category+.
30
47
  def has_category?(category)
31
48
  any? { |change| change.category == category }
32
49
  end
33
50
 
51
+ # @param category [Symbol] a category of change.
52
+ #
53
+ # @return [Gitlab::Dangerfiles::Changes] changes for the given +category+.
34
54
  def by_category(category)
35
55
  Changes.new(select { |change| change.category == category })
36
56
  end
37
57
 
58
+ # @return [Array<Symbol>] an array of the unique categories of changes.
38
59
  def categories
39
60
  map(&:category).uniq
40
61
  end
41
62
 
63
+ # @return [Array<String>] an array of the changed files.
42
64
  def files
43
65
  map(&:file)
44
66
  end
@@ -8,14 +8,17 @@ module Gitlab
8
8
  class CommitLinter < BaseLinter
9
9
  MAX_CHANGED_FILES_IN_COMMIT = 3
10
10
  MAX_CHANGED_LINES_IN_COMMIT = 30
11
- SHORT_REFERENCE_REGEX = %r{([\w\-\/]+)?(?<!`)(#|!|&|%)\d+(?<!`)}.freeze
11
+ # Issue, MR, Epic
12
+ SHORT_REFERENCE_REGEX = %r{([\w\-\/]+)?(?<!`)(#|!|&)\d+(?<!`)}.freeze
13
+ # Milestone
14
+ MS_SHORT_REFERENCE_REGEX = %r{([\w\-\/]+)?(?<!`)%"?\d{1,3}\.\d{1,3}"?(?<!`)}.freeze
12
15
 
13
16
  def self.problems_mapping
14
17
  super.merge(
15
18
  {
16
19
  separator_missing: "The commit subject and body must be separated by a blank line",
17
20
  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",
21
+ "at least #{MAX_CHANGED_FILES_IN_COMMIT} files should describe these changes in the commit body",
19
22
  details_line_too_long: "The commit body should not contain more than #{MAX_LINE_LENGTH} characters per line",
20
23
  message_contains_text_emoji: "Avoid the use of Markdown Emoji such as `:+1:`. These add limited value " \
21
24
  "to the commit message, and are displayed as plain text outside of GitLab",
@@ -139,7 +142,8 @@ module Gitlab
139
142
  end
140
143
 
141
144
  def message_contains_short_reference?
142
- commit.message.match?(SHORT_REFERENCE_REGEX)
145
+ commit.message.match?(SHORT_REFERENCE_REGEX) ||
146
+ commit.message.match?(MS_SHORT_REFERENCE_REGEX)
143
147
  end
144
148
 
145
149
  def emoji_checker
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ module Dangerfiles
5
+ class Config
6
+ # @!attribute code_size_thresholds
7
+ # @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.
8
+ attr_accessor :code_size_thresholds
9
+
10
+ # @!attribute max_commits_count
11
+ # @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.
12
+ attr_accessor :max_commits_count
13
+
14
+ DEFAULT_CHANGES_SIZE_THRESHOLDS = { high: 2_000, medium: 500 }.freeze
15
+ DEFAULT_COMMIT_MESSAGES_MAX_COMMITS_COUNT = 10
16
+
17
+ def initialize
18
+ @code_size_thresholds = DEFAULT_CHANGES_SIZE_THRESHOLDS
19
+ @max_commits_count = DEFAULT_COMMIT_MESSAGES_MAX_COMMITS_COUNT
20
+ end
21
+ end
22
+ end
23
+ end
@@ -4,6 +4,7 @@ require "json"
4
4
 
5
5
  module Gitlab
6
6
  module Dangerfiles
7
+ # @api private
7
8
  class EmojiChecker
8
9
  DIGESTS = File.expand_path("../../../fixtures/emojis/digests.json", __dir__)
9
10
  ALIASES = File.expand_path("../../../fixtures/emojis/aliases.json", __dir__)
@@ -101,11 +101,6 @@ module Gitlab
101
101
  return true if capabilities(project).include?("#{kind} engineering_productivity")
102
102
 
103
103
  capabilities(project).include?("#{kind} backend")
104
- when :product_intelligence
105
- return false unless role[/Engineer, Product Intelligence/]
106
-
107
- # any reviewer of project from Product Intelligence team can review MR
108
- kind == :reviewer && capabilities(project).any?
109
104
  when nil
110
105
  capabilities(project).include?("#{kind}")
111
106
  else
@@ -19,6 +19,8 @@ module Gitlab
19
19
  end
20
20
 
21
21
  def has_draft_flag?(title)
22
+ puts "This method is deprecated in favor of `helper.draft_mr?`."
23
+
22
24
  DRAFT_REGEX.match?(title)
23
25
  end
24
26
 
@@ -1,5 +1,5 @@
1
1
  module Gitlab
2
2
  module Dangerfiles
3
- VERSION = "1.1.0"
3
+ VERSION = "2.1.2"
4
4
  end
5
5
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  module Gitlab
4
4
  module Dangerfiles
5
+ # @api private
5
6
  module Weightage
6
7
  CAPACITY_MULTIPLIER = 2 # change this number to change what it means to be a reduced capacity reviewer 1/this number
7
8
  BASE_REVIEWER_WEIGHT = 1
@@ -5,6 +5,7 @@ require_relative "../weightage"
5
5
  module Gitlab
6
6
  module Dangerfiles
7
7
  module Weightage
8
+ # @api private
8
9
  class Maintainers
9
10
  def initialize(maintainers)
10
11
  @maintainers = maintainers
@@ -18,6 +18,7 @@ module Gitlab
18
18
  # | hungry traintainer | 8 |
19
19
  # +------------------------------+--------------------------------+
20
20
  #
21
+ # @api private
21
22
  class Reviewers
22
23
  DEFAULT_REVIEWER_WEIGHT = Gitlab::Dangerfiles::Weightage::CAPACITY_MULTIPLIER * Gitlab::Dangerfiles::Weightage::BASE_REVIEWER_WEIGHT
23
24
  TRAINTAINER_WEIGHT = 3
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: 1.1.0
4
+ version: 2.1.2
5
5
  platform: ruby
6
6
  authors:
7
- - Rémy Coutable
7
+ - GitLab
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-25 00:00:00.000000000 Z
11
+ date: 2021-06-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: danger-gitlab
@@ -110,7 +110,7 @@ dependencies:
110
110
  version: '0'
111
111
  description: This gem provides common Dangerfile and plugins for GitLab projects.
112
112
  email:
113
- - remy@rymai.me
113
+ - gitlab_rubygems@gitlab.com
114
114
  executables: []
115
115
  extensions: []
116
116
  extra_rdoc_files: []
@@ -119,6 +119,7 @@ files:
119
119
  - ".gitlab-ci.yml"
120
120
  - ".gitlab/merge_request_templates/Release.md"
121
121
  - ".rspec"
122
+ - ".yardopts"
122
123
  - CODE_OF_CONDUCT.md
123
124
  - Gemfile
124
125
  - Guardfile
@@ -130,14 +131,17 @@ files:
130
131
  - fixtures/emojis/aliases.json
131
132
  - fixtures/emojis/digests.json
132
133
  - gitlab-dangerfiles.gemspec
133
- - lib/danger/helper.rb
134
- - lib/danger/roulette.rb
134
+ - lib/danger/plugins/helper.rb
135
+ - lib/danger/plugins/roulette.rb
136
+ - lib/danger/rules/changes_size/Dangerfile
137
+ - lib/danger/rules/commit_messages/Dangerfile
135
138
  - lib/gitlab-dangerfiles.rb
136
139
  - lib/gitlab/Dangerfile
137
140
  - lib/gitlab/dangerfiles.rb
138
141
  - lib/gitlab/dangerfiles/base_linter.rb
139
142
  - lib/gitlab/dangerfiles/changes.rb
140
143
  - lib/gitlab/dangerfiles/commit_linter.rb
144
+ - lib/gitlab/dangerfiles/config.rb
141
145
  - lib/gitlab/dangerfiles/emoji_checker.rb
142
146
  - lib/gitlab/dangerfiles/merge_request_linter.rb
143
147
  - lib/gitlab/dangerfiles/spec_helper.rb
@@ -154,7 +158,7 @@ metadata:
154
158
  allowed_push_host: https://rubygems.org
155
159
  homepage_uri: https://gitlab.com/gitlab-org/gitlab-dangerfiles
156
160
  source_code_uri: https://gitlab.com/gitlab-org/gitlab-dangerfiles
157
- changelog_uri: https://gitlab.com/gitlab-org/gitlab-dangerfiles
161
+ changelog_uri: https://gitlab.com/gitlab-org/gitlab-dangerfiles/-/releases
158
162
  post_install_message:
159
163
  rdoc_options: []
160
164
  require_paths:
@@ -170,7 +174,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
170
174
  - !ruby/object:Gem::Version
171
175
  version: '0'
172
176
  requirements: []
173
- rubygems_version: 3.1.4
177
+ rubygems_version: 3.1.6
174
178
  signing_key:
175
179
  specification_version: 4
176
180
  summary: This gem provides common Dangerfile and plugins for GitLab projects.
data/lib/danger/helper.rb DELETED
@@ -1,312 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "net/http"
4
- require "json"
5
- require "danger"
6
- require_relative "../gitlab/dangerfiles/changes"
7
- require_relative "../gitlab/dangerfiles/teammate"
8
- require_relative "../gitlab/dangerfiles/title_linting"
9
-
10
- module Danger
11
- # Common helper functions for our danger scripts.
12
- class Helper < Danger::Plugin
13
- RELEASE_TOOLS_BOT = "gitlab-release-tools-bot"
14
- DRAFT_REGEX = /\A*#{Regexp.union(/(?i)(\[WIP\]\s*|WIP:\s*|WIP$)/, /(?i)(\[draft\]|\(draft\)|draft:|draft\s\-\s|draft$)/)}+\s*/i.freeze
15
- CATEGORY_LABELS = {
16
- docs: "~documentation", # Docs are reviewed along DevOps stages, so don't need roulette for now.
17
- none: "",
18
- qa: "~QA",
19
- test: "~test ~Quality for `spec/features/*`",
20
- engineering_productivity: '~"Engineering Productivity" for CI, Danger',
21
- ci_template: '~"ci::templates"',
22
- product_intelligence: '~"product intelligence"',
23
- }.freeze
24
-
25
- HTTPError = Class.new(StandardError)
26
-
27
- def gitlab_helper
28
- # Unfortunately the following does not work:
29
- # - respond_to?(:gitlab)
30
- # - respond_to?(:gitlab, true)
31
- gitlab
32
- rescue NoMethodError
33
- nil
34
- end
35
-
36
- def html_link(str)
37
- ci? ? gitlab_helper.html_link(str) : str
38
- end
39
-
40
- def ci?
41
- !gitlab_helper.nil?
42
- end
43
-
44
- # @param [String] url
45
- def http_get_json(url)
46
- rsp = Net::HTTP.get_response(URI.parse(url))
47
-
48
- unless rsp.is_a?(Net::HTTPOK)
49
- raise HTTPError, "Failed to read #{url}: #{rsp.code} #{rsp.message}"
50
- end
51
-
52
- JSON.parse(rsp.body)
53
- end
54
-
55
- def added_files
56
- @added_files ||= if changes_from_api
57
- changes_from_api.select { |file| file["new_file"] }.map { |file| file["new_path"] }
58
- else
59
- git.added_files.to_a
60
- end
61
- end
62
-
63
- def modified_files
64
- @modified_files ||= if changes_from_api
65
- changes_from_api.select { |file| !file["new_file"] && !file["deleted_file"] && !file["renamed_file"] }.map { |file| file["new_path"] }
66
- else
67
- git.modified_files.to_a
68
- end
69
- end
70
-
71
- def renamed_files
72
- @renamed_files ||= if changes_from_api
73
- changes_from_api.select { |file| file["renamed_file"] }.each_with_object([]) do |file, memo|
74
- memo << { before: file["old_path"], after: file["new_path"] }
75
- end
76
- else
77
- git.renamed_files.to_a
78
- end
79
- end
80
-
81
- def deleted_files
82
- @deleted_files ||= if changes_from_api
83
- changes_from_api.select { |file| file["deleted_file"] }.map { |file| file["new_path"] }
84
- else
85
- git.deleted_files.to_a
86
- end
87
- end
88
-
89
- def diff_for_file(filename)
90
- if changes_from_api
91
- changes_hash = changes_from_api.find { |file| file["new_path"] == filename }
92
- changes_hash["diff"] if changes_hash
93
- else
94
- git.diff_for_file(filename)&.patch
95
- end
96
- end
97
-
98
- def changes_from_api
99
- return nil unless ci?
100
- return nil if defined?(@force_changes_from_git)
101
-
102
- @changes_from_api ||= gitlab_helper.api.merge_request_changes(gitlab_helper.mr_json["project_id"], gitlab_helper.mr_json["iid"]).to_h["changes"]
103
- rescue
104
- # Fallback to the Git strategy in any case
105
- @force_changes_from_git = true
106
- nil
107
- end
108
-
109
- # Returns a list of all files that have been added, modified or renamed.
110
- # `modified_files` might contain paths that already have been renamed,
111
- # so we need to remove them from the list.
112
- #
113
- # Considering these changes:
114
- #
115
- # - A new_file.rb
116
- # - D deleted_file.rb
117
- # - M modified_file.rb
118
- # - R renamed_file_before.rb -> renamed_file_after.rb
119
- #
120
- # it will return
121
- # ```
122
- # [ 'new_file.rb', 'modified_file.rb', 'renamed_file_after.rb' ]
123
- # ```
124
- #
125
- # @return [Array<String>]
126
- def all_changed_files
127
- Set.new
128
- .merge(added_files)
129
- .merge(modified_files)
130
- .merge(renamed_files.map { |x| x[:after] })
131
- .subtract(renamed_files.map { |x| x[:before] })
132
- .to_a
133
- .sort
134
- end
135
-
136
- # Returns a string containing changed lines as git diff
137
- #
138
- # Considering changing a line in lib/gitlab/usage_data.rb it will return:
139
- #
140
- # [ "--- a/lib/gitlab/usage_data.rb",
141
- # "+++ b/lib/gitlab/usage_data.rb",
142
- # "+ # Test change",
143
- # "- # Old change" ]
144
- def changed_lines(changed_file)
145
- diff = diff_for_file(changed_file)
146
- return [] unless diff
147
-
148
- diff.split("\n").select { |line| %r{^[+-]}.match?(line) }
149
- end
150
-
151
- def release_automation?
152
- gitlab_helper&.mr_author == RELEASE_TOOLS_BOT
153
- end
154
-
155
- def markdown_list(items)
156
- list = items.map { |item| "* `#{item}`" }.join("\n")
157
-
158
- if items.size > 10
159
- "\n<details>\n\n#{list}\n\n</details>\n"
160
- else
161
- list
162
- end
163
- end
164
-
165
- # @return [Hash<Symbol,Array<String>>]
166
- def changes_by_category(categories)
167
- all_changed_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash|
168
- categories_for_file(file, categories).each { |category| hash[category] << file }
169
- end
170
- end
171
-
172
- # @return [Gitlab::Dangerfiles::Changes]
173
- def changes(categories)
174
- Gitlab::Dangerfiles::Changes.new([]).tap do |changes|
175
- added_files.each do |file|
176
- categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :added, category) }
177
- end
178
-
179
- modified_files.each do |file|
180
- categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :modified, category) }
181
- end
182
-
183
- deleted_files.each do |file|
184
- categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :deleted, category) }
185
- end
186
-
187
- renamed_files.map { |x| x[:before] }.each do |file|
188
- categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :renamed_before, category) }
189
- end
190
-
191
- renamed_files.map { |x| x[:after] }.each do |file|
192
- categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :renamed_after, category) }
193
- end
194
- end
195
- end
196
-
197
- # Determines the categories a file is in, e.g., `[:frontend]`, `[:backend]`, or `%i[frontend engineering_productivity]`
198
- # using filename regex and specific change regex if given.
199
- #
200
- # @return Array<Symbol>
201
- def categories_for_file(file, categories)
202
- _, categories = categories.find do |key, _|
203
- filename_regex, changes_regex = Array(key)
204
-
205
- found = filename_regex.match?(file)
206
- found &&= changed_lines(file).any? { |changed_line| changes_regex.match?(changed_line) } if changes_regex
207
-
208
- found
209
- end
210
-
211
- Array(categories || :unknown)
212
- end
213
-
214
- # Returns the GFM for a category label, making its best guess if it's not
215
- # a category we know about.
216
- #
217
- # @return[String]
218
- def label_for_category(category)
219
- CATEGORY_LABELS.fetch(category, "~#{category}")
220
- end
221
-
222
- def new_teammates(usernames)
223
- usernames.map { |u| Gitlab::Dangerfiles::Teammate.new("username" => u) }
224
- end
225
-
226
- def mr_iid
227
- return "" unless ci?
228
-
229
- gitlab_helper.mr_json["iid"]
230
- end
231
-
232
- def mr_title
233
- return "" unless ci?
234
-
235
- gitlab_helper.mr_json["title"]
236
- end
237
-
238
- def mr_web_url
239
- return "" unless ci?
240
-
241
- gitlab_helper.mr_json["web_url"]
242
- end
243
-
244
- def mr_labels
245
- return [] unless ci?
246
-
247
- gitlab_helper.mr_labels
248
- end
249
-
250
- def mr_target_branch
251
- return "" unless ci?
252
-
253
- gitlab_helper.mr_json["target_branch"]
254
- end
255
-
256
- def draft_mr?
257
- Gitlab::Dangerfiles::TitleLinting.has_draft_flag?(mr_title)
258
- end
259
-
260
- def security_mr?
261
- mr_web_url.include?("/gitlab-org/security/")
262
- end
263
-
264
- def cherry_pick_mr?
265
- Gitlab::Dangerfiles::TitleLinting.has_cherry_pick_flag?(mr_title)
266
- end
267
-
268
- def run_all_rspec_mr?
269
- Gitlab::Dangerfiles::TitleLinting.has_run_all_rspec_flag?(mr_title)
270
- end
271
-
272
- def run_as_if_foss_mr?
273
- Gitlab::Dangerfiles::TitleLinting.has_run_as_if_foss_flag?(mr_title)
274
- end
275
-
276
- def stable_branch?
277
- /\A\d+-\d+-stable-ee/i.match?(mr_target_branch)
278
- end
279
-
280
- def mr_has_labels?(*labels)
281
- labels = labels.flatten.uniq
282
-
283
- (labels & mr_labels) == labels
284
- end
285
-
286
- def labels_list(labels, sep: ", ")
287
- labels.map { |label| %Q{~"#{label}"} }.join(sep)
288
- end
289
-
290
- def prepare_labels_for_mr(labels)
291
- return "" unless labels.any?
292
-
293
- "/label #{labels_list(labels, sep: " ")}"
294
- end
295
-
296
- def changed_files(regex)
297
- all_changed_files.grep(regex)
298
- end
299
-
300
- def has_database_scoped_labels?(current_mr_labels)
301
- current_mr_labels.any? { |label| label.start_with?("database::") }
302
- end
303
-
304
- def has_ci_changes?
305
- changed_files(%r{\A(\.gitlab-ci\.yml|\.gitlab/ci/)}).any?
306
- end
307
-
308
- def group_label(labels)
309
- labels.find { |label| label.start_with?("group::") }
310
- end
311
- end
312
- end