Commit 58f4d50f authored by Piotr Stankowski's avatar Piotr Stankowski

Refactor commit message template evaluation

- Avoid evaluating variables that are not used in template.
- Avoid evaluating variables twice.
- Don't leave variable name in message when value is nil.
parent f2d560de
...@@ -29,48 +29,41 @@ module Gitlab ...@@ -29,48 +29,41 @@ module Gitlab
'target_branch' => ->(merge_request, _) { merge_request.target_branch.to_s }, 'target_branch' => ->(merge_request, _) { merge_request.target_branch.to_s },
'title' => ->(merge_request, _) { merge_request.title }, 'title' => ->(merge_request, _) { merge_request.title },
'issues' => ->(merge_request, _) do 'issues' => ->(merge_request, _) do
return "" if merge_request.visible_closing_issues_for.blank? return if merge_request.visible_closing_issues_for.blank?
closes_issues_references = merge_request.visible_closing_issues_for.map do |issue| closes_issues_references = merge_request.visible_closing_issues_for.map do |issue|
issue.to_reference(merge_request.target_project) issue.to_reference(merge_request.target_project)
end end
"Closes #{closes_issues_references.to_sentence}" "Closes #{closes_issues_references.to_sentence}"
end, end,
'description' => ->(merge_request, _) { merge_request.description.presence || '' }, 'description' => ->(merge_request, _) { merge_request.description },
'reference' => ->(merge_request, _) { merge_request.to_reference(full: true) }, 'reference' => ->(merge_request, _) { merge_request.to_reference(full: true) },
'first_commit' => -> (merge_request, _) { merge_request.first_commit&.safe_message&.strip.presence || '' }, 'first_commit' => -> (merge_request, _) { merge_request.first_commit&.safe_message&.strip },
'first_multiline_commit' => -> (merge_request, _) { merge_request.first_multiline_commit&.safe_message&.strip.presence || merge_request.title }, 'first_multiline_commit' => -> (merge_request, _) { merge_request.first_multiline_commit&.safe_message&.strip.presence || merge_request.title },
'url' => ->(merge_request, _) { Gitlab::UrlBuilder.build(merge_request) }, 'url' => ->(merge_request, _) { Gitlab::UrlBuilder.build(merge_request) },
'approved_by' => ->(merge_request, _) { merge_request.approved_by_users.map { |user| "Approved-by: #{user.name} <#{user.commit_email_or_default}>" }.join("\n") }, 'approved_by' => ->(merge_request, _) { merge_request.approved_by_users.map { |user| "Approved-by: #{user.name} <#{user.commit_email_or_default}>" }.join("\n") },
'merged_by' => ->(_, user) { "#{user&.name} <#{user&.commit_email_or_default}>" } 'merged_by' => ->(_, user) { "#{user&.name} <#{user&.commit_email_or_default}>" }
}.freeze }.freeze
PLACEHOLDERS_REGEX = Regexp.union(PLACEHOLDERS.keys.map do |key| PLACEHOLDERS_COMBINED_REGEX = /%{(#{Regexp.union(PLACEHOLDERS.keys)})}/.freeze
Regexp.new(Regexp.escape(key))
end).freeze
BLANK_PLACEHOLDERS_REGEXES = (PLACEHOLDERS.map do |key, value|
[key, Regexp.new("[\n\r]+%{#{Regexp.escape(key)}}$")]
end).to_h.freeze
def replace_placeholders(message) def replace_placeholders(message)
# convert CRLF to LF # Convert CRLF to LF.
message = message.delete("\r") message = message.delete("\r")
# Remove placeholders that correspond to empty values and are the last word in the line used_variables = message.scan(PLACEHOLDERS_COMBINED_REGEX).map { |value| value[0] }.uniq
# along with all whitespace characters preceding them. values = used_variables.to_h do |variable_name|
# This allows us to recreate previous default merge commit message behaviour - we skipped new line character ["%{#{variable_name}}", PLACEHOLDERS[variable_name].call(merge_request, current_user)]
# before empty description and before closed issues when none were present.
PLACEHOLDERS.each do |key, value|
unless value.call(merge_request, current_user).present?
message = message.gsub(BLANK_PLACEHOLDERS_REGEXES[key], '')
end
end end
names_of_empty_variables = values.filter_map { |name, value| name if value.blank? }
Gitlab::StringPlaceholderReplacer # Remove placeholders that correspond to empty values and are the only word in a line
.replace_string_placeholders(message, PLACEHOLDERS_REGEX) do |key| # along with all whitespace characters preceding them.
PLACEHOLDERS[key].call(merge_request, current_user) message = message.gsub(/[\n\r]+#{Regexp.union(names_of_empty_variables)}$/, '') if names_of_empty_variables.present?
end # Substitute all variables with their values.
message = message.gsub(Regexp.union(values.keys), values) if values.present?
message
end end
end end
end end
......
...@@ -58,6 +58,19 @@ RSpec.describe Gitlab::MergeRequests::CommitMessageGenerator do ...@@ -58,6 +58,19 @@ RSpec.describe Gitlab::MergeRequests::CommitMessageGenerator do
end end
end end
context 'when project has commit template with only the title' do
let(:merge_request) do
double(:merge_request, title: 'Fixes', target_project: project, to_reference: '!123', metrics: nil, merge_user: nil)
end
let(message_template_name) { '%{title}' }
it 'evaluates only necessary variables' do
expect(result_message).to eq 'Fixes'
expect(merge_request).not_to have_received(:to_reference)
end
end
context 'when project has commit template with closed issues' do context 'when project has commit template with closed issues' do
let(message_template_name) { <<~MSG.rstrip } let(message_template_name) { <<~MSG.rstrip }
Merge branch '%{source_branch}' into '%{target_branch}' Merge branch '%{source_branch}' into '%{target_branch}'
...@@ -381,6 +394,57 @@ RSpec.describe Gitlab::MergeRequests::CommitMessageGenerator do ...@@ -381,6 +394,57 @@ RSpec.describe Gitlab::MergeRequests::CommitMessageGenerator do
end end
end end
end end
context 'when project has commit template with the same variable used twice' do
let(message_template_name) { '%{title} %{title}' }
it 'uses custom template' do
expect(result_message).to eq 'Bugfix Bugfix'
end
end
context 'when project has commit template without any variable' do
let(message_template_name) { 'static text' }
it 'uses custom template' do
expect(result_message).to eq 'static text'
end
end
context 'when project has template with all variables' do
let(message_template_name) { <<~MSG.rstrip }
source_branch:%{source_branch}
target_branch:%{target_branch}
title:%{title}
issues:%{issues}
description:%{description}
first_commit:%{first_commit}
first_multiline_commit:%{first_multiline_commit}
url:%{url}
approved_by:%{approved_by}
merged_by:%{merged_by}
MSG
it 'uses custom template' do
expect(result_message).to eq <<~MSG.rstrip
source_branch:feature
target_branch:master
title:Bugfix
issues:
description:Merge Request Description
Next line
first_commit:Feature added
Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
first_multiline_commit:Feature added
Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
url:#{Gitlab::UrlBuilder.build(merge_request)}
approved_by:
merged_by:#{maintainer.name} <#{maintainer.commit_email_or_default}>
MSG
end
end
end end
describe '#merge_message' do describe '#merge_message' do
......
...@@ -1647,13 +1647,10 @@ RSpec.describe MergeRequest, factory_default: :keep do ...@@ -1647,13 +1647,10 @@ RSpec.describe MergeRequest, factory_default: :keep do
end end
it 'uses template from target project' do it 'uses template from target project' do
subject.title = 'Fix everything' request = build(:merge_request, title: 'Fix everything')
subject.compare_commits = [ request.target_project.merge_commit_template = '%{title}'
double(safe_message: 'Commit message', gitaly_commit?: true, merge_commit?: false, description?: false)
]
subject.target_project.merge_commit_template = '%{title}'
expect(subject.default_merge_commit_message) expect(request.default_merge_commit_message)
.to eq('Fix everything') .to eq('Fix everything')
end end
......
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