danger-additional-logging 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +22 -0
- data/README.md +93 -0
- data/bin/danger +5 -0
- data/lib/assets/DangerfileTemplate +13 -0
- data/lib/danger/ci_source/appcenter.rb +55 -0
- data/lib/danger/ci_source/appcircle.rb +83 -0
- data/lib/danger/ci_source/appveyor.rb +64 -0
- data/lib/danger/ci_source/azure_pipelines.rb +61 -0
- data/lib/danger/ci_source/bamboo.rb +41 -0
- data/lib/danger/ci_source/bitbucket_pipelines.rb +37 -0
- data/lib/danger/ci_source/bitrise.rb +78 -0
- data/lib/danger/ci_source/buddybuild.rb +62 -0
- data/lib/danger/ci_source/buildkite.rb +51 -0
- data/lib/danger/ci_source/ci_source.rb +37 -0
- data/lib/danger/ci_source/circle.rb +94 -0
- data/lib/danger/ci_source/circle_api.rb +51 -0
- data/lib/danger/ci_source/cirrus.rb +31 -0
- data/lib/danger/ci_source/code_build.rb +71 -0
- data/lib/danger/ci_source/codefresh.rb +47 -0
- data/lib/danger/ci_source/codemagic.rb +58 -0
- data/lib/danger/ci_source/codeship.rb +44 -0
- data/lib/danger/ci_source/concourse.rb +60 -0
- data/lib/danger/ci_source/custom_ci_with_github.rb +49 -0
- data/lib/danger/ci_source/dotci.rb +50 -0
- data/lib/danger/ci_source/drone.rb +71 -0
- data/lib/danger/ci_source/github_actions.rb +44 -0
- data/lib/danger/ci_source/gitlab_ci.rb +89 -0
- data/lib/danger/ci_source/jenkins.rb +148 -0
- data/lib/danger/ci_source/local_git_repo.rb +117 -0
- data/lib/danger/ci_source/local_only_git_repo.rb +44 -0
- data/lib/danger/ci_source/screwdriver.rb +48 -0
- data/lib/danger/ci_source/semaphore.rb +37 -0
- data/lib/danger/ci_source/support/commits.rb +19 -0
- data/lib/danger/ci_source/support/find_repo_info_from_logs.rb +35 -0
- data/lib/danger/ci_source/support/find_repo_info_from_url.rb +43 -0
- data/lib/danger/ci_source/support/local_pull_request.rb +14 -0
- data/lib/danger/ci_source/support/no_pull_request.rb +7 -0
- data/lib/danger/ci_source/support/no_repo_info.rb +5 -0
- data/lib/danger/ci_source/support/pull_request_finder.rb +190 -0
- data/lib/danger/ci_source/support/remote_pull_request.rb +15 -0
- data/lib/danger/ci_source/support/repo_info.rb +10 -0
- data/lib/danger/ci_source/surf.rb +37 -0
- data/lib/danger/ci_source/teamcity.rb +163 -0
- data/lib/danger/ci_source/travis.rb +51 -0
- data/lib/danger/ci_source/xcode_cloud.rb +38 -0
- data/lib/danger/ci_source/xcode_server.rb +48 -0
- data/lib/danger/clients/rubygems_client.rb +14 -0
- data/lib/danger/commands/dangerfile/gem.rb +43 -0
- data/lib/danger/commands/dangerfile/init.rb +30 -0
- data/lib/danger/commands/dry_run.rb +54 -0
- data/lib/danger/commands/init.rb +297 -0
- data/lib/danger/commands/init_helpers/interviewer.rb +92 -0
- data/lib/danger/commands/local.rb +83 -0
- data/lib/danger/commands/local_helpers/http_cache.rb +38 -0
- data/lib/danger/commands/local_helpers/local_setup.rb +48 -0
- data/lib/danger/commands/local_helpers/pry_setup.rb +32 -0
- data/lib/danger/commands/plugins/plugin_json.rb +44 -0
- data/lib/danger/commands/plugins/plugin_lint.rb +52 -0
- data/lib/danger/commands/plugins/plugin_readme.rb +42 -0
- data/lib/danger/commands/pr.rb +93 -0
- data/lib/danger/commands/runner.rb +94 -0
- data/lib/danger/commands/staging.rb +53 -0
- data/lib/danger/commands/systems.rb +41 -0
- data/lib/danger/comment_generators/bitbucket_server.md.erb +20 -0
- data/lib/danger/comment_generators/bitbucket_server_inline.md.erb +15 -0
- data/lib/danger/comment_generators/bitbucket_server_message_group.md.erb +12 -0
- data/lib/danger/comment_generators/github.md.erb +55 -0
- data/lib/danger/comment_generators/github_inline.md.erb +26 -0
- data/lib/danger/comment_generators/gitlab.md.erb +40 -0
- data/lib/danger/comment_generators/gitlab_inline.md.erb +21 -0
- data/lib/danger/comment_generators/vsts.md.erb +20 -0
- data/lib/danger/comment_generators/vsts_inline.md.erb +17 -0
- data/lib/danger/core_ext/file_list.rb +18 -0
- data/lib/danger/core_ext/string.rb +20 -0
- data/lib/danger/danger_core/dangerfile.rb +348 -0
- data/lib/danger/danger_core/dangerfile_dsl.rb +29 -0
- data/lib/danger/danger_core/dangerfile_generator.rb +11 -0
- data/lib/danger/danger_core/environment_manager.rb +126 -0
- data/lib/danger/danger_core/executor.rb +91 -0
- data/lib/danger/danger_core/message_aggregator.rb +50 -0
- data/lib/danger/danger_core/message_group.rb +68 -0
- data/lib/danger/danger_core/messages/base.rb +57 -0
- data/lib/danger/danger_core/messages/markdown.rb +41 -0
- data/lib/danger/danger_core/messages/violation.rb +53 -0
- data/lib/danger/danger_core/plugins/dangerfile_bitbucket_cloud_plugin.rb +142 -0
- data/lib/danger/danger_core/plugins/dangerfile_bitbucket_server_plugin.rb +211 -0
- data/lib/danger/danger_core/plugins/dangerfile_danger_plugin.rb +274 -0
- data/lib/danger/danger_core/plugins/dangerfile_git_plugin.rb +159 -0
- data/lib/danger/danger_core/plugins/dangerfile_github_plugin.rb +264 -0
- data/lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb +275 -0
- data/lib/danger/danger_core/plugins/dangerfile_local_only_plugin.rb +43 -0
- data/lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb +220 -0
- data/lib/danger/danger_core/plugins/dangerfile_vsts_plugin.rb +191 -0
- data/lib/danger/danger_core/standard_error.rb +142 -0
- data/lib/danger/helpers/array_subclass.rb +61 -0
- data/lib/danger/helpers/comment.rb +32 -0
- data/lib/danger/helpers/comments_helper.rb +179 -0
- data/lib/danger/helpers/comments_parsing_helper.rb +71 -0
- data/lib/danger/helpers/emoji_mapper.rb +41 -0
- data/lib/danger/helpers/find_max_num_violations.rb +31 -0
- data/lib/danger/helpers/message_groups_array_helper.rb +31 -0
- data/lib/danger/plugin_support/gems_resolver.rb +77 -0
- data/lib/danger/plugin_support/plugin.rb +52 -0
- data/lib/danger/plugin_support/plugin_file_resolver.rb +30 -0
- data/lib/danger/plugin_support/plugin_linter.rb +162 -0
- data/lib/danger/plugin_support/plugin_parser.rb +199 -0
- data/lib/danger/plugin_support/templates/readme_table.html.erb +26 -0
- data/lib/danger/request_sources/bitbucket_cloud.rb +169 -0
- data/lib/danger/request_sources/bitbucket_cloud_api.rb +181 -0
- data/lib/danger/request_sources/bitbucket_server.rb +210 -0
- data/lib/danger/request_sources/bitbucket_server_api.rb +129 -0
- data/lib/danger/request_sources/code_insights_api.rb +142 -0
- data/lib/danger/request_sources/github/github.rb +535 -0
- data/lib/danger/request_sources/github/github_review.rb +127 -0
- data/lib/danger/request_sources/github/github_review_resolver.rb +17 -0
- data/lib/danger/request_sources/github/github_review_unsupported.rb +23 -0
- data/lib/danger/request_sources/gitlab.rb +557 -0
- data/lib/danger/request_sources/local_only.rb +50 -0
- data/lib/danger/request_sources/request_source.rb +97 -0
- data/lib/danger/request_sources/support/get_ignored_violation.rb +17 -0
- data/lib/danger/request_sources/vsts.rb +278 -0
- data/lib/danger/request_sources/vsts_api.rb +172 -0
- data/lib/danger/scm_source/git_repo.rb +198 -0
- data/lib/danger/version.rb +4 -0
- data/lib/danger.rb +45 -0
- metadata +351 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Danger
|
|
4
|
+
module RequestSources
|
|
5
|
+
class RequestSource
|
|
6
|
+
DANGER_REPO_NAME = "danger"
|
|
7
|
+
|
|
8
|
+
attr_accessor :ci_source, :scm, :host, :ignored_violations
|
|
9
|
+
|
|
10
|
+
def self.env_vars
|
|
11
|
+
raise "Subclass and overwrite self.env_vars"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def self.optional_env_vars
|
|
15
|
+
[]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.inherited(child_class)
|
|
19
|
+
available_request_sources.add child_class
|
|
20
|
+
super
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.available_request_sources
|
|
24
|
+
@available_request_sources ||= Set.new
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.source_name
|
|
28
|
+
to_s.sub("Danger::RequestSources::", "")
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.available_source_names_and_envs
|
|
32
|
+
available_request_sources.map do |klass|
|
|
33
|
+
" - #{klass.source_name}: #{klass.env_vars.join(', ').yellow}"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def initialize(_ci_source, _environment)
|
|
38
|
+
raise "Subclass and overwrite initialize"
|
|
39
|
+
end
|
|
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
|
+
|
|
51
|
+
# @return [Boolean] whether scm.origins is a valid git repository or not
|
|
52
|
+
def validates_as_ci?
|
|
53
|
+
!!self.scm.origins.match(%r{#{Regexp.escape self.host}(:|/)(.+/.+?)(?:\.git)?$})
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validates_as_api_source?
|
|
57
|
+
raise "Subclass and overwrite validates_as_api_source?"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def scm
|
|
61
|
+
@scm ||= nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def host
|
|
65
|
+
@host ||= nil
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def ignored_violations
|
|
69
|
+
@ignored_violations ||= []
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def update_pull_request!(_warnings: [], _errors: [], _messages: [], _markdowns: [])
|
|
73
|
+
raise "Subclass and overwrite update_pull_request!"
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def setup_danger_branches
|
|
77
|
+
raise "Subclass and overwrite setup_danger_branches"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def fetch_details
|
|
81
|
+
raise "Subclass and overwrite initialize"
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def organisation
|
|
85
|
+
raise "Subclass and overwrite organisation"
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def file_url(_organisation: nil, _repository: nil, _ref: nil, _branch: nil, _path: nil)
|
|
89
|
+
raise "Subclass and overwrite file_url"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def update_build_status(_status)
|
|
93
|
+
raise "Subclass and overwrite update_build_status"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class GetIgnoredViolation
|
|
2
|
+
IGNORE_REGEXP = />*\s*danger\s*:\s*ignore\s*"(?<error>[^"]*)"/i.freeze
|
|
3
|
+
|
|
4
|
+
def initialize(body)
|
|
5
|
+
@body = body
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def call
|
|
9
|
+
return [] unless body
|
|
10
|
+
|
|
11
|
+
body.chomp.scan(IGNORE_REGEXP).flatten
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
attr_reader :body
|
|
17
|
+
end
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
require "danger/helpers/comments_helper"
|
|
2
|
+
require "danger/request_sources/vsts_api"
|
|
3
|
+
|
|
4
|
+
module Danger
|
|
5
|
+
module RequestSources
|
|
6
|
+
class VSTS < RequestSource
|
|
7
|
+
include Danger::Helpers::CommentsHelper
|
|
8
|
+
attr_accessor :pr_json
|
|
9
|
+
|
|
10
|
+
def self.env_vars
|
|
11
|
+
[
|
|
12
|
+
"DANGER_VSTS_API_TOKEN",
|
|
13
|
+
"DANGER_VSTS_HOST"
|
|
14
|
+
]
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.optional_env_vars
|
|
18
|
+
[
|
|
19
|
+
"DANGER_VSTS_API_VERSION"
|
|
20
|
+
]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(ci_source, environment)
|
|
24
|
+
self.ci_source = ci_source
|
|
25
|
+
|
|
26
|
+
@is_vsts_ci = environment.key? "DANGER_VSTS_HOST"
|
|
27
|
+
|
|
28
|
+
@api = VSTSAPI.new(ci_source.repo_slug, ci_source.pull_request_id, environment)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def validates_as_ci?
|
|
32
|
+
@is_vsts_ci
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def validates_as_api_source?
|
|
36
|
+
@api.credentials_given?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def scm
|
|
40
|
+
@scm ||= GitRepo.new
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def client
|
|
44
|
+
@api
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def host
|
|
48
|
+
@host ||= @api.host
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def fetch_details
|
|
52
|
+
self.pr_json = @api.fetch_pr_json
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def setup_danger_branches
|
|
56
|
+
base_branch = self.pr_json[:targetRefName].sub("refs/heads/", "")
|
|
57
|
+
base_commit = self.pr_json[:lastMergeTargetCommit][:commitId]
|
|
58
|
+
head_branch = self.pr_json[:sourceRefName].sub("refs/heads/", "")
|
|
59
|
+
head_commit = self.pr_json[:lastMergeSourceCommit][:commitId]
|
|
60
|
+
|
|
61
|
+
# Next, we want to ensure that we have a version of the current branch at a known location
|
|
62
|
+
scm.ensure_commitish_exists_on_branch! base_branch, base_commit
|
|
63
|
+
self.scm.exec "branch #{EnvironmentManager.danger_base_branch} #{base_commit}"
|
|
64
|
+
|
|
65
|
+
# OK, so we want to ensure that we have a known head branch, this will always represent
|
|
66
|
+
# the head of the PR ( e.g. the most recent commit that will be merged. )
|
|
67
|
+
scm.ensure_commitish_exists_on_branch! head_branch, head_commit
|
|
68
|
+
self.scm.exec "branch #{EnvironmentManager.danger_head_branch} #{head_commit}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def organisation
|
|
72
|
+
nil
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false)
|
|
76
|
+
unless @api.supports_comments?
|
|
77
|
+
return
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
regular_violations = regular_violations_group(
|
|
81
|
+
warnings: warnings,
|
|
82
|
+
errors: errors,
|
|
83
|
+
messages: messages,
|
|
84
|
+
markdowns: markdowns
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
inline_violations = inline_violations_group(
|
|
88
|
+
warnings: warnings,
|
|
89
|
+
errors: errors,
|
|
90
|
+
messages: messages,
|
|
91
|
+
markdowns: markdowns
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
rest_inline_violations = submit_inline_comments!(**{
|
|
95
|
+
danger_id: danger_id,
|
|
96
|
+
previous_violations: {}
|
|
97
|
+
}.merge(inline_violations))
|
|
98
|
+
|
|
99
|
+
main_violations = merge_violations(
|
|
100
|
+
regular_violations, rest_inline_violations
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
comment = generate_description(warnings: main_violations[:warnings], errors: main_violations[:errors])
|
|
104
|
+
comment += "\n\n"
|
|
105
|
+
comment += generate_comment(**{
|
|
106
|
+
previous_violations: {},
|
|
107
|
+
danger_id: danger_id,
|
|
108
|
+
template: "vsts"
|
|
109
|
+
}.merge(main_violations))
|
|
110
|
+
if new_comment || remove_previous_comments
|
|
111
|
+
post_new_comment(comment)
|
|
112
|
+
else
|
|
113
|
+
update_old_comment(comment, danger_id: danger_id)
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def post_new_comment(comment)
|
|
118
|
+
@api.post_comment(comment)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def update_old_comment(new_comment, danger_id: "danger")
|
|
122
|
+
comment_updated = false
|
|
123
|
+
@api.fetch_last_comments.each do |c|
|
|
124
|
+
thread_id = c[:id]
|
|
125
|
+
comment = c[:comments].first
|
|
126
|
+
comment_id = comment[:id]
|
|
127
|
+
comment_content = comment[:content].nil? ? "" : comment[:content]
|
|
128
|
+
# Skip the comment if it wasn't posted by danger
|
|
129
|
+
next unless comment_content.include?("generated_by_#{danger_id}")
|
|
130
|
+
# Skip the comment if it's an inline comment
|
|
131
|
+
next unless c[:threadContext].nil?
|
|
132
|
+
|
|
133
|
+
# Updated the danger posted comment
|
|
134
|
+
@api.update_comment(thread_id, comment_id, new_comment)
|
|
135
|
+
comment_updated = true
|
|
136
|
+
end
|
|
137
|
+
# If no comment was updated, post a new one
|
|
138
|
+
post_new_comment(new_comment) unless comment_updated
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def submit_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: [], danger_id: "danger")
|
|
142
|
+
# Avoid doing any fetchs if there's no inline comments
|
|
143
|
+
return {} if (warnings + errors + messages + markdowns).select(&:inline?).empty?
|
|
144
|
+
|
|
145
|
+
pr_threads = @api.fetch_last_comments
|
|
146
|
+
danger_threads = pr_threads.select do |thread|
|
|
147
|
+
comment = thread[:comments].first
|
|
148
|
+
comment_content = comment[:content].nil? ? "" : comment[:content]
|
|
149
|
+
|
|
150
|
+
next comment_content.include?("generated_by_#{danger_id}")
|
|
151
|
+
end
|
|
152
|
+
non_danger_threads = pr_threads - danger_threads
|
|
153
|
+
|
|
154
|
+
warnings = submit_inline_comments_for_kind!(:warning, warnings, danger_threads, previous_violations["warning"], danger_id: danger_id)
|
|
155
|
+
errors = submit_inline_comments_for_kind!(:error, errors, danger_threads, previous_violations["error"], danger_id: danger_id)
|
|
156
|
+
messages = submit_inline_comments_for_kind!(:message, messages, danger_threads, previous_violations["message"], danger_id: danger_id)
|
|
157
|
+
markdowns = submit_inline_comments_for_kind!(:markdown, markdowns, danger_threads, [], danger_id: danger_id)
|
|
158
|
+
|
|
159
|
+
# submit removes from the array all comments that are still in force
|
|
160
|
+
# so we strike out all remaining ones
|
|
161
|
+
danger_threads.each do |thread|
|
|
162
|
+
violation = violations_from_table(thread[:comments].first[:content]).first
|
|
163
|
+
if !violation.nil? && violation.sticky
|
|
164
|
+
body = generate_inline_comment_body("white_check_mark", violation, danger_id: danger_id, resolved: true, template: "github")
|
|
165
|
+
@api.update_comment(thread[:id], thread[:comments].first[:id], body)
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
{
|
|
170
|
+
warnings: warnings,
|
|
171
|
+
errors: errors,
|
|
172
|
+
messages: messages,
|
|
173
|
+
markdowns: markdowns
|
|
174
|
+
}
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def messages_are_equivalent(m1, m2)
|
|
178
|
+
blob_regexp = %r{blob/[0-9a-z]+/}
|
|
179
|
+
m1.file == m2.file && m1.line == m2.line &&
|
|
180
|
+
m1.message.sub(blob_regexp, "") == m2.message.sub(blob_regexp, "")
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def submit_inline_comments_for_kind!(kind, messages, danger_threads, previous_violations, danger_id: "danger")
|
|
184
|
+
previous_violations ||= []
|
|
185
|
+
is_markdown_content = kind == :markdown
|
|
186
|
+
emoji = { warning: "warning", error: "no_entry_sign", message: "book" }[kind]
|
|
187
|
+
|
|
188
|
+
messages.reject do |m|
|
|
189
|
+
next false unless m.file && m.line
|
|
190
|
+
|
|
191
|
+
# Once we know we're gonna submit it, we format it
|
|
192
|
+
if is_markdown_content
|
|
193
|
+
body = generate_inline_markdown_body(m, danger_id: danger_id, template: "vsts")
|
|
194
|
+
else
|
|
195
|
+
# Hide the inline link behind a span
|
|
196
|
+
m.message = m.message.gsub("\n", "<br />")
|
|
197
|
+
m = process_markdown(m, true)
|
|
198
|
+
body = generate_inline_comment_body(emoji, m, danger_id: danger_id, template: "vsts")
|
|
199
|
+
# A comment might be in previous_violations because only now it's part of the unified diff
|
|
200
|
+
# We remove from the array since it won't have a place in the table anymore
|
|
201
|
+
previous_violations.reject! { |v| messages_are_equivalent(v, m) }
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
matching_threads = danger_threads.select do |comment_data|
|
|
205
|
+
if comment_data.key?(:threadContext) && !comment_data[:threadContext].nil? &&
|
|
206
|
+
comment_data[:threadContext][:filePath] == m.file &&
|
|
207
|
+
comment_data[:threadContext].key?(:rightFileStart) &&
|
|
208
|
+
comment_data[:threadContext][:rightFileStart][:line] == m.line
|
|
209
|
+
# Parse it to avoid problems with strikethrough
|
|
210
|
+
violation = violations_from_table(comment_data[:comments].first[:content]).first
|
|
211
|
+
if violation
|
|
212
|
+
messages_are_equivalent(violation, m)
|
|
213
|
+
else
|
|
214
|
+
blob_regexp = %r{blob/[0-9a-z]+/}
|
|
215
|
+
comment_data[:comments].first[:content].sub(blob_regexp, "") == body.sub(blob_regexp, "")
|
|
216
|
+
end
|
|
217
|
+
else
|
|
218
|
+
false
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
if matching_threads.empty?
|
|
223
|
+
@api.post_inline_comment(body, m.file, m.line)
|
|
224
|
+
|
|
225
|
+
# Not reject because this comment has not completed
|
|
226
|
+
next false
|
|
227
|
+
else
|
|
228
|
+
# Remove the surviving comment so we don't strike it out
|
|
229
|
+
danger_threads.reject! { |c| matching_threads.include? c }
|
|
230
|
+
|
|
231
|
+
# Update the comment to remove the strikethrough if present
|
|
232
|
+
thread = matching_threads.first
|
|
233
|
+
@api.update_comment(thread[:id], thread[:comments].first[:id], body)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Remove this element from the array
|
|
237
|
+
next true
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
private
|
|
242
|
+
|
|
243
|
+
def regular_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
|
|
244
|
+
{
|
|
245
|
+
warnings: warnings.reject(&:inline?),
|
|
246
|
+
errors: errors.reject(&:inline?),
|
|
247
|
+
messages: messages.reject(&:inline?),
|
|
248
|
+
markdowns: markdowns.reject(&:inline?)
|
|
249
|
+
}
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def inline_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
|
|
253
|
+
cmp = proc do |a, b|
|
|
254
|
+
next -1 unless a.file && a.line
|
|
255
|
+
next 1 unless b.file && b.line
|
|
256
|
+
|
|
257
|
+
next a.line <=> b.line if a.file == b.file
|
|
258
|
+
|
|
259
|
+
next a.file <=> b.file
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
# Sort to group inline comments by file
|
|
263
|
+
{
|
|
264
|
+
warnings: warnings.select(&:inline?).sort(&cmp),
|
|
265
|
+
errors: errors.select(&:inline?).sort(&cmp),
|
|
266
|
+
messages: messages.select(&:inline?).sort(&cmp),
|
|
267
|
+
markdowns: markdowns.select(&:inline?).sort(&cmp)
|
|
268
|
+
}
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def merge_violations(*violation_groups)
|
|
272
|
+
violation_groups.inject({}) do |accumulator, group|
|
|
273
|
+
accumulator.merge(group) { |_, old, fresh| old + fresh }
|
|
274
|
+
end
|
|
275
|
+
end
|
|
276
|
+
end
|
|
277
|
+
end
|
|
278
|
+
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "base64"
|
|
4
|
+
require "danger/helpers/comments_helper"
|
|
5
|
+
|
|
6
|
+
module Danger
|
|
7
|
+
module RequestSources
|
|
8
|
+
class VSTSAPI
|
|
9
|
+
attr_accessor :host, :pr_api_endpoint, :min_api_version_for_comments
|
|
10
|
+
|
|
11
|
+
def initialize(slug, pull_request_id, environment)
|
|
12
|
+
self.min_api_version_for_comments = "3.0"
|
|
13
|
+
|
|
14
|
+
user_name = ""
|
|
15
|
+
personal_access_token = environment["DANGER_VSTS_API_TOKEN"]
|
|
16
|
+
|
|
17
|
+
@token = Base64.strict_encode64("#{user_name}:#{personal_access_token}")
|
|
18
|
+
@api_version = environment["DANGER_VSTS_API_VERSION"] ||= self.min_api_version_for_comments
|
|
19
|
+
|
|
20
|
+
self.host = environment["DANGER_VSTS_HOST"]
|
|
21
|
+
if self.host && !(self.host.include? "http://") && !(self.host.include? "https://")
|
|
22
|
+
self.host = "https://" + self.host
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
self.pr_api_endpoint = "#{host}/_apis/git/repositories/#{slug}/pullRequests/#{pull_request_id}"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def supports_comments?
|
|
29
|
+
major_version = @api_version.split(".").first.to_i
|
|
30
|
+
minimum_version_for_comments = self.min_api_version_for_comments.split(".").first.to_i
|
|
31
|
+
|
|
32
|
+
major_version >= minimum_version_for_comments
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def inspect
|
|
36
|
+
inspected = super
|
|
37
|
+
|
|
38
|
+
inspected.gsub!(@token, "********") if @token
|
|
39
|
+
|
|
40
|
+
inspected
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def credentials_given?
|
|
44
|
+
@token && !@token.empty?
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def pull_request(*)
|
|
48
|
+
fetch_pr_json
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def fetch_pr_json
|
|
52
|
+
uri = URI("#{pr_api_endpoint}?api-version=#{@api_version}")
|
|
53
|
+
fetch_json(uri)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def fetch_last_comments
|
|
57
|
+
uri = URI("#{pr_api_endpoint}/threads?api-version=#{@api_version}")
|
|
58
|
+
fetch_json(uri)[:value]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def post_comment(text)
|
|
62
|
+
uri = URI("#{pr_api_endpoint}/threads?api-version=#{@api_version}")
|
|
63
|
+
body = {
|
|
64
|
+
"comments" => [
|
|
65
|
+
{
|
|
66
|
+
"parentCommentId" => 0,
|
|
67
|
+
"content" => text,
|
|
68
|
+
"commentType" => 1
|
|
69
|
+
}
|
|
70
|
+
],
|
|
71
|
+
"properties" => {
|
|
72
|
+
"Microsoft.TeamFoundation.Discussion.SupportsMarkdown" => {
|
|
73
|
+
"type" => "System.Int32",
|
|
74
|
+
"value" => 1
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
"status" => 1
|
|
78
|
+
}.to_json
|
|
79
|
+
post(uri, body)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def post_inline_comment(text, file, line)
|
|
83
|
+
uri = URI("#{pr_api_endpoint}/threads?api-version=#{@api_version}")
|
|
84
|
+
body = {
|
|
85
|
+
"comments" => [
|
|
86
|
+
{
|
|
87
|
+
"parentCommentId" => 0,
|
|
88
|
+
"content" => text,
|
|
89
|
+
"commentType" => 1
|
|
90
|
+
}
|
|
91
|
+
],
|
|
92
|
+
"properties" => {
|
|
93
|
+
"Microsoft.TeamFoundation.Discussion.SupportsMarkdown" => {
|
|
94
|
+
"type" => "System.Int32",
|
|
95
|
+
"value" => 1
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"status" => 1,
|
|
99
|
+
"threadContext" => {
|
|
100
|
+
"filePath" => file,
|
|
101
|
+
"rightFileEnd" => {
|
|
102
|
+
"line" => line + 1,
|
|
103
|
+
"offset" => 1
|
|
104
|
+
},
|
|
105
|
+
"rightFileStart" => {
|
|
106
|
+
"line" => line,
|
|
107
|
+
"offset" => 1
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}.to_json
|
|
111
|
+
post(uri, body)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def update_comment(thread, id, new_comment)
|
|
115
|
+
uri = URI("#{pr_api_endpoint}/threads/#{thread}/comments/#{id}?api-version=#{@api_version}")
|
|
116
|
+
body = {
|
|
117
|
+
"content" => new_comment
|
|
118
|
+
}.to_json
|
|
119
|
+
patch(uri, body)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
private
|
|
123
|
+
|
|
124
|
+
def use_ssl
|
|
125
|
+
return self.pr_api_endpoint.include? "https://"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def fetch_json(uri)
|
|
129
|
+
req = Net::HTTP::Get.new(uri.request_uri, { "Content-Type" => "application/json", "Authorization" => "Basic #{@token}" })
|
|
130
|
+
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl) do |http|
|
|
131
|
+
http.request(req)
|
|
132
|
+
end
|
|
133
|
+
JSON.parse(res.body, symbolize_names: true)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def post(uri, body)
|
|
137
|
+
req = Net::HTTP::Post.new(uri.request_uri, { "Content-Type" => "application/json", "Authorization" => "Basic #{@token}" })
|
|
138
|
+
req.body = body
|
|
139
|
+
|
|
140
|
+
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl) do |http|
|
|
141
|
+
http.request(req)
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# show error to the user when VSTS returned an error
|
|
145
|
+
case res
|
|
146
|
+
when Net::HTTPClientError, Net::HTTPServerError
|
|
147
|
+
# HTTP 4xx - 5xx
|
|
148
|
+
abort "\nError posting comment to VSTS: #{res.code} (#{res.message})\n\n"
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def patch(uri, body)
|
|
153
|
+
puts uri
|
|
154
|
+
puts body
|
|
155
|
+
|
|
156
|
+
req = Net::HTTP::Patch.new(uri.request_uri, { "Content-Type" => "application/json", "Authorization" => "Basic #{@token}" })
|
|
157
|
+
req.body = body
|
|
158
|
+
|
|
159
|
+
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: use_ssl) do |http|
|
|
160
|
+
http.request(req)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# show error to the user when VSTS returned an error
|
|
164
|
+
case res
|
|
165
|
+
when Net::HTTPClientError, Net::HTTPServerError
|
|
166
|
+
# HTTP 4xx - 5xx
|
|
167
|
+
abort "\nError updating comment on VSTS: #{res.code} (#{res.message})\n\n"
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|