danger 8.0.6 → 8.2.3

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 (34) 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 +10 -1
  13. data/lib/danger/ci_source/screwdriver.rb +2 -1
  14. data/lib/danger/ci_source/support/commits.rb +14 -12
  15. data/lib/danger/ci_source/teamcity.rb +2 -0
  16. data/lib/danger/commands/dangerfile/init.rb +1 -1
  17. data/lib/danger/commands/dry_run.rb +1 -1
  18. data/lib/danger/commands/init.rb +1 -1
  19. data/lib/danger/commands/local.rb +1 -1
  20. data/lib/danger/commands/local_helpers/pry_setup.rb +4 -4
  21. data/lib/danger/commands/pr.rb +1 -1
  22. data/lib/danger/commands/staging.rb +1 -1
  23. data/lib/danger/danger_core/dangerfile.rb +10 -6
  24. data/lib/danger/danger_core/environment_manager.rb +2 -0
  25. data/lib/danger/danger_core/plugins/dangerfile_bitbucket_cloud_plugin.rb +1 -1
  26. data/lib/danger/danger_core/plugins/dangerfile_danger_plugin.rb +21 -1
  27. data/lib/danger/plugin_support/plugin.rb +6 -2
  28. data/lib/danger/request_sources/bitbucket_cloud_api.rb +6 -4
  29. data/lib/danger/request_sources/bitbucket_server.rb +73 -9
  30. data/lib/danger/request_sources/code_insights_api.rb +147 -0
  31. data/lib/danger/request_sources/github/github.rb +4 -4
  32. data/lib/danger/request_sources/gitlab.rb +10 -10
  33. data/lib/danger/version.rb +1 -1
  34. metadata +12 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b175fad0150fdcb243c42f37c0b9683a7dc00db8b3be4ef81d01be0103b90bb
4
- data.tar.gz: d422bf7eadd533e5591f1ac43cde404f38d124a3c398469a1b9563c5338e8370
3
+ metadata.gz: cd811a6fad28534b9e0b28ccbed30d0694e4ab3408ad22852a8897a56c55f2ed
4
+ data.tar.gz: 747e7a6ff6fce272eb7b732ced8f6358602159725e5490a3ce783f7ce06dcc83
5
5
  SHA512:
6
- metadata.gz: d36a05ed7713cf0b50936c04feaa4cb49c8ba035b539ff265c9d7e231c2670efba007f5f4a2bf58936cab24c7aa0e09d5c9a2a9edff9714689fc5b03cc31d870
7
- data.tar.gz: 611a647c06bab7c3949ded7ff12b5498b966d54f10056481ae720dfa3801f48ac27085d112f69c186feb5638b2b82b0f7583c0f020af33af8251705429fbf614
6
+ metadata.gz: 54612a3b0e7999fce7289a884febff1548edbfa92af9947a8c8cc4bbef5fa69451ac88b493bc2ba0125e23ef96f7a46846a7a3b1e7adb8ac4d8f98aafa0065ce
7
+ data.tar.gz: 6612a1c511c7fe579c2734f6b4c8b15955dc2a2dd196a4a0277a901d61307b58c2abdd7f000867c1f212d2db065b39c8dfcd9f83c774154b1089a37395f45096
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
@@ -44,10 +46,17 @@ module Danger
44
46
  base_commit = env["CI_COMMIT_SHA"]
45
47
  client = RequestSources::GitLab.new(nil, env).client
46
48
 
47
- if (Gem::Version.new(client.version.version) >= Gem::Version.new("10.7"))
49
+ client_version = Gem::Version.new(client.version.version)
50
+ if (client_version >= Gem::Version.new("10.7"))
48
51
  #Use the 'list merge requests associated with a commit' API, for speeed
49
52
  # (GET /projects/:id/repository/commits/:sha/merge_requests) available for GitLab >= 10.7
50
53
  merge_request = client.commit_merge_requests(project_path, base_commit, state: :opened).first
54
+ if (client_version >= Gem::Version.new("13.8"))
55
+ # Gitlab 13.8.0 started returning merge requests for merge commits and squashed commits
56
+ # By checking for merge_request.state, we can ensure danger only comments on MRs which are open
57
+ return 0 if merge_request.nil?
58
+ return 0 unless merge_request.state == "opened"
59
+ end
51
60
  else
52
61
  merge_requests = client.merge_requests(project_path, state: :opened)
53
62
  merge_request = merge_requests.auto_paginate.find do |mr|
@@ -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]
@@ -1,17 +1,19 @@
1
- class Commits
2
- def initialize(base_head)
3
- @base_head = base_head.strip.split(" ".freeze)
4
- end
1
+ module Danger
2
+ class Commits
3
+ def initialize(base_head)
4
+ @base_head = base_head.strip.split(" ".freeze)
5
+ end
5
6
 
6
- def base
7
- base_head.first
8
- end
7
+ def base
8
+ base_head.first
9
+ end
9
10
 
10
- def head
11
- base_head.last
12
- end
11
+ def head
12
+ base_head.last
13
+ end
13
14
 
14
- private
15
+ private
15
16
 
16
- attr_reader :base_head
17
+ attr_reader :base_head
18
+ end
17
19
  end
@@ -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
  # ```
@@ -4,7 +4,7 @@ require "danger/danger_core/dangerfile_generator"
4
4
 
5
5
  module Danger
6
6
  class DangerfileCommand < Runner
7
- self.summary = "Easily create you Dangerfiles."
7
+ self.summary = "Easily create your Dangerfiles."
8
8
  self.command = "dangerfile"
9
9
 
10
10
  self.abstract_command = true
@@ -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
 
@@ -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
@@ -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
@@ -85,7 +85,7 @@ module Danger
85
85
  if supports_inline_comments
86
86
  @raw_comments = mr_discussions
87
87
  .auto_paginate
88
- .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}) } }
89
89
  @raw_comments
90
90
  .map { |comment| Comment.from_gitlab(comment) }
91
91
  else
@@ -206,10 +206,10 @@ module Danger
206
206
  markdowns: markdowns
207
207
  )
208
208
 
209
- rest_inline_violations = submit_inline_comments!({
209
+ rest_inline_violations = submit_inline_comments!(**{
210
210
  danger_id: danger_id,
211
211
  previous_violations: previous_violations
212
- }.merge(**inline_violations))
212
+ }.merge(inline_violations))
213
213
 
214
214
  main_violations = merge_violations(
215
215
  regular_violations, rest_inline_violations
@@ -224,11 +224,11 @@ module Danger
224
224
 
225
225
  # If there are still violations to show
226
226
  if main_violations_sum.any?
227
- body = generate_comment({
227
+ body = generate_comment(**{
228
228
  template: "gitlab",
229
229
  danger_id: danger_id,
230
230
  previous_violations: previous_violations
231
- }.merge(**main_violations))
231
+ }.merge(main_violations))
232
232
 
233
233
  comment_result =
234
234
  if should_create_new_comment
@@ -359,7 +359,7 @@ module Danger
359
359
  def submit_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: [], danger_id: "danger")
360
360
  comments = mr_discussions
361
361
  .auto_paginate
362
- .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}) } }
363
363
  .select { |comment| Comment.from_gitlab(comment).inline? }
364
364
 
365
365
  danger_comments = comments.select { |comment| Comment.from_gitlab(comment).generated_by_danger?(danger_id) }
@@ -410,7 +410,7 @@ module Danger
410
410
  next false unless m.file && m.line
411
411
  # Reject if it's out of range and in dismiss mode
412
412
  next true if dismiss_out_of_range_messages_for(kind) && is_out_of_range(mr_changes.changes, m)
413
-
413
+
414
414
  # Once we know we're gonna submit it, we format it
415
415
  if is_markdown_content
416
416
  body = generate_inline_markdown_body(m, danger_id: danger_id, template: "gitlab")
@@ -531,10 +531,10 @@ module Danger
531
531
  end
532
532
 
533
533
  def is_out_of_range(changes, message)
534
- change = changes.find { |c| c["new_path"] == message.file }
534
+ change = changes.find { |c| c["new_path"] == message.file }
535
535
  # If there is no changes or rename only or deleted, return out of range.
536
536
  return true if change.nil? || change["diff"].empty? || change["deleted_file"]
537
-
537
+
538
538
  # If new file then return in range
539
539
  return false if change["new_file"]
540
540
 
@@ -544,7 +544,7 @@ module Danger
544
544
  return true
545
545
  end
546
546
 
547
- def generate_addition_lines(diff)
547
+ def generate_addition_lines(diff)
548
548
  range_header_regexp = /@@ -(?<old>[0-9]+)(,([0-9]+))? \+(?<new>[0-9]+)(,([0-9]+))? @@.*/
549
549
  addition_lines = []
550
550
  line_number = 0
@@ -1,4 +1,4 @@
1
1
  module Danger
2
- VERSION = "8.0.6".freeze
2
+ VERSION = "8.2.3".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.6
4
+ version: 8.2.3
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-10-05 00:00:00.000000000 Z
12
+ date: 2021-03-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: claide
@@ -147,16 +147,22 @@ dependencies:
147
147
  name: terminal-table
148
148
  requirement: !ruby/object:Gem::Requirement
149
149
  requirements:
150
- - - "~>"
150
+ - - ">="
151
151
  - !ruby/object:Gem::Version
152
152
  version: '1'
153
+ - - "<"
154
+ - !ruby/object:Gem::Version
155
+ version: '4'
153
156
  type: :runtime
154
157
  prerelease: false
155
158
  version_requirements: !ruby/object:Gem::Requirement
156
159
  requirements:
157
- - - "~>"
160
+ - - ">="
158
161
  - !ruby/object:Gem::Version
159
162
  version: '1'
163
+ - - "<"
164
+ - !ruby/object:Gem::Version
165
+ version: '4'
160
166
  - !ruby/object:Gem::Dependency
161
167
  name: cork
162
168
  requirement: !ruby/object:Gem::Requirement
@@ -214,6 +220,7 @@ files:
214
220
  - lib/danger/ci_source/code_build.rb
215
221
  - lib/danger/ci_source/codefresh.rb
216
222
  - lib/danger/ci_source/codeship.rb
223
+ - lib/danger/ci_source/concourse.rb
217
224
  - lib/danger/ci_source/dotci.rb
218
225
  - lib/danger/ci_source/drone.rb
219
226
  - lib/danger/ci_source/github_actions.rb
@@ -301,6 +308,7 @@ files:
301
308
  - lib/danger/request_sources/bitbucket_cloud_api.rb
302
309
  - lib/danger/request_sources/bitbucket_server.rb
303
310
  - lib/danger/request_sources/bitbucket_server_api.rb
311
+ - lib/danger/request_sources/code_insights_api.rb
304
312
  - lib/danger/request_sources/github/github.rb
305
313
  - lib/danger/request_sources/github/github_review.rb
306
314
  - lib/danger/request_sources/github/github_review_resolver.rb