danger 8.0.5 → 8.2.2

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