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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/lib/danger/ci_source/appcenter.rb +1 -1
  4. data/lib/danger/ci_source/appveyor.rb +3 -0
  5. data/lib/danger/ci_source/azure_pipelines.rb +2 -2
  6. data/lib/danger/ci_source/bitbucket_pipelines.rb +1 -0
  7. data/lib/danger/ci_source/buildkite.rb +1 -1
  8. data/lib/danger/ci_source/circle.rb +1 -1
  9. data/lib/danger/ci_source/concourse.rb +61 -0
  10. data/lib/danger/ci_source/drone.rb +2 -2
  11. data/lib/danger/ci_source/github_actions.rb +3 -2
  12. data/lib/danger/ci_source/gitlab_ci.rb +2 -0
  13. data/lib/danger/ci_source/screwdriver.rb +2 -1
  14. data/lib/danger/ci_source/teamcity.rb +2 -0
  15. data/lib/danger/commands/dry_run.rb +1 -1
  16. data/lib/danger/commands/init.rb +1 -1
  17. data/lib/danger/commands/local.rb +1 -1
  18. data/lib/danger/commands/local_helpers/pry_setup.rb +4 -4
  19. data/lib/danger/commands/pr.rb +1 -1
  20. data/lib/danger/commands/staging.rb +1 -1
  21. data/lib/danger/core_ext/file_list.rb +1 -1
  22. data/lib/danger/danger_core/dangerfile.rb +10 -6
  23. data/lib/danger/danger_core/environment_manager.rb +2 -0
  24. data/lib/danger/danger_core/plugins/dangerfile_bitbucket_cloud_plugin.rb +1 -1
  25. data/lib/danger/danger_core/plugins/dangerfile_danger_plugin.rb +21 -1
  26. data/lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb +17 -0
  27. data/lib/danger/helpers/comments_helper.rb +1 -1
  28. data/lib/danger/plugin_support/plugin.rb +6 -2
  29. data/lib/danger/request_sources/bitbucket_cloud_api.rb +6 -4
  30. data/lib/danger/request_sources/bitbucket_server.rb +73 -9
  31. data/lib/danger/request_sources/code_insights_api.rb +147 -0
  32. data/lib/danger/request_sources/github/github.rb +4 -4
  33. data/lib/danger/request_sources/gitlab.rb +55 -11
  34. data/lib/danger/version.rb +1 -1
  35. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f6ea9c4a38578c0c9cd6e762b689010bce9de3bc6479134cce2f6c45afdce925
4
- data.tar.gz: 4f131339c436c4ee163660341e0471c0c456b0251da17c6a5f5511f2fc3ac708
3
+ metadata.gz: 251a83485fbb01f15fb71afc1e76bed01879d166f6899407c2bee22416fa5ca7
4
+ data.tar.gz: 0fbbc7ce20a833a55e2be4b842e4e89d70a130038c0eec0408396892ceba215c
5
5
  SHA512:
6
- metadata.gz: 93226e7a7fe9b4300efb93ac07e59932eda1b25cae739f1f48d132f0d77f9d5f6a861d226785981f6580587cfd11380db5ec4e0935adea7c3707f35a5dc2af3d
7
- data.tar.gz: 7cca48807d7cb110180f88b62d1af13591ba83791f0f1528384dcd2c4c3247394fec2abfb9c1bc592e496659d3d14f6f46e1813af125570c967713b9d602251c
6
+ metadata.gz: 9780c2dfd1a909732b7571c0141e74d35b92cbdcfbcfad48c2b2bc713dc62e062598301b0bca38a6759ef032ea4318a2d04e5b40d8d7fccb441b2f78f4a59862
7
+ data.tar.gz: 5f93607907d82ffc36c6f5074796dfdacfd2c6771390e4bda57405c3c0f1f1545506a7f03b95b536fec763313612f24f27f4c20e9b4e4603570e0ab1b7e6f572
data/README.md CHANGED
@@ -51,7 +51,7 @@ Some quick links: [Guides Index](https://danger.systems/guides.html), [DSL Refer
51
51
 
52
52
  Brilliant. So, let's get you set up.
53
53
 
54
- ``` sh
54
+ ```sh
55
55
  git clone https://github.com/danger/danger.git
56
56
  cd danger
57
57
  bundle install
@@ -7,7 +7,7 @@ module Danger
7
7
  #
8
8
  # Add a script step to your appcenter-post-build.sh:
9
9
  #
10
- # ``` shell
10
+ # ```shell
11
11
  # #!/usr/bin/env bash
12
12
  # bundle install
13
13
  # bundle exec danger
@@ -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
- # ``` shell
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? "AGENT_ID"
22
+ env.key?("AGENT_ID") && env["BUILD_REPOSITORY_PROVIDER"] != "TfsGit"
23
23
  end
24
24
 
25
25
  def self.validates_as_pr?(env)
@@ -2,6 +2,7 @@ module Danger
2
2
  # ### CI Setup
3
3
  #
4
4
  # Install dependencies and add a danger step to your `bitbucket-pipelines.yml`.
5
+ #
5
6
  # ```yaml
6
7
  # script:
7
8
  # - bundle exec danger --verbose
@@ -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
- # ``` shell
12
+ # ```shell
13
13
  # echo "--- Running Danger"
14
14
  # bundle exec danger
15
15
  # ```
@@ -19,7 +19,7 @@ module Danger
19
19
  #
20
20
  # e.g.
21
21
  #
22
- # ``` yaml
22
+ # ```yaml
23
23
  # - run: bundle exec danger --verbose
24
24
  # ```
25
25
  #
@@ -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
- # ``` shell
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"] == "pull_request"
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
@@ -6,7 +6,8 @@ module Danger
6
6
  # ### CI Setup
7
7
  #
8
8
  # Install dependencies and add a danger step to your screwdriver.yaml:
9
- # ``` yml
9
+ #
10
+ # ```yml
10
11
  # jobs:
11
12
  # danger:
12
13
  # requires: [~pr, ~commit]
@@ -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
  # ```
@@ -22,7 +22,7 @@ module Danger
22
22
  super
23
23
 
24
24
  if argv.flag?("pry", false)
25
- @dangerfile_path = PrySetup.new(cork).setup_pry(@dangerfile_path)
25
+ @dangerfile_path = PrySetup.new(cork).setup_pry(@dangerfile_path, DryRun.command)
26
26
  end
27
27
  end
28
28
 
@@ -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/Dangerfile"
280
+ ui.link "https://github.com/artsy/eigen/blob/master/dangerfile.ts"
281
281
  ui.pause 1
282
282
  end
283
283
 
@@ -32,7 +32,7 @@ module Danger
32
32
  super
33
33
 
34
34
  if argv.flag?("pry", false)
35
- @dangerfile_path = PrySetup.new(cork).setup_pry(@dangerfile_path)
35
+ @dangerfile_path = PrySetup.new(cork).setup_pry(@dangerfile_path, Local.command)
36
36
  end
37
37
  end
38
38
 
@@ -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 pr --pry'."
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
@@ -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
 
@@ -23,7 +23,7 @@ module Danger
23
23
  super
24
24
 
25
25
  if argv.flag?("pry", false)
26
- @dangerfile_path = PrySetup.new(cork).setup_pry(@dangerfile_path)
26
+ @dangerfile_path = PrySetup.new(cork).setup_pry(@dangerfile_path, Staging.command)
27
27
  end
28
28
  end
29
29
 
@@ -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
- return plugin.send(method_sym, *arguments)
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
@@ -90,6 +90,8 @@ module Danger
90
90
  RequestSources::GitLab
91
91
  elsif repo_url =~ /bitbucket\.(org|com)/i
92
92
  RequestSources::BitbucketCloud
93
+ elsif repo_url =~ /dev\.azure\.com/i
94
+ RequestSources::VSTS
93
95
  end
94
96
  end
95
97
 
@@ -97,7 +97,7 @@ module Danger
97
97
  # @return [String]
98
98
  #
99
99
  def pr_author
100
- @bs.pr_json[:author][:username]
100
+ @bs.pr_json[:author][:nickname]
101
101
  end
102
102
 
103
103
  # @!group PR Commit Metadata
@@ -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 :path"
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
- @dangerfile.send(method_sym, *arguments, &block)
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
- uri = "#{pr_api_endpoint}/comments?pagelen=100&q=deleted+%7E+false+AND+user.username+%7E+%22#{@username}%22"
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 prs_api_endpoint(branch_name)
98
- "#{base_url(2)}?q=source.branch.name=\"#{branch_name}\""
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(URI.escape(prs_api_endpoint(branch_name)))
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
- comment = generate_description(warnings: warnings, errors: errors)
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
- errors: errors,
80
- messages: messages,
81
- markdowns: markdowns,
82
- previous_violations: {},
83
- danger_id: danger_id,
84
- template: "bitbucket_server")
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(**inline_violations))
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(**main_violations))
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(**inline_violations))
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(**main_violations))
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
- # Keep the change it's in a file changed in this diff
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
@@ -1,4 +1,4 @@
1
1
  module Danger
2
- VERSION = "8.0.5".freeze
2
+ VERSION = "8.2.2".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: 8.0.5
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: 2020-08-31 00:00:00.000000000 Z
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