danger 3.1.1 → 3.2.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.
@@ -100,16 +100,42 @@ module Danger
100
100
  previous_violations = parse_comment(comment)
101
101
  end
102
102
 
103
- if previous_violations.empty? && (warnings + errors + messages + markdowns).empty?
103
+ main_violations = (warnings + errors + messages + markdowns).reject(&:inline?)
104
+ if previous_violations.empty? && main_violations.empty?
104
105
  # Just remove the comment, if there's nothing to say.
105
106
  delete_old_comments!(danger_id: danger_id)
106
- else
107
- body = generate_comment(warnings: warnings,
108
- errors: errors,
109
- messages: messages,
110
- markdowns: markdowns,
111
- previous_violations: previous_violations,
112
- danger_id: danger_id,
107
+ end
108
+
109
+ cmp = proc do |a, b|
110
+ next -1 unless a.file
111
+ next 1 unless b.file
112
+
113
+ next a.line <=> b.line if a.file == b.file
114
+ next a.file <=> b.file
115
+ end
116
+
117
+ # Sort to group inline comments by file
118
+ # We copy because we need to mutate this arrays for inlines
119
+ comment_warnings = warnings.sort(&cmp)
120
+ comment_errors = errors.sort(&cmp)
121
+ comment_messages = messages.sort(&cmp)
122
+ comment_markdowns = markdowns.sort(&cmp)
123
+
124
+ submit_inline_comments!(warnings: comment_warnings,
125
+ errors: comment_errors,
126
+ messages: comment_messages,
127
+ markdowns: comment_markdowns,
128
+ previous_violations: previous_violations,
129
+ danger_id: danger_id)
130
+
131
+ # If there are still violations to show
132
+ unless main_violations.empty?
133
+ body = generate_comment(warnings: comment_warnings,
134
+ errors: comment_errors,
135
+ messages: comment_messages,
136
+ markdowns: comment_markdowns,
137
+ previous_violations: previous_violations,
138
+ danger_id: danger_id,
113
139
  template: "github")
114
140
 
115
141
  if editable_comments.empty?
@@ -124,7 +150,7 @@ module Danger
124
150
  # Note: this can terminate the entire process.
125
151
  submit_pull_request_status!(warnings: warnings,
126
152
  errors: errors,
127
- details_url: comment_result["html_url"])
153
+ details_url: comment_result[:html_url])
128
154
  end
129
155
 
130
156
  def submit_pull_request_status!(warnings: [], errors: [], details_url: [])
@@ -170,6 +196,178 @@ module Danger
170
196
  end
171
197
  end
172
198
 
199
+ def submit_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: [], danger_id: "danger")
200
+ # Avoid doing any fetchs if there's no inline comments
201
+ return if (warnings + errors + messages).select(&:inline?).empty?
202
+
203
+ diff_lines = self.pr_diff.lines
204
+ pr_comments = client.pull_request_comments(ci_source.repo_slug, ci_source.pull_request_id)
205
+ danger_comments = pr_comments.select { |comment| comment[:body].include?("generated_by_#{danger_id}") }
206
+ non_danger_comments = pr_comments - danger_comments
207
+
208
+ submit_inline_comments_for_kind!("warning", warnings, diff_lines, danger_comments, previous_violations[:warning], danger_id: danger_id)
209
+ submit_inline_comments_for_kind!("no_entry_sign", errors, diff_lines, danger_comments, previous_violations[:error], danger_id: danger_id)
210
+ submit_inline_comments_for_kind!("book", messages, diff_lines, danger_comments, previous_violations[:message], danger_id: danger_id)
211
+ submit_inline_comments_for_kind!(nil, markdowns, diff_lines, danger_comments, [], danger_id: danger_id)
212
+
213
+ # submit removes from the array all comments that are still in force
214
+ # so we strike out all remaining ones
215
+ danger_comments.each do |comment|
216
+ violation = violations_from_table(comment[:body]).first
217
+ if !violation.nil? && violation.sticky
218
+ body = generate_inline_comment_body("white_check_mark", violation, danger_id: danger_id, resolved: true, template: "github")
219
+ client.update_pull_request_comment(ci_source.repo_slug, comment[:id], body)
220
+ else
221
+ # We remove non-sticky violations that have no replies
222
+ # Since there's no direct concept of a reply in GH, we simply consider
223
+ # the existance of non-danger comments in that line as replies
224
+ replies = non_danger_comments.select do |potential|
225
+ potential[:path] == comment[:path] &&
226
+ potential[:position] == comment[:position] &&
227
+ potential[:commit_id] == comment[:commit_id]
228
+ end
229
+
230
+ client.delete_pull_request_comment(ci_source.repo_slug, comment[:id]) if replies.empty?
231
+ end
232
+ end
233
+ end
234
+
235
+ def messages_are_equivalent(m1, m2)
236
+ blob_regexp = %r{blob/[0-9a-z]+/}
237
+ m1.file == m2.file && m1.line == m2.line &&
238
+ m1.message.sub(blob_regexp, "") == m2.message.sub(blob_regexp, "")
239
+ end
240
+
241
+ def submit_inline_comments_for_kind!(emoji, messages, diff_lines, danger_comments, previous_violations, danger_id: "danger")
242
+ head_ref = pr_json[:head][:sha]
243
+ previous_violations ||= []
244
+ is_markdown_content = emoji.nil?
245
+
246
+ submit_inline = proc do |m|
247
+ next false unless m.file && m.line
248
+
249
+ position = find_position_in_diff diff_lines, m
250
+
251
+ # Keep the change if it's line is not in the diff
252
+ next false if position.nil?
253
+
254
+ # Once we know we're gonna submit it, we format it
255
+ if is_markdown_content
256
+ body = generate_inline_markdown_body(m, danger_id: danger_id, template: "github")
257
+ else
258
+ # Hide the inline link behind a span
259
+ m = process_markdown(m, true)
260
+ body = generate_inline_comment_body(emoji, m, danger_id: danger_id, template: "github")
261
+ # A comment might be in previous_violations because only now it's part of the unified diff
262
+ # We remove from the array since it won't have a place in the table anymore
263
+ previous_violations.reject! { |v| messages_are_equivalent(v, m) }
264
+ end
265
+
266
+ matching_comments = danger_comments.select do |comment_data|
267
+ if comment_data[:path] == m.file && comment_data[:commit_id] == head_ref && comment_data[:position] == position
268
+ # Parse it to avoid problems with strikethrough
269
+ violation = violations_from_table(comment_data[:body]).first
270
+ if violation
271
+ messages_are_equivalent(violation, m)
272
+ else
273
+ comment_data[:body] == body
274
+ end
275
+ else
276
+ false
277
+ end
278
+ end
279
+
280
+ if matching_comments.empty?
281
+ client.create_pull_request_comment(ci_source.repo_slug, ci_source.pull_request_id,
282
+ body, head_ref, m.file, position)
283
+ else
284
+ # Remove the surviving comment so we don't strike it out
285
+ danger_comments.reject! { |c| matching_comments.include? c }
286
+
287
+ # Update the comment to remove the strikethrough if present
288
+ comment = matching_comments.first
289
+ client.update_pull_request_comment(ci_source.repo_slug, comment[:id], body)
290
+ end
291
+
292
+ # Remove this element from the array
293
+ next true
294
+ end
295
+
296
+ messages.reject!(&submit_inline)
297
+ end
298
+
299
+ def find_position_in_diff(diff_lines, message)
300
+ range_header_regexp = /@@ -([0-9]+),([0-9]+) \+(?<start>[0-9]+)(,(?<end>[0-9]+))? @@.*/
301
+ file_header_regexp = %r{ a/.*}
302
+
303
+ pattern = "+++ b/" + message.file + "\n"
304
+ file_start = diff_lines.index(pattern)
305
+
306
+ return nil if file_start.nil?
307
+
308
+ position = -1
309
+ file_line = nil
310
+
311
+ diff_lines.drop(file_start).each do |line|
312
+ match = line.match range_header_regexp
313
+
314
+ # file_line is set once we find the hunk the line is in
315
+ # we need to count how many lines in new file we have
316
+ # so we do it one by one ignoring the deleted lines
317
+ if !file_line.nil? && !line.start_with?("-")
318
+ break if file_line == message.line
319
+ file_line += 1
320
+ end
321
+
322
+ # We need to count how many diff lines are between us and
323
+ # the line we're looking for
324
+ position += 1
325
+
326
+ next unless match
327
+
328
+ # If we found the start of another file diff, we went too far
329
+ break if line.match file_header_regexp
330
+
331
+ range_start = match[:start].to_i
332
+ if match[:end]
333
+ range_end = match[:end].to_i + range_start
334
+ else
335
+ range_end = range_start
336
+ end
337
+
338
+ # We are past the line position, just abort
339
+ break if message.line < range_start
340
+ next unless message.line >= range_start && message.line <= range_end
341
+
342
+ file_line = range_start
343
+ end
344
+
345
+ position unless file_line.nil?
346
+ end
347
+
348
+ # See the tests for examples of data coming in looks like
349
+ def parse_message_from_row(row)
350
+ message_regexp = %r{(<(a |span data-)href="https://github.com/#{ci_source.repo_slug}/blob/[0-9a-z]+/(?<file>[^#]+)#L(?<line>[0-9]+)"(>[^<]*</a> - |/>))?(?<message>.*?)}im
351
+ match = message_regexp.match(row)
352
+
353
+ if match[:line]
354
+ line = match[:line].to_i
355
+ else
356
+ line = nil
357
+ end
358
+ Violation.new(row, true, match[:file], line)
359
+ end
360
+
361
+ def markdown_link_to_message(message, hide_link)
362
+ url = "https://github.com/#{ci_source.repo_slug}/blob/#{pr_json[:head][:sha]}/#{message.file}#L#{message.line}"
363
+
364
+ if hide_link
365
+ "<span data-href=\"#{url}\"/>"
366
+ else
367
+ "[#{message.file}#L#{message.line}](#{url}) - "
368
+ end
369
+ end
370
+
173
371
  # @return [String] The organisation name, is nil if it can't be detected
174
372
  def organisation
175
373
  matched = self.issue_json[:repository_url].match(%r{repos\/(.*)\/})
@@ -15,7 +15,10 @@ module Danger
15
15
  end
16
16
 
17
17
  def exec(string)
18
- `git #{string}`.strip
18
+ require "open3"
19
+ Open3.popen2(default_env, "git #{string}") do |_stdin, stdout, _wait_thr|
20
+ stdout.read.rstrip
21
+ end
19
22
  end
20
23
 
21
24
  def head_commit
@@ -25,6 +28,12 @@ module Danger
25
28
  def origins
26
29
  exec("remote show origin -n").lines.grep(/Fetch URL/)[0].split(": ", 2)[1].chomp
27
30
  end
31
+
32
+ private
33
+
34
+ def default_env
35
+ { "LANG" => "en_US.UTF-8" }
36
+ end
28
37
  end
29
38
  end
30
39
 
@@ -1,4 +1,4 @@
1
1
  module Danger
2
- VERSION = "3.1.1".freeze
2
+ VERSION = "3.2.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: 3.1.1
4
+ version: 3.2.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: 2016-09-01 00:00:00.000000000 Z
12
+ date: 2016-09-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: claide
@@ -376,6 +376,7 @@ files:
376
376
  - lib/danger/ci_source/teamcity.rb
377
377
  - lib/danger/ci_source/travis.rb
378
378
  - lib/danger/ci_source/xcode_server.rb
379
+ - lib/danger/clients/rubygems_client.rb
379
380
  - lib/danger/commands/init.rb
380
381
  - lib/danger/commands/init_helpers/interviewer.rb
381
382
  - lib/danger/commands/local.rb
@@ -385,7 +386,9 @@ files:
385
386
  - lib/danger/commands/plugins/plugin_readme.rb
386
387
  - lib/danger/commands/runner.rb
387
388
  - lib/danger/commands/systems.rb
389
+ - lib/danger/comment_generators/bitbucket_server.md.erb
388
390
  - lib/danger/comment_generators/github.md.erb
391
+ - lib/danger/comment_generators/github_inline.md.erb
389
392
  - lib/danger/comment_generators/gitlab.md.erb
390
393
  - lib/danger/core_ext/file_list.rb
391
394
  - lib/danger/core_ext/string.rb
@@ -393,19 +396,24 @@ files:
393
396
  - lib/danger/danger_core/dangerfile_dsl.rb
394
397
  - lib/danger/danger_core/environment_manager.rb
395
398
  - lib/danger/danger_core/executor.rb
399
+ - lib/danger/danger_core/messages/markdown.rb
400
+ - lib/danger/danger_core/messages/violation.rb
401
+ - lib/danger/danger_core/plugins/dangerfile_bitbucket_server_plugin.rb
396
402
  - lib/danger/danger_core/plugins/dangerfile_danger_plugin.rb
397
403
  - lib/danger/danger_core/plugins/dangerfile_git_plugin.rb
398
404
  - lib/danger/danger_core/plugins/dangerfile_github_plugin.rb
399
405
  - lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb
400
406
  - lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb
401
407
  - lib/danger/danger_core/standard_error.rb
402
- - lib/danger/danger_core/violation.rb
403
408
  - lib/danger/helpers/comments_helper.rb
409
+ - lib/danger/helpers/comments_parsing_helper.rb
404
410
  - lib/danger/plugin_support/plugin.rb
405
411
  - lib/danger/plugin_support/plugin_file_resolver.rb
406
412
  - lib/danger/plugin_support/plugin_linter.rb
407
413
  - lib/danger/plugin_support/plugin_parser.rb
408
414
  - lib/danger/plugin_support/templates/readme_table.html.erb
415
+ - lib/danger/request_source/bitbucket_server.rb
416
+ - lib/danger/request_source/bitbucket_server_api.rb
409
417
  - lib/danger/request_source/github.rb
410
418
  - lib/danger/request_source/gitlab.rb
411
419
  - lib/danger/request_source/request_source.rb
@@ -431,8 +439,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
431
439
  version: '0'
432
440
  requirements: []
433
441
  rubyforge_project:
434
- rubygems_version: 2.2.2
442
+ rubygems_version: 2.4.8
435
443
  signing_key:
436
444
  specification_version: 4
437
445
  summary: Like Unit Tests, but for your Team Culture.
438
446
  test_files: []
447
+ has_rdoc:
@@ -1,10 +0,0 @@
1
- module Danger
2
- class Violation
3
- attr_accessor :message, :sticky
4
-
5
- def initialize(message, sticky)
6
- self.message = message
7
- self.sticky = sticky
8
- end
9
- end
10
- end