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
|
# frozen_string_literal: true
require_relative File.expand_path('../../lib/gitlab/danger/commit_linter', __dir__)
COMMIT_MESSAGE_GUIDELINES = "https://docs.gitlab.com/ee/development/contributing/merge_request_workflow.html#commit-messages-guidelines"
MORE_INFO = "For more information, take a look at our [Commit message guidelines](#{COMMIT_MESSAGE_GUIDELINES})."
THE_DANGER_JOB_TEXT = "the `danger-review` job"
MAX_COMMITS_COUNT = 10
def gitlab_danger
@gitlab_danger ||= GitlabDanger.new(helper.gitlab_helper)
end
def fail_commit(commit, message, more_info: true)
self.fail(build_message(commit, message, more_info: more_info))
end
def warn_commit(commit, message, more_info: true)
self.warn(build_message(commit, message, more_info: more_info))
end
def build_message(commit, message, more_info: true)
[message].tap do |full_message|
full_message << ". #{MORE_INFO}" if more_info
full_message.unshift("#{commit.sha}: ") if commit.sha
end.join
end
def squash_mr?
gitlab_danger.ci? ? gitlab.mr_json['squash'] : false
end
def wip_mr?
gitlab_danger.ci? ? gitlab.mr_json['work_in_progress'] : false
end
def danger_job_link
gitlab_danger.ci? ? "[#{THE_DANGER_JOB_TEXT}](#{ENV['CI_JOB_URL']})" : THE_DANGER_JOB_TEXT
end
# Perform various checks against commits. We're not using
# https://github.com/jonallured/danger-commit_lint because its output is not
# very helpful, and it doesn't offer the means of ignoring merge commits.
def lint_commit(commit)
linter = Gitlab::Danger::CommitLinter.new(commit)
# For now we'll ignore merge commits, as getting rid of those is a problem
# separate from enforcing good commit messages.
return linter if linter.merge?
# We ignore revert commits as they are well structured by Git already
return linter if linter.revert?
# If MR is set to squash, we ignore fixup commits
return linter if linter.fixup? && squash_mr?
if linter.fixup?
msg = "Squash or fixup commits must be squashed before merge, or enable squash merge option and re-run #{danger_job_link}."
if wip_mr? || squash_mr?
warn_commit(commit, msg, more_info: false)
else
fail_commit(commit, msg, more_info: false)
end
# Makes no sense to process other rules for fixup commits, they trigger just more noise
return linter
end
# Fail if a suggestion commit is used and squash is not enabled
if linter.suggestion?
unless squash_mr?
fail_commit(commit, "If you are applying suggestions, enable squash in the merge request and re-run #{danger_job_link}.", more_info: false)
end
return linter
end
linter.lint
end
def lint_mr_title(mr_title)
commit = Struct.new(:message, :sha).new(mr_title)
Gitlab::Danger::CommitLinter.new(commit).lint_subject("merge request title")
end
def count_non_fixup_commits(commit_linters)
commit_linters.count { |commit_linter| !commit_linter.fixup? }
end
def lint_commits(commits)
commit_linters = commits.map { |commit| lint_commit(commit) }
failed_commit_linters = commit_linters.select { |commit_linter| commit_linter.failed? }
warn_or_fail_commits(failed_commit_linters, default_to_fail: !squash_mr?)
if count_non_fixup_commits(commit_linters) > MAX_COMMITS_COUNT
level = squash_mr? ? :warn : :fail
self.__send__(level, # rubocop:disable GitlabSecurity/PublicSend
"This merge request includes more than #{MAX_COMMITS_COUNT} commits. " \
'Please rebase these commits into a smaller number of commits or split ' \
'this merge request into multiple smaller merge requests.')
end
if squash_mr?
multi_line_commit_linter = commit_linters.detect { |commit_linter| !commit_linter.merge? && commit_linter.multi_line? }
if multi_line_commit_linter && multi_line_commit_linter.failed?
warn_or_fail_commits(multi_line_commit_linter)
else
title_linter = lint_mr_title(gitlab.mr_json['title'])
if title_linter.failed?
warn_or_fail_commits(title_linter)
end
end
end
end
def warn_or_fail_commits(failed_linters, default_to_fail: true)
level = default_to_fail ? :fail : :warn
Array(failed_linters).each do |linter|
linter.problems.each do |problem_key, problem_desc|
case problem_key
when :subject_above_warning
warn_commit(linter.commit, problem_desc)
else
self.__send__("#{level}_commit", linter.commit, problem_desc) # rubocop:disable GitlabSecurity/PublicSend
end
end
end
end
lint_commits(git.commits)
|