gitlab-dangerfiles 3.3.0 → 3.4.2

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: bf965c37aaf01dff0630442feecb4fbe6e8d96bb2c9f7f714c631487c9eb894e
4
- data.tar.gz: 720fe1ddfd202e0b199dfbd63fdb360a8a7e58d355182b7055ff0fdebb74c886
3
+ metadata.gz: bcb56a4513a5f26239f800a2692bd8feedc8fed15bd2d4d2d93d9d2203e0df70
4
+ data.tar.gz: 0fd4fd6f9cacee27bf394c2b5ef17a7fee6b34b45b215e08524d5641fc0b092d
5
5
  SHA512:
6
- metadata.gz: 31a832aa516290296662a7949b01a13c7dd47e7c05ad6c6e78cb1f189f2190b99094e97a07875d65f4cb6b5504a8816d1a26aa39a618480899f2026c58c2c311
7
- data.tar.gz: 4f18f92c83494aff49ceec1a05f2b380bd9be7aee17a50a4126ec25c011cfc468a672da200d4a93420d4c47bbec19d6678dc389e083a56945d94d52a58862107
6
+ metadata.gz: 8bcfa9f04ce155b7e77393c9d8e2eef9ab6534111f2ce5eaa33d484ed5e811827172ba90f40589b81f1987fd35277eab91afcd5365282e4deb94de92752d5054
7
+ data.tar.gz: 526c301f0e148a4ed6faf4dcffb3afa4d142ab828eb36eb8e745783ba80964a769a4e3456683ad1898d0d4328bc3f25f5847557042fad7ae9f53b20b7d2e22e1
data/Gemfile CHANGED
@@ -7,6 +7,10 @@ gem "rake", "~> 12.0"
7
7
  gem "guard-rspec"
8
8
  gem "yard"
9
9
 
10
+ group :development do
11
+ gem "lefthook", require: false
12
+ end
13
+
10
14
  group :test do
11
15
  gem "pry-byebug", "~> 3.8", require: false
12
16
  end
data/README.md CHANGED
@@ -215,12 +215,19 @@ Latest documentation can be found at <https://www.rubydoc.info/gems/gitlab-dange
215
215
 
216
216
  ## Development
217
217
 
218
+ ### Initial setup
219
+
218
220
  After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
219
221
 
220
222
  To install this gem onto your local machine, run `bundle exec rake install`.
221
223
 
222
224
  To release a new version, update the version number in `version.rb`, and get the MR merged by a maintainer. This will be then be packaged into a gem and pushed to [rubygems.org](https://rubygems.org) by the CI/CD.
223
225
 
226
+ ### Activate lefthook locally
227
+
228
+ ```shell
229
+ lefthook install
230
+ ```
224
231
  ## Contributing
225
232
 
226
233
  Bug reports and merge requests are welcome at https://gitlab.com/gitlab-org/gitlab-dangerfiles. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [code of conduct](https://gitlab.com/gitlab-org/gitlab-dangerfiles/blob/master/CODE_OF_CONDUCT.md).
data/lefthook.yml ADDED
@@ -0,0 +1,11 @@
1
+ # EXAMPLE USAGE
2
+ # Refer for explanation to following link:
3
+ # https://github.com/evilmartians/lefthook/blob/master/docs/full_guide.md
4
+ #
5
+
6
+ pre-push:
7
+ commands:
8
+ # Runs RUby FOrmatter (rufo)
9
+ rufo:
10
+ run: bundle exec rufo --check .
11
+ glob: '*.rb'
@@ -25,6 +25,7 @@ module Danger
25
25
  product_intelligence: '~"product intelligence"',
26
26
  integrations_be: '~"group::integrations" (backend)',
27
27
  integrations_fe: '~"group::integrations" (frontend)',
28
+ "Authentication and Authorization": '~"group::authentication and authorization"',
28
29
  }.freeze
29
30
 
30
31
  # Allows to set specific rule's configuration by passing a block.
@@ -299,6 +300,15 @@ module Danger
299
300
  gitlab_helper.mr_json["target_branch"]
300
301
  end
301
302
 
303
+ # @return [Hash] +{}+ when not in the CI context, and the merge request approval state otherwise.
304
+ def mr_approval_state
305
+ return {} unless ci?
306
+
307
+ gitlab_helper.api.merge_request_approval_state(
308
+ mr_target_project_id, mr_iid
309
+ )
310
+ end
311
+
302
312
  # @return [Boolean] +true+ when not in the CI context, and whether the MR is set to be squashed otherwise.
303
313
  def squash_mr?
304
314
  return true unless ci?
@@ -18,11 +18,20 @@ module Danger
18
18
  Spin = Struct.new(:category, :reviewer, :maintainer, :optional_role, :timezone_experiment)
19
19
  HTTPError = Class.new(StandardError)
20
20
 
21
+ Approval = Struct.new(:category, :spin) do
22
+ def self.from_approval_rule(rule, maintainer)
23
+ category = rule["section"].to_sym
24
+ spin = Spin.new(category, nil, maintainer, :reviewer)
25
+
26
+ new(category, spin)
27
+ end
28
+ end
29
+
21
30
  # Finds the +Gitlab::Dangerfiles::Teammate+ object whose username matches the MR author username.
22
31
  #
23
32
  # @return [Gitlab::Dangerfiles::Teammate]
24
33
  def team_mr_author
25
- @team_mr_author ||= company_members.find { |person| person.username == helper.mr_author }
34
+ @team_mr_author ||= find_member(helper.mr_author)
26
35
  end
27
36
 
28
37
  # Assigns GitLab team members to be reviewer and maintainer
@@ -87,6 +96,18 @@ module Danger
87
96
  spins
88
97
  end
89
98
 
99
+ def required_approvals
100
+ approval_rules = helper.mr_approval_state["rules"]
101
+
102
+ return [] unless approval_rules
103
+
104
+ approval_rules.filter_map do |rule|
105
+ rule["rule_type"] == "code_owner" &&
106
+ rule["approvals_required"] > 0 &&
107
+ Approval.from_approval_rule(rule, spin_for_approver(rule))
108
+ end
109
+ end
110
+
90
111
  private
91
112
 
92
113
  # @param [Gitlab::Dangerfiles::Teammate] person
@@ -115,8 +136,8 @@ module Danger
115
136
  team_mr_author&.integrations_fe?(project, category, helper.mr_labels)
116
137
  end
117
138
 
118
- def new_random(seed)
119
- Random.new(Digest::MD5.hexdigest(seed).to_i(16))
139
+ def random
140
+ @random ||= Random.new(Digest::MD5.hexdigest(helper.mr_source_branch).to_i(16))
120
141
  end
121
142
 
122
143
  def spin_role_for_category(team, role, project, category)
@@ -131,7 +152,7 @@ module Danger
131
152
  # @param [Array<Gitlab::Dangerfiles::Teammate>] people
132
153
  #
133
154
  # @return [Gitlab::Dangerfiles::Teammate]
134
- def spin_for_person(people, random:, timezone_experiment: false)
155
+ def spin_for_person(people, timezone_experiment: false)
135
156
  shuffled_people = people.shuffle(random: random)
136
157
 
137
158
  if timezone_experiment
@@ -141,6 +162,46 @@ module Danger
141
162
  end
142
163
  end
143
164
 
165
+ # Spin a reviewer for a particular approval rule
166
+ #
167
+ # @param [Hash] rule of approval
168
+ #
169
+ # @return [Gitlab::Dangerfiles::Teammate]
170
+ def spin_for_approver(rule)
171
+ # This will filter out approvers who are not even reviewers who
172
+ # don't show up in roulette data we're relying on.
173
+ # That's why `filter_map` is used.
174
+ approvers = rule["eligible_approvers"].filter_map do |approver|
175
+ find_member(approver["username"])
176
+ end
177
+
178
+ spin_for_person(approvers) || spin_for_approver_fallback(rule)
179
+ end
180
+
181
+ # It can be possible that we don't have a valid reviewer for approval.
182
+ # In this case, we sample again without considering:
183
+ #
184
+ # * If they're available
185
+ # * If they're an actual reviewer from roulette data
186
+ #
187
+ # We do this because we strictly require an approval from the approvers.
188
+ #
189
+ # @param [Hash] rule of approval
190
+ #
191
+ # @return [Gitlab::Dangerfiles::Teammate]
192
+ def spin_for_approver_fallback(rule)
193
+ fallback_approvers = rule["eligible_approvers"].map do |approver|
194
+ find_member(approver["username"]) ||
195
+ Gitlab::Dangerfiles::Teammate.new(approver)
196
+ end
197
+
198
+ # Intentionally not using `spin_for_person` to skip `valid_person?`.
199
+ # This should strictly return someone so we don't filter anything,
200
+ # and it's a fallback mechanism which should not happen often that
201
+ # deserves a complex algorithm.
202
+ fallback_approvers.sample(random: random)
203
+ end
204
+
144
205
  def spin_for_category(project, category, timezone_experiment: false)
145
206
  team = project_team(project)
146
207
  reviewers, traintainers, maintainers =
@@ -148,13 +209,11 @@ module Danger
148
209
  spin_role_for_category(team, role, project, category)
149
210
  end
150
211
 
151
- random = new_random(helper.mr_source_branch)
152
-
153
212
  weighted_reviewers = Gitlab::Dangerfiles::Weightage::Reviewers.new(reviewers, traintainers).execute
154
213
  weighted_maintainers = Gitlab::Dangerfiles::Weightage::Maintainers.new(maintainers).execute
155
214
 
156
- reviewer = spin_for_person(weighted_reviewers, random: random, timezone_experiment: timezone_experiment)
157
- maintainer = spin_for_person(weighted_maintainers, random: random, timezone_experiment: timezone_experiment)
215
+ reviewer = spin_for_person(weighted_reviewers, timezone_experiment: timezone_experiment)
216
+ maintainer = spin_for_person(weighted_maintainers, timezone_experiment: timezone_experiment)
158
217
 
159
218
  Spin.new(category, reviewer, maintainer, false, timezone_experiment)
160
219
  end
@@ -167,6 +226,10 @@ module Danger
167
226
  def http_get_json(url)
168
227
  rsp = Net::HTTP.get_response(URI.parse(url))
169
228
 
229
+ if rsp.is_a?(Net::HTTPRedirection)
230
+ raise "Redirection detected: #{rsp.header["location"]}"
231
+ end
232
+
170
233
  unless rsp.is_a?(Net::HTTPOK)
171
234
  raise HTTPError, "Failed to read #{url}: #{rsp.code} #{rsp.message}"
172
235
  end
@@ -187,6 +250,10 @@ module Danger
187
250
  end
188
251
  end
189
252
 
253
+ def find_member(username)
254
+ company_members.find { |person| person.username == username }
255
+ end
256
+
190
257
  # Like +team+, but only returns teammates in the current project, based on
191
258
  # project_name.
192
259
  #
@@ -10,7 +10,8 @@ module Gitlab
10
10
  @options = options
11
11
  @username = options["username"]
12
12
  @name = options["name"]
13
- @markdown_name = options["markdown_name"]
13
+ @markdown_name = options["markdown_name"] ||
14
+ default_markdown_name(*options.values_at("username", "name"))
14
15
  @role = options["role"]
15
16
  @projects = process_projects(options["projects"])
16
17
  @available = options["available"]
@@ -23,6 +24,10 @@ module Gitlab
23
24
  options
24
25
  end
25
26
 
27
+ def inspect
28
+ "#<#{self.class} @username=#{username.inspect}>"
29
+ end
30
+
26
31
  def ==(other)
27
32
  return false unless other.respond_to?(:username)
28
33
 
@@ -62,7 +67,7 @@ module Gitlab
62
67
  end
63
68
 
64
69
  def markdown_name(author: nil)
65
- "#{@markdown_name} (#{utc_offset_text(author)})"
70
+ "#{@markdown_name}#{utc_offset_text(author)}"
66
71
  end
67
72
 
68
73
  def local_hour
@@ -79,6 +84,10 @@ module Gitlab
79
84
 
80
85
  private
81
86
 
87
+ def default_markdown_name(username, name)
88
+ "[#{name}](https://gitlab.com/#{username}) (`@#{username}`)"
89
+ end
90
+
82
91
  def process_projects(projects)
83
92
  return nil unless projects
84
93
 
@@ -88,15 +97,19 @@ module Gitlab
88
97
  end
89
98
 
90
99
  def utc_offset_text(author = nil)
100
+ return unless tz_offset_hours
101
+
91
102
  offset_text = if floored_offset_hours >= 0
92
103
  "UTC+#{floored_offset_hours}"
93
104
  else
94
105
  "UTC#{floored_offset_hours}"
95
106
  end
96
107
 
97
- return offset_text unless author
98
-
99
- "#{offset_text}, #{offset_diff_compared_to_author(author)}"
108
+ if author
109
+ " (#{offset_text}, #{offset_diff_compared_to_author(author)})"
110
+ else
111
+ " (#{offset_text})"
112
+ end
100
113
  end
101
114
 
102
115
  def offset_diff_compared_to_author(author)
@@ -1,5 +1,5 @@
1
1
  module Gitlab
2
2
  module Dangerfiles
3
- VERSION = "3.3.0"
3
+ VERSION = "3.4.2"
4
4
  end
5
5
  end
@@ -58,7 +58,7 @@ module Gitlab
58
58
  danger_plugin.import_plugin(File.expand_path("../danger/plugins/*.rb", __dir__))
59
59
 
60
60
  Dir.glob(File.expand_path("danger/plugins/*.rb", config.project_root)).sort.each do |path|
61
- puts "Importing plugin at #{path}" if dangerfile.verbose
61
+ puts "Importing plugin at #{path}" if helper_plugin.ci?
62
62
  danger_plugin.import_plugin(path)
63
63
  end
64
64
  end
@@ -87,10 +87,9 @@ module Gitlab
87
87
  return if helper_plugin.release_automation?
88
88
 
89
89
  rules = filtered_rules(only, except)
90
- puts "Running rules: #{rules}\n" if dangerfile.verbose
91
90
 
92
91
  rules.each do |rule, path|
93
- puts "Importing rule #{rule} at #{path}" if dangerfile.verbose
92
+ puts "Importing rule #{rule} at #{path}" if helper_plugin.ci?
94
93
  danger_plugin.import_dangerfile(path: path)
95
94
  end
96
95
  end
@@ -116,45 +115,45 @@ module Gitlab
116
115
 
117
116
  attr_reader :dangerfile
118
117
 
118
+ def filtered_rules(only_rules, except_rules)
119
+ only_rules = Array(only_rules).compact.map(&:to_s)
120
+
121
+ rules = allowed_rules_based_on_context.reject { |rule, _v| except_rules.include?(rule) }
122
+
123
+ if only_rules.any?
124
+ rules.select! { |rule, _v| only_rules.include?(rule) }
125
+ end
126
+
127
+ rules.sort.to_h
128
+ end
129
+
130
+ def allowed_rules_based_on_context
131
+ helper_plugin.ci? ? all_rules : local_rules
132
+ end
133
+
134
+ def all_rules
135
+ all_gem_rules.merge(custom_rules)
136
+ end
137
+
119
138
  def all_gem_rules
120
- @all_gem_rules ||= Dir.glob(File.join(RULES_DIR, "*")).sort.each_with_object({}) do |path, memo|
139
+ @all_gem_rules ||= Dir.glob(File.join(RULES_DIR, "*")).each_with_object({}) do |path, memo|
121
140
  rule_name = File.basename(path)
122
141
  memo[rule_name] = path if File.directory?(path) && File.exist?(File.join(path, "Dangerfile"))
123
142
  end
124
143
  end
125
144
 
126
145
  def custom_rules
127
- @custom_rules ||= Dir.glob(File.expand_path("danger/*", config.project_root)).sort.each_with_object({}) do |path, memo|
146
+ @custom_rules ||= Dir.glob(File.expand_path("danger/*", config.project_root)).each_with_object({}) do |path, memo|
128
147
  rule_name = File.basename(path)
129
148
  memo[rule_name] = path if File.directory?(path) && File.exist?(File.join(path, "Dangerfile"))
130
149
  end
131
150
  end
132
151
 
133
- def all_rules
134
- all_gem_rules.merge(custom_rules)
135
- end
136
-
137
152
  def local_rules
138
153
  ci_only_rules = CI_ONLY_RULES | config.ci_only_rules
139
154
  all_rules.reject { |rule, _v| ci_only_rules.include?(rule) }
140
155
  end
141
156
 
142
- def allowed_rules_based_on_context
143
- helper_plugin.ci? ? all_rules : local_rules
144
- end
145
-
146
- def filtered_rules(only_rules, except_rules)
147
- only_rules = Array(only_rules).compact.map(&:to_s)
148
-
149
- rules = allowed_rules_based_on_context.reject { |rule, _v| except_rules.include?(rule) }
150
-
151
- if only_rules.any?
152
- rules.select! { |rule, _v| only_rules.include?(rule) }
153
- end
154
-
155
- rules
156
- end
157
-
158
157
  def danger_plugin
159
158
  @danger_plugin ||= dangerfile.plugins[Danger::DangerfileDangerPlugin]
160
159
  end
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: 3.3.0
4
+ version: 3.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - GitLab
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-06-15 00:00:00.000000000 Z
11
+ date: 2022-06-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake
@@ -163,6 +163,7 @@ files:
163
163
  - fixtures/emojis/aliases.json
164
164
  - fixtures/emojis/digests.json
165
165
  - gitlab-dangerfiles.gemspec
166
+ - lefthook.yml
166
167
  - lib/danger/plugins/changelog.rb
167
168
  - lib/danger/plugins/internal/helper.rb
168
169
  - lib/danger/plugins/roulette.rb