danger 3.1.1 → 3.2.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
  SHA1:
3
- metadata.gz: a3d5dcfa64082b3686cad3e6ae09ca23af616914
4
- data.tar.gz: 151921230dd40b9c27c54c8def4a6740e24b9435
3
+ metadata.gz: 52836eb7928792ec1e8ec0ffb78076c0e1d6cae0
4
+ data.tar.gz: 9d550c596cfe8e25764a0662965fc4e4768ddf54
5
5
  SHA512:
6
- metadata.gz: ff42d5811f941c173b73ea315ba646fcabf692178bbf91d41a19dd5a32d6a151604d40687aff54a3f66ed0e19fa03c19b3fa1e702bda3a3d24a6e4d2cafb2d71
7
- data.tar.gz: 87db09b4a3696166880024b6755c25e17c637724b2e1b4c9e22d1732e7ad12675c7ab1d37d703399b214a4e31971bf24e75d7ffa3205129a98519aae4ddfe2ad
6
+ metadata.gz: 7103cf673b79e9827e1eef36a08c8001540fff0e0f5f535d2d4ce01a4b3d836546ea4cf083e736c5bf4ce0f3a3e9f3f93829713313672fa2d6f10f7c06ab591a
7
+ data.tar.gz: 2af2f1aebc31d5c3b573e2af054a46975160c9b200b3d06511036d0b7a05862b36869273a8d99083724725f11edf97632664200e752b5b63aba3f26695cf6a0b
@@ -26,4 +26,18 @@ module Danger
26
26
  end
27
27
  return Gem::Specification.find_by_name(gem_name).gem_dir
28
28
  end
29
+
30
+ # @return [String] Latest version of Danger on https://rubygems.org
31
+ def self.danger_outdated?
32
+ require "danger/clients/rubygems_client"
33
+ latest_version = RubyGemsClient.latest_danger_version
34
+
35
+ if Gem::Version.new(latest_version) > Gem::Version.new(Danger::VERSION)
36
+ latest_version
37
+ else
38
+ false
39
+ end
40
+ rescue StandardError => _e
41
+ false
42
+ end
29
43
  end
@@ -38,7 +38,7 @@ module Danger
38
38
  end
39
39
 
40
40
  def supported_request_sources
41
- @supported_request_sources ||= [Danger::RequestSources::GitHub, Danger::RequestSources::GitLab]
41
+ @supported_request_sources ||= [Danger::RequestSources::GitHub, Danger::RequestSources::GitLab, Danger::RequestSources::BitbucketServer]
42
42
  end
43
43
 
44
44
  def initialize(env)
@@ -12,6 +12,10 @@ module Danger
12
12
  env.key? "DANGER_USE_LOCAL_GIT"
13
13
  end
14
14
 
15
+ def self.validates_as_pr?(_env)
16
+ false
17
+ end
18
+
15
19
  def git
16
20
  @git ||= GitRepo.new
17
21
  end
@@ -0,0 +1,14 @@
1
+ module Danger
2
+ class RubyGemsClient
3
+ API_URL = "https://rubygems.org/api/v1/versions/danger/latest.json".freeze
4
+ DUMMY_VERSION = "0.0.0".freeze
5
+
6
+ def self.latest_danger_version
7
+ require "json"
8
+ json = JSON.parse(Faraday.get(API_URL).body)
9
+ json.fetch("version") { DUMMY_VERSION }
10
+ rescue StandardError => _e
11
+ DUMMY_VERSION
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,20 @@
1
+ <%- @tables.each do |table| -%>
2
+ <%- if table[:content].any? || table[:resolved].any? -%>
3
+ | | <%= table[:count] %> <%= table[:name] %><%= "s" unless table[:count] == 1 %> |
4
+ |---|---|
5
+ <%- table[:content].each do |violation| -%>
6
+ | <%= character_from_emoji(table[:emoji]) %> | <%= violation.message %> |
7
+ <%- end -%>
8
+ <%- table[:resolved].each do |message| -%>
9
+ | :white_check_mark: | <%= message %> |
10
+ <%- end -%>
11
+
12
+ <%- end -%>
13
+ <%- end -%>
14
+
15
+ <%- @markdowns.each do |current| -%>
16
+ <%= current %>
17
+ <%# the previous line has to be aligned far to the left, otherwise markdown can break easily %>
18
+ <%- end -%>
19
+
20
+ Generated by 🚫 [danger](http://example.com/ "generated_by_<%= @danger_id %>")
@@ -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 -%>
@@ -8,6 +8,7 @@ require "danger/danger_core/plugins/dangerfile_danger_plugin"
8
8
  require "danger/danger_core/plugins/dangerfile_git_plugin"
9
9
  require "danger/danger_core/plugins/dangerfile_github_plugin"
10
10
  require "danger/danger_core/plugins/dangerfile_gitlab_plugin"
11
+ require "danger/danger_core/plugins/dangerfile_bitbucket_server_plugin"
11
12
 
12
13
  module Danger
13
14
  class Dangerfile
@@ -35,7 +36,7 @@ module Danger
35
36
 
36
37
  # The ones that everything would break without
37
38
  def self.essential_plugin_classes
38
- [DangerfileMessagingPlugin, DangerfileGitPlugin, DangerfileDangerPlugin, DangerfileGitHubPlugin, DangerfileGitLabPlugin]
39
+ [DangerfileMessagingPlugin, DangerfileGitPlugin, DangerfileDangerPlugin, DangerfileGitHubPlugin, DangerfileGitLabPlugin, DangerfileBitbucketServerPlugin]
39
40
  end
40
41
 
41
42
  # Both of these methods exist on all objects
@@ -203,7 +204,8 @@ module Danger
203
204
 
204
205
  def print_results
205
206
  status = status_report
206
- return if (status[:errors] + status[:warnings] + status[:messages] + status[:markdowns]).count.zero?
207
+ violations = violation_report
208
+ return if (violations[:errors] + violations[:warnings] + violations[:messages] + status[:markdowns]).count.zero?
207
209
 
208
210
  ui.section("Results:") do
209
211
  [:errors, :warnings, :messages].each do |key|
@@ -216,14 +218,15 @@ module Danger
216
218
  else
217
219
  formatted
218
220
  end
219
- rows = status[key]
221
+ rows = violations[key]
220
222
  print_list(title, rows)
221
223
  end
222
224
 
223
225
  if status[:markdowns].count > 0
224
- ui.section("Markdown:") do
226
+ ui.title("Markdown:") do
225
227
  status[:markdowns].each do |current_markdown|
226
- ui.puts current_markdown
228
+ ui.puts "#{current_markdown.file}\#L#{current_markdown.line}" if current_markdown.file && current_markdown.line
229
+ ui.puts current_markdown.message
227
230
  end
228
231
  end
229
232
  end
@@ -235,7 +238,13 @@ module Danger
235
238
  def print_list(title, rows)
236
239
  ui.title(title) do
237
240
  rows.each do |row|
238
- ui.puts("- [ ] #{row}")
241
+ if row.file && row.line
242
+ path = "#{row.file}\#L#{row.line}: "
243
+ else
244
+ path = ""
245
+ end
246
+
247
+ ui.puts("- [ ] #{path}#{row.message}")
239
248
  end
240
249
  end unless rows.empty?
241
250
  end
@@ -0,0 +1,28 @@
1
+ module Danger
2
+ class Markdown
3
+ attr_accessor :message, :file, :line
4
+
5
+ def initialize(message, file, line)
6
+ self.message = message
7
+ self.file = file
8
+ self.line = line
9
+ end
10
+
11
+ def ==(other)
12
+ return false if other.nil?
13
+ return false unless other.kind_of? self.class
14
+
15
+ other.message == message &&
16
+ other.file == file &&
17
+ other.line == line
18
+ end
19
+
20
+ def to_s
21
+ extra = []
22
+ extra << "file: #{file}" unless file.nil?
23
+ extra << "line: #{line}" unless line.nil?
24
+
25
+ "Markdown #{message} { #{extra.join ', '} }"
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,35 @@
1
+ module Danger
2
+ class Violation
3
+ attr_accessor :message, :sticky, :file, :line
4
+
5
+ def initialize(message, sticky, file, line)
6
+ self.message = message
7
+ self.sticky = sticky
8
+ self.file = file
9
+ self.line = line
10
+ end
11
+
12
+ def ==(other)
13
+ return false if other.nil?
14
+ return false unless other.kind_of? self.class
15
+
16
+ other.message == message &&
17
+ other.sticky == sticky &&
18
+ other.file == file &&
19
+ other.line == line
20
+ end
21
+
22
+ def inline?
23
+ return (file.nil? && line.nil?) == false
24
+ end
25
+
26
+ def to_s
27
+ extra = []
28
+ extra << "sticky: true" if sticky
29
+ extra << "file: #{file}" unless file.nil?
30
+ extra << "line: #{line}" unless line.nil?
31
+
32
+ "Violation #{message} { #{extra.join ', '} }"
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,182 @@
1
+ # coding: utf-8
2
+ require "danger/plugin_support/plugin"
3
+
4
+ module Danger
5
+ # Handles interacting with Bitbucket Server inside a Dangerfile. Provides a few functions which wrap `pr_json` and also
6
+ # through a few standard functions to simplify your code.
7
+ #
8
+ # @example Warn when a PR is classed as work in progress
9
+ #
10
+ # warn "PR is classed as Work in Progress" if bitbucket_server.pr_title.include? "[WIP]"
11
+ #
12
+ # @example Declare a PR to be simple to avoid specific Danger rules
13
+ #
14
+ # declared_trivial = (bitbucket_server.pr_title + bitbucket_server.pr_body).include?("#trivial")
15
+ #
16
+ # @example Ensure that labels have been used on the PR
17
+ #
18
+ # fail "Please add labels to this PR" if bitbucket_server.pr_labels.empty?
19
+ #
20
+ # @example Check if a user is in a specific bitbucket_server org, and message them if so
21
+ #
22
+ # unless bitbucket_server.api.organization_member?('danger', bitbucket_server.pr_author)
23
+ # message "@#{bitbucket_server.pr_author} is not a contributor yet, would you like to join the Danger org?"
24
+ # end
25
+ #
26
+ # @example Ensure there is a summary for a PR
27
+ #
28
+ # fail "Please provide a summary in the Pull Request description" if bitbucket_server.pr_body.length < 5
29
+ #
30
+ # @example Only accept PRs to the develop branch
31
+ #
32
+ # fail "Please re-submit this PR to develop, we may have already fixed your issue." if bitbucket_server.branch_for_base != "develop"
33
+ #
34
+ # @example Highlight when a celebrity makes a pull request
35
+ #
36
+ # message "Welcome, Danger." if bitbucket_server.pr_author == "dangermcshane"
37
+ #
38
+ # @example Ensure that all PRs have an assignee
39
+ #
40
+ # warn "This PR does not have any assignees yet." unless bitbucket_server.pr_json["reviewers"].length == 0
41
+ #
42
+ # @example Send a message with links to a collection of specific files
43
+ #
44
+ # if git.modified_files.include? "config/*.js"
45
+ # config_files = git.modified_files.select { |path| path.include? "config/" }
46
+ # message "This PR changes #{ bitbucket_server.html_link(config_files) }"
47
+ # end
48
+ #
49
+ # @example Highlight with a clickable link if a Package.json is changed
50
+ #
51
+ # warn "#{bitbucket_server.html_link("Package.json")} was edited." if git.modified_files.include? "Package.json"
52
+ #
53
+ # @see danger/danger
54
+ # @tags core, bitbucket_server
55
+ #
56
+ class DangerfileBitbucketServerPlugin < Plugin
57
+ # So that this init can fail.
58
+ def self.new(dangerfile)
59
+ return nil if dangerfile.env.request_source.class != Danger::RequestSources::BitbucketServer
60
+ super
61
+ end
62
+
63
+ # The instance name used in the Dangerfile
64
+ # @return [String]
65
+ #
66
+ def self.instance_name
67
+ "bitbucket_server"
68
+ end
69
+
70
+ def initialize(dangerfile)
71
+ super(dangerfile)
72
+ @bs = dangerfile.env.request_source
73
+ end
74
+
75
+ # @!group Bitbucket Server Misc
76
+ # The hash that represents the PR's JSON. For an example of what this looks like
77
+ # see the [Danger Fixture'd one](https://raw.githubusercontent.com/danger/danger/master/spec/fixtures/bitbucket_server_api/pr_response.json).
78
+ # @return [Hash]
79
+ def pr_json
80
+ @bs.pr_json
81
+ end
82
+
83
+ # @!group PR Metadata
84
+ # The title of the Pull Request.
85
+ # @return [String]
86
+ #
87
+ def pr_title
88
+ @bs.pr_json[:title].to_s
89
+ end
90
+
91
+ # @!group PR Metadata
92
+ # The body text of the Pull Request.
93
+ # @return [String]
94
+ #
95
+ def pr_description
96
+ @bs.pr_json[:description].to_s
97
+ end
98
+ alias pr_body pr_description
99
+
100
+ # @!group PR Metadata
101
+ # The username of the author of the Pull Request.
102
+ # @return [String]
103
+ #
104
+ def pr_author
105
+ @bs.pr_json[:author][:user][:slug].to_s
106
+ end
107
+
108
+ # @!group PR Commit Metadata
109
+ # The branch to which the PR is going to be merged into.
110
+ # @return [String]
111
+ #
112
+ def branch_for_base
113
+ @bs.pr_json[:toRef][:displayId].to_s
114
+ end
115
+
116
+ # @!group PR Commit Metadata
117
+ # A href that represents the current PR
118
+ # @return [String]
119
+ #
120
+ def pr_link
121
+ @bs.pr_json[:links][:self].flat_map { |l| l[:href] }.first.to_s
122
+ end
123
+
124
+ # @!group PR Commit Metadata
125
+ # The branch to which the PR is going to be merged from.
126
+ # @return [String]
127
+ #
128
+ def branch_for_head
129
+ @bs.pr_json[:fromRef][:displayId].to_s
130
+ end
131
+
132
+ # @!group PR Commit Metadata
133
+ # The base commit to which the PR is going to be merged as a parent.
134
+ # @return [String]
135
+ #
136
+ def base_commit
137
+ @bs.pr_json[:toRef][:latestCommit].to_s
138
+ end
139
+
140
+ # @!group PR Commit Metadata
141
+ # The head commit to which the PR is requesting to be merged from.
142
+ # @return [String]
143
+ #
144
+ def head_commit
145
+ @bs.pr_json[:fromRef][:latestCommit].to_s
146
+ end
147
+
148
+ # @!group Bitbucket Server Misc
149
+ # Returns a list of HTML anchors for a file, or files in the head repository.
150
+ # It returns a string of multiple anchors if passed an array.
151
+ # @param [String or Array<String>] paths
152
+ # A list of strings to convert to github anchors
153
+ # @param [Bool] full_path
154
+ # Shows the full path as the link's text, defaults to `true`.
155
+ #
156
+ # @return [String]
157
+ #
158
+ def html_link(paths, full_path: true)
159
+ paths = [paths] unless paths.kind_of?(Array)
160
+ commit = head_commit
161
+ repo = pr_json[:fromRef][:repository][:links][:self].flat_map { |l| l[:href] }.first
162
+
163
+ paths = paths.map do |path|
164
+ path, line = path.split("#")
165
+ url_path = path.start_with?("/") ? path : "/#{path}"
166
+ text = full_path ? path : File.basename(path)
167
+ url_path.gsub!(" ", "%20")
168
+ line_ref = line ? "##{line}" : ""
169
+ create_link("#{repo}/browse/#{url_path}?at=#{commit}#{line_ref}", text)
170
+ end
171
+
172
+ return paths.first if paths.count < 2
173
+ paths.first(paths.count - 1).join(", ") + " & " + paths.last
174
+ end
175
+
176
+ private
177
+
178
+ def create_link(href, text)
179
+ "<a href='#{href}'>#{text}</a>"
180
+ end
181
+ end
182
+ end
@@ -40,7 +40,7 @@ module Danger
40
40
  # @return [void]
41
41
  #
42
42
  def import_plugin(path_or_url)
43
- raise "`import` requires a string" unless path_or_url.kind_of?(String)
43
+ raise "`import_plugin` requires a string" unless path_or_url.kind_of?(String)
44
44
 
45
45
  if path_or_url.start_with?("http")
46
46
  import_url(path_or_url)
@@ -49,23 +49,78 @@ module Danger
49
49
  end
50
50
  end
51
51
 
52
+ # @!group Danger
53
+ # Import a Dangerfile.
54
+ #
55
+ # @param [Hash] opts
56
+ # @option opts [String] :github Github path
57
+ # @option opts [String] :gem Gem name
58
+ # @option opts [String] :path Path to Dangerfile
59
+ # @return [void]
60
+ def import_dangerfile(opts)
61
+ if opts.kind_of?(String)
62
+ warn "Use `import_dangerfile(github: '#{opts}')` instead of `import_dangerfile '#{opts}'`."
63
+ import_dangerfile_from_github(opts)
64
+ elsif opts.kind_of?(Hash)
65
+ if opts.key?(:github)
66
+ import_dangerfile_from_github(opts[:github])
67
+ elsif opts.key?(:path)
68
+ import_dangerfile_from_path(opts[:path])
69
+ elsif opts.key?(:gem)
70
+ import_dangerfile_from_gem(opts[:gem])
71
+ else
72
+ raise "`import` requires a Hash with either :github or :gem"
73
+ end
74
+ else
75
+ raise "`import` requires a Hash" unless opts.kind_of?(Hash)
76
+ end
77
+ end
78
+
79
+ private
80
+
81
+ # @!group Danger
82
+ # Read and execute a local Dangerfile.
83
+ #
84
+ # @param [String] path
85
+ # A path to a Dangerfile.
86
+ # @return [void]
87
+ #
88
+ def import_dangerfile_from_path(path)
89
+ raise "`import_dangerfile_from_path` requires a string" unless path.kind_of?(String)
90
+ local_path = File.join(path, "Dangerfile")
91
+ @dangerfile.parse(Pathname.new(local_path))
92
+ end
93
+
94
+ # @!group Danger
95
+ # Read and execute a Dangerfile from a gem.
96
+ #
97
+ # @param [String] name
98
+ # The name of the gem that contains a Dangerfile.
99
+ # @return [void]
100
+ #
101
+ def import_dangerfile_from_gem(name)
102
+ raise "`import_dangerfile_from_gem` requires a string" unless name.kind_of?(String)
103
+ spec = Gem::Specification.find_by_name(name)
104
+ import_dangerfile_from_path(spec.gem_dir)
105
+ rescue Gem::MissingSpecError
106
+ raise "`import_dangerfile_from_gem` tried to load `#{name}` and failed, did you forget to include it in your Gemfile?"
107
+ end
108
+
52
109
  # @!group Danger
53
110
  # Download and execute a remote Dangerfile.
54
111
  #
55
- # @param [String] repo slug
112
+ # @param [String] slug
56
113
  # A slug that represents the repo where the Dangerfile is.
57
114
  # @return [void]
58
115
  #
59
- def import_dangerfile(slug)
60
- raise "`import` requires a string" unless slug.kind_of?(String)
116
+ def import_dangerfile_from_github(slug)
117
+ raise "`import_dangerfile_from_github` requires a string" unless slug.kind_of?(String)
61
118
  org, repo = slug.split("/")
62
119
  download_url = env.request_source.file_url(organisation: org, repository: repo, branch: "master", path: "Dangerfile")
63
120
  local_path = download(download_url)
64
121
  @dangerfile.parse(Pathname.new(local_path))
65
122
  end
66
123
 
67
- private
68
-
69
124
  # @!group Plugins
70
125
  # Download a local or remote plugin or Dangerfile.
71
126
  # This method will not import the file for you, use plugin.import instead