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,557 @@
|
|
|
1
|
+
require "uri"
|
|
2
|
+
require "danger/helpers/comments_helper"
|
|
3
|
+
require "danger/helpers/comment"
|
|
4
|
+
require "danger/request_sources/support/get_ignored_violation"
|
|
5
|
+
|
|
6
|
+
module Danger
|
|
7
|
+
module RequestSources
|
|
8
|
+
class GitLab < RequestSource
|
|
9
|
+
include Danger::Helpers::CommentsHelper
|
|
10
|
+
attr_accessor :mr_json, :commits_json, :dismiss_out_of_range_messages, :endpoint, :host
|
|
11
|
+
|
|
12
|
+
FIRST_GITLAB_GEM_WITH_VERSION_CHECK = Gem::Version.new("4.6.0")
|
|
13
|
+
FIRST_VERSION_WITH_INLINE_COMMENTS = Gem::Version.new("10.8.0")
|
|
14
|
+
|
|
15
|
+
def self.env_vars
|
|
16
|
+
["DANGER_GITLAB_API_TOKEN"]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.optional_env_vars
|
|
20
|
+
["DANGER_GITLAB_HOST", "DANGER_GITLAB_API_BASE_URL"]
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def initialize(ci_source, environment)
|
|
24
|
+
self.ci_source = ci_source
|
|
25
|
+
self.dismiss_out_of_range_messages = false
|
|
26
|
+
@endpoint = environment["DANGER_GITLAB_API_BASE_URL"] || environment.fetch("CI_API_V4_URL", "https://gitlab.com/api/v4")
|
|
27
|
+
@host = environment.fetch("DANGER_GITLAB_HOST", URI.parse(endpoint).host) || "gitlab.com"
|
|
28
|
+
@token = environment["DANGER_GITLAB_API_TOKEN"]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def client
|
|
32
|
+
raise "No API token given, please provide one using `DANGER_GITLAB_API_TOKEN`" unless @token
|
|
33
|
+
|
|
34
|
+
# The require happens inline so that it won't cause exceptions when just using the `danger` gem.
|
|
35
|
+
require "gitlab"
|
|
36
|
+
|
|
37
|
+
@client ||= Gitlab.client(endpoint: endpoint, private_token: @token)
|
|
38
|
+
rescue LoadError => e
|
|
39
|
+
if e.path == "gitlab"
|
|
40
|
+
puts "The GitLab gem was not installed, you will need to change your Gem from `danger` to `danger-gitlab`.".red
|
|
41
|
+
puts "\n - See https://github.com/danger/danger/blob/master/CHANGELOG.md#400"
|
|
42
|
+
else
|
|
43
|
+
puts "Error: #{e}".red
|
|
44
|
+
end
|
|
45
|
+
abort
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def validates_as_ci?
|
|
49
|
+
includes_port = host.include? ":"
|
|
50
|
+
raise "Port number included in `DANGER_GITLAB_HOST`, this will fail with GitLab CI Runners" if includes_port
|
|
51
|
+
|
|
52
|
+
# We don't call super because in some cases the Git remote doesn't match the GitLab instance host.
|
|
53
|
+
# In Danger::EnvironmentManager#initialize we still check that the request source is #validates_as_api_source?
|
|
54
|
+
# so that should be sufficient to validate GitLab as request source.
|
|
55
|
+
# See https://github.com/danger/danger/issues/1231 and https://gitlab.com/gitlab-com/gl-infra/infrastructure/-/issues/10069.
|
|
56
|
+
true
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def validates_as_api_source?
|
|
60
|
+
@token && !@token.empty?
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def scm
|
|
64
|
+
@scm ||= GitRepo.new
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def base_commit
|
|
68
|
+
@base_commit ||= self.mr_json.diff_refs.base_sha
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def mr_comments
|
|
72
|
+
# @raw_comments contains what we got back from the server.
|
|
73
|
+
# @comments contains Comment objects (that have less information)
|
|
74
|
+
@comments ||= if supports_inline_comments
|
|
75
|
+
@raw_comments = mr_discussions
|
|
76
|
+
.auto_paginate
|
|
77
|
+
.flat_map { |discussion| discussion.notes.map { |note| note.to_h.merge({ "discussion_id" => discussion.id }) } }
|
|
78
|
+
@raw_comments
|
|
79
|
+
.map { |comment| Comment.from_gitlab(comment) }
|
|
80
|
+
else
|
|
81
|
+
@raw_comments = client.merge_request_comments(ci_source.repo_slug, ci_source.pull_request_id, per_page: 100)
|
|
82
|
+
.auto_paginate
|
|
83
|
+
@raw_comments
|
|
84
|
+
.map { |comment| Comment.from_gitlab(comment) }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def mr_discussions
|
|
89
|
+
@mr_discussions ||= client.merge_request_discussions(ci_source.repo_slug, ci_source.pull_request_id)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def mr_diff
|
|
93
|
+
@mr_diff ||= begin
|
|
94
|
+
diffs = mr_changes.changes.map do |change|
|
|
95
|
+
diff = change["diff"]
|
|
96
|
+
if diff.start_with?("--- a/")
|
|
97
|
+
diff
|
|
98
|
+
else
|
|
99
|
+
"--- a/#{change['old_path']}\n+++ b/#{change['new_path']}\n#{diff}"
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
diffs.join("\n")
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def mr_changed_paths
|
|
107
|
+
@mr_changed_paths ||= mr_changes
|
|
108
|
+
.changes.map { |change| change["new_path"] }
|
|
109
|
+
|
|
110
|
+
@mr_changed_paths
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def mr_changes
|
|
114
|
+
@mr_changes ||= client.merge_request_changes(ci_source.repo_slug, ci_source.pull_request_id)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def mr_closes_issues
|
|
118
|
+
@mr_closes_issues ||= client.merge_request_closes_issues(ci_source.repo_slug, ci_source.pull_request_id)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def setup_danger_branches
|
|
122
|
+
# we can use a GitLab specific feature here:
|
|
123
|
+
base_branch = self.mr_json.source_branch
|
|
124
|
+
base_commit = self.mr_json.diff_refs.base_sha
|
|
125
|
+
head_branch = self.mr_json.target_branch
|
|
126
|
+
head_commit = self.mr_json.diff_refs.head_sha
|
|
127
|
+
|
|
128
|
+
# Next, we want to ensure that we have a version of the current branch at a known location
|
|
129
|
+
scm.ensure_commitish_exists_on_branch! base_branch, base_commit
|
|
130
|
+
self.scm.exec "branch #{EnvironmentManager.danger_base_branch} #{base_commit}"
|
|
131
|
+
|
|
132
|
+
# OK, so we want to ensure that we have a known head branch, this will always represent
|
|
133
|
+
# the head of the PR ( e.g. the most recent commit that will be merged. )
|
|
134
|
+
scm.ensure_commitish_exists_on_branch! head_branch, head_commit
|
|
135
|
+
self.scm.exec "branch #{EnvironmentManager.danger_head_branch} #{head_commit}"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def fetch_details
|
|
139
|
+
self.mr_json = client.merge_request(ci_source.repo_slug, self.ci_source.pull_request_id)
|
|
140
|
+
self.ignored_violations = ignored_violations_from_pr
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def ignored_violations_from_pr
|
|
144
|
+
GetIgnoredViolation.new(self.mr_json.description).call
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def supports_inline_comments
|
|
148
|
+
# If we can't check GitLab's version, we assume we don't support inline comments
|
|
149
|
+
@supports_inline_comments ||= if Gem.loaded_specs["gitlab"].version < FIRST_GITLAB_GEM_WITH_VERSION_CHECK
|
|
150
|
+
false
|
|
151
|
+
else
|
|
152
|
+
current_version = Gem::Version.new(client.version.version)
|
|
153
|
+
|
|
154
|
+
current_version >= FIRST_VERSION_WITH_INLINE_COMMENTS
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false)
|
|
159
|
+
if supports_inline_comments
|
|
160
|
+
update_pull_request_with_inline_comments!(warnings: warnings, errors: errors, messages: messages, markdowns: markdowns, danger_id: danger_id, new_comment: new_comment, remove_previous_comments: remove_previous_comments)
|
|
161
|
+
else
|
|
162
|
+
update_pull_request_without_inline_comments!(warnings: warnings, errors: errors, messages: messages, markdowns: markdowns, danger_id: danger_id, new_comment: new_comment, remove_previous_comments: remove_previous_comments)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def update_pull_request_with_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false)
|
|
167
|
+
editable_regular_comments = mr_comments
|
|
168
|
+
.select { |comment| comment.generated_by_danger?(danger_id) }
|
|
169
|
+
.reject(&:inline?)
|
|
170
|
+
|
|
171
|
+
last_comment = editable_regular_comments.last
|
|
172
|
+
should_create_new_comment = new_comment || last_comment.nil? || remove_previous_comments
|
|
173
|
+
|
|
174
|
+
previous_violations =
|
|
175
|
+
if should_create_new_comment
|
|
176
|
+
{}
|
|
177
|
+
else
|
|
178
|
+
parse_comment(last_comment.body)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
regular_violations = regular_violations_group(
|
|
182
|
+
warnings: warnings,
|
|
183
|
+
errors: errors,
|
|
184
|
+
messages: messages,
|
|
185
|
+
markdowns: markdowns
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
inline_violations = inline_violations_group(
|
|
189
|
+
warnings: warnings,
|
|
190
|
+
errors: errors,
|
|
191
|
+
messages: messages,
|
|
192
|
+
markdowns: markdowns
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
rest_inline_violations = submit_inline_comments!(**{
|
|
196
|
+
danger_id: danger_id,
|
|
197
|
+
previous_violations: previous_violations
|
|
198
|
+
}.merge(inline_violations))
|
|
199
|
+
|
|
200
|
+
main_violations = merge_violations(
|
|
201
|
+
regular_violations, rest_inline_violations
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
main_violations_sum = main_violations.values.inject(:+)
|
|
205
|
+
|
|
206
|
+
if (previous_violations.empty? && main_violations_sum.empty?) || remove_previous_comments
|
|
207
|
+
# Just remove the comment, if there's nothing to say or --remove-previous-comments CLI was set.
|
|
208
|
+
delete_old_comments!(danger_id: danger_id)
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# If there are still violations to show
|
|
212
|
+
if main_violations_sum.any?
|
|
213
|
+
body = generate_comment(**{
|
|
214
|
+
template: "gitlab",
|
|
215
|
+
danger_id: danger_id,
|
|
216
|
+
previous_violations: previous_violations
|
|
217
|
+
}.merge(main_violations))
|
|
218
|
+
|
|
219
|
+
comment_result =
|
|
220
|
+
if should_create_new_comment
|
|
221
|
+
client.create_merge_request_note(ci_source.repo_slug, ci_source.pull_request_id, body)
|
|
222
|
+
else
|
|
223
|
+
client.edit_merge_request_note(ci_source.repo_slug, ci_source.pull_request_id, last_comment.id, body)
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def update_pull_request_without_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false, remove_previous_comments: false)
|
|
229
|
+
editable_comments = mr_comments.select { |comment| comment.generated_by_danger?(danger_id) }
|
|
230
|
+
|
|
231
|
+
should_create_new_comment = new_comment || editable_comments.empty? || remove_previous_comments
|
|
232
|
+
|
|
233
|
+
if should_create_new_comment
|
|
234
|
+
previous_violations = {}
|
|
235
|
+
else
|
|
236
|
+
comment = editable_comments.first.body
|
|
237
|
+
previous_violations = parse_comment(comment)
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
if (previous_violations.empty? && (warnings + errors + messages + markdowns).empty?) || remove_previous_comments
|
|
241
|
+
# Just remove the comment, if there's nothing to say or --remove-previous-comments CLI was set.
|
|
242
|
+
delete_old_comments!(danger_id: danger_id)
|
|
243
|
+
else
|
|
244
|
+
body = generate_comment(warnings: warnings,
|
|
245
|
+
errors: errors,
|
|
246
|
+
messages: messages,
|
|
247
|
+
markdowns: markdowns,
|
|
248
|
+
previous_violations: previous_violations,
|
|
249
|
+
danger_id: danger_id,
|
|
250
|
+
template: "gitlab")
|
|
251
|
+
|
|
252
|
+
if editable_comments.empty? or should_create_new_comment
|
|
253
|
+
client.create_merge_request_comment(
|
|
254
|
+
ci_source.repo_slug, ci_source.pull_request_id, body
|
|
255
|
+
)
|
|
256
|
+
else
|
|
257
|
+
original_id = editable_comments.first.id
|
|
258
|
+
client.edit_merge_request_comment(
|
|
259
|
+
ci_source.repo_slug,
|
|
260
|
+
ci_source.pull_request_id,
|
|
261
|
+
original_id,
|
|
262
|
+
{ body: body }
|
|
263
|
+
)
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
|
|
268
|
+
def delete_old_comments!(except: nil, danger_id: "danger")
|
|
269
|
+
@raw_comments.each do |raw_comment|
|
|
270
|
+
comment = Comment.from_gitlab(raw_comment)
|
|
271
|
+
next unless comment.generated_by_danger?(danger_id)
|
|
272
|
+
next if comment.id == except
|
|
273
|
+
next unless raw_comment.kind_of?(Hash) && raw_comment["position"].nil?
|
|
274
|
+
|
|
275
|
+
begin
|
|
276
|
+
client.delete_merge_request_comment(
|
|
277
|
+
ci_source.repo_slug,
|
|
278
|
+
ci_source.pull_request_id,
|
|
279
|
+
comment.id
|
|
280
|
+
)
|
|
281
|
+
rescue StandardError
|
|
282
|
+
end
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
|
|
286
|
+
def markdown_link_to_message(message, _)
|
|
287
|
+
"#{message.file}#L#{message.line}: "
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
# @return [String] The organisation name, is nil if it can't be detected
|
|
291
|
+
def organisation
|
|
292
|
+
nil # TODO: Implement this
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def dismiss_out_of_range_messages_for(kind)
|
|
296
|
+
if self.dismiss_out_of_range_messages.kind_of?(Hash) && self.dismiss_out_of_range_messages[kind]
|
|
297
|
+
self.dismiss_out_of_range_messages[kind]
|
|
298
|
+
elsif self.dismiss_out_of_range_messages == true
|
|
299
|
+
self.dismiss_out_of_range_messages
|
|
300
|
+
else
|
|
301
|
+
false
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
# @return [String] A URL to the specific file, ready to be downloaded
|
|
306
|
+
def file_url(organisation: nil, repository: nil, ref: nil, branch: nil, path: nil)
|
|
307
|
+
ref ||= (branch || "master")
|
|
308
|
+
# According to GitLab Repositories API docs path and id(slug) should be encoded.
|
|
309
|
+
path = URI.encode_www_form_component(path)
|
|
310
|
+
repository = URI.encode_www_form_component(repository)
|
|
311
|
+
"#{endpoint}/projects/#{repository}/repository/files/#{path}/raw?ref=#{ref}&private_token=#{@token}"
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def regular_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
|
|
315
|
+
{
|
|
316
|
+
warnings: warnings.reject(&:inline?),
|
|
317
|
+
errors: errors.reject(&:inline?),
|
|
318
|
+
messages: messages.reject(&:inline?),
|
|
319
|
+
markdowns: markdowns.reject(&:inline?)
|
|
320
|
+
}
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def inline_violations_group(warnings: [], errors: [], messages: [], markdowns: [])
|
|
324
|
+
cmp = proc do |a, b|
|
|
325
|
+
next -1 unless a.file && a.line
|
|
326
|
+
next 1 unless b.file && b.line
|
|
327
|
+
|
|
328
|
+
next a.line <=> b.line if a.file == b.file
|
|
329
|
+
|
|
330
|
+
next a.file <=> b.file
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
# Sort to group inline comments by file
|
|
334
|
+
{
|
|
335
|
+
warnings: warnings.select(&:inline?).sort(&cmp),
|
|
336
|
+
errors: errors.select(&:inline?).sort(&cmp),
|
|
337
|
+
messages: messages.select(&:inline?).sort(&cmp),
|
|
338
|
+
markdowns: markdowns.select(&:inline?).sort(&cmp)
|
|
339
|
+
}
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def merge_violations(*violation_groups)
|
|
343
|
+
violation_groups.inject({}) do |accumulator, group|
|
|
344
|
+
accumulator.merge(group) { |_, old, fresh| old + fresh }
|
|
345
|
+
end
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def submit_inline_comments!(warnings: [], errors: [], messages: [], markdowns: [], previous_violations: [], danger_id: "danger")
|
|
349
|
+
comments = mr_discussions
|
|
350
|
+
.auto_paginate
|
|
351
|
+
.flat_map { |discussion| discussion.notes.map { |note| note.to_h.merge({ "discussion_id" => discussion.id }) } }
|
|
352
|
+
.select { |comment| Comment.from_gitlab(comment).inline? }
|
|
353
|
+
|
|
354
|
+
danger_comments = comments.select { |comment| Comment.from_gitlab(comment).generated_by_danger?(danger_id) }
|
|
355
|
+
non_danger_comments = comments - danger_comments
|
|
356
|
+
|
|
357
|
+
diff_lines = []
|
|
358
|
+
|
|
359
|
+
warnings = submit_inline_comments_for_kind!(:warning, warnings, diff_lines, danger_comments, previous_violations["warning"], danger_id: danger_id)
|
|
360
|
+
errors = submit_inline_comments_for_kind!(:error, errors, diff_lines, danger_comments, previous_violations["error"], danger_id: danger_id)
|
|
361
|
+
messages = submit_inline_comments_for_kind!(:message, messages, diff_lines, danger_comments, previous_violations["message"], danger_id: danger_id)
|
|
362
|
+
markdowns = submit_inline_comments_for_kind!(:markdown, markdowns, diff_lines, danger_comments, [], danger_id: danger_id)
|
|
363
|
+
|
|
364
|
+
# submit removes from the array all comments that are still in force
|
|
365
|
+
# so we strike out all remaining ones
|
|
366
|
+
danger_comments.each do |comment|
|
|
367
|
+
violation = violations_from_table(comment["body"]).first
|
|
368
|
+
if !violation.nil? && violation.sticky
|
|
369
|
+
body = generate_inline_comment_body("white_check_mark", violation, danger_id: danger_id, resolved: true, template: "gitlab")
|
|
370
|
+
client.update_merge_request_discussion_note(ci_source.repo_slug, ci_source.pull_request_id, comment["discussion_id"], comment["id"], body: body)
|
|
371
|
+
else
|
|
372
|
+
# We remove non-sticky violations that have no replies
|
|
373
|
+
# Since there's no direct concept of a reply in GH, we simply consider
|
|
374
|
+
# the existence of non-danger comments in that line as replies
|
|
375
|
+
replies = non_danger_comments.select do |potential|
|
|
376
|
+
potential["path"] == comment["path"] &&
|
|
377
|
+
potential["position"] == comment["position"] &&
|
|
378
|
+
potential["commit_id"] == comment["commit_id"]
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
client.delete_merge_request_comment(ci_source.repo_slug, ci_source.pull_request_id, comment["id"]) if replies.empty?
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
{
|
|
386
|
+
warnings: warnings,
|
|
387
|
+
errors: errors,
|
|
388
|
+
messages: messages,
|
|
389
|
+
markdowns: markdowns
|
|
390
|
+
}
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def submit_inline_comments_for_kind!(kind, messages, _diff_lines, danger_comments, previous_violations, danger_id: "danger")
|
|
394
|
+
previous_violations ||= []
|
|
395
|
+
is_markdown_content = kind == :markdown
|
|
396
|
+
emoji = { warning: "warning", error: "no_entry_sign", message: "book" }[kind]
|
|
397
|
+
|
|
398
|
+
messages.reject do |m|
|
|
399
|
+
next false unless m.file && m.line
|
|
400
|
+
# Reject if it's out of range and in dismiss mode
|
|
401
|
+
next true if dismiss_out_of_range_messages_for(kind) && is_out_of_range(mr_changes.changes, m)
|
|
402
|
+
|
|
403
|
+
# Once we know we're gonna submit it, we format it
|
|
404
|
+
if is_markdown_content
|
|
405
|
+
body = generate_inline_markdown_body(m, danger_id: danger_id, template: "gitlab")
|
|
406
|
+
else
|
|
407
|
+
# Hide the inline link behind a span
|
|
408
|
+
m = process_markdown(m, true)
|
|
409
|
+
body = generate_inline_comment_body(emoji, m, danger_id: danger_id, template: "gitlab")
|
|
410
|
+
# A comment might be in previous_violations because only now it's part of the unified diff
|
|
411
|
+
# We remove from the array since it won't have a place in the table anymore
|
|
412
|
+
previous_violations.reject! { |v| messages_are_equivalent(v, m) }
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
matching_comments = danger_comments.select do |comment_data|
|
|
416
|
+
position = comment_data["position"]
|
|
417
|
+
|
|
418
|
+
if position.nil?
|
|
419
|
+
false
|
|
420
|
+
else
|
|
421
|
+
position["new_path"] == m.file && position["new_line"] == m.line
|
|
422
|
+
end
|
|
423
|
+
end
|
|
424
|
+
|
|
425
|
+
if matching_comments.empty?
|
|
426
|
+
old_position = find_old_position_in_diff mr_changes.changes, m
|
|
427
|
+
next false if old_position.nil?
|
|
428
|
+
|
|
429
|
+
params = {
|
|
430
|
+
body: body,
|
|
431
|
+
position: {
|
|
432
|
+
position_type: "text",
|
|
433
|
+
new_path: m.file,
|
|
434
|
+
new_line: m.line,
|
|
435
|
+
old_path: old_position[:path],
|
|
436
|
+
old_line: old_position[:line],
|
|
437
|
+
base_sha: self.mr_json.diff_refs.base_sha,
|
|
438
|
+
start_sha: self.mr_json.diff_refs.start_sha,
|
|
439
|
+
head_sha: self.mr_json.diff_refs.head_sha
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
begin
|
|
443
|
+
client.create_merge_request_discussion(ci_source.repo_slug, ci_source.pull_request_id, params)
|
|
444
|
+
rescue Gitlab::Error::Error => e
|
|
445
|
+
message = [e, "body: #{body}", "position: #{params[:position].inspect}"].join("\n")
|
|
446
|
+
puts message
|
|
447
|
+
|
|
448
|
+
next false
|
|
449
|
+
end
|
|
450
|
+
else
|
|
451
|
+
# Remove the surviving comment so we don't strike it out
|
|
452
|
+
danger_comments.reject! { |c| matching_comments.include? c }
|
|
453
|
+
|
|
454
|
+
# Update the comment to remove the strikethrough if present
|
|
455
|
+
comment = matching_comments.first
|
|
456
|
+
begin
|
|
457
|
+
client.update_merge_request_discussion_note(ci_source.repo_slug, ci_source.pull_request_id, comment["discussion_id"], comment["id"], body: body)
|
|
458
|
+
rescue Gitlab::Error::Error => e
|
|
459
|
+
message = [e, "body: #{body}"].join("\n")
|
|
460
|
+
puts message
|
|
461
|
+
|
|
462
|
+
next false
|
|
463
|
+
end
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
# Remove this element from the array
|
|
467
|
+
next true
|
|
468
|
+
end
|
|
469
|
+
end
|
|
470
|
+
|
|
471
|
+
def find_old_position_in_diff(changes, message)
|
|
472
|
+
range_header_regexp = /@@ -(?<old>[0-9]+)(,([0-9]+))? \+(?<new>[0-9]+)(,([0-9]+))? @@.*/
|
|
473
|
+
|
|
474
|
+
change = changes.find { |c| c["new_path"] == message.file }
|
|
475
|
+
# If there is no changes or rename only or deleted, return nil.
|
|
476
|
+
return nil if change.nil? || change["diff"].empty? || change["deleted_file"]
|
|
477
|
+
|
|
478
|
+
modified_position = {
|
|
479
|
+
path: change["old_path"],
|
|
480
|
+
line: nil
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
# If the file is new one, old line number must be nil.
|
|
484
|
+
return modified_position if change["new_file"]
|
|
485
|
+
|
|
486
|
+
current_old_line = 0
|
|
487
|
+
current_new_line = 0
|
|
488
|
+
|
|
489
|
+
change["diff"].each_line do |line|
|
|
490
|
+
match = line.match range_header_regexp
|
|
491
|
+
|
|
492
|
+
if match
|
|
493
|
+
# If the message line is at before next diffs, break from loop.
|
|
494
|
+
break if message.line.to_i < match[:new].to_i
|
|
495
|
+
|
|
496
|
+
# The match [:old] line does not appear yet at the header position, so reduce line number.
|
|
497
|
+
current_old_line = match[:old].to_i - 1
|
|
498
|
+
current_new_line = match[:new].to_i - 1
|
|
499
|
+
next
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
if line.start_with?("-")
|
|
503
|
+
current_old_line += 1
|
|
504
|
+
elsif line.start_with?("+")
|
|
505
|
+
current_new_line += 1
|
|
506
|
+
# If the message line starts with '+', old line number must be nil.
|
|
507
|
+
return modified_position if current_new_line == message.line.to_i
|
|
508
|
+
elsif !line.eql?("\\n")
|
|
509
|
+
current_old_line += 1
|
|
510
|
+
current_new_line += 1
|
|
511
|
+
# If the message line doesn't start with '+', old line number must be specified.
|
|
512
|
+
break if current_new_line == message.line.to_i
|
|
513
|
+
end
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
{
|
|
517
|
+
path: change["old_path"],
|
|
518
|
+
line: current_old_line - current_new_line + message.line.to_i
|
|
519
|
+
}
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
def is_out_of_range(changes, message)
|
|
523
|
+
change = changes.find { |c| c["new_path"] == message.file }
|
|
524
|
+
# If there is no changes or rename only or deleted, return out of range.
|
|
525
|
+
return true if change.nil? || change["diff"].empty? || change["deleted_file"]
|
|
526
|
+
|
|
527
|
+
# If new file then return in range
|
|
528
|
+
return false if change["new_file"]
|
|
529
|
+
|
|
530
|
+
addition_lines = generate_addition_lines(change["diff"])
|
|
531
|
+
return false if addition_lines.include?(message.line.to_i)
|
|
532
|
+
|
|
533
|
+
return true
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
def generate_addition_lines(diff)
|
|
537
|
+
range_header_regexp = /@@ -(?<old>[0-9]+)(,([0-9]+))? \+(?<new>[0-9]+)(,([0-9]+))? @@.*/
|
|
538
|
+
addition_lines = []
|
|
539
|
+
line_number = 0
|
|
540
|
+
diff.each_line do |line|
|
|
541
|
+
if line.match range_header_regexp
|
|
542
|
+
line = line.split("+").last
|
|
543
|
+
line = line.split(" ").first
|
|
544
|
+
range_string = line.split(",")
|
|
545
|
+
line_number = range_string[0].to_i - 1
|
|
546
|
+
elsif line.start_with?("+")
|
|
547
|
+
addition_lines.push(line_number)
|
|
548
|
+
elsif line.start_with?("-")
|
|
549
|
+
line_number -= 1
|
|
550
|
+
end
|
|
551
|
+
line_number += 1
|
|
552
|
+
end
|
|
553
|
+
addition_lines
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
end
|
|
557
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
require "danger/helpers/comments_helper"
|
|
2
|
+
require "danger/helpers/comment"
|
|
3
|
+
|
|
4
|
+
module Danger
|
|
5
|
+
module RequestSources
|
|
6
|
+
class LocalOnly < RequestSource
|
|
7
|
+
include Danger::Helpers::CommentsHelper
|
|
8
|
+
attr_accessor :mr_json, :commits_json
|
|
9
|
+
|
|
10
|
+
def self.env_vars
|
|
11
|
+
["DANGER_LOCAL_ONLY"]
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def initialize(ci_source, _environment)
|
|
15
|
+
self.ci_source = ci_source
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def validates_as_ci?
|
|
19
|
+
true
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def validates_as_api_source?
|
|
23
|
+
true
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def scm
|
|
27
|
+
@scm ||= GitRepo.new
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def setup_danger_branches
|
|
31
|
+
# Check that discovered values really exists
|
|
32
|
+
[ci_source.base_commit, ci_source.head_commit].each do |commit|
|
|
33
|
+
raise "Specified commit '#{commit}' not found" if scm.exec("rev-parse --quiet --verify #{commit}").empty?
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
self.scm.exec "branch #{EnvironmentManager.danger_base_branch} #{ci_source.base_commit}"
|
|
37
|
+
self.scm.exec "branch #{EnvironmentManager.danger_head_branch} #{ci_source.head_commit}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def fetch_details; end
|
|
41
|
+
|
|
42
|
+
def update_pull_request!(_hash_needed); end
|
|
43
|
+
|
|
44
|
+
# @return [String] The organisation name, is nil if it can't be detected
|
|
45
|
+
def organisation
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|