danger 5.15.0 → 5.16.0

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: a476be73dbdfc6a2c7706712403d7209cdc47a1a486ddccb9be0ff403ab4aacf
4
- data.tar.gz: 3db92f40356945dc85d35e88afdf04d6271afff0296088fedeeebde6607b1447
3
+ metadata.gz: 3b40ff7a6e2e8ad3341d4e706e8d185a8c4287016e8ae5ed92e1b5609f9a4bf7
4
+ data.tar.gz: 152b14f3da15104d3e57dc1ade876c21a1558faea572d779760f65818340fb3f
5
5
  SHA512:
6
- metadata.gz: da6442b3c91745b44e6e6a0f046faf902d3fdbd6a44ba10fa4168196ce0b52297c8a18fffddeadfe34d805a3df310bb9dfa55d279511e9248c1949fdf210058a
7
- data.tar.gz: '08d9329626584fdb92d846494acd5d7ffaa5b154b0f63bcd57774a98bf8a2162e0e694e721c5bd0e918c256cb78f4f2f88c5fdb881242c7b57eb51ef1cfb3375'
6
+ metadata.gz: c65cc6fa247104fe197ba532d02b7130558e1b3ee014818ca50cb03c31625b07c938d9a4b0fe909bfa136a7feb705c0c23f348e8713cd1d96cab058f6297a70a
7
+ data.tar.gz: 31da304ece4f45a98fe54ea148998750c5f7a4f3a0dec9dd57e2f0b3b907586695d740a32dd423e642c227e9fd33d1f2ce4d10093830a4184d75cb0569354b18
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2019 Orta, Felix Krause
3
+ Copyright (c) 2019-present Orta, Felix Krause
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
@@ -0,0 +1,55 @@
1
+ # https://docs.microsoft.com/en-us/appcenter/build/custom/variables/
2
+ require "uri"
3
+ require "danger/request_sources/github/github"
4
+
5
+ module Danger
6
+ # ### CI Setup
7
+ #
8
+ # Add a script step to your appcenter-post-build.sh:
9
+ #
10
+ # ``` shell
11
+ # #!/usr/bin/env bash
12
+ # bundle install
13
+ # bundle exec danger
14
+ # ```
15
+ #
16
+ # ### Token Setup
17
+ #
18
+ # Add the `DANGER_GITHUB_API_TOKEN` to your environment variables.
19
+ #
20
+ class Appcenter < CI
21
+ def self.validates_as_ci?(env)
22
+ env.key? "APPCENTER_BUILD_ID"
23
+ end
24
+
25
+ def self.validates_as_pr?(env)
26
+ return env["BUILD_REASON"] == "PullRequest"
27
+ end
28
+
29
+ def self.owner_for_github(env)
30
+ URI.parse(env["BUILD_REPOSITORY_URI"]).path.split("/")[1]
31
+ end
32
+
33
+ def self.repo_identifier_for_github(env)
34
+ repo_name = env["BUILD_REPOSITORY_NAME"]
35
+ owner = owner_for_github(env)
36
+ "#{owner}/#{repo_name}"
37
+ end
38
+
39
+ # Hopefully it's a temporary workaround (same as in Codeship integration) because App Center
40
+ # doesn't expose PR's ID. There's a future request https://github.com/Microsoft/appcenter/issues/79
41
+ def self.pr_from_env(env)
42
+ Danger::RequestSources::GitHub.new(nil, env).get_pr_from_branch(repo_identifier_for_github(env), env["BUILD_SOURCEBRANCHNAME"], owner_for_github(env))
43
+ end
44
+
45
+ def supported_request_sources
46
+ @supported_request_sources ||= [Danger::RequestSources::GitHub]
47
+ end
48
+
49
+ def initialize(env)
50
+ self.pull_request_id = self.class.pr_from_env(env)
51
+ self.repo_url = env["BUILD_REPOSITORY_URI"]
52
+ self.repo_slug = self.class.repo_identifier_for_github(env)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,26 @@
1
+ <%- @tables.each do |table| -%>
2
+ <%- if table[:content].any? -%>
3
+ <table data-meta="generated_by_<%= @danger_id %>">
4
+ <tbody>
5
+ <%- table[:content].each do |violation| -%>
6
+ <tr>
7
+ <td>:<%= table[:emoji] %>:</td>
8
+ <td width="100%" data-sticky="<%= violation.sticky %>"><%= "<del>" if table[:resolved] %><%= violation.message %><%= "</del>" if table[:resolved] %></td>
9
+ </tr>
10
+ <%- end -%>
11
+ </tbody>
12
+ </table>
13
+ <%- end -%>
14
+ <%- end -%>
15
+
16
+ <%- @markdowns.each do |current| -%>
17
+ <%= current %>
18
+ <%# the previous line has to be aligned far to the left, otherwise markdown can break easily %>
19
+ <%- end -%>
20
+ <%# We need to add the generated_by_ to identify comments from danger. But with inlines %>
21
+ <%# it might be a little annoying, so we set on the table, but if we have markdown we add the footer anyway %>
22
+ <%- if @markdowns.count > 0 -%>
23
+ <p align="right" data-meta="generated_by_<%= @danger_id %>">
24
+ Generated by :no_entry_sign: <a href="http://danger.systems/">Danger</a>
25
+ </p>
26
+ <%- end -%>
@@ -62,7 +62,15 @@ module Danger
62
62
  def validate_pr!(cork, fail_if_no_pr)
63
63
  unless EnvironmentManager.pr?(system_env)
64
64
  ci_name = EnvironmentManager.local_ci_source(system_env).name.split("::").last
65
- cork.puts "Not a #{ci_name} Pull Request - skipping `danger` run".yellow
65
+
66
+ msg = "Not a #{ci_name} Pull Request - skipping `danger` run. "
67
+ # circle won't run danger properly if the commit is pushed and build runs before the PR exists
68
+ # https://danger.systems/guides/troubleshooting.html#circle-ci-doesnt-run-my-build-consistently
69
+ # the best solution is to enable `fail_if_no_pr`, and then re-run the job once the PR is up
70
+ if ci_name == "CircleCI"
71
+ msg << "If you only created the PR recently, try re-running your workflow."
72
+ end
73
+ cork.puts msg.strip.yellow
66
74
 
67
75
  exit(fail_if_no_pr ? 1 : 0)
68
76
  end
@@ -12,11 +12,19 @@ module Danger
12
12
  end
13
13
 
14
14
  def self.from_gitlab(comment)
15
- self.new(comment.id, comment.body)
15
+ if comment.respond_to?(:id) && comment.respond_to?(:body)
16
+ self.new(comment.id, comment.body)
17
+ else
18
+ self.new(comment["id"], comment["body"])
19
+ end
16
20
  end
17
21
 
18
22
  def generated_by_danger?(danger_id)
19
23
  body.include?("\"generated_by_#{danger_id}\"")
20
24
  end
25
+
26
+ def inline?
27
+ body.include?("")
28
+ end
21
29
  end
22
30
  end
@@ -10,6 +10,9 @@ module Danger
10
10
  include Danger::Helpers::CommentsHelper
11
11
  attr_accessor :mr_json, :commits_json
12
12
 
13
+ FIRST_GITLAB_GEM_WITH_VERSION_CHECK = Gem::Version.new("4.6.0")
14
+ FIRST_VERSION_WITH_INLINE_COMMENTS = Gem::Version.new("10.8.0")
15
+
13
16
  def self.env_vars
14
17
  ["DANGER_GITLAB_API_TOKEN"]
15
18
  end
@@ -67,10 +70,21 @@ module Danger
67
70
  end
68
71
 
69
72
  def mr_comments
73
+ # @raw_comments contains what we got back from the server.
74
+ # @comments contains Comment objects (that have less information)
70
75
  @comments ||= begin
71
- client.merge_request_comments(ci_source.repo_slug, ci_source.pull_request_id, per_page: 100)
72
- .auto_paginate
73
- .map { |comment| Comment.from_gitlab(comment) }
76
+ if supports_inline_comments
77
+ @raw_comments = client.merge_request_discussions(ci_source.repo_slug, ci_source.pull_request_id)
78
+ .auto_paginate
79
+ .flat_map { |discussion| discussion.notes.map { |note| note.merge({"discussion_id" => discussion.id}) } }
80
+ @raw_comments
81
+ .map { |comment| Comment.from_gitlab(comment) }
82
+ else
83
+ @raw_comments = client.merge_request_comments(ci_source.repo_slug, ci_source.pull_request_id, per_page: 100)
84
+ .auto_paginate
85
+ @raw_comments
86
+ .map { |comment| Comment.from_gitlab(comment) }
87
+ end
74
88
  end
75
89
  end
76
90
 
@@ -107,7 +121,89 @@ module Danger
107
121
  GetIgnoredViolation.new(self.mr_json.description).call
108
122
  end
109
123
 
124
+ def supports_inline_comments
125
+ @supports_inline_comments ||= begin
126
+ # If we can't check GitLab's version, we assume we don't support inline comments
127
+ if Gem.loaded_specs["gitlab"].version < FIRST_GITLAB_GEM_WITH_VERSION_CHECK
128
+ false
129
+ else
130
+ current_version = Gem::Version.new(client.version.version)
131
+
132
+ current_version >= FIRST_VERSION_WITH_INLINE_COMMENTS
133
+ end
134
+ end
135
+ end
136
+
110
137
  def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false)
138
+ if supports_inline_comments
139
+ update_pull_request_with_inline_comments!(warnings: warnings, errors: errors, messages: messages, markdowns: markdowns, danger_id: danger_id, new_comment: new_comment, remove_previous_comments: remove_previous_comments)
140
+ else
141
+ update_pull_request_without_inline_comments!(warnings: warnings, errors: errors, messages: messages, markdowns: markdowns, danger_id: danger_id, new_comment: new_comment, remove_previous_comments: remove_previous_comments)
142
+ end
143
+ end
144
+
145
+ def update_pull_request_with_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false)
146
+ editable_comments = mr_comments.select { |comment| comment.generated_by_danger?(danger_id) }
147
+
148
+ last_comment = editable_comments.last
149
+ should_create_new_comment = new_comment || last_comment.nil? || remove_previous_comments
150
+
151
+ previous_violations =
152
+ if should_create_new_comment
153
+ {}
154
+ else
155
+ parse_comment(last_comment.body)
156
+ end
157
+
158
+ regular_violations = regular_violations_group(
159
+ warnings: warnings,
160
+ errors: errors,
161
+ messages: messages,
162
+ markdowns: markdowns
163
+ )
164
+
165
+ inline_violations = inline_violations_group(
166
+ warnings: warnings,
167
+ errors: errors,
168
+ messages: messages,
169
+ markdowns: markdowns
170
+ )
171
+
172
+ rest_inline_violations = submit_inline_comments!({
173
+ danger_id: danger_id,
174
+ previous_violations: previous_violations
175
+ }.merge(inline_violations))
176
+
177
+ main_violations = merge_violations(
178
+ regular_violations, rest_inline_violations
179
+ )
180
+
181
+ main_violations_sum = main_violations.values.inject(:+)
182
+
183
+ if (previous_violations.empty? && main_violations_sum.empty?) || remove_previous_comments
184
+ # Just remove the comment, if there's nothing to say or --remove-previous-comments CLI was set.
185
+ delete_old_comments!(danger_id: danger_id)
186
+ end
187
+
188
+ # If there are still violations to show
189
+ if main_violations_sum.any?
190
+ body = generate_comment({
191
+ template: "gitlab",
192
+ danger_id: danger_id,
193
+ previous_violations: previous_violations
194
+ }.merge(main_violations))
195
+
196
+ comment_result =
197
+ if should_create_new_comment
198
+ client.create_merge_request_note(ci_source.repo_slug, ci_source.pull_request_id, body)
199
+ else
200
+ client.edit_merge_request_note(ci_source.repo_slug, ci_source.pull_request_id, last_comment.id, body)
201
+ end
202
+ end
203
+
204
+ end
205
+
206
+ def update_pull_request_without_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false)
111
207
  editable_comments = mr_comments.select { |comment| comment.generated_by_danger?(danger_id) }
112
208
 
113
209
  should_create_new_comment = new_comment || editable_comments.empty? || remove_previous_comments
@@ -148,14 +244,21 @@ module Danger
148
244
  end
149
245
 
150
246
  def delete_old_comments!(except: nil, danger_id: "danger")
151
- mr_comments.each do |comment|
247
+ @raw_comments.each do |raw_comment|
248
+
249
+ comment = Comment.from_gitlab(raw_comment)
152
250
  next unless comment.generated_by_danger?(danger_id)
153
251
  next if comment.id == except
154
- client.delete_merge_request_comment(
155
- ci_source.repo_slug,
156
- ci_source.pull_request_id,
157
- comment.id
158
- )
252
+ next unless raw_comment.is_a?(Hash) && raw_comment["position"].nil?
253
+
254
+ begin
255
+ client.delete_merge_request_comment(
256
+ ci_source.repo_slug,
257
+ ci_source.pull_request_id,
258
+ comment.id
259
+ )
260
+ rescue
261
+ end
159
262
  end
160
263
  end
161
264
 
@@ -170,6 +273,146 @@ module Danger
170
273
 
171
274
  "https://#{host}/#{organisation}/#{repository}/raw/#{branch}/#{path}"
172
275
  end
276
+
277
+ def regular_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
278
+ {
279
+ warnings: warnings.reject(&:inline?),
280
+ errors: errors.reject(&:inline?),
281
+ messages: messages.reject(&:inline?),
282
+ markdowns: markdowns.reject(&:inline?)
283
+ }
284
+ end
285
+
286
+ def inline_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
287
+ cmp = proc do |a, b|
288
+ next -1 unless a.file && a.line
289
+ next 1 unless b.file && b.line
290
+
291
+ next a.line <=> b.line if a.file == b.file
292
+ next a.file <=> b.file
293
+ end
294
+
295
+ # Sort to group inline comments by file
296
+ {
297
+ warnings: warnings.select(&:inline?).sort(&cmp),
298
+ errors: errors.select(&:inline?).sort(&cmp),
299
+ messages: messages.select(&:inline?).sort(&cmp),
300
+ markdowns: markdowns.select(&:inline?).sort(&cmp)
301
+ }
302
+ end
303
+
304
+ def merge_violations(*violation_groups)
305
+ violation_groups.inject({}) do |accumulator, group|
306
+ accumulator.merge(group) { |_, old, fresh| old + fresh }
307
+ end
308
+ end
309
+
310
+ def submit_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: [], danger_id: "danger")
311
+ comments = client.merge_request_discussions(ci_source.repo_slug, ci_source.pull_request_id)
312
+ .auto_paginate
313
+ .flat_map { |discussion| discussion.notes.map { |note| note.merge({"discussion_id" => discussion.id}) } }
314
+
315
+ danger_comments = comments.select { |comment| Comment.from_gitlab(comment).generated_by_danger?(danger_id) }
316
+ non_danger_comments = comments - danger_comments
317
+
318
+ diff_lines = []
319
+
320
+ warnings = submit_inline_comments_for_kind!(:warning, warnings, diff_lines, danger_comments, previous_violations["warning"], danger_id: danger_id)
321
+ errors = submit_inline_comments_for_kind!(:error, errors, diff_lines, danger_comments, previous_violations["error"], danger_id: danger_id)
322
+ messages = submit_inline_comments_for_kind!(:message, messages, diff_lines, danger_comments, previous_violations["message"], danger_id: danger_id)
323
+ markdowns = submit_inline_comments_for_kind!(:markdown, markdowns, diff_lines, danger_comments, [], danger_id: danger_id)
324
+
325
+ # submit removes from the array all comments that are still in force
326
+ # so we strike out all remaining ones
327
+ danger_comments.each do |comment|
328
+ violation = violations_from_table(comment["body"]).first
329
+ if !violation.nil? && violation.sticky
330
+ body = generate_inline_comment_body("white_check_mark", violation, danger_id: danger_id, resolved: true, template: "gitlab")
331
+ client.update_merge_request_discussion_note(ci_source.repo_slug, ci_source.pull_request_id, comment["discussion_id"], comment["id"], body)
332
+ else
333
+ # We remove non-sticky violations that have no replies
334
+ # Since there's no direct concept of a reply in GH, we simply consider
335
+ # the existance of non-danger comments in that line as replies
336
+ replies = non_danger_comments.select do |potential|
337
+ potential["path"] == comment["path"] &&
338
+ potential["position"] == comment["position"] &&
339
+ potential["commit_id"] == comment["commit_id"]
340
+ end
341
+
342
+ client.delete_merge_request_comment(ci_source.repo_slug, ci_source.pull_request_id, comment["id"]) if replies.empty?
343
+ end
344
+ end
345
+
346
+ {
347
+ warnings: warnings,
348
+ errors: errors,
349
+ messages: messages,
350
+ markdowns: markdowns
351
+ }
352
+ end
353
+
354
+ def submit_inline_comments_for_kind!(kind, messages, diff_lines, danger_comments, previous_violations, danger_id: "danger")
355
+ previous_violations ||= []
356
+ is_markdown_content = kind == :markdown
357
+ emoji = { warning: "warning", error: "no_entry_sign", message: "book" }[kind]
358
+
359
+ messages.reject do |m|
360
+ next false unless m.file && m.line
361
+
362
+ # position = find_position_in_diff diff_lines, m, kind
363
+
364
+ # Keep the change if it's line is not in the diff and not in dismiss mode
365
+ # next dismiss_out_of_range_messages_for(kind) if position.nil?
366
+
367
+ # Once we know we're gonna submit it, we format it
368
+ if is_markdown_content
369
+ body = generate_inline_markdown_body(m, danger_id: danger_id, template: "gitlab")
370
+ else
371
+ # Hide the inline link behind a span
372
+ m = process_markdown(m, true)
373
+ body = generate_inline_comment_body(emoji, m, danger_id: danger_id, template: "gitlab")
374
+ # A comment might be in previous_violations because only now it's part of the unified diff
375
+ # We remove from the array since it won't have a place in the table anymore
376
+ previous_violations.reject! { |v| messages_are_equivalent(v, m) }
377
+ end
378
+
379
+ matching_comments = danger_comments.select do |comment_data|
380
+ position = comment_data["position"]
381
+
382
+ if position.nil?
383
+ false
384
+ else
385
+ position["new_path"] == m.file && position["new_line"] == m.line
386
+ end
387
+ end
388
+
389
+ if matching_comments.empty?
390
+ params = {
391
+ body: body,
392
+ position: {
393
+ position_type: 'text',
394
+ new_path: m.file,
395
+ new_line: m.line,
396
+ base_sha: self.mr_json.diff_refs.base_sha,
397
+ start_sha: self.mr_json.diff_refs.start_sha,
398
+ head_sha: self.mr_json.diff_refs.head_sha
399
+ }
400
+ }
401
+ client.create_merge_request_discussion(ci_source.repo_slug, ci_source.pull_request_id, params)
402
+ else
403
+ # Remove the surviving comment so we don't strike it out
404
+ danger_comments.reject! { |c| matching_comments.include? c }
405
+
406
+ # Update the comment to remove the strikethrough if present
407
+ comment = matching_comments.first
408
+ client.update_merge_request_discussion_note(ci_source.repo_slug, ci_source.pull_request_id, comment["discussion_id"], comment["id"], body)
409
+ end
410
+
411
+ # Remove this element from the array
412
+ next true
413
+ end
414
+ end
415
+
173
416
  end
174
417
  end
175
418
  end
@@ -1,4 +1,4 @@
1
1
  module Danger
2
- VERSION = "5.15.0".freeze
2
+ VERSION = "5.16.0".freeze
3
3
  DESCRIPTION = "Like Unit Tests, but for your Team Culture.".freeze
4
4
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: danger
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.15.0
4
+ version: 5.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Orta Therox
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-02-22 00:00:00.000000000 Z
12
+ date: 2019-02-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: claide
@@ -179,6 +179,7 @@ files:
179
179
  - bin/danger
180
180
  - lib/assets/DangerfileTemplate
181
181
  - lib/danger.rb
182
+ - lib/danger/ci_source/appcenter.rb
182
183
  - lib/danger/ci_source/appveyor.rb
183
184
  - lib/danger/ci_source/bitbucket_pipelines.rb
184
185
  - lib/danger/ci_source/bitrise.rb
@@ -232,6 +233,7 @@ files:
232
233
  - lib/danger/comment_generators/github.md.erb
233
234
  - lib/danger/comment_generators/github_inline.md.erb
234
235
  - lib/danger/comment_generators/gitlab.md.erb
236
+ - lib/danger/comment_generators/gitlab_inline.md.erb
235
237
  - lib/danger/comment_generators/vsts.md.erb
236
238
  - lib/danger/core_ext/file_list.rb
237
239
  - lib/danger/core_ext/string.rb