forked from instructure/canvas-lms
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlint_commit_message
executable file
·170 lines (141 loc) · 6.35 KB
/
lint_commit_message
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
159
160
161
162
163
164
165
166
167
168
169
170
#!/usr/bin/env ruby
# frozen_string_literal: true
require "shellwords"
require "json"
require "jira_ref_parser"
require "yaml"
GERGICH_GIT_PATH = ENV.fetch("GERGICH_GIT_PATH", ".")
def git(command)
Dir.chdir(GERGICH_GIT_PATH) do
`git #{command}`
end
end
# rubocop:disable Style/GlobalVars yeah a global, I'm being lazy and refactoring to an object
def comment(severity, line, message, link_to_guidelines: false)
if ENV["GERRIT_REFSPEC"]
if link_to_guidelines
message += "\n\nHere are some guidelines to keep our git log pretty and useful: http://chris.beams.io/posts/git-commit/"
end
# NOTE: add 6 to all line numbers, cuz parent/author/commit/dates/etc
payload = { path: "/COMMIT_MSG", position: (line || 1) + 6, severity:, message: }
`gergich comment #{Shellwords.escape(payload.to_json)}`
warn "line #{line}: [#{severity}]: #{message}"
elsif line
warn $lines[line - 1]
warn " ^: [#{severity}]: #{message}"
else
warn "[#{severity}]: #{message}"
end
end
commit_message = case ARGV[0]
when "--stdin" then $stdin.read
when nil then git("show --pretty=format:%B -s")
else File.read(ARGV[0])
end
exit if commit_message.match?(/\A\[?wip($|[\] :-])/i)
lines = commit_message.split("\n")
$lines = lines.dup
subject = lines.shift
# rubocop:enable Style/GlobalVars
if lines.shift != ""
comment "error", 2, "add a blank line after the subject line", link_to_guidelines: true
end
SUBJECT_MAX_LINE_LEN = 75
SUBJECT_IDEAL_LINE_LEN = 65
length_exemption_regex = /^Revert/
if subject.size > SUBJECT_MAX_LINE_LEN && subject !~ length_exemption_regex
comment "error",
1,
"subject line can't exceed #{SUBJECT_MAX_LINE_LEN} chars (ideally keep it well under #{SUBJECT_IDEAL_LINE_LEN})",
link_to_guidelines: true
elsif subject.size > SUBJECT_IDEAL_LINE_LEN && subject !~ length_exemption_regex
comment "warn", 1, "subject line shouldn't exceed #{SUBJECT_IDEAL_LINE_LEN} chars", link_to_guidelines: true
end
# not perfect (won't get irregular verbs, only checks first word), but
# close enough... does the right thing on most existing commits
maybe_wrong_tense_regex = /\A(spec: )?[a-z]+([^e]ed|[^aijoqsu]s|ing) /i
actually_ok_regex = /\A
(spec:\s+)?
(
# *ing words
(brand|br|[^ ]*learn|masquerad|upcom|shard|word|grad)ing |
# *ed words
(brand|fail|batch|emb|persist|enrich|(un)?publish|moderat|mut|grad)ed |
# *s words
(.*(ion|ment)|observer|alway|outcome|rail|user|spec|student|quizze|alert|plugin|term)s
)
/xi
maybe_start_with_wrong_tense = subject =~ maybe_wrong_tense_regex &&
subject !~ actually_ok_regex
if maybe_start_with_wrong_tense
comment "warn",
1,
"make sure your commit message has an imperative verb (e.g. \"add\" instead of \"adds\", \"added\", or \"adding\")",
link_to_guidelines: true
end
has_the_word_i = subject =~ /(\A| )i('m)? /i
if has_the_word_i
comment "warn", 1, "just say what the commit does, and not in the first person :P", link_to_guidelines: true
end
BODY_IDEAL_LINE_LENGTH = 75
long_lines = lines.each_with_index.select do |line, _i|
line.size > BODY_IDEAL_LINE_LENGTH
end
unless long_lines.empty?
comment "warn",
long_lines.first[1] + 3,
"try to keep all lines under #{BODY_IDEAL_LINE_LENGTH} characters" +
((long_lines.size > 1) ? " (note that #{long_lines.size - 1} other long lines follow this one)" : ""),
link_to_guidelines: true
end
starts_with_spec = subject =~ /\Aspec: /i
exit if starts_with_spec # we're trusting you here, don't be evil
commit_files = git("show --pretty=format:'' --name-only").split("\n")
if ENV["GERRIT_REFSPEC"]
puts "detected file changes"
puts commit_files
end
touches_db_migrate_file = commit_files.any? { |f| f.start_with?("db/migrate/") }
if touches_db_migrate_file
comment "info", nil, "Your commit modifies migration files. Please review and follow the instructions in https://instructure.atlassian.net/wiki/spaces/CE/pages/49643724/Rails+Migrations, including completing an additional migration review."
end
touches_lti_variable_expander_file = commit_files.any? { |f| f.start_with?("lib/lti/variable_expander") }
touches_lti_variable_expander_docs = commit_files.any? { |f| f.start_with?("doc/api/tools_variable_substitutions") }
if touches_lti_variable_expander_file && !touches_lti_variable_expander_docs
comment "warn", nil, "Changes were made to the variable expander file. The docs are created from the comments and there are no document changes. Do you need to update the docs also? If so, run `rake doc:api` and commit the changes. Otherwise (if you didn't modify the descriptions of any variable expansions, or if you added an internal-only expansion using @internal) you may ignore this warning."
end
gh_issue = (commit_message =~ /(?:refs|fixes|closes|resolves|references) gh-\d+/i)
SAFE_PARTS_REGEX = /UTF-8/
TICKET_REGEX = /([A-Z]+-[0-9]+)/
issue_ids = JiraRefParser.scan_message_for_issue_ids(commit_message)
if issue_ids.empty? && !gh_issue
parts = commit_message.gsub(SAFE_PARTS_REGEX, "")
.split(TICKET_REGEX)
if parts.size > 1
jira_hook_words = JiraRefParser::RefKeywords + JiraRefParser::FixKeywords
comment "warn",
parts.first.count("\n") + 1,
"the jira hooks won't link this commit to #{parts[1]} unless you use one of the following keywords: #{jira_hook_words.join(", ")}"
else
comment "warn", nil, "you should really reference a ticket ლ(ٱ٥ٱლ)"
end
end
only_affects_specs = commit_files.all? { |f| f.start_with?("spec/") }
if only_affects_specs
comment "warn", nil, "since your commit only affects specs, please prefix your commit message with \"spec: \""
exit
end
flag_name = (commit_message =~ /^flag\s{0,3}=\s{0,3}([a-zA-Z][\w-]+)\s{0,3}$/)
unless flag_name
comment "warn", nil, "ლ(ಠ益ಠლ) y u no add release flag? https://instructure.atlassian.net/wiki/spaces/CE/pages/603586589/Commit+Message+Release+Flags"
end
has_a_test_plan = commit_message =~ /test[ -]plan/i
unless has_a_test_plan
comment "warn", nil, "y u no add test plan? ლ(ಠ益ಠლ)"
end
skip_eslint_flag = commit_message.include? "[skip-eslint]"
if skip_eslint_flag
comment "error",
nil,
"[skip-eslint] should only be used for large restructuring changes where no real code changes are actually made"
end