2019-12-12 10:08:41 -05:00
# frozen_string_literal: true
2021-03-12 07:09:33 -05:00
require 'gitlab/dangerfiles/title_linting'
2021-01-04 13:10:11 -05:00
2021-01-20 07:11:06 -05:00
module Tooling
2019-12-12 10:08:41 -05:00
module Danger
module Changelog
2020-06-18 11:08:45 -04:00
NO_CHANGELOG_LABELS = [
'tooling' ,
'tooling::pipelines' ,
'tooling::workflow' ,
'ci-build' ,
'meta'
] . freeze
2019-12-12 10:08:41 -05:00
NO_CHANGELOG_CATEGORIES = % i [ docs none ] . freeze
2021-06-02 08:10:05 -04:00
CHANGELOG_TRAILER_REGEX = / ^(?<name>Changelog): \ s*(?<category>.+)$ /i . freeze
2021-05-21 11:10:51 -04:00
CHANGELOG_EE_TRAILER_REGEX = / ^EE: true$ / . freeze
2021-05-14 05:10:24 -04:00
CHANGELOG_MODIFIED_URL_TEXT = " **CHANGELOG.md was edited.** Please remove the additions and follow the [changelog guidelines](https://docs.gitlab.com/ee/development/changelog.html). \n \n "
2020-08-20 23:10:16 -04:00
CHANGELOG_MISSING_URL_TEXT = " **[CHANGELOG missing](https://docs.gitlab.com/ee/development/changelog.html)**: \n \n "
2019-12-12 10:08:41 -05:00
2021-03-23 20:09:26 -04:00
OPTIONAL_CHANGELOG_MESSAGE = {
local : " 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. " ,
ci : << ~ MSG
2021-05-21 11:10:51 -04:00
If you want to create a changelog entry for GitLab FOSS , add the ` Changelog ` trailer to the commit message you want to add to the changelog .
2020-08-20 23:10:16 -04:00
2021-05-27 08:10:56 -04:00
If you want to create a changelog entry for GitLab EE , also [ add the ` EE: true ` trailer ] ( https : / / docs . gitlab . com / ee / development / changelog . html #gitlab-enterprise-changes) to your commit message.
2020-08-20 23:10:16 -04:00
2021-03-23 20:09:26 -04:00
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.
MSG
} . freeze
2021-05-14 05:10:24 -04:00
SEE_DOC = " See the [changelog documentation](https://docs.gitlab.com/ee/development/changelog.html). "
2020-08-20 23:10:16 -04:00
2021-02-24 07:10:54 -05:00
REQUIRED_CHANGELOG_REASONS = {
db_changes : 'introduces a database migration' ,
feature_flag_removed : 'removes a feature flag'
} . freeze
2021-03-23 20:09:26 -04:00
REQUIRED_CHANGELOG_MESSAGE = {
local : " This merge request requires a changelog entry because it [%<reason>s](https://docs.gitlab.com/ee/development/changelog.html # what-warrants-a-changelog-entry). " ,
ci : << ~ MSG
2021-05-21 11:10:51 -04:00
To create a changelog entry , add the ` Changelog ` trailer to one of your Git commit messages .
2020-08-20 23:10:16 -04:00
2021-03-23 20:09:26 -04:00
This merge request requires a changelog entry because it [ %< reason > s ] ( https : / / docs . gitlab . com / ee / development / changelog . html #what-warrants-a-changelog-entry).
MSG
} . freeze
2020-08-20 23:10:16 -04:00
2021-05-14 05:10:24 -04:00
CATEGORIES = YAML
. load_file ( File . expand_path ( '../../.gitlab/changelog_config.yml' , __dir__ ) )
. fetch ( 'categories' )
. keys
. freeze
class ChangelogCheckResult
attr_reader :errors , :warnings , :markdowns , :messages
def initialize ( errors : [ ] , warnings : [ ] , markdowns : [ ] , messages : [ ] )
@errors = errors
@warnings = warnings
@markdowns = markdowns
@messages = messages
end
private_class_method :new
def self . empty
new
end
def self . error ( error )
new ( errors : [ error ] )
end
def self . warning ( warning )
new ( warnings : [ warning ] )
end
def error ( error )
errors << error
end
def warning ( warning )
warnings << warning
end
def markdown ( markdown )
markdowns << markdown
end
def message ( message )
messages << message
end
end
# rubocop:disable Style/SignalException
def check!
if git . modified_files . include? ( " CHANGELOG.md " )
fail modified_text
end
if present?
add_danger_messages ( check_changelog_path )
elsif required?
required_texts . each { | _ , text | fail ( text ) } # rubocop:disable Lint/UnreachableLoop
elsif optional?
message optional_text
end
2021-05-21 11:10:51 -04:00
check_changelog_commit_categories
2021-05-14 05:10:24 -04:00
end
# rubocop:enable Style/SignalException
# rubocop:disable Style/SignalException
def add_danger_messages ( check_result )
check_result . errors . each { | error | fail ( error ) } # rubocop:disable Lint/UnreachableLoop
check_result . warnings . each { | warning | warn ( warning ) }
check_result . markdowns . each { | markdown_hash | markdown ( ** markdown_hash ) }
check_result . messages . each { | text | message ( text ) }
end
# rubocop:enable Style/SignalException
2021-05-21 11:10:51 -04:00
def check_changelog_commit_categories
changelog_commits . each do | commit |
add_danger_messages ( check_changelog_trailer ( commit ) )
end
end
2021-05-14 05:10:24 -04:00
def check_changelog_trailer ( commit )
trailer = commit . message . match ( CHANGELOG_TRAILER_REGEX )
2021-06-02 08:10:05 -04:00
name = trailer [ :name ]
2021-05-14 05:10:24 -04:00
category = trailer [ :category ]
2021-06-02 08:10:05 -04:00
unless name == 'Changelog'
return ChangelogCheckResult . error ( " The changelog trailer for commit #{ commit . sha } must be `Changelog` (starting with a capital C), not ` #{ name } ` " )
end
2021-05-14 05:10:24 -04:00
return ChangelogCheckResult . empty if CATEGORIES . include? ( category )
ChangelogCheckResult . error ( " Commit #{ commit . sha } uses an invalid changelog category: #{ category } " )
end
def check_changelog_path
check_result = ChangelogCheckResult . empty
return check_result unless present?
ee_changes = project_helper . all_ee_changes . dup
if ee_changes . any? && ! ee_changelog? && ! required?
2021-05-27 08:10:56 -04:00
check_result . warning ( " This MR changes code in `ee/`, but its Changelog commit is missing the [`EE: true` trailer](https://docs.gitlab.com/ee/development/changelog.html # gitlab-enterprise-changes). Consider adding it to your Changelog commits. " )
2021-05-14 05:10:24 -04:00
end
if ee_changes . empty? && ee_changelog?
2021-05-27 08:10:56 -04:00
check_result . warning ( " This MR has a Changelog commit for EE, but no code changes in `ee/`. Consider removing the `EE: true` trailer from your commits. " )
2021-05-14 05:10:24 -04:00
end
if ee_changes . any? && ee_changelog? && required_reasons . include? ( :db_changes )
2021-05-27 08:10:56 -04:00
check_result . warning ( " This MR has a Changelog commit with the `EE: true` trailer, but there are database changes which [requires](https://docs.gitlab.com/ee/development/changelog.html # what-warrants-a-changelog-entry) the Changelog commit to not have the `EE: true` trailer. Consider removing the `EE: true` trailer from your commits. " )
2021-05-14 05:10:24 -04:00
end
check_result
end
2021-02-24 07:10:54 -05:00
def required_reasons
[ ] . tap do | reasons |
2021-03-12 07:09:33 -05:00
reasons << :db_changes if project_helper . changes . added . has_category? ( :migration )
reasons << :feature_flag_removed if project_helper . changes . deleted . has_category? ( :feature_flag )
2021-02-24 07:10:54 -05:00
end
end
2020-08-20 23:10:16 -04:00
def required?
2021-02-24 07:10:54 -05:00
required_reasons . any?
2020-08-20 23:10:16 -04:00
end
def optional?
2021-05-14 05:10:24 -04:00
categories_need_changelog? && mr_without_no_changelog_label?
2019-12-12 10:08:41 -05:00
end
2021-05-14 05:10:24 -04:00
def present?
2021-05-21 11:10:51 -04:00
valid_changelog_commits . any?
2019-12-12 10:08:41 -05:00
end
2021-05-21 11:10:51 -04:00
def changelog_commits
git . commits . select do | commit |
commit . message . match? ( CHANGELOG_TRAILER_REGEX )
end
2021-05-14 05:10:24 -04:00
end
2021-05-21 11:10:51 -04:00
def valid_changelog_commits
changelog_commits . select do | commit |
trailer = commit . message . match ( CHANGELOG_TRAILER_REGEX )
CATEGORIES . include? ( trailer [ :category ] )
end
end
def ee_changelog?
changelog_commits . any? do | commit |
commit . message . match? ( CHANGELOG_EE_TRAILER_REGEX )
end
2019-12-12 10:08:41 -05:00
end
2020-08-20 23:10:16 -04:00
def modified_text
CHANGELOG_MODIFIED_URL_TEXT +
2021-05-21 11:10:51 -04:00
( helper . ci? ? format ( OPTIONAL_CHANGELOG_MESSAGE [ :ci ] ) : OPTIONAL_CHANGELOG_MESSAGE [ :local ] )
2020-08-20 23:10:16 -04:00
end
2021-02-24 07:10:54 -05:00
def required_texts
required_reasons . each_with_object ( { } ) do | required_reason , memo |
memo [ required_reason ] =
CHANGELOG_MISSING_URL_TEXT +
2021-05-21 11:10:51 -04:00
( helper . ci? ? format ( REQUIRED_CHANGELOG_MESSAGE [ :ci ] , reason : REQUIRED_CHANGELOG_REASONS . fetch ( required_reason ) ) : REQUIRED_CHANGELOG_MESSAGE [ :local ] )
2021-02-24 07:10:54 -05:00
end
2020-08-20 23:10:16 -04:00
end
def optional_text
CHANGELOG_MISSING_URL_TEXT +
2021-05-21 11:10:51 -04:00
( helper . ci? ? format ( OPTIONAL_CHANGELOG_MESSAGE [ :ci ] ) : OPTIONAL_CHANGELOG_MESSAGE [ :local ] )
2020-08-20 23:10:16 -04:00
end
2019-12-12 10:08:41 -05:00
private
2021-05-14 05:10:24 -04:00
def read_file ( path )
File . read ( path )
end
2019-12-12 10:08:41 -05:00
def categories_need_changelog?
2021-03-12 07:09:33 -05:00
( project_helper . changes . categories - NO_CHANGELOG_CATEGORIES ) . any?
2019-12-12 10:08:41 -05:00
end
2020-07-15 14:09:09 -04:00
2021-05-14 05:10:24 -04:00
def mr_without_no_changelog_label?
2021-02-24 07:10:54 -05:00
( helper . mr_labels & NO_CHANGELOG_LABELS ) . empty?
2020-07-15 14:09:09 -04:00
end
2019-12-12 10:08:41 -05:00
end
end
end