danger 5.15.0 → 5.16.0

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: 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