gitlab-dangerfiles 0.1.0 → 0.6.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/lib/danger/helper.rb +155 -113
- data/lib/danger/roulette.rb +68 -36
- data/lib/gitlab/dangerfiles.rb +0 -34
- data/lib/gitlab/dangerfiles/base_linter.rb +96 -0
- data/lib/gitlab/dangerfiles/commit_linter.rb +25 -103
- data/lib/gitlab/dangerfiles/merge_request_linter.rb +30 -0
- data/lib/gitlab/dangerfiles/teammate.rb +23 -9
- data/lib/gitlab/dangerfiles/title_linting.rb +38 -0
- data/lib/gitlab/dangerfiles/version.rb +1 -1
- data/lib/gitlab/dangerfiles/weightage.rb +10 -0
- data/lib/gitlab/dangerfiles/weightage/maintainers.rb +33 -0
- data/lib/gitlab/dangerfiles/weightage/reviewers.rb +65 -0
- metadata +9 -25
- data/lib/danger/changelog.rb +0 -39
- data/lib/danger/sidekiq_queues.rb +0 -37
- data/lib/gitlab/dangerfiles/bundle_size/Dangerfile +0 -38
- data/lib/gitlab/dangerfiles/ce_ee_vue_templates/Dangerfile +0 -56
- data/lib/gitlab/dangerfiles/changelog/Dangerfile +0 -90
- data/lib/gitlab/dangerfiles/changes_size/Dangerfile +0 -17
- data/lib/gitlab/dangerfiles/commit_messages/Dangerfile +0 -135
- data/lib/gitlab/dangerfiles/database/Dangerfile +0 -67
- data/lib/gitlab/dangerfiles/documentation/Dangerfile +0 -29
- data/lib/gitlab/dangerfiles/duplicate_yarn_dependencies/Dangerfile +0 -29
- data/lib/gitlab/dangerfiles/eslint/Dangerfile +0 -31
- data/lib/gitlab/dangerfiles/frozen_string/Dangerfile +0 -28
- data/lib/gitlab/dangerfiles/karma/Dangerfile +0 -51
- data/lib/gitlab/dangerfiles/metadata/Dangerfile +0 -50
- data/lib/gitlab/dangerfiles/popen.rb +0 -55
- data/lib/gitlab/dangerfiles/prettier/Dangerfile +0 -41
- data/lib/gitlab/dangerfiles/roulette/Dangerfile +0 -97
- data/lib/gitlab/dangerfiles/sidekiq_queues/Dangerfile +0 -27
- data/lib/gitlab/dangerfiles/specs/Dangerfile +0 -42
- data/lib/gitlab/dangerfiles/tasks.rb +0 -19
- data/lib/gitlab/dangerfiles/telemetry/Dangerfile +0 -32
- data/lib/gitlab/dangerfiles/utility_css/Dangerfile +0 -51
data/lib/gitlab/dangerfiles.rb
CHANGED
@@ -2,39 +2,5 @@ require "gitlab/dangerfiles/version"
|
|
2
2
|
|
3
3
|
module Gitlab
|
4
4
|
module Dangerfiles
|
5
|
-
LOCAL_RULES ||= %w[
|
6
|
-
changes_size
|
7
|
-
commit_messages
|
8
|
-
database
|
9
|
-
documentation
|
10
|
-
duplicate_yarn_dependencies
|
11
|
-
eslint
|
12
|
-
frozen_string
|
13
|
-
karma
|
14
|
-
prettier
|
15
|
-
telemetry
|
16
|
-
utility_css
|
17
|
-
].freeze
|
18
|
-
CI_ONLY_RULES ||= %w[
|
19
|
-
ce_ee_vue_templates
|
20
|
-
changelog
|
21
|
-
metadata
|
22
|
-
roulette
|
23
|
-
sidekiq_queues
|
24
|
-
specs
|
25
|
-
].freeze
|
26
|
-
MESSAGE_PREFIX = "==>".freeze
|
27
|
-
|
28
|
-
def self.load_tasks
|
29
|
-
require "gitlab/dangerfiles/tasks"
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.local_warning_message
|
33
|
-
"#{MESSAGE_PREFIX} Only the following Danger rules can be run locally: #{LOCAL_RULES.join(", ")}"
|
34
|
-
end
|
35
|
-
|
36
|
-
def self.success_message
|
37
|
-
"#{MESSAGE_PREFIX} No Danger rule violations!"
|
38
|
-
end
|
39
5
|
end
|
40
6
|
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "title_linting"
|
4
|
+
|
5
|
+
module Gitlab
|
6
|
+
module Dangerfiles
|
7
|
+
class BaseLinter
|
8
|
+
MIN_SUBJECT_WORDS_COUNT = 3
|
9
|
+
MAX_LINE_LENGTH = 72
|
10
|
+
|
11
|
+
attr_reader :commit, :problems
|
12
|
+
|
13
|
+
def self.problems_mapping
|
14
|
+
{
|
15
|
+
subject_too_short: "The %s must contain at least #{MIN_SUBJECT_WORDS_COUNT} words",
|
16
|
+
subject_too_long: "The %s may not be longer than #{MAX_LINE_LENGTH} characters",
|
17
|
+
subject_starts_with_lowercase: "The %s must start with a capital letter",
|
18
|
+
subject_ends_with_a_period: "The %s must not end with a period",
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.subject_description
|
23
|
+
"commit subject"
|
24
|
+
end
|
25
|
+
|
26
|
+
def initialize(commit)
|
27
|
+
@commit = commit
|
28
|
+
@problems = {}
|
29
|
+
end
|
30
|
+
|
31
|
+
def failed?
|
32
|
+
problems.any?
|
33
|
+
end
|
34
|
+
|
35
|
+
def add_problem(problem_key, *args)
|
36
|
+
@problems[problem_key] = sprintf(self.class.problems_mapping[problem_key], *args)
|
37
|
+
end
|
38
|
+
|
39
|
+
def lint_subject
|
40
|
+
if subject_too_short?
|
41
|
+
add_problem(:subject_too_short, self.class.subject_description)
|
42
|
+
end
|
43
|
+
|
44
|
+
if subject_too_long?
|
45
|
+
add_problem(:subject_too_long, self.class.subject_description)
|
46
|
+
end
|
47
|
+
|
48
|
+
if subject_starts_with_lowercase?
|
49
|
+
add_problem(:subject_starts_with_lowercase, self.class.subject_description)
|
50
|
+
end
|
51
|
+
|
52
|
+
if subject_ends_with_a_period?
|
53
|
+
add_problem(:subject_ends_with_a_period, self.class.subject_description)
|
54
|
+
end
|
55
|
+
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def subject
|
62
|
+
TitleLinting.remove_draft_flag(message_parts[0])
|
63
|
+
end
|
64
|
+
|
65
|
+
def subject_too_short?
|
66
|
+
subject.split(" ").length < MIN_SUBJECT_WORDS_COUNT
|
67
|
+
end
|
68
|
+
|
69
|
+
def subject_too_long?
|
70
|
+
line_too_long?(subject)
|
71
|
+
end
|
72
|
+
|
73
|
+
def line_too_long?(line)
|
74
|
+
line.length > MAX_LINE_LENGTH
|
75
|
+
end
|
76
|
+
|
77
|
+
def subject_starts_with_lowercase?
|
78
|
+
return false if ("A".."Z").cover?(subject[0])
|
79
|
+
|
80
|
+
first_char = subject.sub(/\A(\[.+\]|\w+:)\s/, "")[0]
|
81
|
+
first_char_downcased = first_char.downcase
|
82
|
+
return true unless ("a".."z").cover?(first_char_downcased)
|
83
|
+
|
84
|
+
first_char.downcase == first_char
|
85
|
+
end
|
86
|
+
|
87
|
+
def subject_ends_with_a_period?
|
88
|
+
subject.end_with?(".")
|
89
|
+
end
|
90
|
+
|
91
|
+
def message_parts
|
92
|
+
@message_parts ||= commit.message.split("\n", 3)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -1,40 +1,33 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
defined?(Rails) ? require_dependency(emoji_checker_path) : require_relative(emoji_checker_path)
|
3
|
+
require_relative "emoji_checker"
|
5
4
|
|
6
5
|
module Gitlab
|
7
6
|
module Dangerfiles
|
8
7
|
class CommitLinter
|
9
|
-
MIN_SUBJECT_WORDS_COUNT = 3
|
10
|
-
MAX_LINE_LENGTH = 72
|
11
|
-
MAX_CHANGED_FILES_IN_COMMIT = 3
|
12
8
|
MAX_CHANGED_LINES_IN_COMMIT = 30
|
13
|
-
SHORT_REFERENCE_REGEX = %r{([\w\-\/]+)?(#|!|&|%)\d
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
}.freeze
|
32
|
-
|
33
|
-
attr_reader :commit, :problems
|
9
|
+
SHORT_REFERENCE_REGEX = %r{([\w\-\/]+)?(?<!`)(#|!|&|%)\d+(?<!`)}.freeze
|
10
|
+
|
11
|
+
def self.problems_mapping
|
12
|
+
super.merge(
|
13
|
+
{
|
14
|
+
separator_missing: "The commit subject and body must be separated by a blank line",
|
15
|
+
details_too_many_changes: "Commits that change #{MAX_CHANGED_LINES_IN_COMMIT} or more lines across " \
|
16
|
+
"at least #{MAX_CHANGED_FILES_IN_COMMIT} files must describe these changes in the commit body",
|
17
|
+
details_line_too_long: "The commit body should not contain more than #{MAX_LINE_LENGTH} characters per line",
|
18
|
+
message_contains_text_emoji: "Avoid the use of Markdown Emoji such as `:+1:`. These add limited value " \
|
19
|
+
"to the commit message, and are displayed as plain text outside of GitLab",
|
20
|
+
message_contains_unicode_emoji: "Avoid the use of Unicode Emoji. These add no value to the commit " \
|
21
|
+
"message, and may not be displayed properly everywhere",
|
22
|
+
message_contains_short_reference: "Use full URLs instead of short references (`gitlab-org/gitlab#123` or " \
|
23
|
+
"`!123`), as short references are displayed as plain text outside of GitLab",
|
24
|
+
}
|
25
|
+
)
|
26
|
+
end
|
34
27
|
|
35
28
|
def initialize(commit)
|
36
|
-
|
37
|
-
|
29
|
+
super
|
30
|
+
|
38
31
|
@linted = false
|
39
32
|
end
|
40
33
|
|
@@ -58,19 +51,11 @@ module Gitlab
|
|
58
51
|
!details.nil? && !details.empty?
|
59
52
|
end
|
60
53
|
|
61
|
-
def
|
62
|
-
problems.any?
|
63
|
-
end
|
64
|
-
|
65
|
-
def add_problem(problem_key, *args)
|
66
|
-
@problems[problem_key] = sprintf(PROBLEMS[problem_key], *args)
|
67
|
-
end
|
68
|
-
|
69
|
-
def lint(subject_description = "commit subject")
|
54
|
+
def lint
|
70
55
|
return self if @linted
|
71
56
|
|
72
57
|
@linted = true
|
73
|
-
lint_subject
|
58
|
+
lint_subject
|
74
59
|
lint_separator
|
75
60
|
lint_details
|
76
61
|
lint_message
|
@@ -78,26 +63,6 @@ module Gitlab
|
|
78
63
|
self
|
79
64
|
end
|
80
65
|
|
81
|
-
def lint_subject(subject_description)
|
82
|
-
if subject_too_short?
|
83
|
-
add_problem(:subject_too_short, subject_description)
|
84
|
-
end
|
85
|
-
|
86
|
-
if subject_too_long?
|
87
|
-
add_problem(:subject_too_long, subject_description)
|
88
|
-
end
|
89
|
-
|
90
|
-
if subject_starts_with_lowercase?
|
91
|
-
add_problem(:subject_starts_with_lowercase, subject_description)
|
92
|
-
end
|
93
|
-
|
94
|
-
if subject_ends_with_a_period?
|
95
|
-
add_problem(:subject_ends_with_a_period, subject_description)
|
96
|
-
end
|
97
|
-
|
98
|
-
self
|
99
|
-
end
|
100
|
-
|
101
66
|
private
|
102
67
|
|
103
68
|
def lint_separator
|
@@ -114,15 +79,11 @@ module Gitlab
|
|
114
79
|
end
|
115
80
|
|
116
81
|
details&.each_line do |line|
|
117
|
-
|
118
|
-
|
119
|
-
next unless line_too_long?(line)
|
120
|
-
|
121
|
-
url_size = line.scan(%r((https?://\S+))).sum { |(url)| url.length } # rubocop:disable CodeReuse/ActiveRecord
|
82
|
+
line_without_urls = line.strip.gsub(%r{https?://\S+}, "")
|
122
83
|
|
123
84
|
# If the line includes a URL, we'll allow it to exceed MAX_LINE_LENGTH characters, but
|
124
85
|
# only if the line _without_ the URL does not exceed this limit.
|
125
|
-
next unless line_too_long?(
|
86
|
+
next unless line_too_long?(line_without_urls)
|
126
87
|
|
127
88
|
add_problem(:details_line_too_long)
|
128
89
|
break
|
@@ -159,10 +120,6 @@ module Gitlab
|
|
159
120
|
files_changed > MAX_CHANGED_FILES_IN_COMMIT && lines_changed > MAX_CHANGED_LINES_IN_COMMIT
|
160
121
|
end
|
161
122
|
|
162
|
-
def subject
|
163
|
-
message_parts[0].delete_prefix(WIP_PREFIX)
|
164
|
-
end
|
165
|
-
|
166
123
|
def separator
|
167
124
|
message_parts[1]
|
168
125
|
end
|
@@ -171,37 +128,6 @@ module Gitlab
|
|
171
128
|
message_parts[2]&.gsub(/^Signed-off-by.*$/, "")
|
172
129
|
end
|
173
130
|
|
174
|
-
def line_too_long?(line)
|
175
|
-
case line
|
176
|
-
when String
|
177
|
-
line.length > MAX_LINE_LENGTH
|
178
|
-
when Integer
|
179
|
-
line > MAX_LINE_LENGTH
|
180
|
-
else
|
181
|
-
raise ArgumentError, "The line argument (#{line}) should be a String or an Integer! #{line.class} given."
|
182
|
-
end
|
183
|
-
end
|
184
|
-
|
185
|
-
def subject_too_short?
|
186
|
-
subject.split(" ").length < MIN_SUBJECT_WORDS_COUNT
|
187
|
-
end
|
188
|
-
|
189
|
-
def subject_too_long?
|
190
|
-
line_too_long?(subject)
|
191
|
-
end
|
192
|
-
|
193
|
-
def subject_starts_with_lowercase?
|
194
|
-
first_char = subject.sub(/\A\[.+\]\s/, "")[0]
|
195
|
-
first_char_downcased = first_char.downcase
|
196
|
-
return true unless ("a".."z").cover?(first_char_downcased)
|
197
|
-
|
198
|
-
first_char.downcase == first_char
|
199
|
-
end
|
200
|
-
|
201
|
-
def subject_ends_with_a_period?
|
202
|
-
subject.end_with?(".")
|
203
|
-
end
|
204
|
-
|
205
131
|
def message_contains_text_emoji?
|
206
132
|
emoji_checker.includes_text_emoji?(commit.message)
|
207
133
|
end
|
@@ -217,10 +143,6 @@ module Gitlab
|
|
217
143
|
def emoji_checker
|
218
144
|
@emoji_checker ||= Gitlab::Dangerfiles::EmojiChecker.new
|
219
145
|
end
|
220
|
-
|
221
|
-
def message_parts
|
222
|
-
@message_parts ||= commit.message.split("\n", 3)
|
223
|
-
end
|
224
146
|
end
|
225
147
|
end
|
226
148
|
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base_linter"
|
4
|
+
|
5
|
+
module Gitlab
|
6
|
+
module Dangerfiles
|
7
|
+
class MergeRequestLinter < BaseLinter
|
8
|
+
alias_method :lint, :lint_subject
|
9
|
+
|
10
|
+
def self.subject_description
|
11
|
+
"merge request title"
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.mr_run_options_regex
|
15
|
+
[
|
16
|
+
"RUN AS-IF-FOSS",
|
17
|
+
"UPDATE CACHE",
|
18
|
+
"RUN ALL RSPEC",
|
19
|
+
"SKIP RSPEC FAIL-FAST",
|
20
|
+
].join("|")
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def subject
|
26
|
+
super.gsub(/\[?(#{self.class.mr_run_options_regex})\]?/, "").strip
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -3,27 +3,42 @@
|
|
3
3
|
module Gitlab
|
4
4
|
module Dangerfiles
|
5
5
|
class Teammate
|
6
|
-
attr_reader :username, :name, :role, :projects, :available, :tz_offset_hours
|
6
|
+
attr_reader :options, :username, :name, :role, :projects, :available, :hungry, :reduced_capacity, :tz_offset_hours
|
7
7
|
|
8
8
|
# The options data are produced by https://gitlab.com/gitlab-org/gitlab-roulette/-/blob/master/lib/team_member.rb
|
9
9
|
def initialize(options = {})
|
10
|
+
@options = options
|
10
11
|
@username = options["username"]
|
11
12
|
@name = options["name"]
|
12
13
|
@markdown_name = options["markdown_name"]
|
13
14
|
@role = options["role"]
|
14
15
|
@projects = options["projects"]
|
15
16
|
@available = options["available"]
|
17
|
+
@hungry = options["hungry"]
|
18
|
+
@reduced_capacity = options["reduced_capacity"]
|
16
19
|
@tz_offset_hours = options["tz_offset_hours"]
|
17
20
|
end
|
18
21
|
|
22
|
+
def to_h
|
23
|
+
options
|
24
|
+
end
|
25
|
+
|
26
|
+
def ==(other)
|
27
|
+
return false unless other.respond_to?(:username)
|
28
|
+
|
29
|
+
other.username == username
|
30
|
+
end
|
31
|
+
|
19
32
|
def in_project?(name)
|
20
33
|
projects&.has_key?(name)
|
21
34
|
end
|
22
35
|
|
23
|
-
|
36
|
+
def any_capability?(project, category)
|
37
|
+
capabilities(project).any? { |capability| capability.end_with?(category.to_s) }
|
38
|
+
end
|
39
|
+
|
24
40
|
def reviewer?(project, category, labels)
|
25
|
-
has_capability?(project, category, :reviewer, labels)
|
26
|
-
traintainer?(project, category, labels)
|
41
|
+
has_capability?(project, category, :reviewer, labels)
|
27
42
|
end
|
28
43
|
|
29
44
|
def traintainer?(project, category, labels)
|
@@ -34,9 +49,7 @@ module Gitlab
|
|
34
49
|
has_capability?(project, category, :maintainer, labels)
|
35
50
|
end
|
36
51
|
|
37
|
-
def markdown_name(
|
38
|
-
return @markdown_name unless timezone_experiment
|
39
|
-
|
52
|
+
def markdown_name(author: nil)
|
40
53
|
"#{@markdown_name} (#{utc_offset_text(author)})"
|
41
54
|
end
|
42
55
|
|
@@ -68,9 +81,9 @@ module Gitlab
|
|
68
81
|
|
69
82
|
def offset_diff_compared_to_author(author)
|
70
83
|
diff = floored_offset_hours - author.floored_offset_hours
|
71
|
-
return "same timezone as `@#{author.username}`" if diff
|
84
|
+
return "same timezone as `@#{author.username}`" if diff == 0
|
72
85
|
|
73
|
-
ahead_or_behind = diff < 0 ? "behind" : "ahead"
|
86
|
+
ahead_or_behind = diff < 0 ? "behind" : "ahead of"
|
74
87
|
pluralized_hours = pluralize(diff.abs, "hour", "hours")
|
75
88
|
|
76
89
|
"#{pluralized_hours} #{ahead_or_behind} `@#{author.username}`"
|
@@ -85,6 +98,7 @@ module Gitlab
|
|
85
98
|
when :engineering_productivity
|
86
99
|
return false unless role[/Engineering Productivity/]
|
87
100
|
return true if kind == :reviewer
|
101
|
+
return true if capabilities(project).include?("#{kind} engineering_productivity")
|
88
102
|
|
89
103
|
capabilities(project).include?("#{kind} backend")
|
90
104
|
else
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Gitlab
|
4
|
+
module Dangerfiles
|
5
|
+
module TitleLinting
|
6
|
+
DRAFT_REGEX = /\A*#{Regexp.union(/(?i)(\[WIP\]\s*|WIP:\s*|WIP$)/, /(?i)(\[draft\]|\(draft\)|draft:|draft\s\-\s|draft$)/)}+\s*/i.freeze
|
7
|
+
CHERRY_PICK_REGEX = /cherry[\s-]*pick/i.freeze
|
8
|
+
RUN_ALL_RSPEC_REGEX = /RUN ALL RSPEC/i.freeze
|
9
|
+
RUN_AS_IF_FOSS_REGEX = /RUN AS-IF-FOSS/i.freeze
|
10
|
+
|
11
|
+
module_function
|
12
|
+
|
13
|
+
def sanitize_mr_title(title)
|
14
|
+
remove_draft_flag(title).gsub(/`/, '\\\`')
|
15
|
+
end
|
16
|
+
|
17
|
+
def remove_draft_flag(title)
|
18
|
+
title.gsub(DRAFT_REGEX, "")
|
19
|
+
end
|
20
|
+
|
21
|
+
def has_draft_flag?(title)
|
22
|
+
DRAFT_REGEX.match?(title)
|
23
|
+
end
|
24
|
+
|
25
|
+
def has_cherry_pick_flag?(title)
|
26
|
+
CHERRY_PICK_REGEX.match?(title)
|
27
|
+
end
|
28
|
+
|
29
|
+
def has_run_all_rspec_flag?(title)
|
30
|
+
RUN_ALL_RSPEC_REGEX.match?(title)
|
31
|
+
end
|
32
|
+
|
33
|
+
def has_run_as_if_foss_flag?(title)
|
34
|
+
RUN_AS_IF_FOSS_REGEX.match?(title)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|