danger 8.4.4 → 8.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/danger/ci_source/codefresh.rb +7 -13
- data/lib/danger/ci_source/local_git_repo.rb +29 -37
- data/lib/danger/ci_source/local_only_git_repo.rb +4 -8
- data/lib/danger/ci_source/support/pull_request_finder.rb +33 -37
- data/lib/danger/comment_generators/vsts_inline.md.erb +17 -0
- data/lib/danger/danger_core/plugins/dangerfile_git_plugin.rb +1 -1
- data/lib/danger/helpers/comments_helper.rb +3 -2
- data/lib/danger/request_sources/bitbucket_cloud.rb +0 -1
- data/lib/danger/request_sources/bitbucket_cloud_api.rb +3 -3
- data/lib/danger/request_sources/bitbucket_server.rb +0 -1
- data/lib/danger/request_sources/bitbucket_server_api.rb +3 -4
- data/lib/danger/request_sources/code_insights_api.rb +2 -3
- data/lib/danger/request_sources/github/github.rb +15 -24
- data/lib/danger/request_sources/gitlab.rb +8 -18
- data/lib/danger/request_sources/local_only.rb +1 -2
- data/lib/danger/request_sources/request_source.rb +16 -4
- data/lib/danger/request_sources/vsts.rb +171 -9
- data/lib/danger/request_sources/vsts_api.rb +34 -3
- data/lib/danger/scm_source/git_repo.rb +2 -1
- data/lib/danger/version.rb +1 -1
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3b2a439e378d52312f7b772f1df4ee246d61aaede6b725578e9328e50a6d3a13
|
4
|
+
data.tar.gz: 1728ce2347569a956fe415ae3d9553f3606fd4272054e359947f11aff280ccd5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 81094ad713b156bb548ce7e132ca31e6a01c9a280c8a5ac7faa6a6f79575750a23fde0d0331c9e3a98f049e4dfd32c08cda886faf01ccd09dcfdf68101fa1765
|
7
|
+
data.tar.gz: ac33a566081b19995683e314a47d47513a4b4446634250e3526db681aa5d955f2228d87a989a81c24cb907570ce1c2b8eddf794c7e5cf4f33c641ba04c8db5e0
|
@@ -31,23 +31,17 @@ module Danger
|
|
31
31
|
@supported_request_sources ||= [Danger::RequestSources::GitHub]
|
32
32
|
end
|
33
33
|
|
34
|
-
def
|
35
|
-
return "" if
|
36
|
-
return "" if
|
37
|
-
"#{@env['CF_REPO_OWNER']}/#{@env['CF_REPO_NAME']}".downcase!
|
38
|
-
end
|
39
|
-
|
40
|
-
def repo_url
|
41
|
-
return "" if @env["CF_COMMIT_URL"].to_s.empty?
|
42
|
-
@env["CF_COMMIT_URL"].gsub(/\/commit.+$/, "")
|
43
|
-
end
|
34
|
+
def self.slug_from(env)
|
35
|
+
return "" if env["CF_REPO_OWNER"].to_s.empty?
|
36
|
+
return "" if env["CF_REPO_NAME"].to_s.empty?
|
44
37
|
|
45
|
-
|
46
|
-
@env["CF_PULL_REQUEST_NUMBER"]
|
38
|
+
"#{env['CF_REPO_OWNER']}/#{env['CF_REPO_NAME']}".downcase!
|
47
39
|
end
|
48
40
|
|
49
41
|
def initialize(env)
|
50
|
-
|
42
|
+
self.repo_url = env["CF_COMMIT_URL"].to_s.gsub(/\/commit.+$/, "")
|
43
|
+
self.repo_slug = self.class.slug_from(env)
|
44
|
+
self.pull_request_id = env["CF_PULL_REQUEST_NUMBER"]
|
51
45
|
end
|
52
46
|
end
|
53
47
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
# For more info see: https://github.com/schacon/ruby-git
|
2
3
|
|
3
4
|
require "git"
|
@@ -37,8 +38,8 @@ module Danger
|
|
37
38
|
end
|
38
39
|
|
39
40
|
def initialize(env = {})
|
40
|
-
@
|
41
|
-
|
41
|
+
@remote_info = find_remote_info(env)
|
42
|
+
@found_pull_request = find_pull_request(env)
|
42
43
|
self.repo_slug = remote_info.slug
|
43
44
|
raise_error_for_missing_remote if remote_info.kind_of?(NoRepoInfo)
|
44
45
|
|
@@ -55,7 +56,7 @@ module Danger
|
|
55
56
|
|
56
57
|
private
|
57
58
|
|
58
|
-
attr_reader :
|
59
|
+
attr_reader :remote_info, :found_pull_request
|
59
60
|
|
60
61
|
def raise_error_for_missing_remote
|
61
62
|
raise missing_remote_error_message
|
@@ -66,45 +67,36 @@ module Danger
|
|
66
67
|
"And the repository must host on GitHub.com or GitHub Enterprise."
|
67
68
|
end
|
68
69
|
|
69
|
-
def
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
).call
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
remote_info || NoRepoInfo.new
|
83
|
-
end
|
70
|
+
def find_remote_info(env)
|
71
|
+
if given_pull_request_url?(env)
|
72
|
+
FindRepoInfoFromURL.new(env["LOCAL_GIT_PR_URL"]).call
|
73
|
+
else
|
74
|
+
FindRepoInfoFromLogs.new(
|
75
|
+
env["DANGER_GITHUB_HOST"] || "github.com",
|
76
|
+
run_git("remote show origin -n")
|
77
|
+
).call
|
78
|
+
end || NoRepoInfo.new
|
84
79
|
end
|
85
80
|
|
86
|
-
def
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
git_logs: run_git("log --oneline -1000000".freeze)
|
102
|
-
).call
|
103
|
-
end
|
81
|
+
def find_pull_request(env)
|
82
|
+
if given_pull_request_url?(env)
|
83
|
+
PullRequestFinder.new(
|
84
|
+
remote_info.id,
|
85
|
+
remote_info.slug,
|
86
|
+
remote: true,
|
87
|
+
remote_url: env["LOCAL_GIT_PR_URL"]
|
88
|
+
).call(env: env)
|
89
|
+
else
|
90
|
+
PullRequestFinder.new(
|
91
|
+
env.fetch("LOCAL_GIT_PR_ID") { "" },
|
92
|
+
remote_info.slug,
|
93
|
+
remote: false,
|
94
|
+
git_logs: run_git("log --oneline -1000000")
|
95
|
+
).call(env: env)
|
104
96
|
end
|
105
97
|
end
|
106
98
|
|
107
|
-
def given_pull_request_url?
|
99
|
+
def given_pull_request_url?(env)
|
108
100
|
env["LOCAL_GIT_PR_URL"] && !env["LOCAL_GIT_PR_URL"].empty?
|
109
101
|
end
|
110
102
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "git"
|
2
4
|
require "danger/request_sources/local_only"
|
3
5
|
|
@@ -9,8 +11,8 @@ module Danger
|
|
9
11
|
#
|
10
12
|
class LocalOnlyGitRepo < CI
|
11
13
|
attr_accessor :base_commit, :head_commit
|
12
|
-
HEAD_VAR = "DANGER_LOCAL_HEAD"
|
13
|
-
BASE_VAR = "DANGER_LOCAL_BASE"
|
14
|
+
HEAD_VAR = "DANGER_LOCAL_HEAD"
|
15
|
+
BASE_VAR = "DANGER_LOCAL_BASE"
|
14
16
|
|
15
17
|
def self.validates_as_ci?(env)
|
16
18
|
env.key? "DANGER_USE_LOCAL_ONLY_GIT"
|
@@ -33,15 +35,9 @@ module Danger
|
|
33
35
|
end
|
34
36
|
|
35
37
|
def initialize(env = {})
|
36
|
-
@env = env
|
37
|
-
|
38
38
|
# expects --base/--head specified OR origin/master to be base and HEAD head
|
39
39
|
self.base_commit = env[BASE_VAR] || run_git("rev-parse --abbrev-ref origin/master")
|
40
40
|
self.head_commit = env[HEAD_VAR] || run_git("rev-parse --abbrev-ref HEAD")
|
41
41
|
end
|
42
|
-
|
43
|
-
private
|
44
|
-
|
45
|
-
attr_reader :env
|
46
42
|
end
|
47
43
|
end
|
@@ -1,67 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "danger/ci_source/support/local_pull_request"
|
2
4
|
require "danger/ci_source/support/remote_pull_request"
|
3
5
|
require "danger/ci_source/support/no_pull_request"
|
4
6
|
|
5
7
|
module Danger
|
6
8
|
class PullRequestFinder
|
7
|
-
def initialize(specific_pull_request_id, repo_slug = nil, remote: false, git_logs: "", remote_url: ""
|
9
|
+
def initialize(specific_pull_request_id, repo_slug = nil, remote: false, git_logs: "", remote_url: "")
|
8
10
|
@specific_pull_request_id = specific_pull_request_id
|
9
11
|
@git_logs = git_logs
|
10
12
|
@repo_slug = repo_slug
|
11
13
|
@remote = to_boolean(remote)
|
12
14
|
@remote_url = remote_url
|
13
|
-
@env = env
|
14
15
|
end
|
15
16
|
|
16
|
-
def call
|
17
|
-
|
18
|
-
|
19
|
-
|
17
|
+
def call(env: nil)
|
18
|
+
find_pull_request(env).tap do |pull_request|
|
19
|
+
raise_pull_request_not_found!(pull_request) unless pull_request.valid?
|
20
|
+
end
|
20
21
|
end
|
21
22
|
|
22
23
|
private
|
23
24
|
|
24
|
-
attr_reader :specific_pull_request_id, :git_logs, :repo_slug, :remote, :remote_url
|
25
|
+
attr_reader :specific_pull_request_id, :git_logs, :repo_slug, :remote, :remote_url
|
25
26
|
|
26
27
|
def to_boolean(maybe_string)
|
27
28
|
["true", "1", "yes", "y", true].include?(maybe_string)
|
28
29
|
end
|
29
30
|
|
30
|
-
def
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
raise "No recent Pull Requests found for this repo, danger requires at least one Pull Request for the local mode.".freeze
|
36
|
-
end
|
31
|
+
def raise_pull_request_not_found!(pull_request)
|
32
|
+
if specific_pull_request_id.empty?
|
33
|
+
raise "No recent Pull Requests found for this repo, danger requires at least one Pull Request for the local mode."
|
34
|
+
else
|
35
|
+
raise "Could not find the Pull Request (#{specific_pull_request_id}) inside the git history for this repo."
|
37
36
|
end
|
38
37
|
end
|
39
38
|
|
40
39
|
# @return [String] Log line of most recent merged Pull Request
|
41
|
-
def
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
end
|
40
|
+
def find_pull_request(env)
|
41
|
+
return if pull_request_ref.empty?
|
42
|
+
|
43
|
+
if both_present?
|
44
|
+
LocalPullRequest.new(pick_the_most_recent_one_from_two_matches)
|
45
|
+
elsif only_merged_pull_request_present?
|
46
|
+
LocalPullRequest.new(most_recent_merged_pull_request)
|
47
|
+
elsif only_squash_and_merged_pull_request_present?
|
48
|
+
LocalPullRequest.new(most_recent_squash_and_merged_pull_request)
|
49
|
+
elsif remote
|
50
|
+
remote_pull_request = find_remote_pull_request(env)
|
51
|
+
remote_pull_request ? generate_remote_pull_request(remote_pull_request) : NoPullRequest.new
|
52
|
+
else
|
53
|
+
NoPullRequest.new
|
56
54
|
end
|
57
55
|
end
|
58
56
|
|
59
57
|
# @return [String] "#42"
|
60
58
|
def pull_request_ref
|
61
|
-
!specific_pull_request_id.empty? ? "##{specific_pull_request_id}" : "#\\d+"
|
59
|
+
!specific_pull_request_id.empty? ? "##{specific_pull_request_id}" : "#\\d+"
|
62
60
|
end
|
63
61
|
|
64
|
-
def generate_remote_pull_request
|
62
|
+
def generate_remote_pull_request(remote_pull_request)
|
65
63
|
scm_provider = find_scm_provider(remote_url)
|
66
64
|
|
67
65
|
case scm_provider
|
@@ -88,10 +86,8 @@ module Danger
|
|
88
86
|
end
|
89
87
|
end
|
90
88
|
|
91
|
-
def
|
92
|
-
|
93
|
-
client.pull_request(repo_slug, specific_pull_request_id)
|
94
|
-
end
|
89
|
+
def find_remote_pull_request(env)
|
90
|
+
client(env).pull_request(repo_slug, specific_pull_request_id)
|
95
91
|
end
|
96
92
|
|
97
93
|
def both_present?
|
@@ -135,7 +131,7 @@ module Danger
|
|
135
131
|
!most_recent_squash_and_merged_pull_request.nil? && !most_recent_squash_and_merged_pull_request.empty?
|
136
132
|
end
|
137
133
|
|
138
|
-
def client
|
134
|
+
def client(env)
|
139
135
|
scm_provider = find_scm_provider(remote_url)
|
140
136
|
|
141
137
|
case scm_provider
|
@@ -168,7 +164,7 @@ module Danger
|
|
168
164
|
def api_url
|
169
165
|
ENV.fetch("DANGER_GITHUB_API_HOST") do
|
170
166
|
ENV.fetch("DANGER_GITHUB_API_BASE_URL") do
|
171
|
-
"https://api.github.com/"
|
167
|
+
"https://api.github.com/"
|
172
168
|
end
|
173
169
|
end
|
174
170
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<%- @tables.each do |table| -%>
|
2
|
+
<%- if table[:content].any? || table[:resolved].any? -%>
|
3
|
+
| | |
|
4
|
+
|---|---|
|
5
|
+
<%- table[:content].each do |violation| -%>
|
6
|
+
| <%= @emoji_mapper.map(table[:emoji]) %> | <%= "~~" if table[:resolved] %><%= violation.message %><%= "~~" if table[:resolved] %> |
|
7
|
+
<%- end -%>
|
8
|
+
|
9
|
+
<%- end -%>
|
10
|
+
<%- end -%>
|
11
|
+
|
12
|
+
<%- @markdowns.each do |current| -%>
|
13
|
+
<%= current %>
|
14
|
+
<%# the previous line has to be aligned far to the left, otherwise markdown can break easily %>
|
15
|
+
<%- end -%>
|
16
|
+
|
17
|
+
Generated by :no_entry_sign: [Danger](https://danger.systems/ "generated_by_<%= @danger_id %>")
|
@@ -128,7 +128,7 @@ module Danger
|
|
128
128
|
# @return [Git::Diff::DiffFile] from the gem `git`
|
129
129
|
#
|
130
130
|
def diff_for_file(file)
|
131
|
-
(added_files + modified_files).include?(file) ? @git.diff[file] : nil
|
131
|
+
(added_files + modified_files + deleted_files).include?(file) ? @git.diff[file] : nil
|
132
132
|
end
|
133
133
|
|
134
134
|
# @!group Git Metadata
|
@@ -24,7 +24,8 @@ module Danger
|
|
24
24
|
# @param [Bool] Should hide any generated link created
|
25
25
|
#
|
26
26
|
# @return [String] The Markdown compatible link
|
27
|
-
def markdown_link_to_message(message,
|
27
|
+
def markdown_link_to_message(message, hide_link)
|
28
|
+
return "" if hide_link
|
28
29
|
"#{message.file}#L#{message.line}"
|
29
30
|
end
|
30
31
|
|
@@ -147,7 +148,7 @@ module Danger
|
|
147
148
|
|
148
149
|
def generate_description(warnings: nil, errors: nil, template: "github")
|
149
150
|
emoji_mapper = EmojiMapper.new(template)
|
150
|
-
if errors.empty? && warnings.empty?
|
151
|
+
if (errors.nil? || errors.empty?) && (warnings.nil? || warnings.empty?)
|
151
152
|
return ENV['DANGER_SUCCESS_MESSAGE'] || "All green. #{random_compliment}"
|
152
153
|
else
|
153
154
|
message = "#{emoji_mapper.map('warning')} "
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
# coding: utf-8
|
2
3
|
|
3
4
|
require "danger/helpers/comments_helper"
|
@@ -32,9 +33,8 @@ module Danger
|
|
32
33
|
def inspect
|
33
34
|
inspected = super
|
34
35
|
|
35
|
-
if @password
|
36
|
-
|
37
|
-
end
|
36
|
+
inspected.gsub!(@password, "********") if @password
|
37
|
+
inspected.gsub!(@access_token, "********") if @access_token
|
38
38
|
|
39
39
|
inspected
|
40
40
|
end
|
@@ -31,7 +31,6 @@ module Danger
|
|
31
31
|
|
32
32
|
def initialize(ci_source, environment)
|
33
33
|
self.ci_source = ci_source
|
34
|
-
self.environment = environment
|
35
34
|
|
36
35
|
project, slug = ci_source.repo_slug.split("/")
|
37
36
|
@api = BitbucketServerAPI.new(project, slug, ci_source.pull_request_id, environment)
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
# coding: utf-8
|
2
3
|
|
3
4
|
require "openssl"
|
@@ -12,7 +13,7 @@ module Danger
|
|
12
13
|
@username = environment["DANGER_BITBUCKETSERVER_USERNAME"]
|
13
14
|
@password = environment["DANGER_BITBUCKETSERVER_PASSWORD"]
|
14
15
|
self.host = environment["DANGER_BITBUCKETSERVER_HOST"]
|
15
|
-
self.verify_ssl = environment["DANGER_BITBUCKETSERVER_VERIFY_SSL"]
|
16
|
+
self.verify_ssl = environment["DANGER_BITBUCKETSERVER_VERIFY_SSL"] != "false"
|
16
17
|
if self.host && !(self.host.include? "http://") && !(self.host.include? "https://")
|
17
18
|
self.host = "https://" + self.host
|
18
19
|
end
|
@@ -24,9 +25,7 @@ module Danger
|
|
24
25
|
def inspect
|
25
26
|
inspected = super
|
26
27
|
|
27
|
-
if @password
|
28
|
-
inspected = inspected.sub! @password, "********".freeze
|
29
|
-
end
|
28
|
+
inspected.gsub!(@password, "********") if @password
|
30
29
|
|
31
30
|
inspected
|
32
31
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
# coding: utf-8
|
2
3
|
|
3
4
|
module Danger
|
@@ -26,9 +27,7 @@ module Danger
|
|
26
27
|
def inspect
|
27
28
|
inspected = super
|
28
29
|
|
29
|
-
if @password
|
30
|
-
inspected = inspected.sub! @password, "********".freeze
|
31
|
-
end
|
30
|
+
inspected.gsub!(@password, "********") if @password
|
32
31
|
|
33
32
|
inspected
|
34
33
|
end
|
@@ -14,7 +14,7 @@ module Danger
|
|
14
14
|
class GitHub < RequestSource
|
15
15
|
include Danger::Helpers::CommentsHelper
|
16
16
|
|
17
|
-
attr_accessor :pr_json, :issue_json, :support_tokenless_auth, :dismiss_out_of_range_messages
|
17
|
+
attr_accessor :pr_json, :issue_json, :use_local_git, :support_tokenless_auth, :dismiss_out_of_range_messages, :host, :api_url, :verify_ssl
|
18
18
|
|
19
19
|
def self.env_vars
|
20
20
|
["DANGER_GITHUB_API_TOKEN", "DANGER_GITHUB_BEARER_TOKEN"]
|
@@ -26,12 +26,22 @@ module Danger
|
|
26
26
|
|
27
27
|
def initialize(ci_source, environment)
|
28
28
|
self.ci_source = ci_source
|
29
|
-
self.
|
29
|
+
self.use_local_git = environment["DANGER_USE_LOCAL_GIT"]
|
30
30
|
self.support_tokenless_auth = false
|
31
31
|
self.dismiss_out_of_range_messages = false
|
32
|
+
self.host = environment.fetch("DANGER_GITHUB_HOST", "github.com")
|
33
|
+
# `DANGER_GITHUB_API_HOST` is the old name kept for legacy reasons and
|
34
|
+
# backwards compatibility. `DANGER_GITHUB_API_BASE_URL` is the new
|
35
|
+
# correctly named variable.
|
36
|
+
self.api_url = environment.fetch("DANGER_GITHUB_API_HOST") do
|
37
|
+
environment.fetch("DANGER_GITHUB_API_BASE_URL") do
|
38
|
+
"https://api.github.com/".freeze
|
39
|
+
end
|
40
|
+
end
|
41
|
+
self.verify_ssl = environment["DANGER_OCTOKIT_VERIFY_SSL"] != "false"
|
32
42
|
|
33
|
-
@access_token =
|
34
|
-
@bearer_token =
|
43
|
+
@access_token = environment["DANGER_GITHUB_API_TOKEN"]
|
44
|
+
@bearer_token = environment["DANGER_GITHUB_BEARER_TOKEN"]
|
35
45
|
end
|
36
46
|
|
37
47
|
def get_pr_from_branch(repo_name, branch_name, owner)
|
@@ -46,32 +56,13 @@ module Danger
|
|
46
56
|
end
|
47
57
|
|
48
58
|
def validates_as_api_source?
|
49
|
-
valid_bearer_token? || valid_access_token? ||
|
59
|
+
valid_bearer_token? || valid_access_token? || use_local_git
|
50
60
|
end
|
51
61
|
|
52
62
|
def scm
|
53
63
|
@scm ||= GitRepo.new
|
54
64
|
end
|
55
65
|
|
56
|
-
def host
|
57
|
-
@host = @environment["DANGER_GITHUB_HOST"] || "github.com"
|
58
|
-
end
|
59
|
-
|
60
|
-
def verify_ssl
|
61
|
-
@environment["DANGER_OCTOKIT_VERIFY_SSL"] == "false" ? false : true
|
62
|
-
end
|
63
|
-
|
64
|
-
# `DANGER_GITHUB_API_HOST` is the old name kept for legacy reasons and
|
65
|
-
# backwards compatibility. `DANGER_GITHUB_API_BASE_URL` is the new
|
66
|
-
# correctly named variable.
|
67
|
-
def api_url
|
68
|
-
@environment.fetch("DANGER_GITHUB_API_HOST") do
|
69
|
-
@environment.fetch("DANGER_GITHUB_API_BASE_URL") do
|
70
|
-
"https://api.github.com/".freeze
|
71
|
-
end
|
72
|
-
end
|
73
|
-
end
|
74
|
-
|
75
66
|
def client
|
76
67
|
raise "No API token given, please provide one using `DANGER_GITHUB_API_TOKEN` or `DANGER_GITHUB_BEARER_TOKEN`" if !valid_access_token? && !valid_bearer_token? && !support_tokenless_auth
|
77
68
|
@client ||= begin
|
@@ -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, :dismiss_out_of_range_messages
|
11
|
+
attr_accessor :mr_json, :commits_json, :dismiss_out_of_range_messages, :endpoint, :host
|
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")
|
@@ -23,20 +23,19 @@ module Danger
|
|
23
23
|
|
24
24
|
def initialize(ci_source, environment)
|
25
25
|
self.ci_source = ci_source
|
26
|
-
self.environment = environment
|
27
26
|
self.dismiss_out_of_range_messages = false
|
28
|
-
|
29
|
-
@
|
27
|
+
@endpoint = environment["DANGER_GITLAB_API_BASE_URL"] || environment.fetch("CI_API_V4_URL", "https://gitlab.com/api/v4")
|
28
|
+
@host = environment.fetch("DANGER_GITLAB_HOST", URI.parse(endpoint).host) || "gitlab.com"
|
29
|
+
@token = environment["DANGER_GITLAB_API_TOKEN"]
|
30
30
|
end
|
31
31
|
|
32
32
|
def client
|
33
|
-
token
|
34
|
-
raise "No API token given, please provide one using `DANGER_GITLAB_API_TOKEN`" unless token
|
33
|
+
raise "No API token given, please provide one using `DANGER_GITLAB_API_TOKEN`" unless @token
|
35
34
|
|
36
35
|
# The require happens inline so that it won't cause exceptions when just using the `danger` gem.
|
37
36
|
require "gitlab"
|
38
37
|
|
39
|
-
@client ||= Gitlab.client(endpoint: endpoint, private_token: token)
|
38
|
+
@client ||= Gitlab.client(endpoint: endpoint, private_token: @token)
|
40
39
|
rescue LoadError => e
|
41
40
|
if e.path == "gitlab"
|
42
41
|
puts "The GitLab gem was not installed, you will need to change your Gem from `danger` to `danger-gitlab`.".red
|
@@ -48,7 +47,7 @@ module Danger
|
|
48
47
|
end
|
49
48
|
|
50
49
|
def validates_as_ci?
|
51
|
-
includes_port =
|
50
|
+
includes_port = host.include? ":"
|
52
51
|
raise "Port number included in `DANGER_GITLAB_HOST`, this will fail with GitLab CI Runners" if includes_port
|
53
52
|
|
54
53
|
# We don't call super because in some cases the Git remote doesn't match the GitLab instance host.
|
@@ -66,14 +65,6 @@ module Danger
|
|
66
65
|
@scm ||= GitRepo.new
|
67
66
|
end
|
68
67
|
|
69
|
-
def endpoint
|
70
|
-
@endpoint ||= @environment["DANGER_GITLAB_API_BASE_URL"] || @environment["CI_API_V4_URL"] || "https://gitlab.com/api/v4"
|
71
|
-
end
|
72
|
-
|
73
|
-
def host
|
74
|
-
@host ||= @environment["DANGER_GITLAB_HOST"] || URI.parse(endpoint).host || "gitlab.com"
|
75
|
-
end
|
76
|
-
|
77
68
|
def base_commit
|
78
69
|
@base_commit ||= self.mr_json.diff_refs.base_sha
|
79
70
|
end
|
@@ -326,11 +317,10 @@ module Danger
|
|
326
317
|
# @return [String] A URL to the specific file, ready to be downloaded
|
327
318
|
def file_url(organisation: nil, repository: nil, branch: nil, path: nil)
|
328
319
|
branch ||= 'master'
|
329
|
-
token = @environment["DANGER_GITLAB_API_TOKEN"]
|
330
320
|
# According to GitLab Repositories API docs path and id(slug) should be encoded.
|
331
321
|
path = URI.encode_www_form_component(path)
|
332
322
|
repository = URI.encode_www_form_component(repository)
|
333
|
-
"#{endpoint}/projects/#{repository}/repository/files/#{path}/raw?ref=#{branch}&private_token=#{token}"
|
323
|
+
"#{endpoint}/projects/#{repository}/repository/files/#{path}/raw?ref=#{branch}&private_token=#{@token}"
|
334
324
|
end
|
335
325
|
|
336
326
|
def regular_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
|
@@ -1,9 +1,11 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Danger
|
2
4
|
module RequestSources
|
3
5
|
class RequestSource
|
4
|
-
DANGER_REPO_NAME = "danger"
|
6
|
+
DANGER_REPO_NAME = "danger"
|
5
7
|
|
6
|
-
attr_accessor :ci_source, :
|
8
|
+
attr_accessor :ci_source, :scm, :host, :ignored_violations
|
7
9
|
|
8
10
|
def self.env_vars
|
9
11
|
raise "Subclass and overwrite self.env_vars"
|
@@ -23,12 +25,12 @@ module Danger
|
|
23
25
|
end
|
24
26
|
|
25
27
|
def self.source_name
|
26
|
-
to_s.sub("Danger::RequestSources::"
|
28
|
+
to_s.sub("Danger::RequestSources::", "")
|
27
29
|
end
|
28
30
|
|
29
31
|
def self.available_source_names_and_envs
|
30
32
|
available_request_sources.map do |klass|
|
31
|
-
" - #{klass.source_name}: #{klass.env_vars.join(', '
|
33
|
+
" - #{klass.source_name}: #{klass.env_vars.join(', ').yellow}"
|
32
34
|
end
|
33
35
|
end
|
34
36
|
|
@@ -36,6 +38,16 @@ module Danger
|
|
36
38
|
raise "Subclass and overwrite initialize"
|
37
39
|
end
|
38
40
|
|
41
|
+
def inspect
|
42
|
+
inspected = super
|
43
|
+
|
44
|
+
inspected.gsub!(@token, "********") if @token
|
45
|
+
inspected.gsub!(@access_token, "********") if @access_token
|
46
|
+
inspected.gsub!(@bearer_token, "********") if @bearer_token
|
47
|
+
|
48
|
+
inspected
|
49
|
+
end
|
50
|
+
|
39
51
|
# @return [Boolean] whether scm.origins is a valid git repository or not
|
40
52
|
def validates_as_ci?
|
41
53
|
!!self.scm.origins.match(%r{#{Regexp.escape self.host}(:|/)(.+/.+?)(?:\.git)?$})
|
@@ -24,7 +24,6 @@ module Danger
|
|
24
24
|
|
25
25
|
def initialize(ci_source, environment)
|
26
26
|
self.ci_source = ci_source
|
27
|
-
self.environment = environment
|
28
27
|
|
29
28
|
@is_vsts_git = environment["BUILD_REPOSITORY_PROVIDER"] == "TfsGit"
|
30
29
|
|
@@ -44,6 +43,10 @@ module Danger
|
|
44
43
|
@scm ||= GitRepo.new
|
45
44
|
end
|
46
45
|
|
46
|
+
def client
|
47
|
+
@api
|
48
|
+
end
|
49
|
+
|
47
50
|
def host
|
48
51
|
@host ||= @api.host
|
49
52
|
end
|
@@ -77,15 +80,36 @@ module Danger
|
|
77
80
|
return
|
78
81
|
end
|
79
82
|
|
80
|
-
|
83
|
+
regular_violations = regular_violations_group(
|
84
|
+
warnings: warnings,
|
85
|
+
errors: errors,
|
86
|
+
messages: messages,
|
87
|
+
markdowns: markdowns
|
88
|
+
)
|
89
|
+
|
90
|
+
inline_violations = inline_violations_group(
|
91
|
+
warnings: warnings,
|
92
|
+
errors: errors,
|
93
|
+
messages: messages,
|
94
|
+
markdowns: markdowns
|
95
|
+
)
|
96
|
+
|
97
|
+
rest_inline_violations = submit_inline_comments!(**{
|
98
|
+
danger_id: danger_id,
|
99
|
+
previous_violations: {}
|
100
|
+
}.merge(inline_violations))
|
101
|
+
|
102
|
+
main_violations = merge_violations(
|
103
|
+
regular_violations, rest_inline_violations
|
104
|
+
)
|
105
|
+
|
106
|
+
comment = generate_description(warnings: main_violations[:warnings], errors: main_violations[:errors])
|
81
107
|
comment += "\n\n"
|
82
|
-
comment += generate_comment(
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
danger_id: danger_id,
|
88
|
-
template: "vsts")
|
108
|
+
comment += generate_comment(**{
|
109
|
+
previous_violations: {},
|
110
|
+
danger_id: danger_id,
|
111
|
+
template: "vsts"
|
112
|
+
}.merge(main_violations))
|
89
113
|
if new_comment || remove_previous_comments
|
90
114
|
post_new_comment(comment)
|
91
115
|
else
|
@@ -106,6 +130,8 @@ module Danger
|
|
106
130
|
comment_content = comment[:content].nil? ? "" : comment[:content]
|
107
131
|
# Skip the comment if it wasn't posted by danger
|
108
132
|
next unless comment_content.include?("generated_by_#{danger_id}")
|
133
|
+
# Skip the comment if it's an inline comment
|
134
|
+
next unless c[:threadContext].nil?
|
109
135
|
# Updated the danger posted comment
|
110
136
|
@api.update_comment(thread_id, comment_id, new_comment)
|
111
137
|
comment_updated = true
|
@@ -113,6 +139,142 @@ module Danger
|
|
113
139
|
# If no comment was updated, post a new one
|
114
140
|
post_new_comment(new_comment) unless comment_updated
|
115
141
|
end
|
142
|
+
|
143
|
+
def submit_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: [], danger_id: "danger")
|
144
|
+
# Avoid doing any fetchs if there's no inline comments
|
145
|
+
return {} if (warnings + errors + messages + markdowns).select(&:inline?).empty?
|
146
|
+
|
147
|
+
pr_threads = @api.fetch_last_comments
|
148
|
+
danger_threads = pr_threads.select do |thread|
|
149
|
+
comment = thread[:comments].first
|
150
|
+
comment_content = comment[:content].nil? ? "" : comment[:content]
|
151
|
+
|
152
|
+
next comment_content.include?("generated_by_#{danger_id}")
|
153
|
+
end
|
154
|
+
non_danger_threads = pr_threads - danger_threads
|
155
|
+
|
156
|
+
warnings = submit_inline_comments_for_kind!(:warning, warnings, danger_threads, previous_violations["warning"], danger_id: danger_id)
|
157
|
+
errors = submit_inline_comments_for_kind!(:error, errors, danger_threads, previous_violations["error"], danger_id: danger_id)
|
158
|
+
messages = submit_inline_comments_for_kind!(:message, messages, danger_threads, previous_violations["message"], danger_id: danger_id)
|
159
|
+
markdowns = submit_inline_comments_for_kind!(:markdown, markdowns, danger_threads, [], danger_id: danger_id)
|
160
|
+
|
161
|
+
# submit removes from the array all comments that are still in force
|
162
|
+
# so we strike out all remaining ones
|
163
|
+
danger_threads.each do |thread|
|
164
|
+
violation = violations_from_table(thread[:comments].first[:content]).first
|
165
|
+
if !violation.nil? && violation.sticky
|
166
|
+
body = generate_inline_comment_body("white_check_mark", violation, danger_id: danger_id, resolved: true, template: "github")
|
167
|
+
@api.update_comment(thread[:id], thread[:comments].first[:id], body)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
{
|
172
|
+
warnings: warnings,
|
173
|
+
errors: errors,
|
174
|
+
messages: messages,
|
175
|
+
markdowns: markdowns
|
176
|
+
}
|
177
|
+
end
|
178
|
+
|
179
|
+
def messages_are_equivalent(m1, m2)
|
180
|
+
blob_regexp = %r{blob/[0-9a-z]+/}
|
181
|
+
m1.file == m2.file && m1.line == m2.line &&
|
182
|
+
m1.message.sub(blob_regexp, "") == m2.message.sub(blob_regexp, "")
|
183
|
+
end
|
184
|
+
|
185
|
+
def submit_inline_comments_for_kind!(kind, messages, danger_threads, previous_violations, danger_id: "danger")
|
186
|
+
previous_violations ||= []
|
187
|
+
is_markdown_content = kind == :markdown
|
188
|
+
emoji = { warning: "warning", error: "no_entry_sign", message: "book" }[kind]
|
189
|
+
|
190
|
+
messages.reject do |m|
|
191
|
+
next false unless m.file && m.line
|
192
|
+
|
193
|
+
# Once we know we're gonna submit it, we format it
|
194
|
+
if is_markdown_content
|
195
|
+
body = generate_inline_markdown_body(m, danger_id: danger_id, template: "vsts")
|
196
|
+
else
|
197
|
+
# Hide the inline link behind a span
|
198
|
+
m.message.gsub!("\n", "<br />")
|
199
|
+
m = process_markdown(m, true)
|
200
|
+
body = generate_inline_comment_body(emoji, m, danger_id: danger_id, template: "vsts")
|
201
|
+
# A comment might be in previous_violations because only now it's part of the unified diff
|
202
|
+
# We remove from the array since it won't have a place in the table anymore
|
203
|
+
previous_violations.reject! { |v| messages_are_equivalent(v, m) }
|
204
|
+
end
|
205
|
+
|
206
|
+
matching_threads = danger_threads.select do |comment_data|
|
207
|
+
if comment_data.key?(:threadContext) && !comment_data[:threadContext].nil? &&
|
208
|
+
comment_data[:threadContext][:filePath] == m.file &&
|
209
|
+
comment_data[:threadContext].key?(:rightFileStart) &&
|
210
|
+
comment_data[:threadContext][:rightFileStart][:line] == m.line
|
211
|
+
# Parse it to avoid problems with strikethrough
|
212
|
+
violation = violations_from_table(comment_data[:comments].first[:content]).first
|
213
|
+
if violation
|
214
|
+
messages_are_equivalent(violation, m)
|
215
|
+
else
|
216
|
+
blob_regexp = %r{blob/[0-9a-z]+/}
|
217
|
+
comment_data[:comments].first[:content].sub(blob_regexp, "") == body.sub(blob_regexp, "")
|
218
|
+
end
|
219
|
+
else
|
220
|
+
false
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
if matching_threads.empty?
|
225
|
+
@api.post_inline_comment(body, m.file, m.line)
|
226
|
+
|
227
|
+
# Not reject because this comment has not completed
|
228
|
+
next false
|
229
|
+
else
|
230
|
+
# Remove the surviving comment so we don't strike it out
|
231
|
+
danger_threads.reject! { |c| matching_threads.include? c }
|
232
|
+
|
233
|
+
# Update the comment to remove the strikethrough if present
|
234
|
+
thread = matching_threads.first
|
235
|
+
@api.update_comment(thread[:id], thread[:comments].first[:id], body)
|
236
|
+
end
|
237
|
+
|
238
|
+
# Remove this element from the array
|
239
|
+
next true
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
private
|
244
|
+
|
245
|
+
def regular_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
|
246
|
+
{
|
247
|
+
warnings: warnings.reject(&:inline?),
|
248
|
+
errors: errors.reject(&:inline?),
|
249
|
+
messages: messages.reject(&:inline?),
|
250
|
+
markdowns: markdowns.reject(&:inline?)
|
251
|
+
}
|
252
|
+
end
|
253
|
+
|
254
|
+
def inline_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
|
255
|
+
cmp = proc do |a, b|
|
256
|
+
next -1 unless a.file && a.line
|
257
|
+
next 1 unless b.file && b.line
|
258
|
+
|
259
|
+
next a.line <=> b.line if a.file == b.file
|
260
|
+
next a.file <=> b.file
|
261
|
+
end
|
262
|
+
|
263
|
+
# Sort to group inline comments by file
|
264
|
+
{
|
265
|
+
warnings: warnings.select(&:inline?).sort(&cmp),
|
266
|
+
errors: errors.select(&:inline?).sort(&cmp),
|
267
|
+
messages: messages.select(&:inline?).sort(&cmp),
|
268
|
+
markdowns: markdowns.select(&:inline?).sort(&cmp)
|
269
|
+
}
|
270
|
+
end
|
271
|
+
|
272
|
+
def merge_violations(*violation_groups)
|
273
|
+
violation_groups.inject({}) do |accumulator, group|
|
274
|
+
accumulator.merge(group) { |_, old, fresh| old + fresh }
|
275
|
+
end
|
276
|
+
end
|
277
|
+
|
116
278
|
end
|
117
279
|
end
|
118
280
|
end
|
@@ -1,3 +1,4 @@
|
|
1
|
+
# frozen_string_literal: true
|
1
2
|
# coding: utf-8
|
2
3
|
|
3
4
|
require "base64"
|
@@ -35,9 +36,7 @@ module Danger
|
|
35
36
|
def inspect
|
36
37
|
inspected = super
|
37
38
|
|
38
|
-
if @token
|
39
|
-
inspected = inspected.sub! @token, "********".freeze
|
40
|
-
end
|
39
|
+
inspected.gsub!(@token, "********") if @token
|
41
40
|
|
42
41
|
inspected
|
43
42
|
end
|
@@ -77,6 +76,38 @@ module Danger
|
|
77
76
|
post(uri, body)
|
78
77
|
end
|
79
78
|
|
79
|
+
def post_inline_comment(text, file, line)
|
80
|
+
uri = URI("#{pr_api_endpoint}/threads?api-version=#{@api_version}")
|
81
|
+
body = {
|
82
|
+
"comments" => [
|
83
|
+
{
|
84
|
+
"parentCommentId" => 0,
|
85
|
+
"content" => text,
|
86
|
+
"commentType" => 1
|
87
|
+
}
|
88
|
+
],
|
89
|
+
"properties" => {
|
90
|
+
"Microsoft.TeamFoundation.Discussion.SupportsMarkdown" => {
|
91
|
+
"type" => "System.Int32",
|
92
|
+
"value" => 1
|
93
|
+
}
|
94
|
+
},
|
95
|
+
"status" => 1,
|
96
|
+
"threadContext" => {
|
97
|
+
"filePath" => file,
|
98
|
+
"rightFileEnd" => {
|
99
|
+
"line" => line + 1,
|
100
|
+
"offset" => 1
|
101
|
+
},
|
102
|
+
"rightFileStart" => {
|
103
|
+
"line" => line,
|
104
|
+
"offset" => 1
|
105
|
+
}
|
106
|
+
}
|
107
|
+
}.to_json
|
108
|
+
post(uri, body)
|
109
|
+
end
|
110
|
+
|
80
111
|
def update_comment(thread, id, new_comment)
|
81
112
|
uri = URI("#{pr_api_endpoint}/threads/#{thread}/comments/#{id}?api-version=#{@api_version}")
|
82
113
|
body = {
|
@@ -55,7 +55,8 @@ module Danger
|
|
55
55
|
def exec(string)
|
56
56
|
require "open3"
|
57
57
|
Dir.chdir(self.folder || ".") do
|
58
|
-
|
58
|
+
git_command = string.split(" ").dup.unshift("git")
|
59
|
+
Open3.popen2(default_env, *git_command) do |_stdin, stdout, _wait_thr|
|
59
60
|
stdout.read.rstrip
|
60
61
|
end
|
61
62
|
end
|
data/lib/danger/version.rb
CHANGED
metadata
CHANGED
@@ -1,15 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: danger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 8.
|
4
|
+
version: 8.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Orta Therox
|
8
8
|
- Juanito Fatas
|
9
|
-
autorequire:
|
9
|
+
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2022-
|
12
|
+
date: 2022-04-16 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: claide
|
@@ -271,6 +271,7 @@ files:
|
|
271
271
|
- lib/danger/comment_generators/gitlab.md.erb
|
272
272
|
- lib/danger/comment_generators/gitlab_inline.md.erb
|
273
273
|
- lib/danger/comment_generators/vsts.md.erb
|
274
|
+
- lib/danger/comment_generators/vsts_inline.md.erb
|
274
275
|
- lib/danger/core_ext/file_list.rb
|
275
276
|
- lib/danger/core_ext/string.rb
|
276
277
|
- lib/danger/danger_core/dangerfile.rb
|
@@ -327,7 +328,7 @@ homepage: https://github.com/danger/danger
|
|
327
328
|
licenses:
|
328
329
|
- MIT
|
329
330
|
metadata: {}
|
330
|
-
post_install_message:
|
331
|
+
post_install_message:
|
331
332
|
rdoc_options: []
|
332
333
|
require_paths:
|
333
334
|
- lib
|
@@ -342,8 +343,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
342
343
|
- !ruby/object:Gem::Version
|
343
344
|
version: '0'
|
344
345
|
requirements: []
|
345
|
-
|
346
|
-
|
346
|
+
rubyforge_project:
|
347
|
+
rubygems_version: 2.7.6.2
|
348
|
+
signing_key:
|
347
349
|
specification_version: 4
|
348
350
|
summary: Like Unit Tests, but for your Team Culture.
|
349
351
|
test_files: []
|