gitlab-dangerfiles 1.0.0 → 2.1.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 +4 -4
- data/.gitignore +1 -0
- data/.gitlab-ci.yml +36 -1
- data/.yardopts +5 -0
- data/Gemfile +1 -0
- data/LICENSE.txt +1 -1
- data/README.md +57 -6
- data/gitlab-dangerfiles.gemspec +3 -3
- data/lib/danger/plugins/helper.rb +426 -0
- data/lib/danger/{roulette.rb → plugins/roulette.rb} +82 -63
- data/lib/danger/rules/changes_size/Dangerfile +10 -0
- data/lib/danger/rules/commit_messages/Dangerfile +139 -0
- data/lib/gitlab/dangerfiles.rb +82 -2
- data/lib/gitlab/dangerfiles/changes.rb +22 -0
- data/lib/gitlab/dangerfiles/commit_linter.rb +7 -3
- data/lib/gitlab/dangerfiles/config.rb +23 -0
- data/lib/gitlab/dangerfiles/emoji_checker.rb +1 -0
- data/lib/gitlab/dangerfiles/title_linting.rb +2 -0
- data/lib/gitlab/dangerfiles/version.rb +1 -1
- data/lib/gitlab/dangerfiles/weightage.rb +1 -0
- data/lib/gitlab/dangerfiles/weightage/maintainers.rb +1 -0
- data/lib/gitlab/dangerfiles/weightage/reviewers.rb +1 -0
- metadata +12 -8
- data/lib/danger/helper.rb +0 -311
@@ -1,9 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "
|
4
|
-
require_relative "
|
5
|
-
require_relative "
|
6
|
-
require_relative "../gitlab/dangerfiles/weightage/reviewers"
|
3
|
+
require_relative "../../gitlab/dangerfiles/teammate"
|
4
|
+
require_relative "../../gitlab/dangerfiles/weightage/maintainers"
|
5
|
+
require_relative "../../gitlab/dangerfiles/weightage/reviewers"
|
7
6
|
|
8
7
|
module Danger
|
9
8
|
# Common helper functions for our danger scripts. See Danger::Helper
|
@@ -17,13 +16,21 @@ module Danger
|
|
17
16
|
}.freeze
|
18
17
|
|
19
18
|
Spin = Struct.new(:category, :reviewer, :maintainer, :optional_role, :timezone_experiment)
|
19
|
+
HTTPError = Class.new(StandardError)
|
20
20
|
|
21
|
+
# Finds the +Gitlab::Dangerfiles::Teammate+ object whose username matches the MR author username.
|
22
|
+
#
|
23
|
+
# @return [Gitlab::Dangerfiles::Teammate]
|
21
24
|
def team_mr_author
|
22
|
-
|
25
|
+
company_members.find { |person| person.username == helper.mr_author }
|
23
26
|
end
|
24
27
|
|
25
28
|
# Assigns GitLab team members to be reviewer and maintainer
|
26
|
-
# for
|
29
|
+
# for the given +categories+.
|
30
|
+
#
|
31
|
+
# @param project [String] A project path.
|
32
|
+
# @param categories [Array<Symbol>] An array of categories symbols.
|
33
|
+
# @param timezone_experiment [Boolean] Whether to select reviewers based in timezone or not.
|
27
34
|
#
|
28
35
|
# @return [Array<Spin>]
|
29
36
|
def spin(project, categories = [nil], timezone_experiment: false)
|
@@ -34,6 +41,7 @@ module Danger
|
|
34
41
|
end
|
35
42
|
|
36
43
|
backend_spin = spins.find { |spin| spin.category == :backend }
|
44
|
+
frontend_spin = spins.find { |spin| spin.category == :frontend }
|
37
45
|
|
38
46
|
spins.each do |spin|
|
39
47
|
including_timezone = INCLUDE_TIMEZONE_FOR_CATEGORY.fetch(spin.category, timezone_experiment)
|
@@ -60,81 +68,37 @@ module Danger
|
|
60
68
|
# Fetch an already picked backend maintainer, or pick one otherwise
|
61
69
|
spin.maintainer = backend_spin&.maintainer || spin_for_category(project, :backend, timezone_experiment: including_timezone).maintainer
|
62
70
|
end
|
71
|
+
when :product_intelligence
|
72
|
+
spin.optional_role = :maintainer
|
73
|
+
|
74
|
+
if spin.maintainer.nil?
|
75
|
+
# Fetch an already picked maintainer, or pick one otherwise
|
76
|
+
spin.maintainer = backend_spin&.maintainer || frontend_spin&.maintainer || spin_for_category(project, :backend, timezone_experiment: including_timezone).maintainer
|
77
|
+
end
|
63
78
|
end
|
64
79
|
end
|
65
80
|
|
66
81
|
spins
|
67
82
|
end
|
68
83
|
|
69
|
-
# Looks up the current list of GitLab team members and parses it into a
|
70
|
-
# useful form
|
71
|
-
#
|
72
|
-
# @return [Array<Teammate>]
|
73
|
-
def team
|
74
|
-
@team ||= begin
|
75
|
-
data = helper.http_get_json(ROULETTE_DATA_URL)
|
76
|
-
data.map { |hash| Gitlab::Dangerfiles::Teammate.new(hash) }
|
77
|
-
rescue JSON::ParserError
|
78
|
-
raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}"
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
# Like +team+, but only returns teammates in the current project, based on
|
83
|
-
# project_name.
|
84
|
-
#
|
85
|
-
# @return [Array<Teammate>]
|
86
|
-
def project_team(project_name)
|
87
|
-
team.select { |member| member.in_project?(project_name) }
|
88
|
-
rescue => err
|
89
|
-
warn("Reviewer roulette failed to load team data: #{err.message}")
|
90
|
-
[]
|
91
|
-
end
|
92
|
-
|
93
|
-
# Known issue: If someone is rejected due to OOO, and then becomes not OOO, the
|
94
|
-
# selection will change on next spin
|
95
|
-
# @param [Array<Teammate>] people
|
96
|
-
def spin_for_person(people, random:, timezone_experiment: false)
|
97
|
-
shuffled_people = people.shuffle(random: random)
|
98
|
-
|
99
|
-
if timezone_experiment
|
100
|
-
shuffled_people.find(&method(:valid_person_with_timezone?))
|
101
|
-
else
|
102
|
-
shuffled_people.find(&method(:valid_person?))
|
103
|
-
end
|
104
|
-
end
|
105
|
-
|
106
84
|
private
|
107
85
|
|
108
|
-
# @param [Teammate] person
|
86
|
+
# @param [Gitlab::Dangerfiles::Teammate] person
|
109
87
|
# @return [Boolean]
|
110
88
|
def valid_person?(person)
|
111
89
|
!mr_author?(person) && person.available
|
112
90
|
end
|
113
91
|
|
114
|
-
# @param [Teammate] person
|
92
|
+
# @param [Gitlab::Dangerfiles::Teammate] person
|
115
93
|
# @return [Boolean]
|
116
94
|
def valid_person_with_timezone?(person)
|
117
95
|
valid_person?(person) && HOURS_WHEN_PERSON_CAN_BE_PICKED.cover?(person.local_hour)
|
118
96
|
end
|
119
97
|
|
120
|
-
# @param [Teammate] person
|
98
|
+
# @param [Gitlab::Dangerfiles::Teammate] person
|
121
99
|
# @return [Boolean]
|
122
100
|
def mr_author?(person)
|
123
|
-
person.username ==
|
124
|
-
end
|
125
|
-
|
126
|
-
def mr_author_username
|
127
|
-
helper.gitlab_helper&.mr_author || `whoami`
|
128
|
-
end
|
129
|
-
|
130
|
-
def mr_source_branch
|
131
|
-
return `git rev-parse --abbrev-ref HEAD` unless helper.gitlab_helper&.mr_json
|
132
|
-
|
133
|
-
helper.gitlab_helper.mr_json["source_branch"]
|
134
|
-
end
|
135
|
-
|
136
|
-
def mr_labels
|
137
|
-
helper.gitlab_helper&.mr_labels || []
|
101
|
+
person.username == helper.mr_author
|
138
102
|
end
|
139
103
|
|
140
104
|
def new_random(seed)
|
@@ -143,7 +107,23 @@ module Danger
|
|
143
107
|
|
144
108
|
def spin_role_for_category(team, role, project, category)
|
145
109
|
team.select do |member|
|
146
|
-
member.public_send("#{role}?", project, category, mr_labels) # rubocop:disable GitlabSecurity/PublicSend
|
110
|
+
member.public_send("#{role}?", project, category, helper.mr_labels) # rubocop:disable GitlabSecurity/PublicSend
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
# Known issue: If someone is rejected due to OOO, and then becomes not OOO, the
|
115
|
+
# selection will change on next spin.
|
116
|
+
#
|
117
|
+
# @param [Array<Gitlab::Dangerfiles::Teammate>] people
|
118
|
+
#
|
119
|
+
# @return [Gitlab::Dangerfiles::Teammate]
|
120
|
+
def spin_for_person(people, random:, timezone_experiment: false)
|
121
|
+
shuffled_people = people.shuffle(random: random)
|
122
|
+
|
123
|
+
if timezone_experiment
|
124
|
+
shuffled_people.find(&method(:valid_person_with_timezone?))
|
125
|
+
else
|
126
|
+
shuffled_people.find(&method(:valid_person?))
|
147
127
|
end
|
148
128
|
end
|
149
129
|
|
@@ -154,7 +134,7 @@ module Danger
|
|
154
134
|
spin_role_for_category(team, role, project, category)
|
155
135
|
end
|
156
136
|
|
157
|
-
random = new_random(mr_source_branch)
|
137
|
+
random = new_random(helper.mr_source_branch)
|
158
138
|
|
159
139
|
weighted_reviewers = Gitlab::Dangerfiles::Weightage::Reviewers.new(reviewers, traintainers).execute
|
160
140
|
weighted_maintainers = Gitlab::Dangerfiles::Weightage::Maintainers.new(maintainers).execute
|
@@ -164,5 +144,44 @@ module Danger
|
|
164
144
|
|
165
145
|
Spin.new(category, reviewer, maintainer, false, timezone_experiment)
|
166
146
|
end
|
147
|
+
|
148
|
+
# Fetches the given +url+ and parse its response as JSON.
|
149
|
+
#
|
150
|
+
# @param [String] url
|
151
|
+
#
|
152
|
+
# @return [Hash, Array]
|
153
|
+
def http_get_json(url)
|
154
|
+
rsp = Net::HTTP.get_response(URI.parse(url))
|
155
|
+
|
156
|
+
unless rsp.is_a?(Net::HTTPOK)
|
157
|
+
raise HTTPError, "Failed to read #{url}: #{rsp.code} #{rsp.message}"
|
158
|
+
end
|
159
|
+
|
160
|
+
JSON.parse(rsp.body)
|
161
|
+
end
|
162
|
+
|
163
|
+
# Looks up the current list of GitLab team members and parses it into a
|
164
|
+
# useful form.
|
165
|
+
#
|
166
|
+
# @return [Array<Gitlab::Dangerfiles::Teammate>]
|
167
|
+
def company_members
|
168
|
+
@company_members ||= begin
|
169
|
+
data = http_get_json(ROULETTE_DATA_URL)
|
170
|
+
data.map { |hash| Gitlab::Dangerfiles::Teammate.new(hash) }
|
171
|
+
rescue JSON::ParserError
|
172
|
+
raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}"
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Like +team+, but only returns teammates in the current project, based on
|
177
|
+
# project_name.
|
178
|
+
#
|
179
|
+
# @return [Array<Gitlab::Dangerfiles::Teammate>]
|
180
|
+
def project_team(project_name)
|
181
|
+
company_members.select { |member| member.in_project?(project_name) }
|
182
|
+
rescue => err
|
183
|
+
warn("Reviewer roulette failed to load team data: #{err.message}")
|
184
|
+
[]
|
185
|
+
end
|
167
186
|
end
|
168
187
|
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
thresholds = helper.config.code_size_thresholds
|
4
|
+
lines_changed = git.lines_of_code
|
5
|
+
|
6
|
+
if lines_changed > thresholds[:high]
|
7
|
+
warn "This merge request is definitely too big (#{lines_changed} lines changed), please split it into multiple merge requests."
|
8
|
+
elsif lines_changed > thresholds[:medium]
|
9
|
+
warn "This merge request is quite big (#{lines_changed} lines changed), please consider splitting it into multiple merge requests."
|
10
|
+
end
|
@@ -0,0 +1,139 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "../../../gitlab/dangerfiles/commit_linter"
|
4
|
+
require_relative "../../../gitlab/dangerfiles/merge_request_linter"
|
5
|
+
|
6
|
+
COMMIT_MESSAGE_GUIDELINES = "https://docs.gitlab.com/ee/development/contributing/merge_request_workflow.html#commit-messages-guidelines"
|
7
|
+
MORE_INFO = "For more information, take a look at our [Commit message guidelines](#{COMMIT_MESSAGE_GUIDELINES})."
|
8
|
+
THE_DANGER_JOB_TEXT = "the `%<job_name>` job"
|
9
|
+
MAX_COMMITS_COUNT_EXCEEDED_MESSAGE = <<~MSG
|
10
|
+
This merge request includes more than %<max_commits_count>d commits. Each commit should meet the following criteria:
|
11
|
+
|
12
|
+
1. Have a well-written commit message.
|
13
|
+
1. Has all tests passing when used on its own (e.g. when using git checkout SHA).
|
14
|
+
1. Can be reverted on its own without also requiring the revert of commit that came before it.
|
15
|
+
1. Is small enough that it can be reviewed in isolation in under 30 minutes or so.
|
16
|
+
|
17
|
+
If this merge request contains commits that do not meet this criteria and/or contains intermediate work, please rebase these commits into a smaller number of commits or split this merge request into multiple smaller merge requests.
|
18
|
+
MSG
|
19
|
+
|
20
|
+
max_commits_count = helper.config.max_commits_count
|
21
|
+
|
22
|
+
def fail_commit(commit, message, more_info: true)
|
23
|
+
self.fail(build_message(commit, message, more_info: more_info))
|
24
|
+
end
|
25
|
+
|
26
|
+
def warn_commit(commit, message, more_info: true)
|
27
|
+
self.warn(build_message(commit, message, more_info: more_info))
|
28
|
+
end
|
29
|
+
|
30
|
+
def build_message(commit, message, more_info: true)
|
31
|
+
[message].tap do |full_message|
|
32
|
+
full_message << ". #{MORE_INFO}" if more_info
|
33
|
+
full_message.unshift("#{commit.sha}: ") if commit.sha
|
34
|
+
end.join
|
35
|
+
end
|
36
|
+
|
37
|
+
def danger_job_link
|
38
|
+
helper.ci? ? "[#{format(THE_DANGER_JOB_TEXT, job_name: ENV["CI_JOB_NAME"])}](#{ENV['CI_JOB_URL']})" : THE_DANGER_JOB_TEXT
|
39
|
+
end
|
40
|
+
|
41
|
+
# Perform various checks against commits. We're not using
|
42
|
+
# https://github.com/jonallured/danger-commit_lint because its output is not
|
43
|
+
# very helpful, and it doesn't offer the means of ignoring merge commits.
|
44
|
+
def lint_commit(commit)
|
45
|
+
linter = Gitlab::Dangerfiles::CommitLinter.new(commit)
|
46
|
+
|
47
|
+
# For now we'll ignore merge commits, as getting rid of those is a problem
|
48
|
+
# separate from enforcing good commit messages.
|
49
|
+
return linter if linter.merge?
|
50
|
+
|
51
|
+
# We ignore revert commits as they are well structured by Git already
|
52
|
+
return linter if linter.revert?
|
53
|
+
|
54
|
+
# If MR is set to squash, we ignore fixup commits
|
55
|
+
return linter if linter.fixup? && helper.squash_mr?
|
56
|
+
|
57
|
+
if linter.fixup?
|
58
|
+
msg = "Squash or fixup commits must be squashed before merge, or enable squash merge option and re-run #{danger_job_link}."
|
59
|
+
if helper.draft_mr? || helper.squash_mr?
|
60
|
+
warn_commit(commit, msg, more_info: false)
|
61
|
+
else
|
62
|
+
fail_commit(commit, msg, more_info: false)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Makes no sense to process other rules for fixup commits, they trigger just more noise
|
66
|
+
return linter
|
67
|
+
end
|
68
|
+
|
69
|
+
# Fail if a suggestion commit is used and squash is not enabled
|
70
|
+
if linter.suggestion?
|
71
|
+
unless helper.squash_mr?
|
72
|
+
fail_commit(commit, "If you are applying suggestions, enable squash in the merge request and re-run #{danger_job_link}.", more_info: false)
|
73
|
+
end
|
74
|
+
|
75
|
+
return linter
|
76
|
+
end
|
77
|
+
|
78
|
+
linter.lint
|
79
|
+
end
|
80
|
+
|
81
|
+
def lint_mr_title(mr_title)
|
82
|
+
commit = Struct.new(:message, :sha).new(mr_title)
|
83
|
+
|
84
|
+
Gitlab::Dangerfiles::MergeRequestLinter.new(commit).lint
|
85
|
+
end
|
86
|
+
|
87
|
+
def count_non_fixup_commits(commit_linters)
|
88
|
+
commit_linters.count { |commit_linter| !commit_linter.fixup? }
|
89
|
+
end
|
90
|
+
|
91
|
+
def lint_commits(commits)
|
92
|
+
commit_linters = commits.map { |commit| lint_commit(commit) }
|
93
|
+
|
94
|
+
if helper.squash_mr?
|
95
|
+
multi_line_commit_linter = commit_linters.detect { |commit_linter| !commit_linter.merge? && commit_linter.multi_line? }
|
96
|
+
|
97
|
+
if multi_line_commit_linter && multi_line_commit_linter.failed?
|
98
|
+
warn_or_fail_commits(multi_line_commit_linter)
|
99
|
+
commit_linters.delete(multi_line_commit_linter) # Don't show an error (here) and a warning (below)
|
100
|
+
elsif helper.ci? # We don't have access to the MR title locally
|
101
|
+
title_linter = lint_mr_title(gitlab.mr_json['title'])
|
102
|
+
if title_linter.failed?
|
103
|
+
warn_or_fail_commits(title_linter)
|
104
|
+
end
|
105
|
+
end
|
106
|
+
else
|
107
|
+
if count_non_fixup_commits(commit_linters) > max_commits_count
|
108
|
+
self.warn(format(MAX_COMMITS_COUNT_EXCEEDED_MESSAGE, max_commits_count: max_commits_count))
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
failed_commit_linters = commit_linters.select { |commit_linter| commit_linter.failed? }
|
113
|
+
warn_or_fail_commits(failed_commit_linters, default_to_fail: !helper.squash_mr?)
|
114
|
+
end
|
115
|
+
|
116
|
+
def warn_or_fail_commits(failed_linters, default_to_fail: true)
|
117
|
+
level = default_to_fail ? :fail : :warn
|
118
|
+
|
119
|
+
Array(failed_linters).each do |linter|
|
120
|
+
linter.problems.each do |problem_key, problem_desc|
|
121
|
+
case problem_key
|
122
|
+
when :subject_too_short, :subject_above_warning, :details_too_many_changes, :details_line_too_long
|
123
|
+
warn_commit(linter.commit, problem_desc)
|
124
|
+
else
|
125
|
+
self.__send__("#{level}_commit", linter.commit, problem_desc) # rubocop:disable GitlabSecurity/PublicSend
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
# As part of https://gitlab.com/groups/gitlab-org/-/epics/4826 we are
|
132
|
+
# vendoring workhorse commits from the stand-alone gitlab-workhorse
|
133
|
+
# repo. There is no point in linting commits that we want to vendor as
|
134
|
+
# is.
|
135
|
+
def workhorse_changes?
|
136
|
+
git.diff.any? { |file| file.path.start_with?('workhorse/') }
|
137
|
+
end
|
138
|
+
|
139
|
+
lint_commits(git.commits) unless workhorse_changes?
|
data/lib/gitlab/dangerfiles.rb
CHANGED
@@ -2,8 +2,88 @@ require "gitlab/dangerfiles/version"
|
|
2
2
|
|
3
3
|
module Gitlab
|
4
4
|
module Dangerfiles
|
5
|
-
|
6
|
-
|
5
|
+
RULES_DIR = File.expand_path("../danger/rules", __dir__)
|
6
|
+
EXISTING_RULES = Dir.glob(File.join(RULES_DIR, "*")).each_with_object([]) do |path, memo|
|
7
|
+
if File.directory?(path)
|
8
|
+
memo << File.basename(path)
|
9
|
+
end
|
10
|
+
end
|
11
|
+
LOCAL_RULES = %w[
|
12
|
+
changes_size
|
13
|
+
commit_messages
|
14
|
+
].freeze
|
15
|
+
CI_ONLY_RULES = %w[
|
16
|
+
].freeze
|
17
|
+
|
18
|
+
# This class provides utility methods to import plugins and dangerfiles easily.
|
19
|
+
class Engine
|
20
|
+
# @param dangerfile [Danger::Dangerfile] A +Danger::Dangerfile+ object.
|
21
|
+
#
|
22
|
+
# @example
|
23
|
+
# # In your main Dangerfile:
|
24
|
+
# dangerfiles = Gitlab::Dangerfiles::Engine.new(self)
|
25
|
+
#
|
26
|
+
# @return [Gitlab::Dangerfiles::Engine]
|
27
|
+
def initialize(dangerfile)
|
28
|
+
@dangerfile = dangerfile
|
29
|
+
end
|
30
|
+
|
31
|
+
# Import all available plugins.
|
32
|
+
#
|
33
|
+
# @example
|
34
|
+
# # In your main Dangerfile:
|
35
|
+
# dangerfiles = Gitlab::Dangerfiles::Engine.new(self)
|
36
|
+
#
|
37
|
+
# # Import all plugins
|
38
|
+
# dangerfiles.import_plugins
|
39
|
+
def import_plugins
|
40
|
+
danger_plugin.import_plugin(File.expand_path("../danger/plugins/*.rb", __dir__))
|
41
|
+
end
|
42
|
+
|
43
|
+
# Import available Dangerfiles.
|
44
|
+
#
|
45
|
+
# @param rules [Symbol, Array<String>] Can be either +:all+ (default) to import all rules,
|
46
|
+
# or an array of rules.
|
47
|
+
# Available rules are: +changes_size+.
|
48
|
+
#
|
49
|
+
# @example
|
50
|
+
# # In your main Dangerfile:
|
51
|
+
# dangerfiles = Gitlab::Dangerfiles::Engine.new(self)
|
52
|
+
#
|
53
|
+
# # Import all rules
|
54
|
+
# dangerfiles.import_dangerfiles
|
55
|
+
#
|
56
|
+
# # Or import only a subset of rules
|
57
|
+
# dangerfiles.import_dangerfiles(rules: %w[changes_size])
|
58
|
+
def import_dangerfiles(rules: :all)
|
59
|
+
filtered_rules(rules).each do |rule|
|
60
|
+
danger_plugin.import_dangerfile(path: File.join(RULES_DIR, rule))
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
attr_reader :dangerfile
|
67
|
+
|
68
|
+
def allowed_rules
|
69
|
+
return LOCAL_RULES unless helper_plugin.respond_to?(:ci?)
|
70
|
+
|
71
|
+
helper_plugin.ci? ? LOCAL_RULES | CI_ONLY_RULES : LOCAL_RULES
|
72
|
+
end
|
73
|
+
|
74
|
+
def filtered_rules(rules)
|
75
|
+
rules = EXISTING_RULES if rules == :all
|
76
|
+
|
77
|
+
Array(rules).map(&:to_s) & EXISTING_RULES & allowed_rules
|
78
|
+
end
|
79
|
+
|
80
|
+
def danger_plugin
|
81
|
+
@danger_plugin ||= dangerfile.plugins[Danger::DangerfileDangerPlugin]
|
82
|
+
end
|
83
|
+
|
84
|
+
def helper_plugin
|
85
|
+
@helper_plugin ||= dangerfile.plugins[Danger::Helper]
|
86
|
+
end
|
7
87
|
end
|
8
88
|
end
|
9
89
|
end
|