danger 0.7.4 → 0.8.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 +28 -23
- data/lib/danger.rb +3 -4
- data/lib/danger/ci_source/circle.rb +1 -1
- data/lib/danger/{circle_api.rb → ci_source/circle_api.rb} +0 -0
- data/lib/danger/ci_source/drone.rb +18 -0
- data/lib/danger/ci_source/semaphore.rb +18 -0
- data/lib/danger/commands/local.rb +19 -10
- data/lib/danger/commands/runner.rb +30 -18
- data/lib/danger/comment_generators/github.md.erb +1 -1
- data/lib/danger/{scm_source → core_ext}/file_list.rb +1 -0
- data/lib/danger/danger_core/dangerfile.rb +217 -0
- data/lib/danger/danger_core/dangerfile_dsl.rb +29 -0
- data/lib/danger/{environment_manager.rb → danger_core/environment_manager.rb} +14 -6
- data/lib/danger/danger_core/plugins/dangerfile_git_plugin.rb +69 -0
- data/lib/danger/danger_core/plugins/dangerfile_github_plugin.rb +68 -0
- data/lib/danger/danger_core/plugins/dangerfile_import_plugin.rb +58 -0
- data/lib/danger/danger_core/plugins/dangerfile_messaging_plugin.rb +87 -0
- data/lib/danger/{standard_error.rb → danger_core/standard_error.rb} +1 -1
- data/lib/danger/{violation.rb → danger_core/violation.rb} +0 -0
- data/lib/danger/plugin_support/plugin.rb +31 -0
- data/lib/danger/plugin_support/plugin_parser.rb +70 -0
- data/lib/danger/{request_sources → request_source}/github.rb +2 -33
- data/lib/danger/scm_source/git_repo.rb +1 -30
- data/lib/danger/version.rb +1 -1
- metadata +79 -17
- data/lib/danger/available_values.rb +0 -29
- data/lib/danger/dangerfile.rb +0 -123
- data/lib/danger/dangerfile_dsl.rb +0 -159
- data/lib/danger/plugin.rb +0 -25
- data/lib/danger/plugins/protect_files.rb +0 -34
@@ -0,0 +1,29 @@
|
|
1
|
+
module Danger
|
2
|
+
class Dangerfile
|
3
|
+
# Anything inside this module is considered public API, and in the future
|
4
|
+
# documentation will be generated from it via rdoc.
|
5
|
+
|
6
|
+
module DSL
|
7
|
+
# @!group Danger Zone
|
8
|
+
# Provides access to the raw Travis/Circle/Buildkite/GitHub objects, which
|
9
|
+
# you can use to pull out extra bits of information. _Warning_
|
10
|
+
# the interfaces of these objects is **not** considered a part of the Dangerfile public
|
11
|
+
# API, and is viable to change occasionally on the whims of developers.
|
12
|
+
# @return [EnvironmentManager]
|
13
|
+
|
14
|
+
attr_reader :env
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def initialize
|
19
|
+
load_default_plugins
|
20
|
+
end
|
21
|
+
|
22
|
+
def load_default_plugins
|
23
|
+
Dir["./danger_plugins/*.rb"].each do |file|
|
24
|
+
require File.expand_path(file)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require "danger/ci_source/ci_source"
|
2
|
-
require "danger/
|
2
|
+
require "danger/request_source/github"
|
3
3
|
|
4
4
|
module Danger
|
5
5
|
class EnvironmentManager
|
@@ -15,7 +15,6 @@ module Danger
|
|
15
15
|
if self.ci_source.repo_slug and self.ci_source.pull_request_id
|
16
16
|
break
|
17
17
|
else
|
18
|
-
puts "Not a Pull Request - skipping `danger` run"
|
19
18
|
self.ci_source = nil
|
20
19
|
return nil
|
21
20
|
end
|
@@ -25,18 +24,27 @@ module Danger
|
|
25
24
|
|
26
25
|
# only GitHub for now, open for PRs adding more!
|
27
26
|
self.request_source = GitHub.new(self.ci_source, ENV)
|
27
|
+
# Also Git only for now, also open for PRs adding more!
|
28
|
+
self.scm = GitRepo.new # For now
|
29
|
+
end
|
30
|
+
|
31
|
+
def pr?
|
32
|
+
self.ci_source != nil
|
28
33
|
end
|
29
34
|
|
30
35
|
def fill_environment_vars
|
31
36
|
request_source.fetch_details
|
32
|
-
|
33
|
-
self.scm = GitRepo.new # For now
|
34
37
|
end
|
35
38
|
|
36
39
|
def ensure_danger_branches_are_setup
|
40
|
+
clean_up
|
41
|
+
|
37
42
|
# As this currently just works with GitHub, we can use a github specific feature here:
|
38
43
|
pull_id = ci_source.pull_request_id
|
39
|
-
|
44
|
+
|
45
|
+
# TODO: This isn't optimal, should be hidden behind some kind of facade, but the plugin makes that
|
46
|
+
# difficult to do without accessing the dangerfile
|
47
|
+
test_branch = request_source.pr_json[:base][:sha]
|
40
48
|
|
41
49
|
# Next, we want to ensure that we have a version of the current branch at a known location
|
42
50
|
scm.exec "branch #{danger_base_branch} #{test_branch}"
|
@@ -48,7 +56,7 @@ module Danger
|
|
48
56
|
|
49
57
|
def clean_up
|
50
58
|
[danger_base_branch, danger_base_branch].each do |branch|
|
51
|
-
scm.exec
|
59
|
+
scm.exec("branch -D #{branch}") unless scm.exec("rev-parse --quiet --verify #{branch}").empty?
|
52
60
|
end
|
53
61
|
end
|
54
62
|
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'danger/plugin_support/plugin'
|
2
|
+
require 'danger/core_ext/file_list'
|
3
|
+
|
4
|
+
module Danger
|
5
|
+
class DangerfileGitPlugin < Plugin
|
6
|
+
def initialize(dangerfile)
|
7
|
+
super(dangerfile)
|
8
|
+
raise unless dangerfile.env.scm.class == Danger::GitRepo
|
9
|
+
|
10
|
+
@git = dangerfile.env.scm
|
11
|
+
end
|
12
|
+
|
13
|
+
# @!group Git Files
|
14
|
+
# Paths for files that were added during the diff
|
15
|
+
# @return [FileList] an [Array] subclass
|
16
|
+
#
|
17
|
+
def added_files
|
18
|
+
Danger::FileList.new(@git.diff.select { |diff| diff.type == "new" }.map(&:path))
|
19
|
+
end
|
20
|
+
|
21
|
+
# @!group Git Files
|
22
|
+
# Paths for files that were removed during the diff
|
23
|
+
# @return [FileList] an [Array] subclass
|
24
|
+
#
|
25
|
+
def deleted_files
|
26
|
+
Danger::FileList.new(@git.diff.select { |diff| diff.type == "deleted" }.map(&:path))
|
27
|
+
end
|
28
|
+
|
29
|
+
# @!group Git Files
|
30
|
+
# Paths for files that changed during the diff
|
31
|
+
# @return [FileList] an [Array] subclass
|
32
|
+
#
|
33
|
+
def modified_files
|
34
|
+
Danger::FileList.new(@git.diff.stats[:files].keys)
|
35
|
+
end
|
36
|
+
|
37
|
+
# @!group Git Metadata
|
38
|
+
# The overall lines of code added/removed in the diff
|
39
|
+
# @return Int
|
40
|
+
#
|
41
|
+
def lines_of_code
|
42
|
+
@git.diff.lines
|
43
|
+
end
|
44
|
+
|
45
|
+
# @!group Git Metadata
|
46
|
+
# The overall lines of code removed in the diff
|
47
|
+
# @return Int
|
48
|
+
#
|
49
|
+
def deletions
|
50
|
+
@git.diff.deletions
|
51
|
+
end
|
52
|
+
|
53
|
+
# @!group Git Metadata
|
54
|
+
# The overall lines of code added in the diff
|
55
|
+
# @return Int
|
56
|
+
#
|
57
|
+
def insertions
|
58
|
+
@git.diff.insertions
|
59
|
+
end
|
60
|
+
|
61
|
+
# @!group Git Metadata
|
62
|
+
# The log of commits inside the diff
|
63
|
+
# @return [Git::Log] from the gem `git`
|
64
|
+
#
|
65
|
+
def commits
|
66
|
+
@git.log.to_a
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,68 @@
|
|
1
|
+
require 'danger/plugin_support/plugin'
|
2
|
+
|
3
|
+
module Danger
|
4
|
+
class DangerfileGitHubPlugin < Plugin
|
5
|
+
def initialize(dangerfile)
|
6
|
+
super(dangerfile)
|
7
|
+
return nil unless dangerfile.env.request_source.class == Danger::GitHub
|
8
|
+
|
9
|
+
@github = dangerfile.env.request_source
|
10
|
+
end
|
11
|
+
|
12
|
+
# @!group PR Metadata
|
13
|
+
# The title of the Pull Request
|
14
|
+
# @return String
|
15
|
+
#
|
16
|
+
def pr_title
|
17
|
+
@github.pr_json[:title].to_s
|
18
|
+
end
|
19
|
+
|
20
|
+
# @!group PR Metadata
|
21
|
+
# The body text of the Pull Request
|
22
|
+
# @return String
|
23
|
+
#
|
24
|
+
def pr_body
|
25
|
+
@github.pr_json[:body].to_s
|
26
|
+
end
|
27
|
+
|
28
|
+
# @!group PR Metadata
|
29
|
+
# The username of the author of the Pull Request
|
30
|
+
# @return String
|
31
|
+
#
|
32
|
+
def pr_author
|
33
|
+
@github.pr_json[:user][:login].to_s
|
34
|
+
end
|
35
|
+
|
36
|
+
# @!group PR Metadata
|
37
|
+
# The labels assigned to the Pull Request
|
38
|
+
# @return [String]
|
39
|
+
#
|
40
|
+
def pr_labels
|
41
|
+
@github.issue_json[:labels].map { |l| l[:name] }
|
42
|
+
end
|
43
|
+
|
44
|
+
# @!group PR Commit Metadata
|
45
|
+
# The branch to which the PR is going to be merged into
|
46
|
+
# @return String
|
47
|
+
#
|
48
|
+
def branch_for_merge
|
49
|
+
@github.pr_json[:base][:ref]
|
50
|
+
end
|
51
|
+
|
52
|
+
# @!group PR Commit Metadata
|
53
|
+
# The base commit to which the PR is going to be merged as a parent
|
54
|
+
# @return String
|
55
|
+
#
|
56
|
+
def base_commit
|
57
|
+
@github.pr_json[:base][:sha]
|
58
|
+
end
|
59
|
+
|
60
|
+
# @!group PR Commit Metadata
|
61
|
+
# The head commit to which the PR is requesting to be merged from
|
62
|
+
# @return String
|
63
|
+
#
|
64
|
+
def head_commit
|
65
|
+
@github.pr_json[:head][:sha]
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'danger/plugin_support/plugin'
|
2
|
+
|
3
|
+
module Danger
|
4
|
+
class DangerfileImportPlugin < Plugin
|
5
|
+
# @!group Plugins
|
6
|
+
# Download a local or remote plugin and use it locally
|
7
|
+
#
|
8
|
+
# @param [String] path
|
9
|
+
# a local path or a https URL to the Ruby file to import
|
10
|
+
# a danger plugin from.
|
11
|
+
def import(path)
|
12
|
+
raise "`import` requires a string" unless path.kind_of?(String)
|
13
|
+
path += ".rb" unless path.end_with?(".rb")
|
14
|
+
|
15
|
+
if path.start_with?("http")
|
16
|
+
import_url(path)
|
17
|
+
else
|
18
|
+
import_local(path)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# @!group Plugins
|
23
|
+
# Download a remote plugin and use it locally
|
24
|
+
#
|
25
|
+
# @param [String] url
|
26
|
+
# https URL to the Ruby file to use
|
27
|
+
def import_url(url)
|
28
|
+
raise "URL is not https, for security reasons `danger` only supports encrypted requests" unless url.start_with?("https://")
|
29
|
+
|
30
|
+
require 'tmpdir'
|
31
|
+
require 'faraday'
|
32
|
+
|
33
|
+
@http_client ||= Faraday.new do |b|
|
34
|
+
b.adapter :net_http
|
35
|
+
end
|
36
|
+
content = @http_client.get(url)
|
37
|
+
|
38
|
+
Dir.mktmpdir do |dir|
|
39
|
+
path = File.join(dir, "temporary_remote_action.rb")
|
40
|
+
File.write(path, content.body)
|
41
|
+
import_local(path)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# @!group Plugins
|
46
|
+
# Import one or more local plugins
|
47
|
+
#
|
48
|
+
# @param [String] path
|
49
|
+
# The path to the file to import
|
50
|
+
# Can also be a pattern (./**/*plugin.rb)
|
51
|
+
def import_local(path)
|
52
|
+
Dir[path].each do |file|
|
53
|
+
require File.expand_path(file) # without the expand_path it would fail if the path doesn't start with ./
|
54
|
+
refresh_plugins
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'danger/danger_core/violation'
|
2
|
+
require 'danger/plugin_support/plugin'
|
3
|
+
|
4
|
+
module Danger
|
5
|
+
class DangerfileMessagingPlugin < Plugin
|
6
|
+
def initialize(dangerfile)
|
7
|
+
super(dangerfile)
|
8
|
+
|
9
|
+
@warnings = []
|
10
|
+
@errors = []
|
11
|
+
@messages = []
|
12
|
+
@markdowns = []
|
13
|
+
end
|
14
|
+
|
15
|
+
# @!group Core
|
16
|
+
# Print markdown to below the table
|
17
|
+
#
|
18
|
+
# @param [String] message
|
19
|
+
# The markdown based message to be printed below the table
|
20
|
+
def markdown(message)
|
21
|
+
@markdowns << message
|
22
|
+
puts "Printing markdown #{message}"
|
23
|
+
end
|
24
|
+
|
25
|
+
# @!group Core
|
26
|
+
# Print out a generate message on the PR
|
27
|
+
#
|
28
|
+
# @param [String] message The message to present to the user
|
29
|
+
# @param [Boolean] sticky
|
30
|
+
# Whether the message should be kept after it was fixed,
|
31
|
+
# defaults to `true`.
|
32
|
+
def message(message, sticky: true)
|
33
|
+
@messages << Violation.new(message, sticky)
|
34
|
+
puts "Printing message '#{message}'"
|
35
|
+
end
|
36
|
+
|
37
|
+
# @!group Core
|
38
|
+
# Specifies a problem, but not critical
|
39
|
+
#
|
40
|
+
# @param [String] message The message to present to the user
|
41
|
+
# @param [Boolean] sticky
|
42
|
+
# Whether the message should be kept after it was fixed,
|
43
|
+
# defaults to `true`.
|
44
|
+
def warn(message, sticky: true)
|
45
|
+
return if should_ignore_violation(message)
|
46
|
+
@warnings << Violation.new(message, sticky)
|
47
|
+
puts "Printing warning '#{message}'"
|
48
|
+
end
|
49
|
+
|
50
|
+
# @!group Core
|
51
|
+
# Declares a CI blocking error
|
52
|
+
#
|
53
|
+
# @param [String] message
|
54
|
+
# The message to present to the user
|
55
|
+
# @param [Boolean] sticky
|
56
|
+
# Whether the message should be kept after it was fixed,
|
57
|
+
# defaults to `true`.
|
58
|
+
def fail(message, sticky: true)
|
59
|
+
return if should_ignore_violation(message)
|
60
|
+
@errors << Violation.new(message, sticky)
|
61
|
+
puts "Raising error '#{message}'"
|
62
|
+
end
|
63
|
+
|
64
|
+
def status_report
|
65
|
+
{
|
66
|
+
errors: @errors.map(&:message).clone.freeze,
|
67
|
+
warnings: @warnings.map(&:message).clone.freeze,
|
68
|
+
messages: @messages.map(&:message).clone.freeze,
|
69
|
+
markdowns: @markdowns.clone.freeze
|
70
|
+
}
|
71
|
+
end
|
72
|
+
|
73
|
+
def violation_report
|
74
|
+
{
|
75
|
+
errors: @errors.clone.freeze,
|
76
|
+
warnings: @warnings.clone.freeze,
|
77
|
+
messages: @messages.clone.freeze
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
private
|
82
|
+
|
83
|
+
def should_ignore_violation(message)
|
84
|
+
env.request_source.ignored_violations.include? message
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -2,7 +2,7 @@ require 'claide'
|
|
2
2
|
require 'claide/informative_error'
|
3
3
|
|
4
4
|
module Danger
|
5
|
-
#
|
5
|
+
# From below here - this entire file was taken verbatim for CocoaPods-Core.
|
6
6
|
|
7
7
|
#-------------------------------------------------------------------------#
|
8
8
|
|
File without changes
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Danger
|
2
|
+
class Plugin
|
3
|
+
def initialize(dangerfile)
|
4
|
+
@dangerfile = dangerfile
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.instance_name
|
8
|
+
self.to_s.gsub("Danger", "").danger_underscore.split('/').last
|
9
|
+
end
|
10
|
+
|
11
|
+
# Both of these methods exist on all objects
|
12
|
+
# http://ruby-doc.org/core-2.2.3/Kernel.html#method-i-warn
|
13
|
+
# http://ruby-doc.org/core-2.2.3/Kernel.html#method-i-fail
|
14
|
+
# However, as we're using using them in the DSL, they won't
|
15
|
+
# get method_missing called correctly.
|
16
|
+
|
17
|
+
def warn(*args, &blk)
|
18
|
+
method_missing(:warn, *args, &blk)
|
19
|
+
end
|
20
|
+
|
21
|
+
def fail(*args, &blk)
|
22
|
+
method_missing(:fail, *args, &blk)
|
23
|
+
end
|
24
|
+
|
25
|
+
# Since we have a reference to the Dangerfile containing all the information
|
26
|
+
# We need to redirect the self calls to the Dangerfile
|
27
|
+
def method_missing(method_sym, *arguments, &_block)
|
28
|
+
@dangerfile.send(method_sym, *arguments)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
require 'yard'
|
2
|
+
|
3
|
+
module Danger
|
4
|
+
class PluginParser
|
5
|
+
attr_accessor :registry
|
6
|
+
|
7
|
+
def initialize(path)
|
8
|
+
@path = path
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse
|
12
|
+
# could this go in a singleton-y place instead?
|
13
|
+
# like class initialize?
|
14
|
+
YARD::Tags::Library.define_tag('tags', :tags)
|
15
|
+
|
16
|
+
files = ["lib/danger/plugin_support/plugin.rb", @path]
|
17
|
+
self.registry = YARD::Registry.load(files, true)
|
18
|
+
end
|
19
|
+
|
20
|
+
def classes_in_file
|
21
|
+
self.registry.all
|
22
|
+
.select { |thing| thing.type == :class }
|
23
|
+
.select { |klass| klass.file == @path }
|
24
|
+
end
|
25
|
+
|
26
|
+
def plugins_from_classes(classes)
|
27
|
+
classes.select { |klass| klass.inheritance_tree.map(&:name).include? :Plugin }
|
28
|
+
end
|
29
|
+
|
30
|
+
def to_dict(classes)
|
31
|
+
d_meth = lambda do |meth|
|
32
|
+
return nil if meth.nil?
|
33
|
+
{
|
34
|
+
name: meth.name,
|
35
|
+
body_md: meth.docstring,
|
36
|
+
tags: meth.tags.map do |t|
|
37
|
+
{
|
38
|
+
name: t.tag_name,
|
39
|
+
types: t.types
|
40
|
+
}
|
41
|
+
end
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
d_attr = lambda do |attribute|
|
46
|
+
{
|
47
|
+
read: d_meth.call(attribute[:read]),
|
48
|
+
write: d_meth.call(attribute[:write])
|
49
|
+
}
|
50
|
+
end
|
51
|
+
|
52
|
+
classes.map do |klass|
|
53
|
+
{
|
54
|
+
name: klass.name.to_s,
|
55
|
+
body_md: klass.docstring,
|
56
|
+
example_code: klass.tags.select { |t| t.tag_name == "example" }.map(&:text).compact,
|
57
|
+
attributes: klass.attributes[:instance].map do |pair|
|
58
|
+
{
|
59
|
+
pair.first => d_attr.call(pair.last)
|
60
|
+
}
|
61
|
+
end,
|
62
|
+
methods: (klass.meths - klass.inherited_meths).select { |m| m.visibility == :public }.map { |m| d_meth.call(m) },
|
63
|
+
tags: klass.tags.select { |t| t.tag_name == "tags" }.map(&:name).compact,
|
64
|
+
see: klass.tags.select { |t| t.tag_name == "see" }.map(&:name).map(&:split).flatten.compact,
|
65
|
+
file: klass.file
|
66
|
+
}
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|