gitlab-dangerfiles 0.9.0 → 2.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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