gitlab-dangerfiles 0.1.0 → 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|