danger 6.3.1 → 8.0.1

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: c582d1189749d620a69040b53f7fc73c581efd772c39ab7931cd7ed886522fe8
4
- data.tar.gz: 59a22185cdd2cd993ceef9f01934e7757fa6f4f4cd4229e76634bca4b13dd5b1
3
+ metadata.gz: 5a19704ff72b8d86ac14c19bed3ccd7276c0e0c724200439b5d3a39625409c09
4
+ data.tar.gz: a4b77b3e810f26f237b2057ca4214f21b6108630036aeb61228d3f2ae2e4f163
5
5
  SHA512:
6
- metadata.gz: 9e6f93dae26ee18d328f10d1287c4dc0127b3199e8b055992795d4440180625010b7ecb5d229241b4d415918846cdecd9d46d214fc51b66ed43c399203ce0490
7
- data.tar.gz: f0ea9446eadaa3705337982b51bf1abd49ca96e9ddecf14b888c69b48cad6ceec7ff5d59cd48f18061d8128c6c12c3cc9be12d8149461eedc8d6388f65e17179
6
+ metadata.gz: c4e2eb7edc403b2ef01c2d84cf2dabacdd79426a903084e9a22d8d3506bf0519721eebfe18cff7415d83ffce992b8cb7a416661d8c2da6880dbe0f2871d19c2a
7
+ data.tar.gz: 077f053cadf83b885c866329512426e0d8d7e31c6259b59c3e726532d3bddf28dca78c4aa2d760f20bed13ab7f116e4f736a276c7ab5c4793123572229fe4b0d
@@ -21,7 +21,7 @@ module Danger
21
21
  #
22
22
  # ### bitbucket server and bitrsie
23
23
  #
24
- # Danger will read the environemnt variable GIT_REPOSITORY_URL to construct the Bitbucket Server API URL
24
+ # Danger will read the environment variable GIT_REPOSITORY_URL to construct the Bitbucket Server API URL
25
25
  # finding the project and repo slug in the GIT_REPOSITORY_URL variable. This GIT_REPOSITORY_URL variable
26
26
  # comes from the App Settings tab for your Bitrsie App. If you are manually setting a repo URL in the
27
27
  # Git Clone Repo step, you may need to set adjust this propery in the settings tab, maybe even fake it.
@@ -48,13 +48,16 @@ module Danger
48
48
  def initialize(env)
49
49
  self.pull_request_id = env["BITRISE_PULL_REQUEST"]
50
50
  self.repo_url = env["GIT_REPOSITORY_URL"]
51
-
52
- if repo_url.include? ".com/"
53
- repo_matches = self.repo_url.match(%r{\.com/(.*)})[1].split(/\.git$|$/)[0]
54
- elsif repo_url.include? ".com:"
55
- repo_matches = self.repo_url.match(%r{\.com:(.*)})[1].split(/\.git$|$/)[0]
56
- end
57
51
 
52
+ matcher_url = self.repo_url
53
+
54
+ #If the URL contains https:// as :// leads to inaccurate matching. So we remove it and proceed to match.
55
+ if repo_url.include? "https://"
56
+ matcher_url["https://"] = ''
57
+ end
58
+
59
+ repo_matches = matcher_url.match(%r{([\/:])(([^\/]+\/)+[^\/]+?)(\.git$|$)})[2]
60
+
58
61
  self.repo_slug = repo_matches unless repo_matches.nil?
59
62
 
60
63
  end
@@ -3,25 +3,18 @@ require "danger/request_sources/github/github"
3
3
  module Danger
4
4
  # ### CI Setup
5
5
  #
6
- # You can use `danger/danger` Action in your .github/main.workflow.
6
+ # You can use `danger/danger` Action in your `.github/workflows/xxx.yml`.
7
+ # And so, you can use GITHUB_TOKEN secret as `DANGER_GITHUB_API_TOKEN` environment variable.
7
8
  #
8
9
  # ```
9
- # action "Danger" {
10
- # uses = "danger/danger"
11
- # }
10
+ # ...
11
+ # steps:
12
+ # - uses: actions/checkout@v1
13
+ # - uses: danger/danger@master
14
+ # env:
15
+ # DANGER_GITHUB_API_TOKEN: ${{ secrets.GITHUB_TOKEN }}
12
16
  # ```
13
17
  #
14
- # ### Token Setup
15
- #
16
- # Set DANGER_GITHUB_API_TOKEN to secrets, or you can also use GITHUB_TOKEN.
17
- #
18
- # ```
19
- # action "Danger" {
20
- # uses = "danger/danger"
21
- # secrets = ["GITHUB_TOKEN"]
22
- # }
23
- # ```
24
- #
25
18
  class GitHubActions < CI
26
19
  def self.validates_as_ci?(env)
27
20
  env.key? "GITHUB_ACTION"
@@ -66,12 +66,15 @@ module Danger
66
66
 
67
67
  private
68
68
 
69
+ # this method is a duplicate of Commands::PR#configure_octokit
70
+ # - worth a refactor sometime?
69
71
  def configure_octokit(cache_dir)
70
72
  # setup caching for Github calls to hitting the API rate limit too quickly
71
73
  cache_file = File.join(cache_dir, "danger_local_cache")
72
74
  cache = HTTPCache.new(cache_file, clear_cache: @clear_http_cache)
73
75
  Octokit.middleware = Faraday::RackBuilder.new do |builder|
74
76
  builder.use Faraday::HttpCache, store: cache, serializer: Marshal, shared_cache: false
77
+ builder.use Octokit::Middleware::FollowRedirects
75
78
  builder.use Octokit::Response::RaiseError
76
79
  builder.adapter Faraday.default_adapter
77
80
  end
@@ -83,6 +83,7 @@ module Danger
83
83
  end
84
84
  Octokit.middleware = Faraday::RackBuilder.new do |builder|
85
85
  builder.use Faraday::HttpCache, store: cache, serializer: Marshal, shared_cache: false
86
+ builder.use Octokit::Middleware::FollowRedirects
86
87
  builder.use Octokit::Response::RaiseError
87
88
  builder.adapter Faraday.default_adapter
88
89
  end
@@ -2,7 +2,7 @@
2
2
  <%- table[:content].each do |violation| -%>
3
3
  <%= @emoji_mapper.map(table[:emoji]) if table[:emoji] %> <%= violation.message %>
4
4
  <%- end -%>
5
- <%- table[:resolved].each do |message| -%>
5
+ <%- table[:resolved] && table[:resolved].each do |message| -%>
6
6
  <%= @emoji_mapper.map("white_check_mark") %> <%= message %>
7
7
  <%- end -%>
8
8
  <%- end -%>
@@ -0,0 +1,12 @@
1
+ <%- @message_group.messages.each do |message| -%>
2
+ <%- next if message.type == :markdown -%>
3
+ <%= @emoji_mapper.from_type(message.type) -%> <%= message.message %>
4
+
5
+ <%- end -%>
6
+ <%- @resolved.each do |message| -%>
7
+ <%= @emoji_mapper.map("white_check_mark") %> <%= message %>
8
+ <%- end -%>
9
+
10
+ <%= @message_group.markdowns.map(&:message).join("\n\n") %>
11
+
12
+ Generated by :no_entry_sign: [Danger](https://danger.systems/ "generated_by_<%= @danger_id %>")
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "danger/danger_core/dangerfile_dsl"
4
4
  require "danger/danger_core/standard_error"
5
+ require "danger/danger_core/message_aggregator"
5
6
 
6
7
  require "danger/danger_core/plugins/dangerfile_messaging_plugin"
7
8
  require "danger/danger_core/plugins/dangerfile_danger_plugin"
@@ -60,7 +61,6 @@ module Danger
60
61
  # that the core DSLs have, then starts looking at plugins support.
61
62
 
62
63
  # rubocop:disable Style/MethodMissing
63
-
64
64
  def method_missing(method_sym, *arguments, &_block)
65
65
  @core_plugins.each do |plugin|
66
66
  if plugin.public_methods(false).include?(method_sym)
@@ -243,16 +243,26 @@ module Danger
243
243
 
244
244
  def post_results(danger_id, new_comment, remove_previous_comments)
245
245
  violations = violation_report
246
-
247
- env.request_source.update_pull_request!(
248
- warnings: violations[:warnings].uniq,
249
- errors: violations[:errors].uniq,
250
- messages: violations[:messages].uniq,
251
- markdowns: status_report[:markdowns].uniq,
252
- danger_id: danger_id,
253
- new_comment: new_comment,
254
- remove_previous_comments: remove_previous_comments
255
- )
246
+ report = {
247
+ warnings: violations[:warnings].uniq,
248
+ errors: violations[:errors].uniq,
249
+ messages: violations[:messages].uniq,
250
+ markdowns: status_report[:markdowns].uniq,
251
+ danger_id: danger_id
252
+ }
253
+
254
+ if env.request_source.respond_to?(:update_pr_by_line!) && ENV["DANGER_MESSAGE_AGGREGATION"]
255
+ env.request_source.update_pr_by_line!(message_groups: MessageAggregator.aggregate(**report),
256
+ new_comment: new_comment,
257
+ remove_previous_comments: remove_previous_comments,
258
+ danger_id: report[:danger_id])
259
+ else
260
+ env.request_source.update_pull_request!(
261
+ **report,
262
+ new_comment: new_comment,
263
+ remove_previous_comments: remove_previous_comments
264
+ )
265
+ end
256
266
  end
257
267
 
258
268
  def setup_for_running(base_branch, head_branch)
@@ -63,7 +63,7 @@ module Danger
63
63
  unless EnvironmentManager.pr?(system_env)
64
64
  ci_name = EnvironmentManager.local_ci_source(system_env).name.split("::").last
65
65
 
66
- msg = "Not a #{ci_name} Pull Request - skipping `danger` run. "
66
+ msg = "Not a #{ci_name} #{commit_request(ci_name)} - skipping `danger` run. "
67
67
  # circle won't run danger properly if the commit is pushed and build runs before the PR exists
68
68
  # https://danger.systems/guides/troubleshooting.html#circle-ci-doesnt-run-my-build-consistently
69
69
  # the best solution is to enable `fail_if_no_pr`, and then re-run the job once the PR is up
@@ -83,5 +83,10 @@ module Danger
83
83
  def head_branch(user_specified_head_branch)
84
84
  user_specified_head_branch || EnvironmentManager.danger_head_branch
85
85
  end
86
+
87
+ def commit_request(ci_name)
88
+ return "Merge Request" if ci_name == 'GitLabCI'
89
+ return "Pull Request"
90
+ end
86
91
  end
87
92
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+ require "danger/danger_core/message_group"
3
+ require "danger/helpers/message_groups_array_helper"
4
+
5
+ module Danger
6
+ class MessageAggregator
7
+ def self.aggregate(*args)
8
+ new(*args).aggregate
9
+ end
10
+
11
+ def initialize(warnings: [],
12
+ errors: [],
13
+ messages: [],
14
+ markdowns: [],
15
+ danger_id: "danger")
16
+ @messages = warnings + errors + messages + markdowns
17
+ @danger_id = danger_id
18
+ end
19
+
20
+ # aggregates the messages into an array of MessageGroups
21
+ # @return [[MessageGroup]]
22
+ def aggregate
23
+ # oookay I took some shortcuts with this one.
24
+ # first, sort messages by file and line
25
+ @messages.sort! { |a, b| a.compare_by_file_and_line(b) }
26
+
27
+ # now create an initial empty message group
28
+ first_group = MessageGroup.new(file: nil,
29
+ line: nil)
30
+ @message_groups = @messages.reduce([first_group]) do |groups, msg|
31
+ # We get to take a shortcut because we sorted the messages earlier - only
32
+ # have to see if we can append msg to the last group in the list
33
+ if groups.last << msg
34
+ # we appended it, so return groups unchanged
35
+ groups
36
+ else
37
+ # have to create a new group since msg wasn't appended to the other
38
+ # group
39
+ new_group = MessageGroup.new(file: msg.file,
40
+ line: msg.line)
41
+ new_group << msg
42
+ groups << new_group
43
+ end
44
+ end
45
+
46
+ @message_groups.extend(Helpers::MessageGroupsArrayHelper)
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,68 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Danger
4
+ class MessageGroup
5
+ def initialize(file: nil, line: nil)
6
+ @file = file
7
+ @line = line
8
+ end
9
+
10
+ # Returns whether this `MessageGroup` is for the same line of code as
11
+ # `other`, taking which file they are in to account.
12
+ # @param other [MessageGroup, Markdown, Violation]
13
+ # @return [Boolean] whether this `MessageGroup` is for the same line of code
14
+ def same_line?(other)
15
+ other.file == file && other.line == line
16
+ end
17
+
18
+ # Merges two `MessageGroup`s that represent the same line of code
19
+ # In future, perhaps `MessageGroup` will be able to represent a group of
20
+ # messages for multiple lines.
21
+ def merge(other)
22
+ raise ArgumentError, "Cannot merge with MessageGroup for a different line" unless same_line?(other)
23
+
24
+ @messages = (messages + other.messages).uniq
25
+ end
26
+
27
+ # Adds a message to the group.
28
+ # @param message [Markdown, Violation] the message to add
29
+ def <<(message)
30
+ # TODO: insertion sort
31
+ return nil unless same_line?(message)
32
+
33
+ inserted = false
34
+ messages.each.with_index do |other, idx|
35
+ if (message <=> other) == -1
36
+ inserted = true
37
+ messages.insert(idx, message)
38
+ break
39
+ end
40
+ end
41
+ messages << message unless inserted
42
+ messages
43
+ end
44
+
45
+ # The list of messages in this group. This list will be sorted in decreasing
46
+ # order of severity (error, warning, message, markdown)
47
+ def messages
48
+ @messages ||= []
49
+ end
50
+
51
+ attr_reader :file, :line
52
+
53
+ # @return a hash of statistics. Currently only :warnings_count and
54
+ # :errors_count
55
+ def stats
56
+ stats = { warnings_count: 0, errors_count: 0 }
57
+ messages.each do |msg|
58
+ stats[:warnings_count] += 1 if msg.type == :warning
59
+ stats[:errors_count] += 1 if msg.type == :error
60
+ end
61
+ stats
62
+ end
63
+
64
+ def markdowns
65
+ messages.select { |x| x.type == :markdown }
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,56 @@
1
+ module Danger
2
+ class BaseMessage
3
+ attr_accessor :message, :file, :line, :type
4
+
5
+ def initialize(type:, message:, file: nil, line: nil)
6
+ @type = type
7
+ @message = message
8
+ @file = file
9
+ @line = line
10
+ end
11
+
12
+ def compare_by_file_and_line(other)
13
+ order = cmp_nils(file, other.file)
14
+ return order unless order.nil?
15
+
16
+ order = file <=> other.file
17
+ return order unless order.zero?
18
+
19
+ order = cmp_nils(line, other.line)
20
+ return order unless order.nil?
21
+
22
+ line <=> other.line
23
+ end
24
+ # compares a and b based entirely on whether one or the other is nil
25
+ # arguments are in the same order as `a <=> b`
26
+ # nil is sorted earlier - so cmp_nils(nil, 1) => -1
27
+ #
28
+ # If neither are nil, rather than returning `a <=> b` which would seem
29
+ # like the obvious shortcut, `nil` is returned.
30
+ # This allows us to distinguish between cmp_nils returning 0 for a
31
+ # comparison of filenames, which means "a comparison on the lines is
32
+ # meaningless - you cannot have a line number for a nil file - so they
33
+ # should be sorted the same", and a <=> b returning 0, which means "the
34
+ # files are the same, so compare on the lines"
35
+ #
36
+ # @return 0, 1, -1, or nil
37
+ def cmp_nils(a, b)
38
+ if a.nil? && b.nil?
39
+ 0
40
+ elsif a.nil?
41
+ -1
42
+ elsif b.nil?
43
+ 1
44
+ end
45
+ end
46
+
47
+ def eql?(other)
48
+ return self == other
49
+ end
50
+
51
+ # @return [Boolean] returns true if is a file or line, false otherwise
52
+ def inline?
53
+ file || line
54
+ end
55
+ end
56
+ end
@@ -1,11 +1,11 @@
1
+ # frozen_string_literal: true
2
+ require "danger/danger_core/messages/base"
3
+
1
4
  module Danger
2
- class Markdown
3
- attr_accessor :message, :file, :line
5
+ class Markdown < BaseMessage
4
6
 
5
7
  def initialize(message, file = nil, line = nil)
6
- self.message = message
7
- self.file = file
8
- self.line = line
8
+ super(type: :markdown, message: message, file: file, line: line)
9
9
  end
10
10
 
11
11
  def ==(other)
@@ -25,15 +25,6 @@ module Danger
25
25
  h
26
26
  end
27
27
 
28
- def eql?(other)
29
- return self == other
30
- end
31
-
32
- # @return [Boolean] returns true if is a file or line, false otherwise
33
- def inline?
34
- file || line
35
- end
36
-
37
28
  def to_s
38
29
  extra = []
39
30
  extra << "file: #{file}" unless file
@@ -41,5 +32,11 @@ module Danger
41
32
 
42
33
  "Markdown #{message} { #{extra.join ', '.freeze} }"
43
34
  end
35
+
36
+ def <=>(other)
37
+ return 1 if other.type != :markdown
38
+
39
+ compare_by_file_and_line(other)
40
+ end
44
41
  end
45
42
  end
@@ -1,12 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "danger/danger_core/messages/base"
4
+
1
5
  module Danger
2
- class Violation
3
- attr_accessor :message, :sticky, :file, :line
6
+ class Violation < BaseMessage
7
+ VALID_TYPES = %I[error warning message].freeze
8
+ attr_accessor :sticky
4
9
 
5
- def initialize(message, sticky, file = nil, line = nil)
6
- self.message = message
10
+ def initialize(message, sticky, file = nil, line = nil, type: :warning)
11
+ raise ArgumentError unless VALID_TYPES.include?(type)
12
+
13
+ super(type: type, message: message, file: file, line: line)
7
14
  self.sticky = sticky
8
- self.file = file
9
- self.line = line
10
15
  end
11
16
 
12
17
  def ==(other)
@@ -28,13 +33,12 @@ module Danger
28
33
  h
29
34
  end
30
35
 
31
- def eql?(other)
32
- return self == other
33
- end
36
+ def <=>(other)
37
+ types = VALID_TYPES + [:markdown]
38
+ order = types.index(type) <=> types.index(other.type)
39
+ return order unless order.zero?
34
40
 
35
- # @return [Boolean] returns true if is a file or line, false otherwise
36
- def inline?
37
- file || line
41
+ compare_by_file_and_line(other)
38
42
  end
39
43
 
40
44
  def to_s
@@ -42,8 +46,9 @@ module Danger
42
46
  extra << "sticky: #{sticky}"
43
47
  extra << "file: #{file}" if file
44
48
  extra << "line: #{line}" if line
49
+ extra << "type: #{type}"
45
50
 
46
- "Violation #{message} { #{extra.join ', '.freeze} }"
51
+ "Violation #{message} { #{extra.join ', '} }"
47
52
  end
48
53
  end
49
54
  end
@@ -136,14 +136,14 @@ module Danger
136
136
  # @return [Hash] with keys `:insertions`, `:deletions` giving line counts, and `:before`, `:after` giving file contents, or nil if the file has no changes or does not exist
137
137
  #
138
138
  def info_for_file(file)
139
- return nil unless modified_files.include?(file)
139
+ return nil unless modified_files.include?(file) || added_files.include?(file) || deleted_files.include?(file)
140
140
  stats = @git.diff.stats[:files][file]
141
141
  diff = @git.diff[file]
142
142
  {
143
143
  insertions: stats[:insertions],
144
144
  deletions: stats[:deletions],
145
- before: diff.blob(:src).contents,
146
- after: diff.blob(:dst).contents
145
+ before: added_files.include?(file) || deleted_files.include?(file) ? nil : diff.blob(:src).contents,
146
+ after: added_files.include?(file) || deleted_files.include?(file) ? nil : diff.blob(:dst).contents
147
147
  }
148
148
  end
149
149
 
@@ -124,7 +124,7 @@ module Danger
124
124
  line = options.fetch(:line, nil)
125
125
 
126
126
  messages.flatten.each do |message|
127
- @messages << Violation.new(message, sticky, file, line) if message
127
+ @messages << Violation.new(message, sticky, file, line, type: :message) if message
128
128
  end
129
129
  end
130
130
 
@@ -149,7 +149,7 @@ module Danger
149
149
 
150
150
  warnings.flatten.each do |warning|
151
151
  next if should_ignore_violation(warning)
152
- @warnings << Violation.new(warning, sticky, file, line) if warning
152
+ @warnings << Violation.new(warning, sticky, file, line, type: :warning) if warning
153
153
  end
154
154
  end
155
155
 
@@ -174,7 +174,7 @@ module Danger
174
174
 
175
175
  failures.flatten.each do |failure|
176
176
  next if should_ignore_violation(failure)
177
- @errors << Violation.new(failure, sticky, file, line) if failure
177
+ @errors << Violation.new(failure, sticky, file, line, type: :error) if failure
178
178
  end
179
179
  end
180
180
 
@@ -76,7 +76,7 @@ module Danger
76
76
  }
77
77
  end
78
78
 
79
- def apply_template(tables: [], markdowns: [], danger_id: "danger", template: "github")
79
+ def apply_template(tables: [], markdowns: [], danger_id: "danger", template: "github", request_source: template)
80
80
  require "erb"
81
81
 
82
82
  md_template = File.join(Danger.gem_path, "lib/danger/comment_generators/#{template}.md.erb")
@@ -86,7 +86,7 @@ module Danger
86
86
  @tables = tables
87
87
  @markdowns = markdowns.map(&:message)
88
88
  @danger_id = danger_id
89
- @emoji_mapper = EmojiMapper.new(template)
89
+ @emoji_mapper = EmojiMapper.new(request_source.sub("_inline",""))
90
90
 
91
91
  return ERB.new(File.read(md_template), 0, "-").result(binding)
92
92
  end
@@ -104,7 +104,32 @@ module Danger
104
104
  )
105
105
  end
106
106
 
107
- def generate_inline_comment_body(emoji, message, danger_id: "danger", resolved: [], template: "github")
107
+ # resolved is essentially reserved for future use - eventually we might
108
+ # have some nice generic resolved-thing going :)
109
+ def generate_message_group_comment(message_group:,
110
+ danger_id: "danger",
111
+ resolved: [],
112
+ template: "github")
113
+ # cheating a bit - I don't want to alter the apply_template API
114
+ # so just sneak around behind its back setting some instance variables
115
+ # to get them to show up in the template
116
+ @message_group = message_group
117
+ @resolved = resolved
118
+ request_source_name = template.sub("_message_group", "")
119
+
120
+
121
+ apply_template(danger_id: danger_id,
122
+ markdowns: message_group.markdowns,
123
+ template: template,
124
+ request_source: request_source_name)
125
+ .sub(/\A\n*/, "")
126
+ end
127
+
128
+ def generate_inline_comment_body(emoji,
129
+ message,
130
+ danger_id: "danger",
131
+ resolved: false,
132
+ template: "github")
108
133
  apply_template(
109
134
  tables: [{ content: [message], resolved: resolved, emoji: emoji }],
110
135
  danger_id: danger_id,
@@ -15,16 +15,25 @@ module Danger
15
15
  }
16
16
  }.freeze
17
17
 
18
+ TYPE_TO_EMOJI = {
19
+ error: "no_entry_sign",
20
+ warning: "warning",
21
+ message: "book",
22
+ }.freeze
23
+
18
24
  def initialize(template)
19
- template.sub!('_inline', '')
20
25
  @template = DATA.has_key?(template) ? template : "github"
21
26
  end
22
27
 
23
28
  def map(emoji)
24
- emoji.delete! ":"
29
+ emoji&.delete! ":"
25
30
  DATA[template][emoji]
26
31
  end
27
32
 
33
+ def from_type(type)
34
+ map(TYPE_TO_EMOJI[type])
35
+ end
36
+
28
37
  private
29
38
 
30
39
  attr_reader :template
@@ -0,0 +1,31 @@
1
+ module Danger
2
+ module Helpers
3
+ module MessageGroupsArrayHelper
4
+ FakeArray = Struct.new(:count) do
5
+ def empty?
6
+ count.zero?
7
+ end
8
+ end
9
+
10
+ def fake_warnings_array
11
+ FakeArray.new(counts[:warnings])
12
+ end
13
+
14
+ def fake_errors_array
15
+ FakeArray.new(counts[:errors])
16
+ end
17
+
18
+ def counts
19
+ return @counts if @counts
20
+
21
+ @counts = { warnings: 0, errors: 0 }
22
+ each do |message_group, counts|
23
+ group_stats = message_group.stats
24
+ @counts[:warnings] += group_stats[:warnings_count]
25
+ @counts[:errors] += group_stats[:errors_count]
26
+ end
27
+ @counts
28
+ end
29
+ end
30
+ end
31
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "danger/helpers/comments_helper"
4
4
  require "danger/request_sources/bitbucket_cloud_api"
5
+ require "danger/danger_core/message_group"
5
6
 
6
7
  module Danger
7
8
  module RequestSources
@@ -12,6 +13,7 @@ module Danger
12
13
  def self.env_vars
13
14
  [
14
15
  "DANGER_BITBUCKETCLOUD_USERNAME",
16
+ "DANGER_BITBUCKETCLOUD_UUID",
15
17
  "DANGER_BITBUCKETCLOUD_PASSWORD"
16
18
  ]
17
19
  end
@@ -90,6 +92,50 @@ module Danger
90
92
  @api.post_comment(comment)
91
93
  end
92
94
 
95
+ def update_pr_by_line!(message_groups:,
96
+ danger_id: "danger",
97
+ new_comment: false,
98
+ remove_previous_comments: false)
99
+ if !new_comment || remove_previous_comments
100
+ delete_old_comments(danger_id: danger_id)
101
+ end
102
+
103
+ summary_body = generate_description(warnings: message_groups.fake_warnings_array,
104
+ errors: message_groups.fake_errors_array,
105
+ template: "bitbucket_server")
106
+ summary_body += "\n\n"
107
+
108
+
109
+ # this isn't the most elegant thing in the world, but we need the group
110
+ # with file: nil, line: nil so we can combine its info in with the
111
+ # summary_body
112
+ summary_group = message_groups.first
113
+ if summary_group && summary_group.file.nil? && summary_group.line.nil?
114
+ # remove summary_group from message_groups so it doesn't get a
115
+ # duplicate comment posted in the message_groups loop below
116
+ message_groups.shift
117
+ else
118
+ summary_group = MessageGroup.new(file: nil, line: nil)
119
+ end
120
+
121
+ summary_body += generate_message_group_comment(
122
+ message_group: summary_group,
123
+ danger_id: danger_id,
124
+ template: "bitbucket_server_message_group"
125
+ )
126
+
127
+ @api.post_comment(summary_body)
128
+
129
+ message_groups.each do |message_group|
130
+ body = generate_message_group_comment(message_group: message_group,
131
+ danger_id: danger_id,
132
+ template: "bitbucket_server_message_group")
133
+ @api.post_comment(body,
134
+ file: message_group.file,
135
+ line: message_group.line)
136
+ end
137
+ end
138
+
93
139
  def update_inline_comments_for_kind!(kind, messages, danger_id: "danger")
94
140
  emoji = { warnings: "warning", errors: "no_entry_sign", messages: "book" }[kind]
95
141
 
@@ -6,8 +6,10 @@ module Danger
6
6
  module RequestSources
7
7
  class BitbucketCloudAPI
8
8
  attr_accessor :host, :project, :slug, :access_token, :pull_request_id
9
+ attr_reader :my_uuid
9
10
 
10
11
  def initialize(repo_slug, pull_request_id, branch_name, environment)
12
+ initialize_my_uuid(environment["DANGER_BITBUCKETCLOUD_UUID"])
11
13
  @username = environment["DANGER_BITBUCKETCLOUD_USERNAME"]
12
14
  @password = environment["DANGER_BITBUCKETCLOUD_PASSWORD"]
13
15
  self.project, self.slug = repo_slug.split("/")
@@ -16,6 +18,17 @@ module Danger
16
18
  self.host = "https://bitbucket.org/"
17
19
  end
18
20
 
21
+ def initialize_my_uuid(uuid)
22
+ return if uuid.nil?
23
+ return @my_uuid = uuid if uuid.empty?
24
+
25
+ if uuid.start_with?("{") && uuid.end_with?("}")
26
+ @my_uuid = uuid
27
+ else
28
+ @my_uuid = "{#{uuid}}"
29
+ end
30
+ end
31
+
19
32
  def inspect
20
33
  inspected = super
21
34
 
@@ -27,12 +40,9 @@ module Danger
27
40
  end
28
41
 
29
42
  def credentials_given?
30
- @username && !@username.empty? && @password && !@password.empty?
31
- end
32
-
33
- def my_uuid
34
- uri = URI("https://api.bitbucket.org/2.0/users/#{@username}")
35
- @my_uuid ||= fetch_json(uri)[:uuid]
43
+ @my_uuid && !@my_uuid.empty? &&
44
+ @username && !@username.empty? &&
45
+ @password && !@password.empty?
36
46
  end
37
47
 
38
48
  def pull_request(*)
@@ -157,7 +167,9 @@ module Danger
157
167
  end
158
168
 
159
169
  def credentials_not_available
160
- "Credentials not available. Provide DANGER_BITBUCKETCLOUD_USERNAME and DANGER_BITBUCKETCLOUD_PASSWORD as environment variables."
170
+ "Credentials not available. Provide DANGER_BITBUCKETCLOUD_USERNAME, " \
171
+ "DANGER_BITBUCKETCLOUD_UUID, and DANGER_BITBUCKETCLOUD_PASSWORD " \
172
+ "as environment variables."
161
173
  end
162
174
 
163
175
  def error_fetching_json(url, status_code)
@@ -170,7 +170,7 @@ module Danger
170
170
  rest_inline_violations = submit_inline_comments!({
171
171
  danger_id: danger_id,
172
172
  previous_violations: previous_violations
173
- }.merge(inline_violations))
173
+ }.merge(**inline_violations))
174
174
 
175
175
  main_violations = merge_violations(
176
176
  regular_violations, rest_inline_violations
@@ -189,7 +189,7 @@ module Danger
189
189
  template: "github",
190
190
  danger_id: danger_id,
191
191
  previous_violations: previous_violations
192
- }.merge(main_violations))
192
+ }.merge(**main_violations))
193
193
 
194
194
  comment_result =
195
195
  if should_create_new_comment
@@ -50,7 +50,11 @@ module Danger
50
50
  includes_port = self.host.include? ":"
51
51
  raise "Port number included in `DANGER_GITLAB_HOST`, this will fail with GitLab CI Runners" if includes_port
52
52
 
53
- super
53
+ # We don't call super because in some cases the Git remote doesn't match the GitLab instance host.
54
+ # In Danger::EnvironmentManager#initialize we still check that the request source is #validates_as_api_source?
55
+ # so that should be sufficient to validate GitLab as request source.
56
+ # See https://github.com/danger/danger/issues/1231 and https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/10069.
57
+ true
54
58
  end
55
59
 
56
60
  def validates_as_api_source?
@@ -302,6 +306,9 @@ module Danger
302
306
  def file_url(organisation: nil, repository: nil, branch: nil, path: nil)
303
307
  branch ||= 'master'
304
308
  token = @environment["DANGER_GITLAB_API_TOKEN"]
309
+ # According to GitLab Repositories API docs path and id(slug) should be encoded.
310
+ path = URI.encode_www_form_component(path)
311
+ repository = URI.encode_www_form_component(repository)
305
312
  "#{endpoint}/projects/#{repository}/repository/files/#{path}/raw?ref=#{branch}&private_token=#{token}"
306
313
  end
307
314
 
@@ -1,4 +1,4 @@
1
1
  module Danger
2
- VERSION = "6.3.1".freeze
2
+ VERSION = "8.0.1".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: 6.3.1
4
+ version: 8.0.1
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: 2020-03-03 00:00:00.000000000 Z
12
+ date: 2020-06-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: claide
@@ -45,14 +45,14 @@ dependencies:
45
45
  requirements:
46
46
  - - "~>"
47
47
  - !ruby/object:Gem::Version
48
- version: '1.6'
48
+ version: '1.7'
49
49
  type: :runtime
50
50
  prerelease: false
51
51
  version_requirements: !ruby/object:Gem::Requirement
52
52
  requirements:
53
53
  - - "~>"
54
54
  - !ruby/object:Gem::Version
55
- version: '1.6'
55
+ version: '1.7'
56
56
  - !ruby/object:Gem::Dependency
57
57
  name: colored2
58
58
  requirement: !ruby/object:Gem::Requirement
@@ -71,16 +71,22 @@ dependencies:
71
71
  name: faraday
72
72
  requirement: !ruby/object:Gem::Requirement
73
73
  requirements:
74
- - - "~>"
74
+ - - ">="
75
75
  - !ruby/object:Gem::Version
76
- version: '0.9'
76
+ version: 0.9.0
77
+ - - "<"
78
+ - !ruby/object:Gem::Version
79
+ version: '2.0'
77
80
  type: :runtime
78
81
  prerelease: false
79
82
  version_requirements: !ruby/object:Gem::Requirement
80
83
  requirements:
81
- - - "~>"
84
+ - - ">="
82
85
  - !ruby/object:Gem::Version
83
- version: '0.9'
86
+ version: 0.9.0
87
+ - - "<"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.0'
84
90
  - !ruby/object:Gem::Dependency
85
91
  name: faraday-http-cache
86
92
  requirement: !ruby/object:Gem::Requirement
@@ -250,6 +256,7 @@ files:
250
256
  - lib/danger/commands/systems.rb
251
257
  - lib/danger/comment_generators/bitbucket_server.md.erb
252
258
  - lib/danger/comment_generators/bitbucket_server_inline.md.erb
259
+ - lib/danger/comment_generators/bitbucket_server_message_group.md.erb
253
260
  - lib/danger/comment_generators/github.md.erb
254
261
  - lib/danger/comment_generators/github_inline.md.erb
255
262
  - lib/danger/comment_generators/gitlab.md.erb
@@ -262,6 +269,9 @@ files:
262
269
  - lib/danger/danger_core/dangerfile_generator.rb
263
270
  - lib/danger/danger_core/environment_manager.rb
264
271
  - lib/danger/danger_core/executor.rb
272
+ - lib/danger/danger_core/message_aggregator.rb
273
+ - lib/danger/danger_core/message_group.rb
274
+ - lib/danger/danger_core/messages/base.rb
265
275
  - lib/danger/danger_core/messages/markdown.rb
266
276
  - lib/danger/danger_core/messages/violation.rb
267
277
  - lib/danger/danger_core/plugins/dangerfile_bitbucket_cloud_plugin.rb
@@ -280,6 +290,7 @@ files:
280
290
  - lib/danger/helpers/comments_parsing_helper.rb
281
291
  - lib/danger/helpers/emoji_mapper.rb
282
292
  - lib/danger/helpers/find_max_num_violations.rb
293
+ - lib/danger/helpers/message_groups_array_helper.rb
283
294
  - lib/danger/plugin_support/gems_resolver.rb
284
295
  - lib/danger/plugin_support/plugin.rb
285
296
  - lib/danger/plugin_support/plugin_file_resolver.rb
@@ -314,7 +325,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
314
325
  requirements:
315
326
  - - ">="
316
327
  - !ruby/object:Gem::Version
317
- version: 2.3.0
328
+ version: 2.4.0
318
329
  required_rubygems_version: !ruby/object:Gem::Requirement
319
330
  requirements:
320
331
  - - ">="