danger 3.1.1 → 3.2.0

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