Commit 60b802d4 authored by Rémy Coutable's avatar Rémy Coutable

Lint MR title instead of commits when squash is enabled

Also, move commit linting to a new Gitlab::Danger::CommitLinter class.
Signed-off-by: default avatarRémy Coutable <remy@rymai.me>
parent eca77e31
......@@ -38,9 +38,13 @@ rescue StandardError => e
warn "There was a problem trying to check the Changelog. Exception: #{e.name} - #{e.message}"
end
def sanitized_mr_title
helper.sanitize_mr_title(gitlab.mr_json["title"])
end
if git.modified_files.include?("CHANGELOG.md")
fail "**CHANGELOG.md was edited.** Please remove the additions and create a CHANGELOG entry.\n\n" +
format(CREATE_CHANGELOG_MESSAGE, mr_iid: gitlab.mr_json["iid"], mr_title: changelog.sanitized_mr_title, labels: changelog.presented_no_changelog_labels)
format(CREATE_CHANGELOG_MESSAGE, mr_iid: gitlab.mr_json["iid"], mr_title: sanitized_mr_title, labels: changelog.presented_no_changelog_labels)
end
changelog_found = changelog.found
......@@ -50,6 +54,6 @@ if changelog.needed?
check_changelog(changelog_found)
else
message "**[CHANGELOG missing](https://docs.gitlab.com/ce/development/changelog.html)**: If this merge request [doesn't need a CHANGELOG entry](https://docs.gitlab.com/ee/development/changelog.html#what-warrants-a-changelog-entry), feel free to ignore this message.\n\n" +
format(CREATE_CHANGELOG_MESSAGE, mr_iid: gitlab.mr_json["iid"], mr_title: changelog.sanitized_mr_title, labels: changelog.presented_no_changelog_labels)
format(CREATE_CHANGELOG_MESSAGE, mr_iid: gitlab.mr_json["iid"], mr_title: sanitized_mr_title, labels: changelog.presented_no_changelog_labels)
end
end
# frozen_string_literal: true
require 'json'
require_relative File.expand_path('../../lib/gitlab/danger/commit_linter', __dir__)
URL_LIMIT_SUBJECT = "https://chris.beams.io/posts/git-commit/#limit-50"
URL_GIT_COMMIT = "https://chris.beams.io/posts/git-commit/"
# rubocop: disable Style/SignalException
# rubocop: disable Metrics/CyclomaticComplexity
# rubocop: disable Metrics/PerceivedComplexity
# 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.
class EmojiChecker
DIGESTS = File.expand_path('../../fixtures/emojis/digests.json', __dir__)
ALIASES = File.expand_path('../../fixtures/emojis/aliases.json', __dir__)
# A regex that indicates a piece of text _might_ include an Emoji. The regex
# alone is not enough, as we'd match `:foo:bar:baz`. Instead, we use this
# regex to save us from having to check for all possible emoji names when we
# know one definitely is not included.
LIKELY_EMOJI = /:[\+a-z0-9_\-]+:/.freeze
def initialize
names = JSON.parse(File.read(DIGESTS)).keys +
JSON.parse(File.read(ALIASES)).keys
@emoji = names.map { |name| ":#{name}:" }
end
def includes_emoji?(text)
return false unless text.match?(LIKELY_EMOJI)
@emoji.any? { |emoji| text.include?(emoji) }
end
end
MAX_COMMITS_COUNT = 10
def gitlab_danger
@gitlab_danger ||= GitlabDanger.new(helper.gitlab_helper)
end
def fail_commit(commit, message)
fail("#{commit.sha}: #{message}")
self.fail("#{commit.sha}: #{message}")
end
def warn_commit(commit, message)
warn("#{commit.sha}: #{message}")
end
def lines_changed_in_commit(commit)
commit.diff_parent.stats[:total][:lines]
self.warn("#{commit.sha}: #{message}")
end
def subject_starts_with_capital?(subject)
first_char = subject.chars.first
first_char.upcase == first_char
end
def too_many_changed_lines?(commit)
commit.diff_parent.stats[:total][:files] > 3 &&
lines_changed_in_commit(commit) >= 30
end
def emoji_checker
@emoji_checker ||= EmojiChecker.new
def squash_mr?
gitlab_danger.ci? ? gitlab.mr_json['squash'] : false
end
def unicode_emoji_regex
@unicode_emoji_regex ||= %r((
[\u{1F300}-\u{1F5FF}] |
[\u{1F1E6}-\u{1F1FF}] |
[\u{2700}-\u{27BF}] |
[\u{1F900}-\u{1F9FF}] |
[\u{1F600}-\u{1F64F}] |
[\u{1F680}-\u{1F6FF}] |
[\u{2600}-\u{26FF}]
))x
def wip_mr?
gitlab_danger.ci? ? gitlab.mr_json['work_in_progress'] : false
end
def count_filtered_commits(commits)
commits.count do |commit|
!commit.message.start_with?('fixup!', 'squash!')
end
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)
def lint_commit(commit) # rubocop:disable Metrics/AbcSize
# For now we'll ignore merge commits, as getting rid of those is a problem
# separate from enforcing good commit messages.
return false if commit.message.start_with?('Merge branch')
return linter if linter.merge?
# We ignore revert commits as they are well structured by Git already
return false if commit.message.start_with?('Revert "')
return linter if linter.revert?
is_squash = gitlab_danger.ci? ? gitlab.mr_json['squash'] : false
is_wip = gitlab_danger.ci? ? gitlab.mr_json['work_in_progress'] : false
is_fixup = commit.message.start_with?('fixup!', 'squash!')
if is_fixup
# The MR is set to squash - Danger adds an informative notice
# The MR is not set to squash - Danger fails. if also WIP warn only, not error
if is_squash
return false
end
# If MR is set to squash, we ignore fixup commits
return linter if linter.fixup? && squash_mr?
if is_wip
warn_commit(
commit,
'Squash or Fixup commits must be squashed before merge, or enable squash merge option'
)
if linter.fixup?
msg = 'Squash or fixup commits must be squashed before merge, or enable squash merge option'
if wip_mr? || squash_mr?
warn_commit(commit, msg)
else
fail_commit(
commit,
'Squash or Fixup commits must be squashed before merge, or enable squash merge option'
)
fail_commit(commit, msg)
end
# Makes no sense to process other rules for fixup commits, they trigger just more noise
return false
return linter
end
# Fail if a suggestion commit is used and squash is not enabled
if commit.message.start_with?('Apply suggestion to')
if is_squash
return false
else
fail_commit(
commit,
'If you are applying suggestions, enable squash in the merge request and re-run the failed job'
)
return true
if linter.suggestion?
unless squash_mr?
fail_commit(commit, "If you are applying suggestions, enable squash in the merge request and re-run the `danger-review` job")
end
end
failures = false
subject, separator, details = commit.message.split("\n", 3)
if subject.split.length < 3
fail_commit(
commit,
'The commit subject must contain at least three words'
)
failures = true
return linter
end
if subject.length > 72
fail_commit(
commit,
'The commit subject may not be longer than 72 characters'
)
failures = true
elsif subject.length > 50
warn_commit(
commit,
"This commit's subject line is acceptable, but please try to [reduce it to 50 characters](#{URL_LIMIT_SUBJECT})."
)
end
unless subject_starts_with_capital?(subject)
fail_commit(commit, 'The commit subject must start with a capital letter')
failures = true
end
if subject.end_with?('.')
fail_commit(commit, 'The commit subject must not end with a period')
failures = true
end
if separator && !separator.empty?
fail_commit(
commit,
'The commit subject and body must be separated by a blank line'
)
failures = true
end
details&.each_line do |line|
line = line.strip
next if line.length <= 72
url_size = line.scan(%r((https?://\S+))).sum { |(url)| url.length }
# If the line includes a URL, we'll allow it to exceed 72 characters, but
# only if the line _without_ the URL does not exceed this limit.
next if line.length - url_size <= 72
fail_commit(
commit,
'The commit body should not contain more than 72 characters per line'
)
linter.lint
end
failures = true
end
def lint_mr_title(mr_title)
commit = Struct.new(:message, :sha).new(mr_title)
if !details && too_many_changed_lines?(commit)
fail_commit(
commit,
'Commits that change 30 or more lines across at least three files ' \
'must describe these changes in the commit body'
)
Gitlab::Danger::CommitLinter.new(commit).lint_subject("merge request title")
end
failures = true
end
def count_non_fixup_commits(commit_linters)
commit_linters.count { |commit_linter| !commit_linter.fixup? }
end
if emoji_checker.includes_emoji?(commit.message)
warn_commit(
commit,
'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'
)
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?)
failures = true
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 commit.message.match?(unicode_emoji_regex)
fail_commit(
commit,
'Avoid the use of Unicode Emoji. ' \
'These add no value to the commit message, ' \
'and may not be displayed properly everywhere'
)
if squash_mr?
multi_line_commit_linter = commit_linters.detect { |commit_linter| commit_linter.multi_line? }
failures = true
if multi_line_commit_linter && multi_line_commit_linter.lint.failed?
warn_or_fail_commits(multi_line_commit_linter)
fail_message('The commit message that will be used in the squash commit does not meet our Git commit message standards.')
else
title_linter = lint_mr_title(gitlab.mr_json['title'])
if title_linter.failed?
warn_or_fail_commits(title_linter)
fail_message('The merge request title that will be used in the squash commit does not meet our Git commit message standards.')
end
end
else
if failed_commit_linters.any?
fail_message('One or more commit messages do not meet our Git commit message standards.')
end
end
end
if commit.message.match?(%r(([\w\-\/]+)?(#|!|&|%)\d+\b))
fail_commit(
commit,
'Use full URLs instead of short references ' \
'(`gitlab-org/gitlab#123` or `!123`), as short references are ' \
'displayed as plain text outside of GitLab'
)
failures = true
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
failures
end
def lint_commits(commits)
failed = commits.select do |commit|
lint_commit(commit)
end
def fail_message(intro)
markdown(<<~MARKDOWN)
## Commit message standards
if failed.any?
markdown(<<~MARKDOWN)
## Commit message standards
#{intro}
One or more commit messages do not meet our Git commit message standards.
For more information on how to write a good commit message, take a look at
[How to Write a Git Commit Message](#{URL_GIT_COMMIT}).
For more information on how to write a good commit message, take a look at
[How to Write a Git Commit Message](#{URL_GIT_COMMIT}).
Here is an example of a good commit message:
Here is an example of a good commit message:
Reject ruby interpolation in externalized strings
Reject ruby interpolation in externalized strings
When using ruby interpolation in externalized strings, they can't be
detected. Which means they will never be presented to be translated.
When using ruby interpolation in externalized strings, they can't be
detected. Which means they will never be presented to be translated.
To mix variables into translations we need to use `sprintf`
instead.
To mix variables into translations we need to use `sprintf`
instead.
Instead of:
Instead of:
_("Hello \#{subject}")
_("Hello \#{subject}")
Use:
Use:
_("Hello %{subject}") % { subject: 'world' }
_("Hello %{subject}") % { subject: 'world' }
This is an example of a bad commit message:
This is an example of a bad commit message:
updated README.md
updated README.md
This commit message is bad because although it tells us that README.md is
updated, it doesn't tell us why or how it was updated.
MARKDOWN
end
This commit message is bad because although it tells us that README.md is
updated, it doesn't tell us why or how it was updated.
MARKDOWN
end
lint_commits(git.commits)
if count_filtered_commits(git.commits) > 10
fail(
'This merge request includes more than 10 commits. ' \
'Please rebase these commits into a smaller number of commits.'
)
end
# frozen_string_literal: true
emoji_checker_path = File.expand_path('emoji_checker', __dir__)
defined?(Rails) ? require_dependency(emoji_checker_path) : require_relative(emoji_checker_path)
module Gitlab
module Danger
class CommitLinter
MIN_SUBJECT_WORDS_COUNT = 3
MAX_LINE_LENGTH = 72
WARN_SUBJECT_LENGTH = 50
URL_LIMIT_SUBJECT = "https://chris.beams.io/posts/git-commit/#limit-50"
MAX_CHANGED_FILES_IN_COMMIT = 3
MAX_CHANGED_LINES_IN_COMMIT = 30
SHORT_REFERENCE_REGEX = %r{([\w\-\/]+)?(#|!|&|%)\d+\b}.freeze
DEFAULT_SUBJECT_DESCRIPTION = 'commit subject'
PROBLEMS = {
subject_too_short: "The %s must contain at least #{MIN_SUBJECT_WORDS_COUNT} words",
subject_too_long: "The %s may not be longer than #{MAX_LINE_LENGTH} characters",
subject_above_warning: "The %s length is acceptable, but please try to [reduce it to #{WARN_SUBJECT_LENGTH} characters](#{URL_LIMIT_SUBJECT}).",
subject_starts_with_lowercase: "The %s must start with a capital letter",
subject_ends_with_a_period: "The %s must not end with a period",
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"
}.freeze
attr_reader :commit, :problems
def initialize(commit)
@commit = commit
@problems = {}
@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 failed?
problems.any?
end
def add_problem(problem_key, *args)
@problems[problem_key] = sprintf(PROBLEMS[problem_key], *args)
end
def lint(subject_description = "commit subject")
return self if @linted
@linted = true
lint_subject(subject_description)
lint_separator
lint_details
lint_message
self
end
def lint_subject(subject_description)
if subject_too_short?
add_problem(:subject_too_short, subject_description)
end
if subject_too_long?
add_problem(:subject_too_long, subject_description)
elsif subject_above_warning?
add_problem(:subject_above_warning, subject_description)
end
if subject_starts_with_lowercase?
add_problem(:subject_starts_with_lowercase, subject_description)
end
if subject_ends_with_a_period?
add_problem(:subject_ends_with_a_period, subject_description)
end
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 = line.strip
next unless line_too_long?(line)
url_size = line.scan(%r((https?://\S+))).sum { |(url)| url.length } # rubocop:disable CodeReuse/ActiveRecord
# 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.length - url_size)
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 subject
message_parts[0]
end
def separator
message_parts[1]
end
def details
message_parts[2]
end
def line_too_long?(line)
case line
when String
line.length > MAX_LINE_LENGTH
when Integer
line > MAX_LINE_LENGTH
else
raise ArgumentError, "The line argument (#{line}) should be a String or an Integer! #{line.class} given."
end
end
def subject_too_short?
subject.split(' ').length < MIN_SUBJECT_WORDS_COUNT
end
def subject_too_long?
line_too_long?(subject)
end
def subject_above_warning?
subject.length > WARN_SUBJECT_LENGTH
end
def subject_starts_with_lowercase?
first_char = subject[0]
first_char.downcase == first_char
end
def subject_ends_with_a_period?
subject.end_with?('.')
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
def message_parts
@message_parts ||= commit.message.split("\n", 3)
end
end
end
end
# frozen_string_literal: true
require 'json'
module Gitlab
module Danger
class EmojiChecker
DIGESTS = File.expand_path('../../../fixtures/emojis/digests.json', __dir__)
ALIASES = File.expand_path('../../../fixtures/emojis/aliases.json', __dir__)
# A regex that indicates a piece of text _might_ include an Emoji. The regex
# alone is not enough, as we'd match `:foo:bar:baz`. Instead, we use this
# regex to save us from having to check for all possible emoji names when we
# know one definitely is not included.
LIKELY_EMOJI = /:[\+a-z0-9_\-]+:/.freeze
UNICODE_EMOJI_REGEX = %r{(
[\u{1F300}-\u{1F5FF}] |
[\u{1F1E6}-\u{1F1FF}] |
[\u{2700}-\u{27BF}] |
[\u{1F900}-\u{1F9FF}] |
[\u{1F600}-\u{1F64F}] |
[\u{1F680}-\u{1F6FF}] |
[\u{2600}-\u{26FF}]
)}x.freeze
def initialize
names = JSON.parse(File.read(DIGESTS)).keys +
JSON.parse(File.read(ALIASES)).keys
@emoji = names.map { |name| ":#{name}:" }
end
def includes_text_emoji?(text)
return false unless text.match?(LIKELY_EMOJI)
@emoji.any? { |emoji| text.include?(emoji) }
end
def includes_unicode_emoji?(text)
text.match?(UNICODE_EMOJI_REGEX)
end
end
end
end
......@@ -174,6 +174,10 @@ module Gitlab
labels - current_mr_labels
end
def sanitize_mr_title(title)
title.gsub(/^WIP: */, '').gsub(/`/, '\\\`')
end
def security_mr?
return false unless gitlab_helper
......
......@@ -106,18 +106,6 @@ describe Gitlab::Danger::Changelog do
end
end
describe '#sanitized_mr_title' do
subject { changelog.sanitized_mr_title }
[
'WIP: My MR title',
'My MR title'
].each do |mr_title|
let(:mr_json) { { "title" => mr_title } }
it { is_expected.to eq("My MR title") }
end
end
describe '#ee_changelog?' do
context 'is ee changelog' do
[
......
# frozen_string_literal: true
require 'fast_spec_helper'
require 'rspec-parameterized'
require_relative 'danger_spec_helper'
require 'gitlab/danger/commit_linter'
describe Gitlab::Danger::CommitLinter do
using RSpec::Parameterized::TableSyntax
let(:total_files_changed) { 2 }
let(:total_lines_changed) { 10 }
let(:stats) { { total: { files: total_files_changed, lines: total_lines_changed } } }
let(:diff_parent) { Struct.new(:stats).new(stats) }
let(:commit_class) do
Struct.new(:message, :sha, :diff_parent)
end
let(:commit_message) { 'A commit message' }
let(:commit_sha) { 'abcd1234' }
let(:commit) { commit_class.new(commit_message, commit_sha, diff_parent) }
subject(:commit_linter) { described_class.new(commit) }
describe '#fixup?' do
where(:commit_message, :is_fixup) do
'A commit message' | false
'fixup!' | true
'fixup! A commit message' | true
'squash!' | true
'squash! A commit message' | true
end
with_them do
it 'is true when commit message starts with "fixup!" or "squash!"' do
expect(commit_linter.fixup?).to be(is_fixup)
end
end
end
describe '#suggestion?' do
where(:commit_message, :is_suggestion) do
'A commit message' | false
'Apply suggestion to' | true
'Apply suggestion to "A commit message"' | true
end
with_them do
it 'is true when commit message starts with "Apply suggestion to"' do
expect(commit_linter.suggestion?).to be(is_suggestion)
end
end
end
describe '#merge?' do
where(:commit_message, :is_merge) do
'A commit message' | false
'Merge branch' | true
'Merge branch "A commit message"' | true
end
with_them do
it 'is true when commit message starts with "Merge branch"' do
expect(commit_linter.merge?).to be(is_merge)
end
end
end
describe '#revert?' do
where(:commit_message, :is_revert) do
'A commit message' | false
'Revert' | false
'Revert "' | true
'Revert "A commit message"' | true
end
with_them do
it 'is true when commit message starts with "Revert \""' do
expect(commit_linter.revert?).to be(is_revert)
end
end
end
describe '#multi_line?' do
where(:commit_message, :is_multi_line) do
"A commit message" | false
"A commit message\n" | false
"A commit message\n\n" | false
"A commit message\n\nWith details" | true
end
with_them do
it 'is true when commit message contains details' do
expect(commit_linter.multi_line?).to be(is_multi_line)
end
end
end
describe '#failed?' do
context 'with no failures' do
it { expect(commit_linter).not_to be_failed }
end
context 'with failures' do
before do
commit_linter.add_problem(:details_line_too_long)
end
it { expect(commit_linter).to be_failed }
end
end
describe '#add_problem' do
it 'stores messages in #failures' do
commit_linter.add_problem(:details_line_too_long)
expect(commit_linter.problems).to eq({ details_line_too_long: described_class::PROBLEMS[:details_line_too_long] })
end
end
shared_examples 'a valid commit' do
it 'does not have any problem' do
commit_linter.lint
expect(commit_linter.problems).to be_empty
end
end
describe '#lint' do
describe 'subject' do
context 'when subject valid' do
it_behaves_like 'a valid commit'
end
context 'when subject is too short' do
let(:commit_message) { 'A B' }
it 'adds a problem' do
expect(commit_linter).to receive(:add_problem).with(:subject_too_short, described_class::DEFAULT_SUBJECT_DESCRIPTION)
commit_linter.lint
end
end
context 'when subject is too long' do
let(:commit_message) { 'A B ' + 'C' * described_class::MAX_LINE_LENGTH }
it 'adds a problem' do
expect(commit_linter).to receive(:add_problem).with(:subject_too_long, described_class::DEFAULT_SUBJECT_DESCRIPTION)
commit_linter.lint
end
end
context 'when subject is too short and too long' do
let(:commit_message) { 'A ' + 'B' * described_class::MAX_LINE_LENGTH }
it 'adds a problem' do
expect(commit_linter).to receive(:add_problem).with(:subject_too_short, described_class::DEFAULT_SUBJECT_DESCRIPTION)
expect(commit_linter).to receive(:add_problem).with(:subject_too_long, described_class::DEFAULT_SUBJECT_DESCRIPTION)
commit_linter.lint
end
end
context 'when subject is above warning' do
let(:commit_message) { 'A B ' + 'C' * described_class::WARN_SUBJECT_LENGTH }
it 'adds a problem' do
expect(commit_linter).to receive(:add_problem).with(:subject_above_warning, described_class::DEFAULT_SUBJECT_DESCRIPTION)
commit_linter.lint
end
end
context 'when subject starts with lowercase' do
let(:commit_message) { 'a B C' }
it 'adds a problem' do
expect(commit_linter).to receive(:add_problem).with(:subject_starts_with_lowercase, described_class::DEFAULT_SUBJECT_DESCRIPTION)
commit_linter.lint
end
end
context 'when subject ands with a period' do
let(:commit_message) { 'A B C.' }
it 'adds a problem' do
expect(commit_linter).to receive(:add_problem).with(:subject_ends_with_a_period, described_class::DEFAULT_SUBJECT_DESCRIPTION)
commit_linter.lint
end
end
end
describe 'separator' do
context 'when separator is missing' do
let(:commit_message) { "A B C\n" }
it_behaves_like 'a valid commit'
end
context 'when separator is a blank line' do
let(:commit_message) { "A B C\n\nMore details." }
it_behaves_like 'a valid commit'
end
context 'when separator is missing' do
let(:commit_message) { "A B C\nMore details." }
it 'adds a problem' do
expect(commit_linter).to receive(:add_problem).with(:separator_missing)
commit_linter.lint
end
end
end
describe 'details' do
context 'when details are valid' do
let(:commit_message) { "A B C\n\nMore details." }
it_behaves_like 'a valid commit'
end
context 'when no details are given and many files are changed' do
let(:total_files_changed) { described_class::MAX_CHANGED_FILES_IN_COMMIT + 1 }
it_behaves_like 'a valid commit'
end
context 'when no details are given and many lines are changed' do
let(:total_lines_changed) { described_class::MAX_CHANGED_LINES_IN_COMMIT + 1 }
it_behaves_like 'a valid commit'
end
context 'when no details are given and many files and lines are changed' do
let(:total_files_changed) { described_class::MAX_CHANGED_FILES_IN_COMMIT + 1 }
let(:total_lines_changed) { described_class::MAX_CHANGED_LINES_IN_COMMIT + 1 }
it 'adds a problem' do
expect(commit_linter).to receive(:add_problem).with(:details_too_many_changes)
commit_linter.lint
end
end
context 'when details exceeds the max line length' do
let(:commit_message) { "A B C\n\n" + 'D' * (described_class::MAX_LINE_LENGTH + 1) }
it 'adds a problem' do
expect(commit_linter).to receive(:add_problem).with(:details_line_too_long)
commit_linter.lint
end
end
context 'when details exceeds the max line length including a URL' do
let(:commit_message) { "A B C\n\nhttps://gitlab.com" + 'D' * described_class::MAX_LINE_LENGTH }
it_behaves_like 'a valid commit'
end
end
describe 'message' do
context 'when message includes a text emoji' do
let(:commit_message) { "A commit message :+1:" }
it 'adds a problem' do
expect(commit_linter).to receive(:add_problem).with(:message_contains_text_emoji)
commit_linter.lint
end
end
context 'when message includes a unicode emoji' do
let(:commit_message) { "A commit message 🚀" }
it 'adds a problem' do
expect(commit_linter).to receive(:add_problem).with(:message_contains_unicode_emoji)
commit_linter.lint
end
end
context 'when message includes a short reference' do
[
'A commit message to fix #1234',
'A commit message to fix !1234',
'A commit message to fix &1234',
'A commit message to fix %1234',
'A commit message to fix gitlab#1234',
'A commit message to fix gitlab!1234',
'A commit message to fix gitlab&1234',
'A commit message to fix gitlab%1234',
'A commit message to fix gitlab-org/gitlab#1234',
'A commit message to fix gitlab-org/gitlab!1234',
'A commit message to fix gitlab-org/gitlab&1234',
'A commit message to fix gitlab-org/gitlab%1234'
].each do |message|
let(:commit_message) { message }
it 'adds a problem' do
expect(commit_linter).to receive(:add_problem).with(:message_contains_short_reference)
commit_linter.lint
end
end
end
end
end
end
# frozen_string_literal: true
require 'fast_spec_helper'
require 'rspec-parameterized'
require 'gitlab/danger/emoji_checker'
describe Gitlab::Danger::EmojiChecker do
using RSpec::Parameterized::TableSyntax
describe '#includes_text_emoji?' do
where(:text, :includes_emoji) do
'Hello World!' | false
':+1:' | true
'Hello World! :+1:' | true
end
with_them do
it 'is true when text includes a text emoji' do
expect(subject.includes_text_emoji?(text)).to be(includes_emoji)
end
end
end
describe '#includes_unicode_emoji?' do
where(:text, :includes_emoji) do
'Hello World!' | false
'🚀' | true
'Hello World! 🚀' | true
end
with_them do
it 'is true when text includes a text emoji' do
expect(subject.includes_unicode_emoji?(text)).to be(includes_emoji)
end
end
end
end
......@@ -313,6 +313,19 @@ describe Gitlab::Danger::Helper do
end
end
describe '#sanitize_mr_title' do
where(:mr_title, :expected_mr_title) do
'My MR title' | 'My MR title'
'WIP: My MR title' | 'My MR title'
end
with_them do
subject { helper.sanitize_mr_title(mr_title) }
it { is_expected.to eq(expected_mr_title) }
end
end
describe '#security_mr?' do
it 'returns false when `gitlab_helper` is unavailable' do
expect(helper).to receive(:gitlab_helper).and_return(nil)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment