gitlab-dangerfiles 0.9.0 → 2.1.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.
@@ -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__)
@@ -45,17 +45,63 @@ end
45
45
 
46
46
  RSpec.shared_context "with dangerfile" do
47
47
  let(:dangerfile) { DangerSpecHelper.testing_dangerfile }
48
- let(:added_files) { %w[added1] }
49
- let(:modified_files) { %w[modified1] }
50
- let(:deleted_files) { %w[deleted1] }
51
- let(:renamed_before_file) { "renamed_before" }
52
- let(:renamed_after_file) { "renamed_after" }
48
+ let(:added_files) { %w[added-from-git] }
49
+ let(:modified_files) { %w[modified-from-git] }
50
+ let(:deleted_files) { %w[deleted-from-git] }
51
+ let(:renamed_before_file) { "renamed_before-from-git" }
52
+ let(:renamed_after_file) { "renamed_after-from-git" }
53
53
  let(:renamed_files) { [{ before: renamed_before_file, after: renamed_after_file }] }
54
54
  let(:change_class) { Gitlab::Dangerfiles::Change }
55
55
  let(:changes_class) { Gitlab::Dangerfiles::Changes }
56
56
  let(:changes) { changes_class.new([]) }
57
57
  let(:mr_title) { "Fake Title" }
58
58
  let(:mr_labels) { [] }
59
+ let(:mr_changes_from_api) do
60
+ {
61
+ "changes" => [
62
+ {
63
+ "old_path" => "added-from-api",
64
+ "new_path" => "added-from-api",
65
+ "a_mode" => "100644",
66
+ "b_mode" => "100644",
67
+ "new_file" => true,
68
+ "renamed_file" => false,
69
+ "deleted_file" => false,
70
+ "diff" => "@@ -49,6 +49,14 @@\n- vendor/ruby/\n policy: pull\n \n+.danger-review-cache:\n",
71
+ },
72
+ {
73
+ "old_path" => "modified-from-api",
74
+ "new_path" => "modified-from-api",
75
+ "a_mode" => "100644",
76
+ "b_mode" => "100644",
77
+ "new_file" => false,
78
+ "renamed_file" => false,
79
+ "deleted_file" => false,
80
+ "diff" => "@@ -49,6 +49,14 @@\n- vendor/ruby/\n policy: pull\n \n+.danger-review-cache:\n",
81
+ },
82
+ {
83
+ "old_path" => "renamed_before-from-api",
84
+ "new_path" => "renamed_after-from-api",
85
+ "a_mode" => "100644",
86
+ "b_mode" => "100644",
87
+ "new_file" => false,
88
+ "renamed_file" => true,
89
+ "deleted_file" => false,
90
+ "diff" => "@@ -49,6 +49,14 @@\n- vendor/ruby/\n policy: pull\n \n+.danger-review-cache:\n",
91
+ },
92
+ {
93
+ "old_path" => "deleted-from-api",
94
+ "new_path" => "deleted-from-api",
95
+ "a_mode" => "100644",
96
+ "b_mode" => "100644",
97
+ "new_file" => false,
98
+ "renamed_file" => false,
99
+ "deleted_file" => true,
100
+ "diff" => "@@ -49,6 +49,14 @@\n- vendor/ruby/\n policy: pull\n \n+.danger-review-cache:\n",
101
+ },
102
+ ],
103
+ }
104
+ end
59
105
 
60
106
  let(:fake_git) { double("fake-git", added_files: added_files, modified_files: modified_files, deleted_files: deleted_files, renamed_files: renamed_files) }
61
107
  let(:fake_helper) { double("fake-helper", changes: changes, mr_iid: 1234, mr_title: mr_title, mr_labels: mr_labels) }
@@ -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 = "0.9.0"
3
+ VERSION = "2.1.0"
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,17 +1,17 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: gitlab-dangerfiles
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 2.1.0
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-17 00:00:00.000000000 Z
11
+ date: 2021-06-11 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
- name: danger
14
+ name: danger-gitlab
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
17
  - - ">="
@@ -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,257 +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
- # Returns a list of all files that have been added, modified or renamed.
55
- # `git.modified_files` might contain paths that already have been renamed,
56
- # so we need to remove them from the list.
57
- #
58
- # Considering these changes:
59
- #
60
- # - A new_file.rb
61
- # - D deleted_file.rb
62
- # - M modified_file.rb
63
- # - R renamed_file_before.rb -> renamed_file_after.rb
64
- #
65
- # it will return
66
- # ```
67
- # [ 'new_file.rb', 'modified_file.rb', 'renamed_file_after.rb' ]
68
- # ```
69
- #
70
- # @return [Array<String>]
71
- def all_changed_files
72
- Set.new
73
- .merge(git.added_files.to_a)
74
- .merge(git.modified_files.to_a)
75
- .merge(git.renamed_files.map { |x| x[:after] })
76
- .subtract(git.renamed_files.map { |x| x[:before] })
77
- .to_a
78
- .sort
79
- end
80
-
81
- # Returns a string containing changed lines as git diff
82
- #
83
- # Considering changing a line in lib/gitlab/usage_data.rb it will return:
84
- #
85
- # [ "--- a/lib/gitlab/usage_data.rb",
86
- # "+++ b/lib/gitlab/usage_data.rb",
87
- # "+ # Test change",
88
- # "- # Old change" ]
89
- def changed_lines(changed_file)
90
- diff = git.diff_for_file(changed_file)
91
- return [] unless diff
92
-
93
- diff.patch.split("\n").select { |line| %r{^[+-]}.match?(line) }
94
- end
95
-
96
- def release_automation?
97
- gitlab_helper&.mr_author == RELEASE_TOOLS_BOT
98
- end
99
-
100
- def markdown_list(items)
101
- list = items.map { |item| "* `#{item}`" }.join("\n")
102
-
103
- if items.size > 10
104
- "\n<details>\n\n#{list}\n\n</details>\n"
105
- else
106
- list
107
- end
108
- end
109
-
110
- # @return [Hash<Symbol,Array<String>>]
111
- def changes_by_category(categories)
112
- all_changed_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash|
113
- categories_for_file(file, categories).each { |category| hash[category] << file }
114
- end
115
- end
116
-
117
- # @return [Gitlab::Dangerfiles::Changes]
118
- def changes(categories)
119
- Gitlab::Dangerfiles::Changes.new([]).tap do |changes|
120
- git.added_files.each do |file|
121
- categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :added, category) }
122
- end
123
-
124
- git.modified_files.each do |file|
125
- categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :modified, category) }
126
- end
127
-
128
- git.deleted_files.each do |file|
129
- categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :deleted, category) }
130
- end
131
-
132
- git.renamed_files.map { |x| x[:before] }.each do |file|
133
- categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :renamed_before, category) }
134
- end
135
-
136
- git.renamed_files.map { |x| x[:after] }.each do |file|
137
- categories_for_file(file, categories).each { |category| changes << Gitlab::Dangerfiles::Change.new(file, :renamed_after, category) }
138
- end
139
- end
140
- end
141
-
142
- # Determines the categories a file is in, e.g., `[:frontend]`, `[:backend]`, or `%i[frontend engineering_productivity]`
143
- # using filename regex and specific change regex if given.
144
- #
145
- # @return Array<Symbol>
146
- def categories_for_file(file, categories)
147
- _, categories = categories.find do |key, _|
148
- filename_regex, changes_regex = Array(key)
149
-
150
- found = filename_regex.match?(file)
151
- found &&= changed_lines(file).any? { |changed_line| changes_regex.match?(changed_line) } if changes_regex
152
-
153
- found
154
- end
155
-
156
- Array(categories || :unknown)
157
- end
158
-
159
- # Returns the GFM for a category label, making its best guess if it's not
160
- # a category we know about.
161
- #
162
- # @return[String]
163
- def label_for_category(category)
164
- CATEGORY_LABELS.fetch(category, "~#{category}")
165
- end
166
-
167
- def new_teammates(usernames)
168
- usernames.map { |u| Gitlab::Dangerfiles::Teammate.new("username" => u) }
169
- end
170
-
171
- def mr_iid
172
- return "" unless gitlab_helper
173
-
174
- gitlab_helper.mr_json["iid"]
175
- end
176
-
177
- def mr_title
178
- return "" unless gitlab_helper
179
-
180
- gitlab_helper.mr_json["title"]
181
- end
182
-
183
- def mr_web_url
184
- return "" unless gitlab_helper
185
-
186
- gitlab_helper.mr_json["web_url"]
187
- end
188
-
189
- def mr_labels
190
- return [] unless gitlab_helper
191
-
192
- gitlab_helper.mr_labels
193
- end
194
-
195
- def mr_target_branch
196
- return "" unless gitlab_helper
197
-
198
- gitlab_helper.mr_json["target_branch"]
199
- end
200
-
201
- def draft_mr?
202
- Gitlab::Dangerfiles::TitleLinting.has_draft_flag?(mr_title)
203
- end
204
-
205
- def security_mr?
206
- mr_web_url.include?("/gitlab-org/security/")
207
- end
208
-
209
- def cherry_pick_mr?
210
- Gitlab::Dangerfiles::TitleLinting.has_cherry_pick_flag?(mr_title)
211
- end
212
-
213
- def run_all_rspec_mr?
214
- Gitlab::Dangerfiles::TitleLinting.has_run_all_rspec_flag?(mr_title)
215
- end
216
-
217
- def run_as_if_foss_mr?
218
- Gitlab::Dangerfiles::TitleLinting.has_run_as_if_foss_flag?(mr_title)
219
- end
220
-
221
- def stable_branch?
222
- /\A\d+-\d+-stable-ee/i.match?(mr_target_branch)
223
- end
224
-
225
- def mr_has_labels?(*labels)
226
- labels = labels.flatten.uniq
227
-
228
- (labels & mr_labels) == labels
229
- end
230
-
231
- def labels_list(labels, sep: ", ")
232
- labels.map { |label| %Q{~"#{label}"} }.join(sep)
233
- end
234
-
235
- def prepare_labels_for_mr(labels)
236
- return "" unless labels.any?
237
-
238
- "/label #{labels_list(labels, sep: " ")}"
239
- end
240
-
241
- def changed_files(regex)
242
- all_changed_files.grep(regex)
243
- end
244
-
245
- def has_database_scoped_labels?(current_mr_labels)
246
- current_mr_labels.any? { |label| label.start_with?("database::") }
247
- end
248
-
249
- def has_ci_changes?
250
- changed_files(%r{\A(\.gitlab-ci\.yml|\.gitlab/ci/)}).any?
251
- end
252
-
253
- def group_label(labels)
254
- labels.find { |label| label.start_with?("group::") }
255
- end
256
- end
257
- end