1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
|
# frozen_string_literal: true
emoji_checker_path = File.expand_path('emoji_checker', __dir__)
base_linter_path = File.expand_path('base_linter', __dir__)
if defined?(Rails)
require_dependency(base_linter_path)
require_dependency(emoji_checker_path)
else
require_relative(base_linter_path)
require_relative(emoji_checker_path)
end
module Gitlab
module Danger
class CommitLinter < BaseLinter
MAX_CHANGED_FILES_IN_COMMIT = 3
MAX_CHANGED_LINES_IN_COMMIT = 30
SHORT_REFERENCE_REGEX = %r{([\w\-\/]+)?(?<!`)(#|!|&|%)\d+(?<!`)}.freeze
def self.problems_mapping
super.merge(
{
separator_missing: "The commit subject and body must be separated by a blank line",
details_too_many_changes: "Commits that change #{MAX_CHANGED_LINES_IN_COMMIT} or more lines across " \
"at least #{MAX_CHANGED_FILES_IN_COMMIT} files must describe these changes in the commit body",
details_line_too_long: "The commit body should not contain more than #{MAX_LINE_LENGTH} characters per line",
message_contains_text_emoji: "Avoid the use of Markdown Emoji such as `:+1:`. These add limited value " \
"to the commit message, and are displayed as plain text outside of GitLab",
message_contains_unicode_emoji: "Avoid the use of Unicode Emoji. These add no value to the commit " \
"message, and may not be displayed properly everywhere",
message_contains_short_reference: "Use full URLs instead of short references (`gitlab-org/gitlab#123` or " \
"`!123`), as short references are displayed as plain text outside of GitLab"
}
)
end
def initialize(commit)
super
@linted = false
end
def fixup?
commit.message.start_with?('fixup!', 'squash!')
end
def suggestion?
commit.message.start_with?('Apply suggestion to')
end
def merge?
commit.message.start_with?('Merge branch')
end
def revert?
commit.message.start_with?('Revert "')
end
def multi_line?
!details.nil? && !details.empty?
end
def lint
return self if @linted
@linted = true
lint_subject
lint_separator
lint_details
lint_message
self
end
private
def lint_separator
return self unless separator && !separator.empty?
add_problem(:separator_missing)
self
end
def lint_details
if !multi_line? && many_changes?
add_problem(:details_too_many_changes)
end
details&.each_line do |line|
line_without_urls = line.strip.gsub(%r{https?://\S+}, '')
# If the line includes a URL, we'll allow it to exceed MAX_LINE_LENGTH characters, but
# only if the line _without_ the URL does not exceed this limit.
next unless line_too_long?(line_without_urls)
add_problem(:details_line_too_long)
break
end
self
end
def lint_message
if message_contains_text_emoji?
add_problem(:message_contains_text_emoji)
end
if message_contains_unicode_emoji?
add_problem(:message_contains_unicode_emoji)
end
if message_contains_short_reference?
add_problem(:message_contains_short_reference)
end
self
end
def files_changed
commit.diff_parent.stats[:total][:files]
end
def lines_changed
commit.diff_parent.stats[:total][:lines]
end
def many_changes?
files_changed > MAX_CHANGED_FILES_IN_COMMIT && lines_changed > MAX_CHANGED_LINES_IN_COMMIT
end
def separator
message_parts[1]
end
def details
message_parts[2]&.gsub(/^Signed-off-by.*$/, '')
end
def message_contains_text_emoji?
emoji_checker.includes_text_emoji?(commit.message)
end
def message_contains_unicode_emoji?
emoji_checker.includes_unicode_emoji?(commit.message)
end
def message_contains_short_reference?
commit.message.match?(SHORT_REFERENCE_REGEX)
end
def emoji_checker
@emoji_checker ||= Gitlab::Danger::EmojiChecker.new
end
end
end
end
|