gitlab-dangerfiles 1.0.0 → 2.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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__)
@@ -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.0.0"
3
+ VERSION = "2.1.1"
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.0.0
4
+ version: 2.1.1
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,311 +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
- }.freeze
23
-
24
- HTTPError = Class.new(StandardError)
25
-
26
- def gitlab_helper
27
- # Unfortunately the following does not work:
28
- # - respond_to?(:gitlab)
29
- # - respond_to?(:gitlab, true)
30
- gitlab
31
- rescue NoMethodError
32
- nil
33
- end
34
-
35
- def html_link(str)
36
- ci? ? gitlab_helper.html_link(str) : str
37
- end
38
-
39
- def ci?
40
- !gitlab_helper.nil?
41
- end
42
-
43
- # @param [String] url
44
- def http_get_json(url)
45
- rsp = Net::HTTP.get_response(URI.parse(url))
46
-
47
- unless rsp.is_a?(Net::HTTPOK)
48
- raise HTTPError, "Failed to read #{url}: #{rsp.code} #{rsp.message}"
49
- end
50
-
51
- JSON.parse(rsp.body)
52
- end
53
-
54
- def added_files
55
- @added_files ||= if changes_from_api
56
- changes_from_api.select { |file| file["new_file"] }.map { |file| file["new_path"] }
57
- else
58
- git.added_files.to_a
59
- end
60
- end
61
-
62
- def modified_files
63
- @modified_files ||= if changes_from_api
64
- changes_from_api.select { |file| !file["new_file"] && !file["deleted_file"] && !file["renamed_file"] }.map { |file| file["new_path"] }
65
- else
66
- git.modified_files.to_a
67
- end
68
- end
69
-
70
- def renamed_files
71
- @renamed_files ||= if changes_from_api
72
- changes_from_api.select { |file| file["renamed_file"] }.each_with_object([]) do |file, memo|
73
- memo << { before: file["old_path"], after: file["new_path"] }
74
- end
75
- else
76
- git.renamed_files.to_a
77
- end
78
- end
79
-
80
- def deleted_files
81
- @deleted_files ||= if changes_from_api
82
- changes_from_api.select { |file| file["deleted_file"] }.map { |file| file["new_path"] }
83
- else
84
- git.deleted_files.to_a
85
- end
86
- end
87
-
88
- def diff_for_file(filename)
89
- if changes_from_api
90
- changes_hash = changes_from_api.find { |file| file["new_path"] == filename }
91
- changes_hash["diff"] if changes_hash
92
- else
93
- git.diff_for_file(filename)&.patch
94
- end
95
- end
96
-
97
- def changes_from_api
98
- return nil unless ci?
99
- return nil if defined?(@force_changes_from_git)
100
-
101
- @changes_from_api ||= gitlab_helper.api.merge_request_changes(gitlab_helper.mr_json["project_id"], gitlab_helper.mr_json["iid"]).to_h["changes"]
102
- rescue
103
- # Fallback to the Git strategy in any case
104
- @force_changes_from_git = true
105
- nil
106
- end
107
-
108
- # Returns a list of all files that have been added, modified or renamed.
109
- # `modified_files` might contain paths that already have been renamed,
110
- # so we need to remove them from the list.
111
- #
112
- # Considering these changes:
113
- #
114
- # - A new_file.rb
115
- # - D deleted_file.rb
116
- # - M modified_file.rb
117
- # - R renamed_file_before.rb -> renamed_file_after.rb
118
- #
119
- # it will return
120
- # ```
121
- # [ 'new_file.rb', 'modified_file.rb', 'renamed_file_after.rb' ]
122
- # ```
123
- #
124
- # @return [Array<String>]
125
- def all_changed_files
126
- Set.new
127
- .merge(added_files)
128
- .merge(modified_files)
129
- .merge(renamed_files.map { |x| x[:after] })
130
- .subtract(renamed_files.map { |x| x[:before] })
131
- .to_a
132
- .sort
133
- end
134
-
135
- # Returns a string containing changed lines as git diff
136
- #
137
- # Considering changing a line in lib/gitlab/usage_data.rb it will return:
138
- #
139
- # [ "--- a/lib/gitlab/usage_data.rb",
140
- # "+++ b/lib/gitlab/usage_data.rb",
141
- # "+ # Test change",
142
- # "- # Old change" ]
143
- def changed_lines(changed_file)
144
- diff = diff_for_file(changed_file)
145
- return [] unless diff
146
-
147
- diff.split("\n").select { |line| %r{^[+-]}.match?(line) }
148
- end
149
-
150
- def release_automation?
151
- gitlab_helper&.mr_author == RELEASE_TOOLS_BOT
152
- end
153
-
154
- def markdown_list(items)
155
- list = items.map { |item| "* `#{item}`" }.join("\n")
156
-
157
- if items.size > 10
158
- "\n<details>\n\n#{list}\n\n</details>\n"
159
- else
160
- list
161
- end
162
- end
163
-
164
- # @return [Hash<Symbol,Array<String>>]
165
- def changes_by_category(categories)
166
- all_changed_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash|
167
- categories_for_file(file, categories).each { |category| hash[category] << file }
168
- end
169
- end
170
-
171
- # @return [Gitlab::Dangerfiles::Changes]
172
- def changes(categories)
173
- Gitlab::Dangerfiles::Changes.new([]).tap do |changes|
174
- added_files.each do |file|
175
- categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :added, category) }
176
- end
177
-
178
- modified_files.each do |file|
179
- categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :modified, category) }
180
- end
181
-
182
- deleted_files.each do |file|
183
- categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :deleted, category) }
184
- end
185
-
186
- renamed_files.map { |x| x[:before] }.each do |file|
187
- categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :renamed_before, category) }
188
- end
189
-
190
- renamed_files.map { |x| x[:after] }.each do |file|
191
- categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :renamed_after, category) }
192
- end
193
- end
194
- end
195
-
196
- # Determines the categories a file is in, e.g., `[:frontend]`, `[:backend]`, or `%i[frontend engineering_productivity]`
197
- # using filename regex and specific change regex if given.
198
- #
199
- # @return Array<Symbol>
200
- def categories_for_file(file, categories)
201
- _, categories = categories.find do |key, _|
202
- filename_regex, changes_regex = Array(key)
203
-
204
- found = filename_regex.match?(file)
205
- found &&= changed_lines(file).any? { |changed_line| changes_regex.match?(changed_line) } if changes_regex
206
-
207
- found
208
- end
209
-
210
- Array(categories || :unknown)
211
- end
212
-
213
- # Returns the GFM for a category label, making its best guess if it's not
214
- # a category we know about.
215
- #
216
- # @return[String]
217
- def label_for_category(category)
218
- CATEGORY_LABELS.fetch(category, "~#{category}")
219
- end
220
-
221
- def new_teammates(usernames)
222
- usernames.map { |u| Gitlab::Dangerfiles::Teammate.new("username" => u) }
223
- end
224
-
225
- def mr_iid
226
- return "" unless ci?
227
-
228
- gitlab_helper.mr_json["iid"]
229
- end
230
-
231
- def mr_title
232
- return "" unless ci?
233
-
234
- gitlab_helper.mr_json["title"]
235
- end
236
-
237
- def mr_web_url
238
- return "" unless ci?
239
-
240
- gitlab_helper.mr_json["web_url"]
241
- end
242
-
243
- def mr_labels
244
- return [] unless ci?
245
-
246
- gitlab_helper.mr_labels
247
- end
248
-
249
- def mr_target_branch
250
- return "" unless ci?
251
-
252
- gitlab_helper.mr_json["target_branch"]
253
- end
254
-
255
- def draft_mr?
256
- Gitlab::Dangerfiles::TitleLinting.has_draft_flag?(mr_title)
257
- end
258
-
259
- def security_mr?
260
- mr_web_url.include?("/gitlab-org/security/")
261
- end
262
-
263
- def cherry_pick_mr?
264
- Gitlab::Dangerfiles::TitleLinting.has_cherry_pick_flag?(mr_title)
265
- end
266
-
267
- def run_all_rspec_mr?
268
- Gitlab::Dangerfiles::TitleLinting.has_run_all_rspec_flag?(mr_title)
269
- end
270
-
271
- def run_as_if_foss_mr?
272
- Gitlab::Dangerfiles::TitleLinting.has_run_as_if_foss_flag?(mr_title)
273
- end
274
-
275
- def stable_branch?
276
- /\A\d+-\d+-stable-ee/i.match?(mr_target_branch)
277
- end
278
-
279
- def mr_has_labels?(*labels)
280
- labels = labels.flatten.uniq
281
-
282
- (labels & mr_labels) == labels
283
- end
284
-
285
- def labels_list(labels, sep: ", ")
286
- labels.map { |label| %Q{~"#{label}"} }.join(sep)
287
- end
288
-
289
- def prepare_labels_for_mr(labels)
290
- return "" unless labels.any?
291
-
292
- "/label #{labels_list(labels, sep: " ")}"
293
- end
294
-
295
- def changed_files(regex)
296
- all_changed_files.grep(regex)
297
- end
298
-
299
- def has_database_scoped_labels?(current_mr_labels)
300
- current_mr_labels.any? { |label| label.start_with?("database::") }
301
- end
302
-
303
- def has_ci_changes?
304
- changed_files(%r{\A(\.gitlab-ci\.yml|\.gitlab/ci/)}).any?
305
- end
306
-
307
- def group_label(labels)
308
- labels.find { |label| label.start_with?("group::") }
309
- end
310
- end
311
- end