danger 8.0.5 → 8.2.2
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 +4 -4
- data/README.md +1 -1
- data/lib/danger/ci_source/appcenter.rb +1 -1
- data/lib/danger/ci_source/appveyor.rb +3 -0
- data/lib/danger/ci_source/azure_pipelines.rb +2 -2
- data/lib/danger/ci_source/bitbucket_pipelines.rb +1 -0
- data/lib/danger/ci_source/buildkite.rb +1 -1
- data/lib/danger/ci_source/circle.rb +1 -1
- data/lib/danger/ci_source/concourse.rb +61 -0
- data/lib/danger/ci_source/drone.rb +2 -2
- data/lib/danger/ci_source/github_actions.rb +3 -2
- data/lib/danger/ci_source/gitlab_ci.rb +2 -0
- data/lib/danger/ci_source/screwdriver.rb +2 -1
- data/lib/danger/ci_source/teamcity.rb +2 -0
- data/lib/danger/commands/dry_run.rb +1 -1
- data/lib/danger/commands/init.rb +1 -1
- data/lib/danger/commands/local.rb +1 -1
- data/lib/danger/commands/local_helpers/pry_setup.rb +4 -4
- data/lib/danger/commands/pr.rb +1 -1
- data/lib/danger/commands/staging.rb +1 -1
- data/lib/danger/core_ext/file_list.rb +1 -1
- data/lib/danger/danger_core/dangerfile.rb +10 -6
- data/lib/danger/danger_core/environment_manager.rb +2 -0
- data/lib/danger/danger_core/plugins/dangerfile_bitbucket_cloud_plugin.rb +1 -1
- data/lib/danger/danger_core/plugins/dangerfile_danger_plugin.rb +21 -1
- data/lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb +17 -0
- data/lib/danger/helpers/comments_helper.rb +1 -1
- data/lib/danger/plugin_support/plugin.rb +6 -2
- data/lib/danger/request_sources/bitbucket_cloud_api.rb +6 -4
- data/lib/danger/request_sources/bitbucket_server.rb +73 -9
- data/lib/danger/request_sources/code_insights_api.rb +147 -0
- data/lib/danger/request_sources/github/github.rb +4 -4
- data/lib/danger/request_sources/gitlab.rb +55 -11
- data/lib/danger/version.rb +1 -1
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 251a83485fbb01f15fb71afc1e76bed01879d166f6899407c2bee22416fa5ca7
|
4
|
+
data.tar.gz: 0fbbc7ce20a833a55e2be4b842e4e89d70a130038c0eec0408396892ceba215c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9780c2dfd1a909732b7571c0141e74d35b92cbdcfbcfad48c2b2bc713dc62e062598301b0bca38a6759ef032ea4318a2d04e5b40d8d7fccb441b2f78f4a59862
|
7
|
+
data.tar.gz: 5f93607907d82ffc36c6f5074796dfdacfd2c6771390e4bda57405c3c0f1f1545506a7f03b95b536fec763313612f24f27f4c20e9b4e4603570e0ab1b7e6f572
|
data/README.md
CHANGED
@@ -3,6 +3,7 @@ module Danger
|
|
3
3
|
# ### CI Setup
|
4
4
|
#
|
5
5
|
# Install dependencies and add a danger step to your `appveyor.yml`.
|
6
|
+
#
|
6
7
|
# ```yaml
|
7
8
|
# install:
|
8
9
|
# - cmd: >-
|
@@ -19,6 +20,7 @@ module Danger
|
|
19
20
|
# For public repositories, add your plain token to environment variables in `appveyor.yml`.
|
20
21
|
# Encrypted environment variables will not be decrypted on PR builds.
|
21
22
|
# see here: https://www.appveyor.com/docs/build-configuration/#secure-variables
|
23
|
+
#
|
22
24
|
# ```yaml
|
23
25
|
# environment:
|
24
26
|
# DANGER_GITHUB_API_TOKEN: <YOUR_TOKEN_HERE>
|
@@ -26,6 +28,7 @@ module Danger
|
|
26
28
|
#
|
27
29
|
# For private repositories, enter your token in `Settings>Environment>Environment variables>Add variable` and turn on `variable encryption`.
|
28
30
|
# You will see encrypted variable text in `Settings>Export YAML` so just copy to your `appveyor.yml`.
|
31
|
+
#
|
29
32
|
# ```yaml
|
30
33
|
# environment:
|
31
34
|
# DANGER_GITHUB_API_TOKEN:
|
@@ -7,7 +7,7 @@ module Danger
|
|
7
7
|
#
|
8
8
|
# Add a script step:
|
9
9
|
#
|
10
|
-
# ```
|
10
|
+
# ```shell
|
11
11
|
# #!/usr/bin/env bash
|
12
12
|
# bundle install
|
13
13
|
# bundle exec danger
|
@@ -19,7 +19,7 @@ module Danger
|
|
19
19
|
#
|
20
20
|
class AzurePipelines < CI
|
21
21
|
def self.validates_as_ci?(env)
|
22
|
-
env.key?
|
22
|
+
env.key?("AGENT_ID") && env["BUILD_REPOSITORY_PROVIDER"] != "TfsGit"
|
23
23
|
end
|
24
24
|
|
25
25
|
def self.validates_as_pr?(env)
|
@@ -9,7 +9,7 @@ module Danger
|
|
9
9
|
# With BuildKite you run the server yourself, so you will want to run it as a part of your build process.
|
10
10
|
# It is common to have build steps, so we would recommend adding this to your scrip:
|
11
11
|
#
|
12
|
-
# ```
|
12
|
+
# ```shell
|
13
13
|
# echo "--- Running Danger"
|
14
14
|
# bundle exec danger
|
15
15
|
# ```
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require "git"
|
2
|
+
require "danger/request_sources/local_only"
|
3
|
+
|
4
|
+
module Danger
|
5
|
+
# Concourse CI Integration
|
6
|
+
#
|
7
|
+
# https://concourse-ci.org/
|
8
|
+
#
|
9
|
+
# ### CI Setup
|
10
|
+
#
|
11
|
+
# With Concourse, you run the docker images yourself, so you will want to add `yarn danger ci` within one of your build jobs.
|
12
|
+
#
|
13
|
+
# ```shell
|
14
|
+
# build:
|
15
|
+
# image: golang
|
16
|
+
# commands:
|
17
|
+
# - ...
|
18
|
+
# - yarn danger ci
|
19
|
+
# ```
|
20
|
+
#
|
21
|
+
# ### Environment Variable Setup
|
22
|
+
#
|
23
|
+
# As this is self-hosted, you will need to add the `CONCOURSE` environment variable `export CONCOURSE=true` to your build environment,
|
24
|
+
# as well as setting environment variables for `PULL_REQUEST_ID` and `REPO_SLUG`. Assuming you are using the github pull request resource
|
25
|
+
# https://github.com/jtarchie/github-pullrequest-resource the id of the PR can be accessed from `git config --get pullrequest.id`.
|
26
|
+
#
|
27
|
+
# ### Token Setup
|
28
|
+
#
|
29
|
+
# Once again as this is self-hosted, you will need to add `DANGER_GITHUB_API_TOKEN` environment variable to the build environment.
|
30
|
+
# The suggested method of storing the token is within the vault - https://concourse-ci.org/creds.html
|
31
|
+
|
32
|
+
class Concourse < CI
|
33
|
+
def self.validates_as_ci?(env)
|
34
|
+
env.key? "CONCOURSE"
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.validates_as_pr?(env)
|
38
|
+
exists = ["PULL_REQUEST_ID", "REPO_SLUG"].all? { |x| env[x] && !env[x].empty? }
|
39
|
+
exists && env["PULL_REQUEST_ID"].to_i > 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def supported_request_sources
|
43
|
+
@supported_request_sources ||= [
|
44
|
+
Danger::RequestSources::GitHub,
|
45
|
+
Danger::RequestSources::GitLab,
|
46
|
+
Danger::RequestSources::BitbucketServer,
|
47
|
+
Danger::RequestSources::BitbucketCloud
|
48
|
+
]
|
49
|
+
end
|
50
|
+
|
51
|
+
def initialize(env)
|
52
|
+
self.repo_slug = env["REPO_SLUG"]
|
53
|
+
|
54
|
+
if env["PULL_REQUEST_ID"].to_i > 0
|
55
|
+
self.pull_request_id = env["PULL_REQUEST_ID"]
|
56
|
+
end
|
57
|
+
self.repo_url = GitRepo.new.origins
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
61
|
+
end
|
@@ -8,7 +8,7 @@ module Danger
|
|
8
8
|
# With Drone you run the docker images yourself, so you will want to add `bundle exec danger` at the end of
|
9
9
|
# your `.drone.yml`.
|
10
10
|
#
|
11
|
-
# ```
|
11
|
+
# ```shell
|
12
12
|
# build:
|
13
13
|
# image: golang
|
14
14
|
# commands:
|
@@ -24,7 +24,7 @@ module Danger
|
|
24
24
|
# Drone secrets: http://readme.drone.io/usage/secret-guide/
|
25
25
|
# NOTE: This is a new syntax in DroneCI 0.6+
|
26
26
|
#
|
27
|
-
# ```
|
27
|
+
# ```yml
|
28
28
|
# build:
|
29
29
|
# image: golang
|
30
30
|
# secrets:
|
@@ -6,7 +6,7 @@ module Danger
|
|
6
6
|
# You can use `danger/danger` Action in your `.github/workflows/xxx.yml`.
|
7
7
|
# And so, you can use GITHUB_TOKEN secret as `DANGER_GITHUB_API_TOKEN` environment variable.
|
8
8
|
#
|
9
|
-
# ```
|
9
|
+
# ```yml
|
10
10
|
# ...
|
11
11
|
# steps:
|
12
12
|
# - uses: actions/checkout@v1
|
@@ -21,7 +21,8 @@ module Danger
|
|
21
21
|
end
|
22
22
|
|
23
23
|
def self.validates_as_pr?(env)
|
24
|
-
env["GITHUB_EVENT_NAME"]
|
24
|
+
value = env["GITHUB_EVENT_NAME"]
|
25
|
+
value == "pull_request" || value == "pull_request_target"
|
25
26
|
end
|
26
27
|
|
27
28
|
def supported_request_sources
|
@@ -7,6 +7,7 @@ module Danger
|
|
7
7
|
# ### CI Setup
|
8
8
|
#
|
9
9
|
# Install dependencies and add a danger step to your .gitlab-ci.yml:
|
10
|
+
#
|
10
11
|
# ```yml
|
11
12
|
# before_script:
|
12
13
|
# - bundle install
|
@@ -14,6 +15,7 @@ module Danger
|
|
14
15
|
# script:
|
15
16
|
# - bundle exec danger
|
16
17
|
# ```
|
18
|
+
#
|
17
19
|
# ### Token Setup
|
18
20
|
#
|
19
21
|
# Add the `DANGER_GITLAB_API_TOKEN` to your pipeline env variables if you
|
@@ -27,6 +27,7 @@ module Danger
|
|
27
27
|
# branch="%teamcity.build.branch%"
|
28
28
|
# export GITHUB_PULL_REQUEST_ID=(${branch//\// })
|
29
29
|
# ```
|
30
|
+
#
|
30
31
|
# Or if you are using the pull request feature you can set an environment parameter called `GITHUB_PULL_REQUEST_ID`
|
31
32
|
# to the value of: `%teamcity.pullRequest.number`
|
32
33
|
#
|
@@ -59,6 +60,7 @@ module Danger
|
|
59
60
|
#
|
60
61
|
# You will also need to set the `BITBUCKET_BRANCH_NAME` environment variable.
|
61
62
|
# TeamCity provides `%teamcity.build.branch%`, which you can use at the top of your Simple Command Runner:
|
63
|
+
#
|
62
64
|
# ```sh
|
63
65
|
# export BITBUCKET_BRANCH_NAME="%teamcity.build.branch%"
|
64
66
|
# ```
|
data/lib/danger/commands/init.rb
CHANGED
@@ -277,7 +277,7 @@ module Danger
|
|
277
277
|
ui.say "- You can look at the following Dangerfiles to get some more ideas:"
|
278
278
|
ui.pause 0.6
|
279
279
|
ui.link "https://github.com/danger/danger/blob/master/Dangerfile"
|
280
|
-
ui.link "https://github.com/artsy/eigen/blob/master/
|
280
|
+
ui.link "https://github.com/artsy/eigen/blob/master/dangerfile.ts"
|
281
281
|
ui.pause 1
|
282
282
|
end
|
283
283
|
|
@@ -4,9 +4,9 @@ module Danger
|
|
4
4
|
@cork = cork
|
5
5
|
end
|
6
6
|
|
7
|
-
def setup_pry(dangerfile_path)
|
7
|
+
def setup_pry(dangerfile_path, command)
|
8
8
|
return dangerfile_path if dangerfile_path.empty?
|
9
|
-
validate_pry_available
|
9
|
+
validate_pry_available(command)
|
10
10
|
FileUtils.cp dangerfile_path, DANGERFILE_COPY
|
11
11
|
File.open(DANGERFILE_COPY, "a") do |f|
|
12
12
|
f.write("\nbinding.pry; File.delete(\"#{DANGERFILE_COPY}\")")
|
@@ -20,10 +20,10 @@ module Danger
|
|
20
20
|
|
21
21
|
DANGERFILE_COPY = "_Dangerfile.tmp".freeze
|
22
22
|
|
23
|
-
def validate_pry_available
|
23
|
+
def validate_pry_available(command)
|
24
24
|
Kernel.require "pry"
|
25
25
|
rescue LoadError
|
26
|
-
cork.warn "Pry was not found, and is required for 'danger
|
26
|
+
cork.warn "Pry was not found, and is required for 'danger #{command} --pry'."
|
27
27
|
cork.print_warnings
|
28
28
|
abort
|
29
29
|
end
|
data/lib/danger/commands/pr.rb
CHANGED
@@ -37,7 +37,7 @@ module Danger
|
|
37
37
|
@dangerfile_path = dangerfile if File.exist?(dangerfile)
|
38
38
|
|
39
39
|
if argv.flag?("pry", false)
|
40
|
-
@dangerfile_path = PrySetup.new(cork).setup_pry(@dangerfile_path)
|
40
|
+
@dangerfile_path = PrySetup.new(cork).setup_pry(@dangerfile_path, PR.command)
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
@@ -9,7 +9,7 @@ module Danger
|
|
9
9
|
def include?(pattern)
|
10
10
|
self.each do |current|
|
11
11
|
unless current.nil?
|
12
|
-
return true if File.fnmatch(pattern, current) || pattern == current
|
12
|
+
return true if File.fnmatch(pattern, current, File::FNM_EXTGLOB) || pattern == current
|
13
13
|
end
|
14
14
|
end
|
15
15
|
return false
|
@@ -49,22 +49,26 @@ module Danger
|
|
49
49
|
# However, as we're using using them in the DSL, they won't
|
50
50
|
# get method_missing called correctly without overriding them.
|
51
51
|
|
52
|
-
def warn(*args, &blk)
|
53
|
-
method_missing(:warn, *args, &blk)
|
52
|
+
def warn(*args, **kargs, &blk)
|
53
|
+
method_missing(:warn, *args, **kargs, &blk)
|
54
54
|
end
|
55
55
|
|
56
|
-
def fail(*args, &blk)
|
57
|
-
method_missing(:fail, *args, &blk)
|
56
|
+
def fail(*args, **kargs, &blk)
|
57
|
+
method_missing(:fail, *args, **kargs, &blk)
|
58
58
|
end
|
59
59
|
|
60
60
|
# When an undefined method is called, we check to see if it's something
|
61
61
|
# that the core DSLs have, then starts looking at plugins support.
|
62
62
|
|
63
63
|
# rubocop:disable Style/MethodMissing
|
64
|
-
def method_missing(method_sym, *arguments, &_block)
|
64
|
+
def method_missing(method_sym, *arguments, **keyword_arguments, &_block)
|
65
65
|
@core_plugins.each do |plugin|
|
66
66
|
if plugin.public_methods(false).include?(method_sym)
|
67
|
-
|
67
|
+
if keyword_arguments.empty?
|
68
|
+
return plugin.send(method_sym, *arguments)
|
69
|
+
else
|
70
|
+
return plugin.send(method_sym, *arguments, **keyword_arguments)
|
71
|
+
end
|
68
72
|
end
|
69
73
|
end
|
70
74
|
super
|
@@ -33,6 +33,11 @@ module Danger
|
|
33
33
|
#
|
34
34
|
# danger.import_dangerfile(github: "ruby-grape/danger", branch: "custom", path: "path/to/Dangerfile")
|
35
35
|
#
|
36
|
+
# @example Import a plugin available over HTTP
|
37
|
+
#
|
38
|
+
# custom_url = "https://custom.bitbucket.com/project-name/Dangerfile?raw"
|
39
|
+
# danger.import_dangerfile(url: custom_url)
|
40
|
+
#
|
36
41
|
# @see danger/danger
|
37
42
|
# @tags core, plugins
|
38
43
|
|
@@ -84,8 +89,10 @@ module Danger
|
|
84
89
|
import_dangerfile_from_path(opts[:path])
|
85
90
|
elsif opts.key?(:gem)
|
86
91
|
import_dangerfile_from_gem(opts[:gem])
|
92
|
+
elsif opts.key?(:url)
|
93
|
+
import_dangerfile_from_url(opts[:url])
|
87
94
|
else
|
88
|
-
raise "`import` requires a Hash with either :github, :gitlab, :gem, or :
|
95
|
+
raise "`import` requires a Hash with either :github, :gitlab, :gem, :path or :url"
|
89
96
|
end
|
90
97
|
else
|
91
98
|
raise "`import` requires a Hash"
|
@@ -180,6 +187,19 @@ module Danger
|
|
180
187
|
@dangerfile.parse(Pathname.new(local_path))
|
181
188
|
end
|
182
189
|
|
190
|
+
# @!group Danger
|
191
|
+
# Download and execute a remote Dangerfile.
|
192
|
+
#
|
193
|
+
# @param [String] url
|
194
|
+
# A https url where the Dangerfile is.
|
195
|
+
# @return [void]
|
196
|
+
#
|
197
|
+
def import_dangerfile_from_url(url)
|
198
|
+
raise "`import_dangerfile_from_url` requires a string" unless url.kind_of?(String)
|
199
|
+
local_path = download(url)
|
200
|
+
@dangerfile.parse(Pathname.new(local_path))
|
201
|
+
end
|
202
|
+
|
183
203
|
# @!group Plugins
|
184
204
|
# Download a local or remote plugin or Dangerfile.
|
185
205
|
# This method will not import the file for you, use plugin.import instead
|
@@ -227,6 +227,23 @@ module Danger
|
|
227
227
|
paths.first(paths.count - 1).join(", ") + " & " + paths.last
|
228
228
|
end
|
229
229
|
|
230
|
+
# @!group Gitlab Misc
|
231
|
+
# Use to ignore inline messages which lay outside a diff's range, thereby not posting the comment.
|
232
|
+
# You can set hash to change behavior per each kinds. (ex. `{warning: true, error: false}`)
|
233
|
+
# @param [Bool] or [Hash<Symbol, Bool>] dismiss
|
234
|
+
# Ignore out of range inline messages, defaults to `true`
|
235
|
+
#
|
236
|
+
# @return [void]
|
237
|
+
def dismiss_out_of_range_messages(dismiss = true)
|
238
|
+
if dismiss.kind_of?(Hash)
|
239
|
+
@gitlab.dismiss_out_of_range_messages = dismiss
|
240
|
+
elsif dismiss.kind_of?(TrueClass)
|
241
|
+
@gitlab.dismiss_out_of_range_messages = true
|
242
|
+
elsif dismiss.kind_of?(FalseClass)
|
243
|
+
@gitlab.dismiss_out_of_range_messages = false
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
230
247
|
%i(title body author labels json diff).each do |suffix|
|
231
248
|
alias_method "pr_#{suffix}".to_sym, "mr_#{suffix}".to_sym
|
232
249
|
end
|
@@ -11,7 +11,7 @@ module Danger
|
|
11
11
|
include Danger::Helpers::CommentsParsingHelper
|
12
12
|
|
13
13
|
def markdown_parser(text)
|
14
|
-
Kramdown::Document.new(text, input: "GFM")
|
14
|
+
Kramdown::Document.new(text, input: "GFM", smart_quotes: %w[apos apos quot quot])
|
15
15
|
end
|
16
16
|
|
17
17
|
# !@group Extension points
|
@@ -19,8 +19,12 @@ module Danger
|
|
19
19
|
# We need to redirect the self calls to the Dangerfile
|
20
20
|
|
21
21
|
# rubocop:disable Style/MethodMissing
|
22
|
-
def method_missing(method_sym, *arguments, &block)
|
23
|
-
|
22
|
+
def method_missing(method_sym, *arguments, **keyword_arguments, &block)
|
23
|
+
if keyword_arguments.empty?
|
24
|
+
@dangerfile.send(method_sym, *arguments, &block)
|
25
|
+
else
|
26
|
+
@dangerfile.send(method_sym, *arguments, **keyword_arguments, &block)
|
27
|
+
end
|
24
28
|
end
|
25
29
|
|
26
30
|
def self.all_plugins
|
@@ -57,7 +57,8 @@ module Danger
|
|
57
57
|
def fetch_comments
|
58
58
|
values = []
|
59
59
|
# TODO: use a url parts encoder to encode the query
|
60
|
-
|
60
|
+
corrected_uuid = @my_uuid[1...-1] if !@my_uuid.nil? # Endpoint doesnt support curly brackets for this, so remove them for this
|
61
|
+
uri = "#{pr_api_endpoint}/comments?pagelen=100&q=deleted+%7E+false+AND+user.uuid+%7E+%22#{corrected_uuid}%22"
|
61
62
|
|
62
63
|
while uri
|
63
64
|
json = fetch_json(URI(uri))
|
@@ -94,12 +95,13 @@ module Danger
|
|
94
95
|
"#{base_url(2)}/#{pull_request_id}"
|
95
96
|
end
|
96
97
|
|
97
|
-
def
|
98
|
-
|
98
|
+
def prs_api_url(branch_name)
|
99
|
+
encoded_branch_name = URI.encode_www_form_component(branch_name)
|
100
|
+
"#{base_url(2)}?q=source.branch.name=\"#{encoded_branch_name}\""
|
99
101
|
end
|
100
102
|
|
101
103
|
def fetch_pr_from_branch(branch_name)
|
102
|
-
uri = URI(
|
104
|
+
uri = URI(prs_api_url(branch_name))
|
103
105
|
fetch_json(uri)[:values][0][:id]
|
104
106
|
end
|
105
107
|
|
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
require "danger/helpers/comments_helper"
|
4
4
|
require "danger/request_sources/bitbucket_server_api"
|
5
|
+
require "danger/request_sources/code_insights_api"
|
6
|
+
require_relative "request_source"
|
5
7
|
|
6
8
|
module Danger
|
7
9
|
module RequestSources
|
@@ -17,12 +19,21 @@ module Danger
|
|
17
19
|
]
|
18
20
|
end
|
19
21
|
|
22
|
+
def self.optional_env_vars
|
23
|
+
["DANGER_BITBUCKETSERVER_CODE_INSIGHTS_REPORT_KEY",
|
24
|
+
"DANGER_BITBUCKETSERVER_CODE_INSIGHTS_REPORT_TITLE",
|
25
|
+
"DANGER_BITBUCKETSERVER_CODE_INSIGHTS_REPORT_DESCRIPTION",
|
26
|
+
"DANGER_BITBUCKETSERVER_CODE_INSIGHTS_REPORT_LOGO_URL"
|
27
|
+
]
|
28
|
+
end
|
29
|
+
|
20
30
|
def initialize(ci_source, environment)
|
21
31
|
self.ci_source = ci_source
|
22
32
|
self.environment = environment
|
23
33
|
|
24
34
|
project, slug = ci_source.repo_slug.split("/")
|
25
35
|
@api = BitbucketServerAPI.new(project, slug, ci_source.pull_request_id, environment)
|
36
|
+
@code_insights = CodeInsightsAPI.new(project, slug, environment)
|
26
37
|
end
|
27
38
|
|
28
39
|
def validates_as_ci?
|
@@ -73,16 +84,42 @@ module Danger
|
|
73
84
|
def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false)
|
74
85
|
delete_old_comments(danger_id: danger_id) if !new_comment || remove_previous_comments
|
75
86
|
|
76
|
-
|
87
|
+
# If configured, send a Code Insights API to provide the PR with a quality report
|
88
|
+
# which includes inline code violations found by Danger as Annotations.
|
89
|
+
# If no inline violations occurred, an empty, successful (green) report will be sent.
|
90
|
+
if @code_insights.ready?
|
91
|
+
inline_violations = inline_violations_group(warnings: warnings, errors: errors, messages: messages)
|
92
|
+
inline_warnings = inline_violations[:warnings] || []
|
93
|
+
inline_errors = inline_violations[:errors] || []
|
94
|
+
inline_messages = inline_violations[:messages] || []
|
95
|
+
|
96
|
+
head_commit = self.pr_json[:fromRef][:latestCommit]
|
97
|
+
@code_insights.send_report(head_commit,
|
98
|
+
inline_warnings,
|
99
|
+
inline_errors,
|
100
|
+
inline_messages)
|
101
|
+
end
|
102
|
+
|
103
|
+
# If we're sending inline comments separately via Code Insights,
|
104
|
+
# the main body comment should contain only generic, non-file specific messages.
|
105
|
+
if @code_insights.ready?
|
106
|
+
main_violations = main_violations_group(warnings: warnings, errors: errors, messages: messages)
|
107
|
+
warnings = main_violations[:warnings] || []
|
108
|
+
errors = main_violations[:errors] || []
|
109
|
+
messages = main_violations[:messages] || []
|
110
|
+
markdowns = main_violations[:markdowns] || []
|
111
|
+
end
|
112
|
+
|
113
|
+
comment = generate_description(warnings: warnings,
|
114
|
+
errors: errors)
|
77
115
|
comment += "\n\n"
|
78
116
|
comment += generate_comment(warnings: warnings,
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
117
|
+
errors: errors,
|
118
|
+
messages: messages,
|
119
|
+
markdowns: markdowns,
|
120
|
+
previous_violations: {},
|
121
|
+
danger_id: danger_id,
|
122
|
+
template: "bitbucket_server")
|
86
123
|
@api.post_comment(comment)
|
87
124
|
end
|
88
125
|
|
@@ -91,7 +128,34 @@ module Danger
|
|
91
128
|
@api.delete_comment(c[:id], c[:version]) if c[:text] =~ /generated_by_#{danger_id}/
|
92
129
|
end
|
93
130
|
end
|
94
|
-
|
131
|
+
|
132
|
+
def main_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
|
133
|
+
{
|
134
|
+
warnings: warnings.reject(&:inline?),
|
135
|
+
errors: errors.reject(&:inline?),
|
136
|
+
messages: messages.reject(&:inline?),
|
137
|
+
markdowns: markdowns.reject(&:inline?)
|
138
|
+
}
|
139
|
+
end
|
140
|
+
|
141
|
+
def inline_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
|
142
|
+
cmp = proc do |a, b|
|
143
|
+
next -1 unless a.file && a.line
|
144
|
+
next 1 unless b.file && b.line
|
145
|
+
|
146
|
+
next a.line <=> b.line if a.file == b.file
|
147
|
+
next a.file <=> b.file
|
148
|
+
end
|
149
|
+
|
150
|
+
# Sort to group inline comments by file
|
151
|
+
{
|
152
|
+
warnings: warnings.select(&:inline?).sort(&cmp),
|
153
|
+
errors: errors.select(&:inline?).sort(&cmp),
|
154
|
+
messages: messages.select(&:inline?).sort(&cmp),
|
155
|
+
markdowns: markdowns.select(&:inline?).sort(&cmp)
|
156
|
+
}
|
157
|
+
end
|
158
|
+
|
95
159
|
def update_pr_build_status(status, build_job_link, description)
|
96
160
|
changeset = self.pr_json[:fromRef][:latestCommit]
|
97
161
|
# Support for older versions of Bitbucket Server
|
@@ -0,0 +1,147 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
|
3
|
+
module Danger
|
4
|
+
module RequestSources
|
5
|
+
#
|
6
|
+
# Provides ability for Danger to interact with Atlassian's Code Insights API in order to provide code quality
|
7
|
+
# reports along with inline comments for specific lines in specific files.
|
8
|
+
# See https://developer.atlassian.com/server/bitbucket/how-tos/code-insights/ for more details.
|
9
|
+
#
|
10
|
+
# Currently this functionality is implemented only for Bitbucket Server request source.
|
11
|
+
class CodeInsightsAPI
|
12
|
+
attr_accessor :username, :password, :host, :report_key, :report_title, :report_description, :logo_url
|
13
|
+
|
14
|
+
def initialize(project, slug, environment)
|
15
|
+
@username = environment["DANGER_BITBUCKETSERVER_USERNAME"] || ""
|
16
|
+
@password = environment["DANGER_BITBUCKETSERVER_PASSWORD"] || ""
|
17
|
+
@host = environment["DANGER_BITBUCKETSERVER_HOST"] || ""
|
18
|
+
@report_key = environment["DANGER_BITBUCKETSERVER_CODE_INSIGHTS_REPORT_KEY"] || ""
|
19
|
+
@report_title = environment["DANGER_BITBUCKETSERVER_CODE_INSIGHTS_REPORT_TITLE"] || ""
|
20
|
+
@report_description = environment["DANGER_BITBUCKETSERVER_CODE_INSIGHTS_REPORT_DESCRIPTION"] || ""
|
21
|
+
@logo_url = environment["DANGER_BITBUCKETSERVER_CODE_INSIGHTS_REPORT_LOGO_URL"] || ""
|
22
|
+
@project = project
|
23
|
+
@slug = slug
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect
|
27
|
+
inspected = super
|
28
|
+
|
29
|
+
if @password
|
30
|
+
inspected = inspected.sub! @password, "********".freeze
|
31
|
+
end
|
32
|
+
|
33
|
+
inspected
|
34
|
+
end
|
35
|
+
|
36
|
+
def ready?
|
37
|
+
!(@report_key.empty? || @report_title.empty? || @report_description.empty? || @username.empty? || @password.empty? || @host.empty?)
|
38
|
+
end
|
39
|
+
|
40
|
+
def delete_report(commit)
|
41
|
+
uri = URI(report_endpoint_at_commit(commit))
|
42
|
+
request = Net::HTTP::Delete.new(uri.request_uri, {"Content-Type" => "application/json"})
|
43
|
+
request.basic_auth @username, @password
|
44
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl) do |http|
|
45
|
+
http.request(request)
|
46
|
+
end
|
47
|
+
|
48
|
+
# show failure when server returns an error
|
49
|
+
case response
|
50
|
+
when Net::HTTPClientError, Net::HTTPServerError
|
51
|
+
# HTTP 4xx - 5xx
|
52
|
+
abort "\nError deleting report from Code Insights API: #{response.code} (#{response.message}) - #{response.body}\n\n"
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
def send_report(commit, inline_warnings, inline_errors, inline_messages)
|
58
|
+
delete_report(commit)
|
59
|
+
put_report(commit, inline_errors.count)
|
60
|
+
should_post_annotations = !(inline_warnings + inline_errors + inline_messages).empty?
|
61
|
+
if should_post_annotations
|
62
|
+
post_annotations(commit, inline_warnings, inline_errors, inline_messages)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def put_report(commit, inline_errors_count)
|
67
|
+
uri = URI(report_endpoint_at_commit(commit))
|
68
|
+
request = Net::HTTP::Put.new(uri.request_uri, {"Content-Type" => "application/json"})
|
69
|
+
request.basic_auth @username, @password
|
70
|
+
request.body = {"title": @report_title,
|
71
|
+
"details": @report_description,
|
72
|
+
"result": (inline_errors_count > 0) ? "FAIL" : "PASS",
|
73
|
+
"reporter": @username,
|
74
|
+
"link": "https://github.com/danger/danger",
|
75
|
+
"logoURL": @logo_url
|
76
|
+
}.to_json
|
77
|
+
|
78
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl) do |http|
|
79
|
+
http.request(request)
|
80
|
+
end
|
81
|
+
|
82
|
+
# show failure when server returns an error
|
83
|
+
case response
|
84
|
+
when Net::HTTPClientError, Net::HTTPServerError
|
85
|
+
# HTTP 4xx - 5xx
|
86
|
+
abort "\nError putting report to Code Insights API: #{response.code} (#{response.message}) - #{response.body}\n\n"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
def post_annotations(commit, inline_warnings, inline_errors, inline_messages)
|
91
|
+
uri = URI(annotation_endpoint_at_commit(commit))
|
92
|
+
|
93
|
+
annotations = []
|
94
|
+
|
95
|
+
inline_messages.each do |violation|
|
96
|
+
annotations << violation_hash_with_severity(violation, "LOW")
|
97
|
+
end
|
98
|
+
|
99
|
+
inline_warnings.each do |violation|
|
100
|
+
annotations << violation_hash_with_severity(violation, "MEDIUM")
|
101
|
+
end
|
102
|
+
|
103
|
+
inline_errors.each do |violation|
|
104
|
+
annotations << violation_hash_with_severity(violation, "HIGH")
|
105
|
+
end
|
106
|
+
|
107
|
+
body = {annotations: annotations}.to_json
|
108
|
+
request = Net::HTTP::Post.new(uri.request_uri, {"Content-Type" => "application/json"})
|
109
|
+
request.basic_auth @username, @password
|
110
|
+
request.body = body
|
111
|
+
|
112
|
+
response = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl) do |http|
|
113
|
+
http.request(request)
|
114
|
+
end
|
115
|
+
|
116
|
+
# show failure when server returns an error
|
117
|
+
case response
|
118
|
+
when Net::HTTPClientError, Net::HTTPServerError
|
119
|
+
# HTTP 4xx - 5xx
|
120
|
+
abort "\nError posting comment to Code Insights API: #{response.code} (#{response.message}) - #{response.body}\n\n"
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def violation_hash_with_severity(violation, severity)
|
125
|
+
annotation = {}
|
126
|
+
annotation["message"] = violation.message
|
127
|
+
annotation["severity"] = severity
|
128
|
+
annotation["path"] = violation.file
|
129
|
+
annotation["line"] = violation.line.to_i
|
130
|
+
return annotation
|
131
|
+
end
|
132
|
+
|
133
|
+
def report_endpoint_at_commit(commit)
|
134
|
+
"#{@host}/rest/insights/1.0/projects/#{@project}/repos/#{@slug}/commits/#{commit}/reports/#{@report_key}"
|
135
|
+
end
|
136
|
+
|
137
|
+
def annotation_endpoint_at_commit(commit)
|
138
|
+
report_endpoint_at_commit(commit) + "/annotations"
|
139
|
+
end
|
140
|
+
|
141
|
+
def use_ssl
|
142
|
+
@host.include? "https://"
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
@@ -171,10 +171,10 @@ module Danger
|
|
171
171
|
markdowns: markdowns
|
172
172
|
)
|
173
173
|
|
174
|
-
rest_inline_violations = submit_inline_comments!({
|
174
|
+
rest_inline_violations = submit_inline_comments!(**{
|
175
175
|
danger_id: danger_id,
|
176
176
|
previous_violations: previous_violations
|
177
|
-
}.merge(
|
177
|
+
}.merge(inline_violations))
|
178
178
|
|
179
179
|
main_violations = merge_violations(
|
180
180
|
regular_violations, rest_inline_violations
|
@@ -189,11 +189,11 @@ module Danger
|
|
189
189
|
|
190
190
|
# If there are still violations to show
|
191
191
|
if main_violations_sum.any?
|
192
|
-
body = generate_comment({
|
192
|
+
body = generate_comment(**{
|
193
193
|
template: "github",
|
194
194
|
danger_id: danger_id,
|
195
195
|
previous_violations: previous_violations
|
196
|
-
}.merge(
|
196
|
+
}.merge(main_violations))
|
197
197
|
|
198
198
|
comment_result =
|
199
199
|
if should_create_new_comment
|
@@ -8,7 +8,7 @@ module Danger
|
|
8
8
|
module RequestSources
|
9
9
|
class GitLab < RequestSource
|
10
10
|
include Danger::Helpers::CommentsHelper
|
11
|
-
attr_accessor :mr_json, :commits_json
|
11
|
+
attr_accessor :mr_json, :commits_json, :dismiss_out_of_range_messages
|
12
12
|
|
13
13
|
FIRST_GITLAB_GEM_WITH_VERSION_CHECK = Gem::Version.new("4.6.0")
|
14
14
|
FIRST_VERSION_WITH_INLINE_COMMENTS = Gem::Version.new("10.8.0")
|
@@ -24,6 +24,7 @@ module Danger
|
|
24
24
|
def initialize(ci_source, environment)
|
25
25
|
self.ci_source = ci_source
|
26
26
|
self.environment = environment
|
27
|
+
self.dismiss_out_of_range_messages = false
|
27
28
|
|
28
29
|
@token = @environment["DANGER_GITLAB_API_TOKEN"]
|
29
30
|
end
|
@@ -84,7 +85,7 @@ module Danger
|
|
84
85
|
if supports_inline_comments
|
85
86
|
@raw_comments = mr_discussions
|
86
87
|
.auto_paginate
|
87
|
-
.flat_map { |discussion| discussion.notes.map { |note| note.merge({"discussion_id" => discussion.id}) } }
|
88
|
+
.flat_map { |discussion| discussion.notes.map { |note| note.to_h.merge({"discussion_id" => discussion.id}) } }
|
88
89
|
@raw_comments
|
89
90
|
.map { |comment| Comment.from_gitlab(comment) }
|
90
91
|
else
|
@@ -205,10 +206,10 @@ module Danger
|
|
205
206
|
markdowns: markdowns
|
206
207
|
)
|
207
208
|
|
208
|
-
rest_inline_violations = submit_inline_comments!({
|
209
|
+
rest_inline_violations = submit_inline_comments!(**{
|
209
210
|
danger_id: danger_id,
|
210
211
|
previous_violations: previous_violations
|
211
|
-
}.merge(
|
212
|
+
}.merge(inline_violations))
|
212
213
|
|
213
214
|
main_violations = merge_violations(
|
214
215
|
regular_violations, rest_inline_violations
|
@@ -223,11 +224,11 @@ module Danger
|
|
223
224
|
|
224
225
|
# If there are still violations to show
|
225
226
|
if main_violations_sum.any?
|
226
|
-
body = generate_comment({
|
227
|
+
body = generate_comment(**{
|
227
228
|
template: "gitlab",
|
228
229
|
danger_id: danger_id,
|
229
230
|
previous_violations: previous_violations
|
230
|
-
}.merge(
|
231
|
+
}.merge(main_violations))
|
231
232
|
|
232
233
|
comment_result =
|
233
234
|
if should_create_new_comment
|
@@ -302,6 +303,16 @@ module Danger
|
|
302
303
|
nil # TODO: Implement this
|
303
304
|
end
|
304
305
|
|
306
|
+
def dismiss_out_of_range_messages_for(kind)
|
307
|
+
if self.dismiss_out_of_range_messages.kind_of?(Hash) && self.dismiss_out_of_range_messages[kind]
|
308
|
+
self.dismiss_out_of_range_messages[kind]
|
309
|
+
elsif self.dismiss_out_of_range_messages == true
|
310
|
+
self.dismiss_out_of_range_messages
|
311
|
+
else
|
312
|
+
false
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
305
316
|
# @return [String] A URL to the specific file, ready to be downloaded
|
306
317
|
def file_url(organisation: nil, repository: nil, branch: nil, path: nil)
|
307
318
|
branch ||= 'master'
|
@@ -348,7 +359,7 @@ module Danger
|
|
348
359
|
def submit_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: [], danger_id: "danger")
|
349
360
|
comments = mr_discussions
|
350
361
|
.auto_paginate
|
351
|
-
.flat_map { |discussion| discussion.notes.map { |note| note.merge({"discussion_id" => discussion.id}) } }
|
362
|
+
.flat_map { |discussion| discussion.notes.map { |note| note.to_h.merge({"discussion_id" => discussion.id}) } }
|
352
363
|
.select { |comment| Comment.from_gitlab(comment).inline? }
|
353
364
|
|
354
365
|
danger_comments = comments.select { |comment| Comment.from_gitlab(comment).generated_by_danger?(danger_id) }
|
@@ -397,9 +408,8 @@ module Danger
|
|
397
408
|
|
398
409
|
messages.reject do |m|
|
399
410
|
next false unless m.file && m.line
|
400
|
-
|
401
|
-
|
402
|
-
next if !mr_changed_paths.include?(m.file)
|
411
|
+
# Reject if it's out of range and in dismiss mode
|
412
|
+
next true if dismiss_out_of_range_messages_for(kind) && is_out_of_range(mr_changes.changes, m)
|
403
413
|
|
404
414
|
# Once we know we're gonna submit it, we format it
|
405
415
|
if is_markdown_content
|
@@ -473,7 +483,6 @@ module Danger
|
|
473
483
|
range_header_regexp = /@@ -(?<old>[0-9]+)(,([0-9]+))? \+(?<new>[0-9]+)(,([0-9]+))? @@.*/
|
474
484
|
|
475
485
|
change = changes.find { |c| c["new_path"] == message.file }
|
476
|
-
|
477
486
|
# If there is no changes or rename only or deleted, return nil.
|
478
487
|
return nil if change.nil? || change["diff"].empty? || change["deleted_file"]
|
479
488
|
|
@@ -520,6 +529,41 @@ module Danger
|
|
520
529
|
line: current_old_line - current_new_line + message.line.to_i
|
521
530
|
}
|
522
531
|
end
|
532
|
+
|
533
|
+
def is_out_of_range(changes, message)
|
534
|
+
change = changes.find { |c| c["new_path"] == message.file }
|
535
|
+
# If there is no changes or rename only or deleted, return out of range.
|
536
|
+
return true if change.nil? || change["diff"].empty? || change["deleted_file"]
|
537
|
+
|
538
|
+
# If new file then return in range
|
539
|
+
return false if change["new_file"]
|
540
|
+
|
541
|
+
addition_lines = generate_addition_lines(change["diff"])
|
542
|
+
return false if addition_lines.include?(message.line.to_i)
|
543
|
+
|
544
|
+
return true
|
545
|
+
end
|
546
|
+
|
547
|
+
def generate_addition_lines(diff)
|
548
|
+
range_header_regexp = /@@ -(?<old>[0-9]+)(,([0-9]+))? \+(?<new>[0-9]+)(,([0-9]+))? @@.*/
|
549
|
+
addition_lines = []
|
550
|
+
line_number = 0
|
551
|
+
diff.each_line do |line|
|
552
|
+
if line.match range_header_regexp
|
553
|
+
line = line.split('+').last
|
554
|
+
line = line.split(' ').first
|
555
|
+
range_string = line.split(',')
|
556
|
+
line_number = range_string[0].to_i - 1
|
557
|
+
elsif line.start_with?('+')
|
558
|
+
addition_lines.push(line_number)
|
559
|
+
elsif line.start_with?('-')
|
560
|
+
line_number=line_number-1
|
561
|
+
end
|
562
|
+
line_number=line_number+1
|
563
|
+
end
|
564
|
+
addition_lines
|
565
|
+
end
|
566
|
+
|
523
567
|
end
|
524
568
|
end
|
525
569
|
end
|
data/lib/danger/version.rb
CHANGED
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: 8.
|
4
|
+
version: 8.2.2
|
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:
|
12
|
+
date: 2021-01-19 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: claide
|
@@ -214,6 +214,7 @@ files:
|
|
214
214
|
- lib/danger/ci_source/code_build.rb
|
215
215
|
- lib/danger/ci_source/codefresh.rb
|
216
216
|
- lib/danger/ci_source/codeship.rb
|
217
|
+
- lib/danger/ci_source/concourse.rb
|
217
218
|
- lib/danger/ci_source/dotci.rb
|
218
219
|
- lib/danger/ci_source/drone.rb
|
219
220
|
- lib/danger/ci_source/github_actions.rb
|
@@ -301,6 +302,7 @@ files:
|
|
301
302
|
- lib/danger/request_sources/bitbucket_cloud_api.rb
|
302
303
|
- lib/danger/request_sources/bitbucket_server.rb
|
303
304
|
- lib/danger/request_sources/bitbucket_server_api.rb
|
305
|
+
- lib/danger/request_sources/code_insights_api.rb
|
304
306
|
- lib/danger/request_sources/github/github.rb
|
305
307
|
- lib/danger/request_sources/github/github_review.rb
|
306
308
|
- lib/danger/request_sources/github/github_review_resolver.rb
|