gitlab-dangerfiles 1.1.1 → 2.1.3

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