danger 3.4.2 → 4.0.0
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 +4 -4
- data/README.md +2 -0
- data/lib/danger/ci_source/jenkins.rb +32 -1
- data/lib/danger/ci_source/local_git_repo.rb +79 -21
- data/lib/danger/ci_source/support/commits.rb +17 -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 +27 -0
- data/lib/danger/ci_source/support/local_pull_request.rb +20 -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/{merged_pull_request_finder.rb → pull_request_finder.rb} +71 -44
- 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/commands/dangerfile/gem.rb +43 -0
- data/lib/danger/commands/dangerfile/init.rb +30 -0
- data/lib/danger/commands/init.rb +2 -2
- data/lib/danger/commands/pr.rb +119 -0
- data/lib/danger/commands/runner.rb +8 -1
- data/lib/danger/commands/systems.rb +6 -4
- data/lib/danger/danger_core/dangerfile.rb +11 -7
- data/lib/danger/danger_core/dangerfile_generator.rb +11 -0
- data/lib/danger/danger_core/environment_manager.rb +1 -1
- data/lib/danger/danger_core/executor.rb +3 -1
- data/lib/danger/danger_core/plugins/dangerfile_gitlab_plugin.rb +4 -4
- data/lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb +14 -13
- data/lib/danger/request_sources/github.rb +17 -14
- data/lib/danger/request_sources/gitlab.rb +26 -12
- data/lib/danger/request_sources/support/get_ignored_violation.rb +17 -0
- data/lib/danger/scm_source/git_repo.rb +45 -23
- data/lib/danger/version.rb +1 -1
- metadata +27 -28
- data/lib/assets/PluginTemplate.rb.template +0 -20
- data/lib/danger/ci_source/support/remote_finder.rb +0 -26
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
require "danger/commands/local_helpers/http_cache"
|
|
2
|
+
require "faraday/http_cache"
|
|
3
|
+
require "fileutils"
|
|
4
|
+
require "octokit"
|
|
5
|
+
require "tmpdir"
|
|
6
|
+
|
|
7
|
+
module Danger
|
|
8
|
+
class PR < Runner
|
|
9
|
+
self.summary = "Run the Dangerfile against Pull Requests (works with forks, too).".freeze
|
|
10
|
+
self.command = "pr".freeze
|
|
11
|
+
|
|
12
|
+
def self.options
|
|
13
|
+
[
|
|
14
|
+
["--clear-http-cache", "Clear the local http cache before running Danger locally."],
|
|
15
|
+
["--pry", "Drop into a Pry shell after evaluating the Dangerfile."],
|
|
16
|
+
["--dangerfile=<path/to/dangerfile>", "The location of your Dangerfile"]
|
|
17
|
+
]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def initialize(argv)
|
|
21
|
+
@pr_url = argv.shift_argument
|
|
22
|
+
@clear_http_cache = argv.flag?("clear-http-cache", false)
|
|
23
|
+
dangerfile = argv.option("dangerfile", "Dangerfile")
|
|
24
|
+
|
|
25
|
+
super
|
|
26
|
+
|
|
27
|
+
@dangerfile_path = dangerfile if File.exist?(dangerfile)
|
|
28
|
+
|
|
29
|
+
setup_pry if should_pry?(argv)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def should_pry?(argv)
|
|
33
|
+
argv.flag?("pry", false) && !@dangerfile_path.empty? && validate_pry_available
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def setup_pry
|
|
37
|
+
File.delete "_Dangerfile.tmp" if File.exist? "_Dangerfile.tmp"
|
|
38
|
+
FileUtils.cp @dangerfile_path, "_Dangerfile.tmp"
|
|
39
|
+
File.open("_Dangerfile.tmp", "a") do |f|
|
|
40
|
+
f.write("binding.pry; File.delete(\"_Dangerfile.tmp\")")
|
|
41
|
+
end
|
|
42
|
+
@dangerfile_path = "_Dangerfile.tmp"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def validate_pry_available
|
|
46
|
+
require "pry"
|
|
47
|
+
rescue LoadError
|
|
48
|
+
cork.warn "Pry was not found, and is required for 'danger pr --pry'."
|
|
49
|
+
cork.print_warnings
|
|
50
|
+
abort
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def validate!
|
|
54
|
+
super
|
|
55
|
+
unless @dangerfile_path
|
|
56
|
+
help! "Could not find a Dangerfile."
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def run
|
|
61
|
+
ENV["DANGER_USE_LOCAL_GIT"] = "YES"
|
|
62
|
+
ENV["LOCAL_GIT_PR_URL"] = @pr_url if @pr_url
|
|
63
|
+
|
|
64
|
+
# setup caching for Github calls to hitting the API rate limit too quickly
|
|
65
|
+
cache_file = File.join(ENV["DANGER_TMPDIR"] || Dir.tmpdir, "danger_local_cache")
|
|
66
|
+
cache = HTTPCache.new(cache_file, clear_cache: @clear_http_cache)
|
|
67
|
+
Octokit.middleware = Faraday::RackBuilder.new do |builder|
|
|
68
|
+
builder.use Faraday::HttpCache, store: cache, serializer: Marshal, shared_cache: false
|
|
69
|
+
builder.use Octokit::Response::RaiseError
|
|
70
|
+
builder.adapter Faraday.default_adapter
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
env = EnvironmentManager.new(ENV, cork)
|
|
74
|
+
dm = Dangerfile.new(env, cork)
|
|
75
|
+
dm.init_plugins
|
|
76
|
+
|
|
77
|
+
source = dm.env.ci_source
|
|
78
|
+
if source.nil? or source.repo_slug.empty?
|
|
79
|
+
cork.puts "danger pr failed because it only works with GitHub projects at the moment. Sorry.".red
|
|
80
|
+
exit 0
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
gh = dm.env.request_source
|
|
84
|
+
|
|
85
|
+
cork.puts "Running your Dangerfile against this PR - https://#{gh.host}/#{source.repo_slug}/pull/#{source.pull_request_id}"
|
|
86
|
+
|
|
87
|
+
if verbose != true
|
|
88
|
+
cork.puts "Turning on --verbose"
|
|
89
|
+
dm.verbose = true
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
cork.puts
|
|
93
|
+
|
|
94
|
+
# We can use tokenless here, as it's running on someone's computer
|
|
95
|
+
# and is IP locked, as opposed to on the CI.
|
|
96
|
+
gh.support_tokenless_auth = true
|
|
97
|
+
|
|
98
|
+
begin
|
|
99
|
+
gh.fetch_details
|
|
100
|
+
rescue Octokit::NotFound
|
|
101
|
+
cork.puts "Local repository was not found on GitHub. If you're trying to test a private repository please provide a valid API token through " + "DANGER_GITHUB_API_TOKEN".yellow + " environment variable."
|
|
102
|
+
return
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
dm.env.request_source = gh
|
|
106
|
+
|
|
107
|
+
begin
|
|
108
|
+
dm.env.fill_environment_vars
|
|
109
|
+
dm.env.ensure_danger_branches_are_setup
|
|
110
|
+
dm.env.scm.diff_for_folder(".", from: Danger::EnvironmentManager.danger_base_branch, to: Danger::EnvironmentManager.danger_head_branch)
|
|
111
|
+
|
|
112
|
+
dm.parse(Pathname.new(@dangerfile_path))
|
|
113
|
+
dm.print_results
|
|
114
|
+
ensure
|
|
115
|
+
dm.env.clean_up
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
@@ -3,6 +3,7 @@ module Danger
|
|
|
3
3
|
require "danger/commands/init"
|
|
4
4
|
require "danger/commands/local"
|
|
5
5
|
require "danger/commands/systems"
|
|
6
|
+
require "danger/commands/pr"
|
|
6
7
|
|
|
7
8
|
# manually set claide plugins as a subcommand
|
|
8
9
|
require "claide_plugin"
|
|
@@ -19,6 +20,9 @@ module Danger
|
|
|
19
20
|
require "danger/commands/plugins/plugin_json"
|
|
20
21
|
require "danger/commands/plugins/plugin_readme"
|
|
21
22
|
|
|
23
|
+
require "danger/commands/dangerfile/init"
|
|
24
|
+
require "danger/commands/dangerfile/gem"
|
|
25
|
+
|
|
22
26
|
attr_accessor :cork
|
|
23
27
|
|
|
24
28
|
self.summary = "Run the Dangerfile."
|
|
@@ -33,6 +37,7 @@ module Danger
|
|
|
33
37
|
@base = argv.option("base")
|
|
34
38
|
@head = argv.option("head")
|
|
35
39
|
@fail_on_errors = argv.option("fail-on-errors", false)
|
|
40
|
+
@new_comment = argv.flag?("new-comment")
|
|
36
41
|
@danger_id = argv.option("danger_id", "danger")
|
|
37
42
|
@cork = Cork::Board.new(silent: argv.option("silent", false),
|
|
38
43
|
verbose: argv.option("verbose", false))
|
|
@@ -52,7 +57,8 @@ module Danger
|
|
|
52
57
|
["--head=[master|dev|stable]", "A branch/tag/commit to use as the head"],
|
|
53
58
|
["--fail-on-errors=<true|false>", "Should always fail the build process, defaults to false"],
|
|
54
59
|
["--dangerfile=<path/to/dangerfile>", "The location of your Dangerfile"],
|
|
55
|
-
["--danger_id=<id>", "The identifier of this Danger instance"]
|
|
60
|
+
["--danger_id=<id>", "The identifier of this Danger instance"],
|
|
61
|
+
["--new-comment", "Makes Danger post a new comment instead of editing it's previous one"]
|
|
56
62
|
].concat(super)
|
|
57
63
|
end
|
|
58
64
|
|
|
@@ -62,6 +68,7 @@ module Danger
|
|
|
62
68
|
head: @head,
|
|
63
69
|
dangerfile_path: @dangerfile_path,
|
|
64
70
|
danger_id: @danger_id,
|
|
71
|
+
new_comment: @new_comment,
|
|
65
72
|
fail_on_errors: @fail_on_errors
|
|
66
73
|
)
|
|
67
74
|
end
|
|
@@ -16,10 +16,12 @@ module Danger
|
|
|
16
16
|
require "yard"
|
|
17
17
|
# Pull out all the Danger::CI subclasses docs
|
|
18
18
|
registry = YARD::Registry.load(ci_source_paths, true)
|
|
19
|
-
ci_sources =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
ci_sources = begin
|
|
20
|
+
registry.all(:class)
|
|
21
|
+
.select { |klass| klass.inheritance_tree.map(&:name).include? :CI }
|
|
22
|
+
.reject { |source| source.name == :CI }
|
|
23
|
+
.reject { |source| source.name == :LocalGitRepo }
|
|
24
|
+
end
|
|
23
25
|
|
|
24
26
|
# Fail if anything is added and not documented
|
|
25
27
|
cis_without_docs = ci_sources.select { |source| source.base_docstring.empty? }
|
|
@@ -122,12 +122,15 @@ module Danger
|
|
|
122
122
|
when :api
|
|
123
123
|
value = "Octokit::Client"
|
|
124
124
|
|
|
125
|
-
when :pr_json
|
|
126
|
-
value = "[Skipped]"
|
|
125
|
+
when :pr_json, :mr_json
|
|
126
|
+
value = "[Skipped JSON]"
|
|
127
|
+
|
|
128
|
+
when :pr_diff, :mr_diff
|
|
129
|
+
value = "[Skipped Diff]"
|
|
127
130
|
|
|
128
131
|
else
|
|
129
132
|
value = plugin.send(method)
|
|
130
|
-
value = wrap_text(value) if value.kind_of?(String)
|
|
133
|
+
value = wrap_text(value.encode("utf-8")) if value.kind_of?(String)
|
|
131
134
|
# So that we either have one value per row
|
|
132
135
|
# or we have [] for an empty array
|
|
133
136
|
value = value.join("\n") if value.kind_of?(Array) && value.count > 0
|
|
@@ -238,7 +241,7 @@ module Danger
|
|
|
238
241
|
violation_report[:errors].count > 0
|
|
239
242
|
end
|
|
240
243
|
|
|
241
|
-
def post_results(danger_id)
|
|
244
|
+
def post_results(danger_id, new_comment)
|
|
242
245
|
violations = violation_report
|
|
243
246
|
|
|
244
247
|
env.request_source.update_pull_request!(
|
|
@@ -246,7 +249,8 @@ module Danger
|
|
|
246
249
|
errors: violations[:errors],
|
|
247
250
|
messages: violations[:messages],
|
|
248
251
|
markdowns: status_report[:markdowns],
|
|
249
|
-
danger_id: danger_id
|
|
252
|
+
danger_id: danger_id,
|
|
253
|
+
new_comment: new_comment
|
|
250
254
|
)
|
|
251
255
|
end
|
|
252
256
|
|
|
@@ -255,7 +259,7 @@ module Danger
|
|
|
255
259
|
env.scm.diff_for_folder(".".freeze, from: base_branch, to: head_branch)
|
|
256
260
|
end
|
|
257
261
|
|
|
258
|
-
def run(base_branch, head_branch, dangerfile_path, danger_id)
|
|
262
|
+
def run(base_branch, head_branch, dangerfile_path, danger_id, new_comment)
|
|
259
263
|
# Setup internal state
|
|
260
264
|
init_plugins
|
|
261
265
|
env.fill_environment_vars
|
|
@@ -270,7 +274,7 @@ module Danger
|
|
|
270
274
|
# Push results to the API
|
|
271
275
|
# Pass along the details of the run to the request source
|
|
272
276
|
# to send back to the code review site.
|
|
273
|
-
post_results(danger_id)
|
|
277
|
+
post_results(danger_id, new_comment)
|
|
274
278
|
|
|
275
279
|
# Print results in the terminal
|
|
276
280
|
print_results
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module Danger
|
|
2
|
+
class DangerfileGenerator
|
|
3
|
+
# returns the string for a Dangerfile based on a folder's contents'
|
|
4
|
+
def self.create_dangerfile(_path, _ui)
|
|
5
|
+
# Use this template for now, but this is a really ripe place to
|
|
6
|
+
# improve now!
|
|
7
|
+
dir = Danger.gem_path
|
|
8
|
+
File.read(File.join(dir, "lib", "assets", "DangerfileTemplate"))
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -39,7 +39,7 @@ module Danger
|
|
|
39
39
|
self.request_source = request_source
|
|
40
40
|
end
|
|
41
41
|
|
|
42
|
-
raise_error_for_no_request_source(env, ui) unless self.request_source
|
|
42
|
+
raise_error_for_no_request_source(env, self.ui) unless self.request_source
|
|
43
43
|
self.scm = self.request_source.scm
|
|
44
44
|
end
|
|
45
45
|
|
|
@@ -11,6 +11,7 @@ module Danger
|
|
|
11
11
|
head: nil,
|
|
12
12
|
dangerfile_path: nil,
|
|
13
13
|
danger_id: nil,
|
|
14
|
+
new_comment: nil,
|
|
14
15
|
fail_on_errors: nil)
|
|
15
16
|
# Create a silent Cork instance if cork is nil, as it's likely a test
|
|
16
17
|
cork ||= Cork::Board.new(silent: false, verbose: false)
|
|
@@ -27,7 +28,8 @@ module Danger
|
|
|
27
28
|
base_branch(base),
|
|
28
29
|
head_branch(head),
|
|
29
30
|
dangerfile_path,
|
|
30
|
-
danger_id
|
|
31
|
+
danger_id,
|
|
32
|
+
new_comment
|
|
31
33
|
)
|
|
32
34
|
end
|
|
33
35
|
|
|
@@ -28,15 +28,15 @@ module Danger
|
|
|
28
28
|
#
|
|
29
29
|
# fail "Please re-submit this MR to develop, we may have already fixed your issue." if gitlab.branch_for_merge != "develop"
|
|
30
30
|
#
|
|
31
|
-
# @example Note when MRs don't reference a milestone,
|
|
31
|
+
# @example Note when MRs don't reference a milestone, make the warning stick around on subsequent runs
|
|
32
32
|
#
|
|
33
33
|
# has_milestone = gitlab.mr_json["milestone"] != nil
|
|
34
|
-
# warn("This MR does not refer to an existing milestone", sticky:
|
|
34
|
+
# warn("This MR does not refer to an existing milestone", sticky: true) unless has_milestone
|
|
35
35
|
#
|
|
36
|
-
# @example Note when a MR cannot be manually merged
|
|
36
|
+
# @example Note when a MR cannot be manually merged
|
|
37
37
|
#
|
|
38
38
|
# can_merge = gitlab.mr_json["mergeable"]
|
|
39
|
-
# warn("This MR cannot be merged yet."
|
|
39
|
+
# warn("This MR cannot be merged yet.") unless can_merge
|
|
40
40
|
#
|
|
41
41
|
# @example Highlight when a celebrity makes a merge request.
|
|
42
42
|
#
|
|
@@ -8,14 +8,15 @@ module Danger
|
|
|
8
8
|
#
|
|
9
9
|
# The message within which Danger communicates back is amended on each run in a session.
|
|
10
10
|
#
|
|
11
|
-
# Each of `message`, `warn` and `fail` have a `sticky` flag, `
|
|
12
|
-
# means that the message will be crossed out instead of being removed.
|
|
13
|
-
# subsequent runs.
|
|
11
|
+
# Each of `message`, `warn` and `fail` have a `sticky` flag, `false` by default, which
|
|
12
|
+
# when `true` means that the message will be crossed out instead of being removed.
|
|
13
|
+
# If it's not called again on subsequent runs.
|
|
14
14
|
#
|
|
15
15
|
# By default, using `fail` would fail the corresponding build. Either via an API call, or
|
|
16
|
-
# via the return value for the danger command.
|
|
16
|
+
# via the return value for the danger command. If you have linters with errors for this call
|
|
17
|
+
# you can use `messaging.fail` instead.
|
|
17
18
|
#
|
|
18
|
-
# It is possible to have Danger ignore specific warnings or errors by writing `Danger: Ignore "[warning/error text]`.
|
|
19
|
+
# It is possible to have Danger ignore specific warnings or errors by writing `Danger: Ignore "[warning/error text]"`.
|
|
19
20
|
#
|
|
20
21
|
# Sidenote: Messaging is the only plugin which adds functions to the root of the Dangerfile.
|
|
21
22
|
#
|
|
@@ -23,9 +24,9 @@ module Danger
|
|
|
23
24
|
#
|
|
24
25
|
# fail "This build didn't pass tests"
|
|
25
26
|
#
|
|
26
|
-
# @example Failing a build,
|
|
27
|
+
# @example Failing a build, and note that on subsequent runs
|
|
27
28
|
#
|
|
28
|
-
# fail("This build didn't pass tests", sticky:
|
|
29
|
+
# fail("This build didn't pass tests", sticky: true)
|
|
29
30
|
#
|
|
30
31
|
# @example Passing a warning
|
|
31
32
|
#
|
|
@@ -87,14 +88,14 @@ module Danger
|
|
|
87
88
|
# The message to present to the user
|
|
88
89
|
# @param [Boolean] sticky
|
|
89
90
|
# Whether the message should be kept after it was fixed,
|
|
90
|
-
# defaults to `
|
|
91
|
+
# defaults to `false`.
|
|
91
92
|
# @param [String] file
|
|
92
93
|
# Optional. Path to the file that the message is for.
|
|
93
94
|
# @param [String] line
|
|
94
95
|
# Optional. The line in the file to present the message in.
|
|
95
96
|
# @return [void]
|
|
96
97
|
#
|
|
97
|
-
def message(message, sticky:
|
|
98
|
+
def message(message, sticky: false, file: nil, line: nil)
|
|
98
99
|
@messages << Violation.new(message, sticky, file, line)
|
|
99
100
|
end
|
|
100
101
|
|
|
@@ -105,14 +106,14 @@ module Danger
|
|
|
105
106
|
# The message to present to the user
|
|
106
107
|
# @param [Boolean] sticky
|
|
107
108
|
# Whether the message should be kept after it was fixed,
|
|
108
|
-
# defaults to `
|
|
109
|
+
# defaults to `false`.
|
|
109
110
|
# @param [String] file
|
|
110
111
|
# Optional. Path to the file that the message is for.
|
|
111
112
|
# @param [String] line
|
|
112
113
|
# Optional. The line in the file to present the message in.
|
|
113
114
|
# @return [void]
|
|
114
115
|
#
|
|
115
|
-
def warn(message, sticky:
|
|
116
|
+
def warn(message, sticky: false, file: nil, line: nil)
|
|
116
117
|
return if should_ignore_violation(message)
|
|
117
118
|
@warnings << Violation.new(message, sticky, file, line)
|
|
118
119
|
end
|
|
@@ -124,14 +125,14 @@ module Danger
|
|
|
124
125
|
# The message to present to the user
|
|
125
126
|
# @param [Boolean] sticky
|
|
126
127
|
# Whether the message should be kept after it was fixed,
|
|
127
|
-
# defaults to `
|
|
128
|
+
# defaults to `false`.
|
|
128
129
|
# @param [String] file
|
|
129
130
|
# Optional. Path to the file that the message is for.
|
|
130
131
|
# @param [String] line
|
|
131
132
|
# Optional. The line in the file to present the message in.
|
|
132
133
|
# @return [void]
|
|
133
134
|
#
|
|
134
|
-
def fail(message, sticky:
|
|
135
|
+
def fail(message, sticky: false, file: nil, line: nil)
|
|
135
136
|
return if should_ignore_violation(message)
|
|
136
137
|
@errors << Violation.new(message, sticky, file, line)
|
|
137
138
|
end
|
|
@@ -3,6 +3,8 @@ require "octokit"
|
|
|
3
3
|
require "danger/helpers/comments_helper"
|
|
4
4
|
require "danger/helpers/comment"
|
|
5
5
|
|
|
6
|
+
require "danger/request_sources/support/get_ignored_violation"
|
|
7
|
+
|
|
6
8
|
module Danger
|
|
7
9
|
module RequestSources
|
|
8
10
|
class GitHub < RequestSource
|
|
@@ -83,13 +85,11 @@ module Danger
|
|
|
83
85
|
end
|
|
84
86
|
|
|
85
87
|
fetch_issue_details(self.pr_json)
|
|
86
|
-
self.ignored_violations = ignored_violations_from_pr
|
|
88
|
+
self.ignored_violations = ignored_violations_from_pr
|
|
87
89
|
end
|
|
88
90
|
|
|
89
|
-
def ignored_violations_from_pr
|
|
90
|
-
|
|
91
|
-
return [] if pr_body.nil?
|
|
92
|
-
pr_body.chomp.scan(/>\s*danger\s*:\s*ignore\s*"(.*)"/i).flatten
|
|
91
|
+
def ignored_violations_from_pr
|
|
92
|
+
GetIgnoredViolation.new(self.pr_json["body"]).call
|
|
93
93
|
end
|
|
94
94
|
|
|
95
95
|
def fetch_issue_details(pr_json)
|
|
@@ -98,20 +98,23 @@ module Danger
|
|
|
98
98
|
end
|
|
99
99
|
|
|
100
100
|
def issue_comments
|
|
101
|
-
@comments ||=
|
|
102
|
-
|
|
101
|
+
@comments ||= begin
|
|
102
|
+
client.issue_comments(ci_source.repo_slug, ci_source.pull_request_id)
|
|
103
|
+
.map { |comment| Comment.from_github(comment) }
|
|
104
|
+
end
|
|
103
105
|
end
|
|
104
106
|
|
|
105
107
|
# Sending data to GitHub
|
|
106
|
-
def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger")
|
|
108
|
+
def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false)
|
|
107
109
|
comment_result = {}
|
|
108
110
|
editable_comments = issue_comments.select { |comment| comment.generated_by_danger?(danger_id) }
|
|
111
|
+
last_comment = editable_comments.last
|
|
112
|
+
should_create_new_comment = new_comment || last_comment.nil?
|
|
109
113
|
|
|
110
|
-
if
|
|
114
|
+
if should_create_new_comment
|
|
111
115
|
previous_violations = {}
|
|
112
116
|
else
|
|
113
|
-
|
|
114
|
-
previous_violations = parse_comment(comment)
|
|
117
|
+
previous_violations = parse_comment(last_comment.body)
|
|
115
118
|
end
|
|
116
119
|
|
|
117
120
|
main_violations = (warnings + errors + messages + markdowns).reject(&:inline?)
|
|
@@ -152,11 +155,10 @@ module Danger
|
|
|
152
155
|
danger_id: danger_id,
|
|
153
156
|
template: "github")
|
|
154
157
|
|
|
155
|
-
if
|
|
158
|
+
if should_create_new_comment
|
|
156
159
|
comment_result = client.add_comment(ci_source.repo_slug, ci_source.pull_request_id, body)
|
|
157
160
|
else
|
|
158
|
-
|
|
159
|
-
comment_result = client.update_comment(ci_source.repo_slug, original_id, body)
|
|
161
|
+
comment_result = client.update_comment(ci_source.repo_slug, last_comment.id, body)
|
|
160
162
|
end
|
|
161
163
|
end
|
|
162
164
|
|
|
@@ -197,6 +199,7 @@ module Danger
|
|
|
197
199
|
end
|
|
198
200
|
else
|
|
199
201
|
puts message
|
|
202
|
+
puts "\nDanger does not have write access to the PR to set a PR status.".yellow
|
|
200
203
|
end
|
|
201
204
|
end
|
|
202
205
|
end
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# coding: utf-8
|
|
2
|
-
require "gitlab"
|
|
3
2
|
require "danger/helpers/comments_helper"
|
|
4
3
|
require "danger/helpers/comment"
|
|
5
4
|
|
|
5
|
+
require "danger/request_sources/support/get_ignored_violation"
|
|
6
|
+
|
|
6
7
|
module Danger
|
|
7
8
|
module RequestSources
|
|
8
9
|
class GitLab < RequestSource
|
|
@@ -27,10 +28,19 @@ module Danger
|
|
|
27
28
|
def client
|
|
28
29
|
token = @environment["DANGER_GITLAB_API_TOKEN"]
|
|
29
30
|
raise "No API token given, please provide one using `DANGER_GITLAB_API_TOKEN`" unless token
|
|
31
|
+
|
|
32
|
+
# The require happens inline so that it won't cause exceptions when just using the `danger` gem.
|
|
33
|
+
require "gitlab"
|
|
34
|
+
|
|
30
35
|
params = { private_token: token }
|
|
31
36
|
params[:endpoint] = endpoint
|
|
32
37
|
|
|
33
38
|
@client ||= Gitlab.client(params)
|
|
39
|
+
|
|
40
|
+
rescue LoadError
|
|
41
|
+
puts "The GitLab gem was not installed, you will need to change your Gem from `danger` to `danger-gitlab`.".red
|
|
42
|
+
puts "\n - See https://github.com/danger/danger/blob/master/CHANGELOG.md#400"
|
|
43
|
+
abort
|
|
34
44
|
end
|
|
35
45
|
|
|
36
46
|
def validates_as_api_source?
|
|
@@ -55,13 +65,17 @@ module Danger
|
|
|
55
65
|
end
|
|
56
66
|
|
|
57
67
|
def mr_comments
|
|
58
|
-
@comments ||=
|
|
59
|
-
|
|
68
|
+
@comments ||= begin
|
|
69
|
+
client.merge_request_comments(escaped_ci_slug, ci_source.pull_request_id)
|
|
70
|
+
.map { |comment| Comment.from_gitlab(comment) }
|
|
71
|
+
end
|
|
60
72
|
end
|
|
61
73
|
|
|
62
74
|
def mr_diff
|
|
63
|
-
@mr_diff ||=
|
|
64
|
-
|
|
75
|
+
@mr_diff ||= begin
|
|
76
|
+
client.merge_request_changes(escaped_ci_slug, ci_source.pull_request_id)
|
|
77
|
+
.changes.map { |change| change["diff"] }.join("\n")
|
|
78
|
+
end
|
|
65
79
|
end
|
|
66
80
|
|
|
67
81
|
def escaped_ci_slug
|
|
@@ -84,19 +98,19 @@ module Danger
|
|
|
84
98
|
def fetch_details
|
|
85
99
|
self.mr_json = client.merge_request(escaped_ci_slug, self.ci_source.pull_request_id)
|
|
86
100
|
self.commits_json = client.merge_request_commits(escaped_ci_slug, self.ci_source.pull_request_id)
|
|
87
|
-
self.ignored_violations = ignored_violations_from_pr
|
|
101
|
+
self.ignored_violations = ignored_violations_from_pr
|
|
88
102
|
end
|
|
89
103
|
|
|
90
|
-
def ignored_violations_from_pr
|
|
91
|
-
|
|
92
|
-
return [] if pr_body.nil?
|
|
93
|
-
pr_body.chomp.scan(/>\s*danger\s*:\s*ignore\s*"(.*)"/i).flatten
|
|
104
|
+
def ignored_violations_from_pr
|
|
105
|
+
GetIgnoredViolation.new(self.mr_json.description).call
|
|
94
106
|
end
|
|
95
107
|
|
|
96
|
-
def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger")
|
|
108
|
+
def update_pull_request!(warnings: [], errors: [], messages: [], markdowns: [], danger_id: "danger", new_comment: false)
|
|
97
109
|
editable_comments = mr_comments.select { |comment| comment.generated_by_danger?(danger_id) }
|
|
98
110
|
|
|
99
|
-
|
|
111
|
+
should_create_new_comment = new_comment || editable_comments.empty?
|
|
112
|
+
|
|
113
|
+
if should_create_new_comment
|
|
100
114
|
previous_violations = {}
|
|
101
115
|
else
|
|
102
116
|
comment = editable_comments.first.body
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
class GetIgnoredViolation
|
|
2
|
+
IGNORE_REGEXP = />*\s*danger\s*:\s*ignore\s*"(?<error>[^"]*)"/i
|
|
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
|
|
@@ -12,10 +12,10 @@ module Danger
|
|
|
12
12
|
|
|
13
13
|
ensure_commitish_exists!(from)
|
|
14
14
|
ensure_commitish_exists!(to)
|
|
15
|
-
merge_base = repo.merge_base(from, to)
|
|
16
15
|
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
merge_base = find_merge_base(repo, from, to)
|
|
17
|
+
|
|
18
|
+
self.diff = repo.diff(merge_base, to)
|
|
19
19
|
self.log = repo.log.between(from, to)
|
|
20
20
|
end
|
|
21
21
|
|
|
@@ -29,7 +29,7 @@ module Danger
|
|
|
29
29
|
end
|
|
30
30
|
|
|
31
31
|
def head_commit
|
|
32
|
-
exec
|
|
32
|
+
exec("rev-parse HEAD")
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
def origins
|
|
@@ -37,38 +37,60 @@ module Danger
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def ensure_commitish_exists!(commitish)
|
|
40
|
-
|
|
40
|
+
git_shallow_fetch if commit_not_exists?(commitish)
|
|
41
|
+
|
|
42
|
+
if commit_not_exists?(commitish)
|
|
43
|
+
raise_if_we_cannot_find_the_commit(commitish)
|
|
44
|
+
end
|
|
41
45
|
end
|
|
42
46
|
|
|
43
47
|
private
|
|
44
48
|
|
|
49
|
+
def git_shallow_fetch
|
|
50
|
+
exec("fetch --unshallow")
|
|
51
|
+
end
|
|
52
|
+
|
|
45
53
|
def default_env
|
|
46
54
|
{ "LANG" => "en_US.UTF-8" }
|
|
47
55
|
end
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
56
|
|
|
51
|
-
|
|
52
|
-
#
|
|
53
|
-
|
|
54
|
-
#
|
|
55
|
-
# for which the fix comes from an unmerged PR from 2012
|
|
56
|
-
# https://github.com/schacon/ruby-git/pull/43
|
|
57
|
+
def raise_if_we_cannot_find_the_commit(commitish)
|
|
58
|
+
raise "Commit #{commitish[0..7]} doesn't exist. Are you running `danger local/pr` against the correct repository? Also this usually happens when you rebase/reset and force-pushed."
|
|
59
|
+
end
|
|
57
60
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
def commit_exists?(sha1)
|
|
62
|
+
!commit_not_exists?(sha1)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def commit_not_exists?(sha1)
|
|
66
|
+
exec("rev-parse --quiet --verify #{sha1}^{commit}").empty?
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def find_merge_base(repo, from, to)
|
|
70
|
+
possible_merge_base = possible_merge_base(repo, from, to)
|
|
71
|
+
|
|
72
|
+
unless possible_merge_base
|
|
73
|
+
git_shallow_fetch
|
|
74
|
+
possible_merge_base = possible_merge_base(repo, from, to)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
raise "Cannot find a merge base between #{from} and #{to}." unless possible_merge_base
|
|
78
|
+
|
|
79
|
+
possible_merge_base
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def possible_merge_base(repo, from, to)
|
|
83
|
+
[repo.merge_base(from, to)].find { |base| commit_exists?(base) }
|
|
62
84
|
end
|
|
63
85
|
end
|
|
86
|
+
end
|
|
64
87
|
|
|
65
|
-
|
|
88
|
+
module Git
|
|
89
|
+
class Base
|
|
90
|
+
# Use git-merge-base https://git-scm.com/docs/git-merge-base to
|
|
91
|
+
# find as good common ancestors as possible for a merge
|
|
66
92
|
def merge_base(commit1, commit2, *other_commits)
|
|
67
|
-
|
|
68
|
-
arr_opts << commit1
|
|
69
|
-
arr_opts << commit2
|
|
70
|
-
arr_opts += other_commits
|
|
71
|
-
command("merge-base", arr_opts)
|
|
93
|
+
Open3.popen2("git", "merge-base", "--all", commit1, commit2, *other_commits) { |_stdin, stdout, _wait_thr| stdout.read.rstrip }
|
|
72
94
|
end
|
|
73
95
|
end
|
|
74
96
|
end
|
data/lib/danger/version.rb
CHANGED