gitlab-dangerfiles 1.1.1 → 2.1.3
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 +190 -88
- data/lib/danger/plugins/roulette.rb +75 -43
- data/lib/danger/rules/changes_size/Dangerfile +10 -0
- data/lib/danger/rules/commit_messages/Dangerfile +138 -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/teammate.rb +0 -5
- 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 +10 -6
@@ -16,23 +16,32 @@ module Danger
|
|
16
16
|
}.freeze
|
17
17
|
|
18
18
|
Spin = Struct.new(:category, :reviewer, :maintainer, :optional_role, :timezone_experiment)
|
19
|
+
HTTPError = Class.new(StandardError)
|
19
20
|
|
21
|
+
# Finds the +Gitlab::Dangerfiles::Teammate+ object whose username matches the MR author username.
|
22
|
+
#
|
23
|
+
# @return [Gitlab::Dangerfiles::Teammate]
|
20
24
|
def team_mr_author
|
21
|
-
|
25
|
+
company_members.find { |person| person.username == helper.mr_author }
|
22
26
|
end
|
23
27
|
|
24
28
|
# Assigns GitLab team members to be reviewer and maintainer
|
25
|
-
# 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.
|
26
34
|
#
|
27
35
|
# @return [Array<Spin>]
|
28
36
|
def spin(project, categories = [nil], timezone_experiment: false)
|
29
|
-
spins = categories.
|
37
|
+
spins = categories.sort_by(&:to_s).map do |category|
|
30
38
|
including_timezone = INCLUDE_TIMEZONE_FOR_CATEGORY.fetch(category, timezone_experiment)
|
31
39
|
|
32
40
|
spin_for_category(project, category, timezone_experiment: including_timezone)
|
33
41
|
end
|
34
42
|
|
35
43
|
backend_spin = spins.find { |spin| spin.category == :backend }
|
44
|
+
frontend_spin = spins.find { |spin| spin.category == :frontend }
|
36
45
|
|
37
46
|
spins.each do |spin|
|
38
47
|
including_timezone = INCLUDE_TIMEZONE_FOR_CATEGORY.fetch(spin.category, timezone_experiment)
|
@@ -61,64 +70,32 @@ module Danger
|
|
61
70
|
end
|
62
71
|
when :product_intelligence
|
63
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
|
64
78
|
end
|
65
79
|
end
|
66
80
|
|
67
81
|
spins
|
68
82
|
end
|
69
83
|
|
70
|
-
# Looks up the current list of GitLab team members and parses it into a
|
71
|
-
# useful form
|
72
|
-
#
|
73
|
-
# @return [Array<Teammate>]
|
74
|
-
def team
|
75
|
-
@team ||= begin
|
76
|
-
data = helper.http_get_json(ROULETTE_DATA_URL)
|
77
|
-
data.map { |hash| Gitlab::Dangerfiles::Teammate.new(hash) }
|
78
|
-
rescue JSON::ParserError
|
79
|
-
raise "Failed to parse JSON response from #{ROULETTE_DATA_URL}"
|
80
|
-
end
|
81
|
-
end
|
82
|
-
|
83
|
-
# Like +team+, but only returns teammates in the current project, based on
|
84
|
-
# project_name.
|
85
|
-
#
|
86
|
-
# @return [Array<Teammate>]
|
87
|
-
def project_team(project_name)
|
88
|
-
team.select { |member| member.in_project?(project_name) }
|
89
|
-
rescue => err
|
90
|
-
warn("Reviewer roulette failed to load team data: #{err.message}")
|
91
|
-
[]
|
92
|
-
end
|
93
|
-
|
94
|
-
# Known issue: If someone is rejected due to OOO, and then becomes not OOO, the
|
95
|
-
# selection will change on next spin
|
96
|
-
# @param [Array<Teammate>] people
|
97
|
-
def spin_for_person(people, random:, timezone_experiment: false)
|
98
|
-
shuffled_people = people.shuffle(random: random)
|
99
|
-
|
100
|
-
if timezone_experiment
|
101
|
-
shuffled_people.find(&method(:valid_person_with_timezone?))
|
102
|
-
else
|
103
|
-
shuffled_people.find(&method(:valid_person?))
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
84
|
private
|
108
85
|
|
109
|
-
# @param [Teammate] person
|
86
|
+
# @param [Gitlab::Dangerfiles::Teammate] person
|
110
87
|
# @return [Boolean]
|
111
88
|
def valid_person?(person)
|
112
89
|
!mr_author?(person) && person.available
|
113
90
|
end
|
114
91
|
|
115
|
-
# @param [Teammate] person
|
92
|
+
# @param [Gitlab::Dangerfiles::Teammate] person
|
116
93
|
# @return [Boolean]
|
117
94
|
def valid_person_with_timezone?(person)
|
118
95
|
valid_person?(person) && HOURS_WHEN_PERSON_CAN_BE_PICKED.cover?(person.local_hour)
|
119
96
|
end
|
120
97
|
|
121
|
-
# @param [Teammate] person
|
98
|
+
# @param [Gitlab::Dangerfiles::Teammate] person
|
122
99
|
# @return [Boolean]
|
123
100
|
def mr_author?(person)
|
124
101
|
person.username == helper.mr_author
|
@@ -134,6 +111,22 @@ module Danger
|
|
134
111
|
end
|
135
112
|
end
|
136
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?))
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
137
130
|
def spin_for_category(project, category, timezone_experiment: false)
|
138
131
|
team = project_team(project)
|
139
132
|
reviewers, traintainers, maintainers =
|
@@ -151,5 +144,44 @@ module Danger
|
|
151
144
|
|
152
145
|
Spin.new(category, reviewer, maintainer, false, timezone_experiment)
|
153
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
|
154
186
|
end
|
155
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,138 @@
|
|
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 = helper.config.max_commits_count
|
10
|
+
MAX_COMMITS_COUNT_EXCEEDED_MESSAGE = <<~MSG
|
11
|
+
This merge request includes more than %<max_commits_count>d commits. Each commit should meet the following criteria:
|
12
|
+
|
13
|
+
1. Have a well-written commit message.
|
14
|
+
1. Has all tests passing when used on its own (e.g. when using git checkout SHA).
|
15
|
+
1. Can be reverted on its own without also requiring the revert of commit that came before it.
|
16
|
+
1. Is small enough that it can be reviewed in isolation in under 30 minutes or so.
|
17
|
+
|
18
|
+
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.
|
19
|
+
MSG
|
20
|
+
|
21
|
+
def fail_commit(commit, message, more_info: true)
|
22
|
+
self.fail(build_message(commit, message, more_info: more_info))
|
23
|
+
end
|
24
|
+
|
25
|
+
def warn_commit(commit, message, more_info: true)
|
26
|
+
self.warn(build_message(commit, message, more_info: more_info))
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_message(commit, message, more_info: true)
|
30
|
+
[message].tap do |full_message|
|
31
|
+
full_message << ". #{MORE_INFO}" if more_info
|
32
|
+
full_message.unshift("#{commit.sha}: ") if commit.sha
|
33
|
+
end.join
|
34
|
+
end
|
35
|
+
|
36
|
+
def danger_job_link
|
37
|
+
helper.ci? ? "[#{format(THE_DANGER_JOB_TEXT, job_name: ENV["CI_JOB_NAME"])}](#{ENV['CI_JOB_URL']})" : THE_DANGER_JOB_TEXT
|
38
|
+
end
|
39
|
+
|
40
|
+
# Perform various checks against commits. We're not using
|
41
|
+
# https://github.com/jonallured/danger-commit_lint because its output is not
|
42
|
+
# very helpful, and it doesn't offer the means of ignoring merge commits.
|
43
|
+
def lint_commit(commit)
|
44
|
+
linter = Gitlab::Dangerfiles::CommitLinter.new(commit)
|
45
|
+
|
46
|
+
# For now we'll ignore merge commits, as getting rid of those is a problem
|
47
|
+
# separate from enforcing good commit messages.
|
48
|
+
return linter if linter.merge?
|
49
|
+
|
50
|
+
# We ignore revert commits as they are well structured by Git already
|
51
|
+
return linter if linter.revert?
|
52
|
+
|
53
|
+
# If MR is set to squash, we ignore fixup commits
|
54
|
+
return linter if linter.fixup? && helper.squash_mr?
|
55
|
+
|
56
|
+
if linter.fixup?
|
57
|
+
msg = "Squash or fixup commits must be squashed before merge, or enable squash merge option and re-run #{danger_job_link}."
|
58
|
+
if helper.draft_mr? || helper.squash_mr?
|
59
|
+
warn_commit(commit, msg, more_info: false)
|
60
|
+
else
|
61
|
+
fail_commit(commit, msg, more_info: false)
|
62
|
+
end
|
63
|
+
|
64
|
+
# Makes no sense to process other rules for fixup commits, they trigger just more noise
|
65
|
+
return linter
|
66
|
+
end
|
67
|
+
|
68
|
+
# Fail if a suggestion commit is used and squash is not enabled
|
69
|
+
if linter.suggestion?
|
70
|
+
unless helper.squash_mr?
|
71
|
+
fail_commit(commit, "If you are applying suggestions, enable squash in the merge request and re-run #{danger_job_link}.", more_info: false)
|
72
|
+
end
|
73
|
+
|
74
|
+
return linter
|
75
|
+
end
|
76
|
+
|
77
|
+
linter.lint
|
78
|
+
end
|
79
|
+
|
80
|
+
def lint_mr_title(mr_title)
|
81
|
+
commit = Struct.new(:message, :sha).new(mr_title)
|
82
|
+
|
83
|
+
Gitlab::Dangerfiles::MergeRequestLinter.new(commit).lint
|
84
|
+
end
|
85
|
+
|
86
|
+
def count_non_fixup_commits(commit_linters)
|
87
|
+
commit_linters.count { |commit_linter| !commit_linter.fixup? }
|
88
|
+
end
|
89
|
+
|
90
|
+
def lint_commits(commits)
|
91
|
+
commit_linters = commits.map { |commit| lint_commit(commit) }
|
92
|
+
|
93
|
+
if helper.squash_mr?
|
94
|
+
multi_line_commit_linter = commit_linters.detect { |commit_linter| !commit_linter.merge? && commit_linter.multi_line? }
|
95
|
+
|
96
|
+
if multi_line_commit_linter && multi_line_commit_linter.failed?
|
97
|
+
warn_or_fail_commits(multi_line_commit_linter)
|
98
|
+
commit_linters.delete(multi_line_commit_linter) # Don't show an error (here) and a warning (below)
|
99
|
+
elsif helper.ci? # We don't have access to the MR title locally
|
100
|
+
title_linter = lint_mr_title(gitlab.mr_json['title'])
|
101
|
+
if title_linter.failed?
|
102
|
+
warn_or_fail_commits(title_linter)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
else
|
106
|
+
if count_non_fixup_commits(commit_linters) > MAX_COMMITS_COUNT
|
107
|
+
self.warn(format(MAX_COMMITS_COUNT_EXCEEDED_MESSAGE, max_commits_count: MAX_COMMITS_COUNT))
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
failed_commit_linters = commit_linters.select { |commit_linter| commit_linter.failed? }
|
112
|
+
warn_or_fail_commits(failed_commit_linters, default_to_fail: !helper.squash_mr?)
|
113
|
+
end
|
114
|
+
|
115
|
+
def warn_or_fail_commits(failed_linters, default_to_fail: true)
|
116
|
+
level = default_to_fail ? :fail : :warn
|
117
|
+
|
118
|
+
Array(failed_linters).each do |linter|
|
119
|
+
linter.problems.each do |problem_key, problem_desc|
|
120
|
+
case problem_key
|
121
|
+
when :subject_too_short, :subject_above_warning, :details_too_many_changes, :details_line_too_long
|
122
|
+
warn_commit(linter.commit, problem_desc)
|
123
|
+
else
|
124
|
+
self.__send__("#{level}_commit", linter.commit, problem_desc) # rubocop:disable GitlabSecurity/PublicSend
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# As part of https://gitlab.com/groups/gitlab-org/-/epics/4826 we are
|
131
|
+
# vendoring workhorse commits from the stand-alone gitlab-workhorse
|
132
|
+
# repo. There is no point in linting commits that we want to vendor as
|
133
|
+
# is.
|
134
|
+
def workhorse_changes?
|
135
|
+
git.diff.any? { |file| file.path.start_with?('workhorse/') }
|
136
|
+
end
|
137
|
+
|
138
|
+
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
|
@@ -4,41 +4,63 @@ require_relative "title_linting"
|
|
4
4
|
|
5
5
|
module Gitlab
|
6
6
|
module Dangerfiles
|
7
|
+
# @!attribute file
|
8
|
+
# @return [String] the file name that's changed.
|
9
|
+
# @!attribute change_type
|
10
|
+
# @return [Symbol] the type of change (+:added+, +:modified+, +:deleted+, +:renamed_before+, +:renamed_after+).
|
11
|
+
# @!attribute category
|
12
|
+
# @return [Symbol] the category of the change.
|
13
|
+
# This is defined by consumers of the gem through +helper.changes_by_category+ or +helper.changes+.
|
7
14
|
Change = Struct.new(:file, :change_type, :category)
|
8
15
|
|
9
16
|
class Changes < ::SimpleDelegator
|
17
|
+
# Return an +Gitlab::Dangerfiles::Changes+ object with only the changes for the added files.
|
18
|
+
#
|
19
|
+
# @return [Gitlab::Dangerfiles::Changes]
|
10
20
|
def added
|
11
21
|
select_by_change_type(:added)
|
12
22
|
end
|
13
23
|
|
24
|
+
# @return [Gitlab::Dangerfiles::Changes] the changes for the modified files.
|
14
25
|
def modified
|
15
26
|
select_by_change_type(:modified)
|
16
27
|
end
|
17
28
|
|
29
|
+
# @return [Gitlab::Dangerfiles::Changes] the changes for the deleted files.
|
18
30
|
def deleted
|
19
31
|
select_by_change_type(:deleted)
|
20
32
|
end
|
21
33
|
|
34
|
+
# @return [Gitlab::Dangerfiles::Changes] the changes for the renamed files (before the rename).
|
22
35
|
def renamed_before
|
23
36
|
select_by_change_type(:renamed_before)
|
24
37
|
end
|
25
38
|
|
39
|
+
# @return [Gitlab::Dangerfiles::Changes] the changes for the renamed files (after the rename).
|
26
40
|
def renamed_after
|
27
41
|
select_by_change_type(:renamed_after)
|
28
42
|
end
|
29
43
|
|
44
|
+
# @param category [Symbol] A category of change.
|
45
|
+
#
|
46
|
+
# @return [Boolean] whether there are any change for the given +category+.
|
30
47
|
def has_category?(category)
|
31
48
|
any? { |change| change.category == category }
|
32
49
|
end
|
33
50
|
|
51
|
+
# @param category [Symbol] a category of change.
|
52
|
+
#
|
53
|
+
# @return [Gitlab::Dangerfiles::Changes] changes for the given +category+.
|
34
54
|
def by_category(category)
|
35
55
|
Changes.new(select { |change| change.category == category })
|
36
56
|
end
|
37
57
|
|
58
|
+
# @return [Array<Symbol>] an array of the unique categories of changes.
|
38
59
|
def categories
|
39
60
|
map(&:category).uniq
|
40
61
|
end
|
41
62
|
|
63
|
+
# @return [Array<String>] an array of the changed files.
|
42
64
|
def files
|
43
65
|
map(&:file)
|
44
66
|
end
|