gitlab-dangerfiles 1.1.1 → 2.1.3

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: f5e1069be5416d6980879af3330451834b57881c46101119bfc5917e2057edd7
4
- data.tar.gz: e911c514f8d32d65e4aecef6ad111c80c808dd273b12775d57438532ba8a05b3
3
+ metadata.gz: d49d453f0fec847768490aa8c5f7d5790620f588f2c1afb7c2d9d707ae2155b9
4
+ data.tar.gz: 4af3ce84ee6a755477b0f9dc2f97ba43349f3fcbbdf6494a754ccb940345b6e7
5
5
  SHA512:
6
- metadata.gz: b3814724620240a6485ffce4191fb8a2461c267dda5dad2466b6dc5173fbe848b66cae92aaea69ef82a49dd4726db3a7e77c106fdbd73ea58738eaf4613c4cba
7
- data.tar.gz: 0fcabbe03f856d29951ae2f35e9f6f9784fa22af8d8d6bfa13a41452b4a1c35a454135751443882e6a2762325ef05dd6b8bd1b3dde58c5c7661db65ffef4c5d7
6
+ metadata.gz: e1b2d019c5d1f83c1f01f1dfb4001c02fe8634feade3016cd7b7b479abb27aa4d5c409b45b37928fd379822d9a138c9efb3504fe0c8f3c5e23f2f211771d82c3
7
+ data.tar.gz: acd1dcd443b2ee129f0db7a13b4946bb373e164ef1e52c06b1b1a91e9256124304375d09c4dcaa26f51b667886c1345e3c55f36653f4a5be146dc7d2c900e448
data/.gitignore CHANGED
@@ -9,6 +9,7 @@
9
9
  /pkg/
10
10
  /spec/reports/
11
11
  /tmp/
12
+ /doc/
12
13
 
13
14
  # rspec failure tracking
14
15
  .rspec_status
data/.gitlab-ci.yml CHANGED
@@ -11,7 +11,7 @@ workflow:
11
11
  # For tags, create a pipeline.
12
12
  - if: '$CI_COMMIT_TAG'
13
13
 
14
- default:
14
+ .default:
15
15
  image: ruby:2.7
16
16
  tags:
17
17
  - gitlab-org
@@ -29,15 +29,50 @@ default:
29
29
  policy: pull
30
30
 
31
31
  test:rspec:
32
+ extends: .default
32
33
  stage: test
33
34
  script:
34
35
  - bundle exec rspec
35
36
 
36
37
  test:rufo:
38
+ extends: .default
37
39
  stage: test
38
40
  script:
39
41
  - bundle exec rufo --check .
40
42
 
41
43
  include:
44
+ - template: Security/Dependency-Scanning.gitlab-ci.yml
45
+ - template: Security/License-Scanning.gitlab-ci.yml
46
+ - template: Security/SAST.gitlab-ci.yml
47
+ - template: Security/Secret-Detection.gitlab-ci.yml
42
48
  - project: 'gitlab-org/quality/pipeline-common'
43
49
  file: '/ci/gem-release.yml'
50
+
51
+ # run security jobs on MRs
52
+ # see: https://gitlab.com/gitlab-org/gitlab/-/issues/218444#note_478761991
53
+
54
+ brakeman-sast:
55
+ rules:
56
+ - if: '$CI_MERGE_REQUEST_IID'
57
+ - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
58
+
59
+ gemnasium-dependency_scanning:
60
+ rules:
61
+ - if: '$CI_MERGE_REQUEST_IID'
62
+ - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
63
+
64
+ bundler-audit-dependency_scanning:
65
+ rules:
66
+ - if: '$CI_MERGE_REQUEST_IID'
67
+ - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
68
+
69
+ license_scanning:
70
+ rules:
71
+ - if: '$CI_MERGE_REQUEST_IID'
72
+ - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
73
+
74
+ secret_detection:
75
+ rules:
76
+ - if: '$CI_MERGE_REQUEST_IID'
77
+ - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
78
+
data/.yardopts ADDED
@@ -0,0 +1,5 @@
1
+ --protected
2
+ --no-private
3
+ -
4
+ CODE_OF_CONDUCT.md
5
+ LICENSE.txt
data/Gemfile CHANGED
@@ -5,3 +5,4 @@ gemspec
5
5
 
6
6
  gem "rake", "~> 12.0"
7
7
  gem "guard-rspec"
8
+ gem "yard"
data/LICENSE.txt CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2020 Rémy Coutable
3
+ Copyright (c) 2020-2021 GitLab
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
data/README.md CHANGED
@@ -1,8 +1,6 @@
1
1
  # Gitlab::Dangerfiles
2
2
 
3
- Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/gitlab/dangerfiles`. To experiment with that code, run `bin/console` for an interactive prompt.
4
-
5
- TODO: Delete this and the text above, and describe your gem
3
+ The goal of this gem is to centralize Danger plugins and rules that to be used by multiple GitLab projects.
6
4
 
7
5
  ## Installation
8
6
 
@@ -14,15 +12,68 @@ gem 'gitlab-dangerfiles'
14
12
 
15
13
  And then execute:
16
14
 
17
- $ bundle install
15
+ ```sh
16
+ $ bundle install
17
+ ```
18
18
 
19
19
  Or install it yourself as:
20
20
 
21
- $ gem install gitlab-dangerfiles
21
+ ```sh
22
+ $ gem install gitlab-dangerfiles
23
+ ```
22
24
 
23
25
  ## Usage
24
26
 
25
- TODO: Write usage instructions here
27
+ ### Importing plugins and rules
28
+
29
+ In your project's `Dangerfile`, add the following two line to import the plugins and rules from this gem:
30
+
31
+ ```ruby
32
+ # Get an instance of Gitlab::Dangerfiles
33
+ gitlab_dangerfiles = Gitlab::Dangerfiles::Engine.new(self)
34
+
35
+ # Import all plugins from the gem
36
+ gitlab_dangerfiles.import_plugins
37
+
38
+ # Import all rules from the gem
39
+ gitlab_dangerfiles.import_dangerfiles
40
+
41
+ # Or import a subset of rules from the gem
42
+ gitlab_dangerfiles.import_dangerfiles(rules: [:changes_size])
43
+ ```
44
+
45
+ ### Plugins
46
+
47
+ Danger plugins are located under `lib/danger/plugins`.
48
+
49
+ - `Danger::Helper` available in `Dangerfile`s as `helper`
50
+ - `Danger::Roulette` available in `Dangerfile`s as `roulette`
51
+
52
+ For the full documentation about the plugins, please see https://www.rubydoc.info/gems/gitlab-dangerfiles.
53
+
54
+ ### Rules
55
+
56
+
57
+ Danger rules are located under `lib/danger/rules`.
58
+
59
+ #### `changes_size`
60
+
61
+ ##### Available configurations
62
+
63
+ - `code_size_thresholds`: A hash of the form `{ high: 42, medium: 12 }` where
64
+ `:high` is the lines changed threshold which triggers an error, and
65
+ `:medium` is the lines changed threshold which triggers a warning.
66
+
67
+ #### `commit_messages`
68
+
69
+ ##### Available configurations
70
+
71
+ - `max_commits_count`: The maximum number of allowed non-squashed/non-fixup commits for a given MR.
72
+ A warning is triggered if the MR has more commits.
73
+
74
+ ## Documentation
75
+
76
+ Latest documentation can be found at <https://www.rubydoc.info/gems/gitlab-dangerfiles>.
26
77
 
27
78
  ## Development
28
79
 
@@ -3,8 +3,8 @@ require_relative "lib/gitlab/dangerfiles/version"
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = "gitlab-dangerfiles"
5
5
  spec.version = Gitlab::Dangerfiles::VERSION
6
- spec.authors = ["Rémy Coutable"]
7
- spec.email = ["remy@rymai.me"]
6
+ spec.authors = ["GitLab"]
7
+ spec.email = ["gitlab_rubygems@gitlab.com"]
8
8
 
9
9
  spec.summary = %q{This gem provides common Dangerfile and plugins for GitLab projects.}
10
10
  spec.description = %q{This gem provides common Dangerfile and plugins for GitLab projects.}
@@ -16,7 +16,7 @@ Gem::Specification.new do |spec|
16
16
 
17
17
  spec.metadata["homepage_uri"] = spec.homepage
18
18
  spec.metadata["source_code_uri"] = "https://gitlab.com/gitlab-org/gitlab-dangerfiles"
19
- spec.metadata["changelog_uri"] = "https://gitlab.com/gitlab-org/gitlab-dangerfiles"
19
+ spec.metadata["changelog_uri"] = "https://gitlab.com/gitlab-org/gitlab-dangerfiles/-/releases"
20
20
 
21
21
  # Specify which files should be added to the gem when it is released.
22
22
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
@@ -4,6 +4,7 @@ require "net/http"
4
4
  require "json"
5
5
 
6
6
  require_relative "../../gitlab/dangerfiles/changes"
7
+ require_relative "../../gitlab/dangerfiles/config"
7
8
  require_relative "../../gitlab/dangerfiles/teammate"
8
9
  require_relative "../../gitlab/dangerfiles/title_linting"
9
10
 
@@ -11,7 +12,6 @@ module Danger
11
12
  # Common helper functions for our danger scripts.
12
13
  class Helper < Danger::Plugin
13
14
  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
15
  CATEGORY_LABELS = {
16
16
  docs: "~documentation", # Docs are reviewed along DevOps stages, so don't need roulette for now.
17
17
  none: "",
@@ -22,36 +22,47 @@ module Danger
22
22
  product_intelligence: '~"product intelligence"',
23
23
  }.freeze
24
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
25
+ # Allows to set specific rule's configuration by passing a block.
26
+ #
27
+ # @yield [c] Yield a Gitlab::Dangerfiles::Config object
28
+ #
29
+ # @yieldparam [Gitlab::Dangerfiles::Config] The Gitlab::Dangerfiles::Config object
30
+ # @yieldreturn [Gitlab::Dangerfiles::Config] The Gitlab::Dangerfiles::Config object
31
+ #
32
+ # @example
33
+ # helper.config do |config|
34
+ # config.code_size_thresholds = { high: 42, medium: 12 }
35
+ # end
36
+ #
37
+ # @return [Gitlab::Dangerfiles::Config]
38
+ def config
39
+ (@config ||= Gitlab::Dangerfiles::Config.new).tap do |c|
40
+ yield c if block_given?
41
+ end
34
42
  end
35
43
 
36
- def html_link(str)
37
- ci? ? gitlab_helper.html_link(str) : str
44
+ # @example
45
+ # <a href='https://gitlab.com/artsy/eigen/blob/561827e46167077b5e53515b4b7349b8ae04610b/file.txt'>file.txt</a>
46
+ #
47
+ # @param [String, Array<String>] paths
48
+ # A list of strings to convert to gitlab anchors
49
+ # @param [Boolean] full_path
50
+ # Shows the full path as the link's text, defaults to +true+.
51
+ #
52
+ # @see https://danger.systems/reference.html Danger reference where #html_link is described
53
+ # @see https://github.com/danger/danger/blob/eca19719d3e585fe1cc46bc5377f9aa955ebf609/lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb#L216 Danger reference where #html_link is implemented
54
+ #
55
+ # @return [String] a list of HTML anchors for a file, or multiple files
56
+ def html_link(paths, full_path: true)
57
+ ci? ? gitlab_helper.html_link(paths, full_path: full_path) : paths
38
58
  end
39
59
 
60
+ # @return [Boolean] whether we're in the CI context or not.
40
61
  def ci?
41
62
  !gitlab_helper.nil?
42
63
  end
43
64
 
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
-
65
+ # @return [Array<String>] a list of filenames added in this MR.
55
66
  def added_files
56
67
  @added_files ||= if changes_from_api
57
68
  changes_from_api.select { |file| file["new_file"] }.map { |file| file["new_path"] }
@@ -60,6 +71,7 @@ module Danger
60
71
  end
61
72
  end
62
73
 
74
+ # @return [Array<String>] a list of filenames modifier in this MR.
63
75
  def modified_files
64
76
  @modified_files ||= if changes_from_api
65
77
  changes_from_api.select { |file| !file["new_file"] && !file["deleted_file"] && !file["renamed_file"] }.map { |file| file["new_path"] }
@@ -68,6 +80,7 @@ module Danger
68
80
  end
69
81
  end
70
82
 
83
+ # @return [Array<String>] a list of filenames renamed in this MR.
71
84
  def renamed_files
72
85
  @renamed_files ||= if changes_from_api
73
86
  changes_from_api.select { |file| file["renamed_file"] }.each_with_object([]) do |file, memo|
@@ -78,6 +91,7 @@ module Danger
78
91
  end
79
92
  end
80
93
 
94
+ # @return [Array<String>] a list of filenames deleted in this MR.
81
95
  def deleted_files
82
96
  @deleted_files ||= if changes_from_api
83
97
  changes_from_api.select { |file| file["deleted_file"] }.map { |file| file["new_path"] }
@@ -86,43 +100,20 @@ module Danger
86
100
  end
87
101
  end
88
102
 
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.mr_changes.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:
103
+ # @example
104
+ # # Considering these changes:
105
+ # # - A new_file.rb
106
+ # # - D deleted_file.rb
107
+ # # - M modified_file.rb
108
+ # # - R renamed_file_before.rb -> renamed_file_after.rb
109
+ # # it will return:
114
110
  #
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
111
+ # #=> ['new_file.rb', 'modified_file.rb', 'renamed_file_after.rb']
119
112
  #
120
- # it will return
121
- # ```
122
- # [ 'new_file.rb', 'modified_file.rb', 'renamed_file_after.rb' ]
123
- # ```
124
113
  #
125
- # @return [Array<String>]
114
+ # @return [Array<String>] a list of all files that have been added, modified or renamed.
115
+ # +modified_files+ might contain paths that already have been renamed,
116
+ # so we need to remove them from the list.
126
117
  def all_changed_files
127
118
  Set.new
128
119
  .merge(added_files)
@@ -133,16 +124,19 @@ module Danger
133
124
  .sort
134
125
  end
135
126
 
136
- # Returns a string containing changed lines as git diff
127
+ # @param filename [String] A file name for which we want the diff.
128
+ #
129
+ # @example
130
+ # # Considering changing a line in lib/gitlab/usage_data.rb, it will return:
137
131
  #
138
- # Considering changing a line in lib/gitlab/usage_data.rb it will return:
132
+ # ["--- a/lib/gitlab/usage_data.rb",
133
+ # "+++ b/lib/gitlab/usage_data.rb",
134
+ # "+ # Test change",
135
+ # "- # Old change"]
139
136
  #
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)
137
+ # @return [Array<String>] an array of changed lines in Git diff format.
138
+ def changed_lines(filename)
139
+ diff = diff_for_file(filename)
146
140
  return [] unless diff
147
141
 
148
142
  diff.split("\n").select { |line| %r{^[+-]}.match?(line) }
@@ -152,6 +146,14 @@ module Danger
152
146
  gitlab_helper&.mr_author == RELEASE_TOOLS_BOT
153
147
  end
154
148
 
149
+ # @param items [Array<String>] An array of items to transform into a bullet list.
150
+ #
151
+ # @example
152
+ # markdown_list(%w[foo bar])
153
+ # # => * foo
154
+ # * bar
155
+ #
156
+ # @return [String] a bullet list for the given +items+. If there are more than 10 items, wrap the list in a +<details></details>+ block.
155
157
  def markdown_list(items)
156
158
  list = items.map { |item| "* `#{item}`" }.join("\n")
157
159
 
@@ -162,14 +164,24 @@ module Danger
162
164
  end
163
165
  end
164
166
 
165
- # @return [Hash<Symbol,Array<String>>]
167
+ # @param categories [{Regexp => Array<Symbol>}, {Array<Regexp> => Array<Symbol>}] A hash of the form +{ filename_regex => categories, [filename_regex, changes_regex] => categories }+.
168
+ # +filename_regex+ is the regex pattern to match file names. +changes_regex+ is the regex pattern to
169
+ # match changed lines in files that match +filename_regex+
170
+ #
171
+ # @return [{Symbol => Array<String>}] a hash of the type +{ category1: ["file1", "file2"], category2: ["file3", "file4"] }+
172
+ # using filename regex (+filename_regex+) and specific change regex (+changes_regex+) from the given +categories+ hash.
166
173
  def changes_by_category(categories)
167
174
  all_changed_files.each_with_object(Hash.new { |h, k| h[k] = [] }) do |file, hash|
168
175
  categories_for_file(file, categories).each { |category| hash[category] << file }
169
176
  end
170
177
  end
171
178
 
172
- # @return [Gitlab::Dangerfiles::Changes]
179
+ # @param categories [{Regexp => Array<Symbol>}, {Array<Regexp> => Array<Symbol>}] A hash of the form +{ filename_regex => categories, [filename_regex, changes_regex] => categories }+.
180
+ # +filename_regex+ is the regex pattern to match file names. +changes_regex+ is the regex pattern to
181
+ # match changed lines in files that match +filename_regex+
182
+ #
183
+ # @return [Gitlab::Dangerfiles::Changes] a +Gitlab::Dangerfiles::Changes+ object that represents the changes of an MR
184
+ # using filename regex (+filename_regex+) and specific change regex (+changes_regex+) from the given +categories+ hash.
173
185
  def changes(categories)
174
186
  Gitlab::Dangerfiles::Changes.new([]).tap do |changes|
175
187
  added_files.each do |file|
@@ -194,16 +206,19 @@ module Danger
194
206
  end
195
207
  end
196
208
 
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.
209
+ # @param filename [String] A file name.
210
+ # @param categories [{Regexp => Array<Symbol>}, {Array<Regexp> => Array<Symbol>}] A hash of the form +{ filename_regex => categories, [filename_regex, changes_regex] => categories }+.
211
+ # +filename_regex+ is the regex pattern to match file names. +changes_regex+ is the regex pattern to
212
+ # match changed lines in files that match +filename_regex+
199
213
  #
200
- # @return Array<Symbol>
201
- def categories_for_file(file, categories)
214
+ # @return [Array<Symbol>] the categories a file is in, e.g., +[:frontend]+, +[:backend]+, or +%i[frontend engineering_productivity]+
215
+ # using filename regex (+filename_regex+) and specific change regex (+changes_regex+) from the given +categories+ hash.
216
+ def categories_for_file(filename, categories)
202
217
  _, categories = categories.find do |key, _|
203
218
  filename_regex, changes_regex = Array(key)
204
219
 
205
- found = filename_regex.match?(file)
206
- found &&= changed_lines(file).any? { |changed_line| changes_regex.match?(changed_line) } if changes_regex
220
+ found = filename_regex.match?(filename)
221
+ found &&= changed_lines(filename).any? { |changed_line| changes_regex.match?(changed_line) } if changes_regex
207
222
 
208
223
  found
209
224
  end
@@ -211,114 +226,201 @@ module Danger
211
226
  Array(categories || :unknown)
212
227
  end
213
228
 
214
- # Returns the GFM for a category label, making its best guess if it's not
215
- # a category we know about.
229
+ # @param category [Symbol] A category.
216
230
  #
217
- # @return[String]
231
+ # @return [String] the GFM for a category label, making its best guess if it's not
232
+ # a category we know about.
218
233
  def label_for_category(category)
219
234
  CATEGORY_LABELS.fetch(category, "~#{category}")
220
235
  end
221
236
 
222
- def new_teammates(usernames)
223
- usernames.map { |u| Gitlab::Dangerfiles::Teammate.new("username" => u) }
224
- end
225
-
237
+ # @return [String] +""+ when not in the CI context, and the MR IID as a string otherwise.
226
238
  def mr_iid
227
239
  return "" unless ci?
228
240
 
229
- gitlab_helper.mr_json["iid"]
241
+ gitlab_helper.mr_json["iid"].to_s
230
242
  end
231
243
 
244
+ # @return [String] +`whoami`+ when not in the CI context, and the MR author username otherwise.
232
245
  def mr_author
233
246
  return `whoami`.strip unless ci?
234
247
 
235
248
  gitlab_helper.mr_author
236
249
  end
237
250
 
251
+ # @return [String] +""+ when not in the CI context, and the MR title otherwise.
238
252
  def mr_title
239
253
  return "" unless ci?
240
254
 
241
255
  gitlab_helper.mr_json["title"]
242
256
  end
243
257
 
258
+ # @return [String] +""+ when not in the CI context, and the MR URL otherwise.
244
259
  def mr_web_url
245
260
  return "" unless ci?
246
261
 
247
262
  gitlab_helper.mr_json["web_url"]
248
263
  end
249
264
 
265
+ # @return [Array<String>] +[]+ when not in the CI context, and the MR labels otherwise.
250
266
  def mr_labels
251
267
  return [] unless ci?
252
268
 
253
269
  gitlab_helper.mr_labels
254
270
  end
255
271
 
272
+ # @return [String] +`git rev-parse --abbrev-ref HEAD`+ when not in the CI context, and the MR source branch otherwise.
256
273
  def mr_source_branch
257
274
  return `git rev-parse --abbrev-ref HEAD`.strip unless ci?
258
275
 
259
276
  gitlab_helper.mr_json["source_branch"]
260
277
  end
261
278
 
279
+ # @return [String] +""+ when not in the CI context, and the MR target branch otherwise.
262
280
  def mr_target_branch
263
281
  return "" unless ci?
264
282
 
265
283
  gitlab_helper.mr_json["target_branch"]
266
284
  end
267
285
 
286
+ # @return [Boolean] +true+ when not in the CI context, and whether the MR is set to be squashed otherwise.
287
+ def squash_mr?
288
+ return true unless ci?
289
+
290
+ gitlab.mr_json["squash"]
291
+ end
292
+
293
+ # @return [Boolean] whether a MR is a Draft or not.
268
294
  def draft_mr?
269
- Gitlab::Dangerfiles::TitleLinting.has_draft_flag?(mr_title)
295
+ return false unless ci?
296
+
297
+ gitlab.mr_json["work_in_progress"]
270
298
  end
271
299
 
300
+ # @return [Boolean] whether a MR is opened in the security mirror or not.
272
301
  def security_mr?
273
302
  mr_web_url.include?("/gitlab-org/security/")
274
303
  end
275
304
 
305
+ # @return [Boolean] whether a MR title includes "cherry-pick" or not.
276
306
  def cherry_pick_mr?
277
307
  Gitlab::Dangerfiles::TitleLinting.has_cherry_pick_flag?(mr_title)
278
308
  end
279
309
 
310
+ # @return [Boolean] whether a MR title includes "RUN ALL RSPEC" or not.
280
311
  def run_all_rspec_mr?
281
312
  Gitlab::Dangerfiles::TitleLinting.has_run_all_rspec_flag?(mr_title)
282
313
  end
283
314
 
315
+ # @return [Boolean] whether a MR title includes "RUN AS-IF-FOSS" or not.
284
316
  def run_as_if_foss_mr?
285
317
  Gitlab::Dangerfiles::TitleLinting.has_run_as_if_foss_flag?(mr_title)
286
318
  end
287
319
 
320
+ # @return [Boolean] whether a MR targets a stable branch or not.
288
321
  def stable_branch?
289
322
  /\A\d+-\d+-stable-ee/i.match?(mr_target_branch)
290
323
  end
291
324
 
325
+ # Whether a MR has any database-scoped labels (i.e. +"database::*"+) set or not.
326
+ #
327
+ # @return [Boolean]
328
+ def has_database_scoped_labels?
329
+ mr_labels.any? { |label| label.start_with?("database::") }
330
+ end
331
+
332
+ # @return [Boolean] whether a MR has any CI-related changes (i.e. +".gitlab-ci.yml"+ or +".gitlab/ci/*"+) or not.
333
+ def has_ci_changes?
334
+ changed_files(%r{\A(\.gitlab-ci\.yml|\.gitlab/ci/)}).any?
335
+ end
336
+
337
+ # @param labels [Array<String>] An array of labels.
338
+ #
339
+ # @return [Boolean] whether a MR has the given +labels+ set or not.
292
340
  def mr_has_labels?(*labels)
293
341
  labels = labels.flatten.uniq
294
342
 
295
343
  (labels & mr_labels) == labels
296
344
  end
297
345
 
346
+ # @param labels [Array<String>] An array of labels.
347
+ # @param sep [String] A separator.
348
+ #
349
+ # @example
350
+ # labels_list(["foo", "bar baz"], sep: "; ")
351
+ # # => '~"foo"; ~"bar baz"'
352
+ #
353
+ # @return [String] the list of +labels+ ready for being used in a Markdown comment, separated by +sep+.
298
354
  def labels_list(labels, sep: ", ")
299
355
  labels.map { |label| %Q{~"#{label}"} }.join(sep)
300
356
  end
301
357
 
358
+ # @deprecated Use {#quick_action_label} instead.
302
359
  def prepare_labels_for_mr(labels)
360
+ quick_action_label(labels)
361
+ end
362
+
363
+ # @param labels [Array<String>] An array of labels.
364
+ #
365
+ # @example
366
+ # quick_action_label(["foo", "bar baz"])
367
+ # # => '/label ~"foo" ~"bar baz"'
368
+ #
369
+ # @return [String] a quick action to set the +given+ labels. Returns +""+ if +labels+ is empty.
370
+ def quick_action_label(labels)
303
371
  return "" unless labels.any?
304
372
 
305
373
  "/label #{labels_list(labels, sep: " ")}"
306
374
  end
307
375
 
376
+ # @param regex [Regexp] A Regexp to match against.
377
+ #
378
+ # @return [Array<String>] changed files matching the given +regex+.
308
379
  def changed_files(regex)
309
380
  all_changed_files.grep(regex)
310
381
  end
311
382
 
312
- def has_database_scoped_labels?(current_mr_labels)
313
- current_mr_labels.any? { |label| label.start_with?("database::") }
383
+ # @return [Array<String>] the group labels (i.e. +"group::*"+) set on the MR.
384
+ def group_label
385
+ mr_labels.find { |label| label.start_with?("group::") }
314
386
  end
315
387
 
316
- def has_ci_changes?
317
- changed_files(%r{\A(\.gitlab-ci\.yml|\.gitlab/ci/)}).any?
388
+ private
389
+
390
+ # @return [Danger::RequestSources::GitLab, nil] the +gitlab+ helper, or +nil+ when it's not available.
391
+ def gitlab_helper
392
+ # Unfortunately the following does not work:
393
+ # - respond_to?(:gitlab)
394
+ # - respond_to?(:gitlab, true)
395
+ gitlab
396
+ rescue NoMethodError
397
+ nil
398
+ end
399
+
400
+ # @param filename [String] A filename for which we want the diff.
401
+ #
402
+ # @return [String] the raw diff as a string for the given +filename+.
403
+ def diff_for_file(filename)
404
+ if changes_from_api
405
+ changes_hash = changes_from_api.find { |file| file["new_path"] == filename }
406
+ changes_hash["diff"] if changes_hash
407
+ else
408
+ git.diff_for_file(filename)&.patch
409
+ end
318
410
  end
319
411
 
320
- def group_label(labels)
321
- labels.find { |label| label.start_with?("group::") }
412
+ # Fetches MR changes from the API instead of Git (default).
413
+ #
414
+ # @return [Array<Hash>, nil]
415
+ def changes_from_api
416
+ return nil unless ci?
417
+ return nil if defined?(@force_changes_from_git)
418
+
419
+ @changes_from_api ||= gitlab_helper.mr_changes.to_h["changes"]
420
+ rescue
421
+ # Fallback to the Git strategy in any case
422
+ @force_changes_from_git = true
423
+ nil
322
424
  end
323
425
  end
324
426
  end