From 34d6a50171e63d8b25e062dcc46599f77d665186 Mon Sep 17 00:00:00 2001 From: Piotr Stankowski Date: Thu, 11 Nov 2021 17:54:10 +0100 Subject: [PATCH 1/5] Add squash commit template column in project settings table This commit adds squash_commit_template column in project_settings. It also adds this field to project's model. --- app/controllers/projects_controller.rb | 1 + app/models/project.rb | 1 + app/models/project_setting.rb | 1 + ...dd_squash_commit_template_to_project_settings.rb | 9 +++++++++ ...ash_commit_template_limit_to_project_settings.rb | 13 +++++++++++++ db/schema_migrations/20211111164025 | 1 + db/schema_migrations/20211111164047 | 1 + db/structure.sql | 2 ++ .../gitlab/import_export/safe_model_attributes.yml | 1 + 9 files changed, 30 insertions(+) create mode 100644 db/migrate/20211111164025_add_squash_commit_template_to_project_settings.rb create mode 100644 db/migrate/20211111164047_add_squash_commit_template_limit_to_project_settings.rb create mode 100644 db/schema_migrations/20211111164025 create mode 100644 db/schema_migrations/20211111164047 diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 5b17b75a963758..ace6fbbf184c16 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -452,6 +452,7 @@ def project_params_attributes :packages_enabled, :service_desk_enabled, :merge_commit_template, + :squash_commit_template, project_setting_attributes: project_setting_attributes ] + [project_feature_attributes: project_feature_attributes] end diff --git a/app/models/project.rb b/app/models/project.rb index 83eeb43bd21d23..b488aca018e3d4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -452,6 +452,7 @@ def self.integration_association_name(name) to: :project_setting delegate :active?, to: :prometheus_integration, allow_nil: true, prefix: true delegate :merge_commit_template, :merge_commit_template=, to: :project_setting, allow_nil: true + delegate :squash_commit_template, :squash_commit_template=, to: :project_setting, allow_nil: true delegate :log_jira_dvcs_integration_usage, :jira_dvcs_server_last_sync_at, :jira_dvcs_cloud_last_sync_at, to: :feature_usage diff --git a/app/models/project_setting.rb b/app/models/project_setting.rb index 6c8d2226bc9240..fc834286876f25 100644 --- a/app/models/project_setting.rb +++ b/app/models/project_setting.rb @@ -13,6 +13,7 @@ class ProjectSetting < ApplicationRecord self.primary_key = :project_id validates :merge_commit_template, length: { maximum: 500 } + validates :squash_commit_template, length: { maximum: 500 } def squash_enabled_by_default? %w[always default_on].include?(squash_option) diff --git a/db/migrate/20211111164025_add_squash_commit_template_to_project_settings.rb b/db/migrate/20211111164025_add_squash_commit_template_to_project_settings.rb new file mode 100644 index 00000000000000..6120a6ed0b41a4 --- /dev/null +++ b/db/migrate/20211111164025_add_squash_commit_template_to_project_settings.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddSquashCommitTemplateToProjectSettings < Gitlab::Database::Migration[1.0] + enable_lock_retries! + + def change + add_column :project_settings, :squash_commit_template, :text # rubocop:disable Migration/AddLimitToTextColumns + end +end diff --git a/db/migrate/20211111164047_add_squash_commit_template_limit_to_project_settings.rb b/db/migrate/20211111164047_add_squash_commit_template_limit_to_project_settings.rb new file mode 100644 index 00000000000000..578d2271d60138 --- /dev/null +++ b/db/migrate/20211111164047_add_squash_commit_template_limit_to_project_settings.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +class AddSquashCommitTemplateLimitToProjectSettings < Gitlab::Database::Migration[1.0] + disable_ddl_transaction! + + def up + add_text_limit :project_settings, :squash_commit_template, 500 + end + + def down + remove_text_limit :project_settings, :squash_commit_template + end +end diff --git a/db/schema_migrations/20211111164025 b/db/schema_migrations/20211111164025 new file mode 100644 index 00000000000000..409cc160b9eafd --- /dev/null +++ b/db/schema_migrations/20211111164025 @@ -0,0 +1 @@ +d78fe687517e14ff67dc76eff63391e33b73d29446d2a0445595175c7cd6806a \ No newline at end of file diff --git a/db/schema_migrations/20211111164047 b/db/schema_migrations/20211111164047 new file mode 100644 index 00000000000000..30e0875cf73f0e --- /dev/null +++ b/db/schema_migrations/20211111164047 @@ -0,0 +1 @@ +c8ed7f8c0f818156dba9c25be848da97d4eb6dbf0aa9c48f87e940f3ca0967d9 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index c9b3243f02536e..e9a274f0129be9 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -18284,7 +18284,9 @@ CREATE TABLE project_settings ( warn_about_potentially_unwanted_characters boolean DEFAULT true NOT NULL, merge_commit_template text, has_shimo boolean DEFAULT false NOT NULL, + squash_commit_template text, CONSTRAINT check_3a03e7557a CHECK ((char_length(previous_default_branch) <= 4096)), + CONSTRAINT check_b09644994b CHECK ((char_length(squash_commit_template) <= 500)), CONSTRAINT check_bde223416c CHECK ((show_default_award_emojis IS NOT NULL)), CONSTRAINT check_eaf7cfb6a7 CHECK ((char_length(merge_commit_template) <= 500)) ); diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 9daa3b32fd121f..2e047290d2cdc3 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -562,6 +562,7 @@ Project: - autoclose_referenced_issues - suggestion_commit_message - merge_commit_template +- squash_commit_template ProjectTracingSetting: - external_url Author: -- GitLab From 42375dd47442ec61f05e7b1eed52f447a30429bc Mon Sep 17 00:00:00 2001 From: Piotr Stankowski Date: Thu, 11 Nov 2021 18:20:28 +0100 Subject: [PATCH 2/5] Create default squash commit message using customizable template Use new squash_commit_template field (when set) to create default value for squash commit message. Changelog: added --- app/models/merge_request.rb | 6 +- ...ge_request_merge_commit_template.html.haml | 2 +- ...message.rb => commit_message_generator.rb} | 44 ++-- ...er_customizes_merge_commit_message_spec.rb | 51 +++- .../commit_message_generator_spec.rb | 243 ++++++++++++++++++ .../merge_commit_message_spec.rb | 219 ---------------- spec/models/merge_request_spec.rb | 7 + 7 files changed, 325 insertions(+), 247 deletions(-) rename lib/gitlab/merge_requests/{merge_commit_message.rb => commit_message_generator.rb} (83%) create mode 100644 spec/lib/gitlab/merge_requests/commit_message_generator_spec.rb delete mode 100644 spec/lib/gitlab/merge_requests/merge_commit_message_spec.rb diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 37776cacaeb3e2..14e797327fe92a 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -1317,7 +1317,7 @@ def target_branch_exists? def default_merge_commit_message(include_description: false) if self.target_project.merge_commit_template.present? && !include_description - return ::Gitlab::MergeRequests::MergeCommitMessage.new(merge_request: self).message + return ::Gitlab::MergeRequests::CommitMessageGenerator.new(merge_request: self).merge_message end closes_issues_references = visible_closing_issues_for.map do |issue| @@ -1340,6 +1340,10 @@ def default_merge_commit_message(include_description: false) end def default_squash_commit_message + if self.target_project.squash_commit_template.present? + return ::Gitlab::MergeRequests::CommitMessageGenerator.new(merge_request: self).squash_message + end + title end diff --git a/app/views/projects/_merge_request_merge_commit_template.html.haml b/app/views/projects/_merge_request_merge_commit_template.html.haml index 185b730e0bb1ce..ba170af9de5afb 100644 --- a/app/views/projects/_merge_request_merge_commit_template.html.haml +++ b/app/views/projects/_merge_request_merge_commit_template.html.haml @@ -12,6 +12,6 @@ %p.form-text.text-muted = s_('ProjectSettings|Maximum 500 characters.') = s_('ProjectSettings|Supported variables:') - - Gitlab::MergeRequests::MergeCommitMessage::PLACEHOLDERS.keys.each do |placeholder| + - Gitlab::MergeRequests::CommitMessageGenerator::PLACEHOLDERS.keys.each do |placeholder| %code = "%{#{placeholder}}".html_safe diff --git a/lib/gitlab/merge_requests/merge_commit_message.rb b/lib/gitlab/merge_requests/commit_message_generator.rb similarity index 83% rename from lib/gitlab/merge_requests/merge_commit_message.rb rename to lib/gitlab/merge_requests/commit_message_generator.rb index 2a6a7859b33667..c420385b7c1ba2 100644 --- a/lib/gitlab/merge_requests/merge_commit_message.rb +++ b/lib/gitlab/merge_requests/commit_message_generator.rb @@ -1,31 +1,21 @@ # frozen_string_literal: true module Gitlab module MergeRequests - class MergeCommitMessage + class CommitMessageGenerator def initialize(merge_request:) @merge_request = merge_request end - def message + def merge_message return unless @merge_request.target_project.merge_commit_template.present? - message = @merge_request.target_project.merge_commit_template - message = message.delete("\r") + replace_placeholders(@merge_request.target_project.merge_commit_template) + end - # Remove placeholders that correspond to empty values and are the last word in the line - # along with all whitespace characters preceding them. - # This allows us to recreate previous default merge commit message behaviour - we skipped new line character - # before empty description and before closed issues when none were present. - PLACEHOLDERS.each do |key, value| - unless value.call(merge_request).present? - message = message.gsub(BLANK_PLACEHOLDERS_REGEXES[key], '') - end - end + def squash_message + return unless @merge_request.target_project.squash_commit_template.present? - Gitlab::StringPlaceholderReplacer - .replace_string_placeholders(message, PLACEHOLDERS_REGEX) do |key| - PLACEHOLDERS[key].call(merge_request) - end + replace_placeholders(@merge_request.target_project.squash_commit_template) end private @@ -55,6 +45,26 @@ def message BLANK_PLACEHOLDERS_REGEXES = (PLACEHOLDERS.map do |key, value| [key, Regexp.new("[\n\r]+%{#{Regexp.escape(key)}}$")] end).to_h.freeze + + def replace_placeholders(message) + # convert CRLF to LF + message = message.delete("\r") + + # Remove placeholders that correspond to empty values and are the last word in the line + # along with all whitespace characters preceding them. + # This allows us to recreate previous default merge commit message behaviour - we skipped new line character + # before empty description and before closed issues when none were present. + PLACEHOLDERS.each do |key, value| + unless value.call(merge_request).present? + message = message.gsub(BLANK_PLACEHOLDERS_REGEXES[key], '') + end + end + + Gitlab::StringPlaceholderReplacer + .replace_string_placeholders(message, PLACEHOLDERS_REGEX) do |key| + PLACEHOLDERS[key].call(merge_request) + end + end end end end diff --git a/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb index 06795344c5c816..67a232607cd019 100644 --- a/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb +++ b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb @@ -7,19 +7,26 @@ let(:user) { project.creator } let(:issue_1) { create(:issue, project: project)} let(:issue_2) { create(:issue, project: project)} + let(:source_branch) { 'csv' } + let(:target_branch) { 'master' } + let(:squash) { false } let(:merge_request) do create( :merge_request, - :simple, source_project: project, - description: "Description\n\nclosing #{issue_1.to_reference}, #{issue_2.to_reference}" + target_project: project, + source_branch: source_branch, + target_branch: target_branch, + description: "Description\n\nclosing #{issue_1.to_reference}, #{issue_2.to_reference}", + squash: squash ) end - let(:textbox) { page.find(:css, '#merge-message-edit', visible: false) } - let(:default_message) do + let(:merge_textbox) { page.find(:css, '#merge-message-edit', visible: false) } + let(:squash_textbox) { page.find(:css, '#squash-message-edit', visible: false) } + let(:default_merge_commit_message) do [ - "Merge branch 'feature' into 'master'", + "Merge branch '#{source_branch}' into '#{target_branch}'", merge_request.title, "Closes #{issue_1.to_reference} and #{issue_2.to_reference}", "See merge request #{merge_request.to_reference(full: true)}" @@ -35,8 +42,8 @@ it 'has commit message without description' do expect(page).not_to have_selector('#merge-message-edit') first('.js-mr-widget-commits-count').click - expect(textbox).to be_visible - expect(textbox.value).to eq(default_message) + expect(merge_textbox).to be_visible + expect(merge_textbox.value).to eq(default_merge_commit_message) end context 'when target project has merge commit template set' do @@ -45,8 +52,34 @@ it 'uses merge commit template' do expect(page).not_to have_selector('#merge-message-edit') first('.js-mr-widget-commits-count').click - expect(textbox).to be_visible - expect(textbox.value).to eq(merge_request.title) + expect(merge_textbox).to be_visible + expect(merge_textbox.value).to eq(merge_request.title) + end + end + + context 'when squash is performed' do + let(:squash) { true } + + it 'has default message with merge request title' do + expect(page).not_to have_selector('#squash-message-edit') + first('.js-mr-widget-commits-count').click + expect(squash_textbox).to be_visible + expect(merge_textbox).to be_visible + expect(squash_textbox.value).to eq(merge_request.title) + expect(merge_textbox.value).to eq(default_merge_commit_message) + end + + context 'when target project has squash commit template set' do + let(:project) { create(:project, :public, :repository, squash_commit_template: '%{description}') } + + it 'uses squash commit template' do + expect(page).not_to have_selector('#squash-message-edit') + first('.js-mr-widget-commits-count').click + expect(squash_textbox).to be_visible + expect(merge_textbox).to be_visible + expect(squash_textbox.value).to eq(merge_request.description) + expect(merge_textbox.value).to eq(default_merge_commit_message) + end end end end diff --git a/spec/lib/gitlab/merge_requests/commit_message_generator_spec.rb b/spec/lib/gitlab/merge_requests/commit_message_generator_spec.rb new file mode 100644 index 00000000000000..4de5c9b9c82ae0 --- /dev/null +++ b/spec/lib/gitlab/merge_requests/commit_message_generator_spec.rb @@ -0,0 +1,243 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::MergeRequests::CommitMessageGenerator do + let(:merge_commit_template) { nil } + let(:squash_commit_template) { nil } + let(:project) do + create( + :project, + :public, + :repository, + merge_commit_template: merge_commit_template, + squash_commit_template: squash_commit_template + ) + end + + let(:user) { project.creator } + let(:merge_request_description) { "Merge Request Description\nNext line" } + let(:merge_request_title) { 'Bugfix' } + let(:merge_request) do + create( + :merge_request, + :simple, + source_project: project, + target_project: project, + author: user, + description: merge_request_description, + title: merge_request_title + ) + end + + subject { described_class.new(merge_request: merge_request) } + + shared_examples_for 'commit message with template' do |message_template_name| + it 'returns nil when template is not set in target project' do + expect(result_message).to be_nil + end + + context 'when project has custom commit template' do + let(message_template_name) { <<~MSG.rstrip } + %{title} + + See merge request %{reference} + MSG + + it 'uses custom template' do + expect(result_message).to eq <<~MSG.rstrip + Bugfix + + See merge request #{merge_request.to_reference(full: true)} + MSG + end + end + + context 'when project has commit template with closed issues' do + let(message_template_name) { <<~MSG.rstrip } + Merge branch '%{source_branch}' into '%{target_branch}' + + %{title} + + %{issues} + + See merge request %{reference} + MSG + + it 'omits issues and new lines when no issues are mentioned in description' do + expect(result_message).to eq <<~MSG.rstrip + Merge branch 'feature' into 'master' + + Bugfix + + See merge request #{merge_request.to_reference(full: true)} + MSG + end + + context 'when MR closes issues' do + let(:issue_1) { create(:issue, project: project) } + let(:issue_2) { create(:issue, project: project) } + let(:merge_request_description) { "Description\n\nclosing #{issue_1.to_reference}, #{issue_2.to_reference}" } + + it 'includes them and keeps new line characters' do + expect(result_message).to eq <<~MSG.rstrip + Merge branch 'feature' into 'master' + + Bugfix + + Closes #{issue_1.to_reference} and #{issue_2.to_reference} + + See merge request #{merge_request.to_reference(full: true)} + MSG + end + end + end + + context 'when project has commit template with description' do + let(message_template_name) { <<~MSG.rstrip } + Merge branch '%{source_branch}' into '%{target_branch}' + + %{title} + + %{description} + + See merge request %{reference} + MSG + + it 'uses template' do + expect(result_message).to eq <<~MSG.rstrip + Merge branch 'feature' into 'master' + + Bugfix + + Merge Request Description + Next line + + See merge request #{merge_request.to_reference(full: true)} + MSG + end + + context 'when description is empty string' do + let(:merge_request_description) { '' } + + it 'skips description placeholder and removes new line characters before it' do + expect(result_message).to eq <<~MSG.rstrip + Merge branch 'feature' into 'master' + + Bugfix + + See merge request #{merge_request.to_reference(full: true)} + MSG + end + end + + context 'when description is nil' do + let(:merge_request_description) { nil } + + it 'skips description placeholder and removes new line characters before it' do + expect(result_message).to eq <<~MSG.rstrip + Merge branch 'feature' into 'master' + + Bugfix + + See merge request #{merge_request.to_reference(full: true)} + MSG + end + end + + context 'when description is blank string' do + let(:merge_request_description) { "\n\r \n" } + + it 'skips description placeholder and removes new line characters before it' do + expect(result_message).to eq <<~MSG.rstrip + Merge branch 'feature' into 'master' + + Bugfix + + See merge request #{merge_request.to_reference(full: true)} + MSG + end + end + end + + context 'when custom commit template contains placeholder in the middle or beginning of the line' do + let(message_template_name) { <<~MSG.rstrip } + Merge branch '%{source_branch}' into '%{target_branch}' + + %{description} %{title} + + See merge request %{reference} + MSG + + it 'uses custom template' do + expect(result_message).to eq <<~MSG.rstrip + Merge branch 'feature' into 'master' + + Merge Request Description + Next line Bugfix + + See merge request #{merge_request.to_reference(full: true)} + MSG + end + + context 'when description is empty string' do + let(:merge_request_description) { '' } + + it 'does not remove new line characters before empty placeholder' do + expect(result_message).to eq <<~MSG.rstrip + Merge branch 'feature' into 'master' + + Bugfix + + See merge request #{merge_request.to_reference(full: true)} + MSG + end + end + end + + context 'when project has template with CRLF newlines' do + let(message_template_name) do + "Merge branch '%{source_branch}' into '%{target_branch}'\r\n\r\n%{title}\r\n\r\n%{description}\r\n\r\nSee merge request %{reference}" + end + + it 'converts it to LF newlines' do + expect(result_message).to eq <<~MSG.rstrip + Merge branch 'feature' into 'master' + + Bugfix + + Merge Request Description + Next line + + See merge request #{merge_request.to_reference(full: true)} + MSG + end + + context 'when description is empty string' do + let(:merge_request_description) { '' } + + it 'skips description placeholder and removes new line characters before it' do + expect(result_message).to eq <<~MSG.rstrip + Merge branch 'feature' into 'master' + + Bugfix + + See merge request #{merge_request.to_reference(full: true)} + MSG + end + end + end + end + + describe '#merge_message' do + let(:result_message) { subject.merge_message } + + it_behaves_like 'commit message with template', :merge_commit_template + end + + describe '#squash_message' do + let(:result_message) { subject.squash_message } + + it_behaves_like 'commit message with template', :squash_commit_template + end +end diff --git a/spec/lib/gitlab/merge_requests/merge_commit_message_spec.rb b/spec/lib/gitlab/merge_requests/merge_commit_message_spec.rb deleted file mode 100644 index 884f8df5e562c7..00000000000000 --- a/spec/lib/gitlab/merge_requests/merge_commit_message_spec.rb +++ /dev/null @@ -1,219 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Gitlab::MergeRequests::MergeCommitMessage do - let(:merge_commit_template) { nil } - let(:project) { create(:project, :public, :repository, merge_commit_template: merge_commit_template) } - let(:user) { project.creator } - let(:merge_request_description) { "Merge Request Description\nNext line" } - let(:merge_request_title) { 'Bugfix' } - let(:merge_request) do - create( - :merge_request, - :simple, - source_project: project, - target_project: project, - author: user, - description: merge_request_description, - title: merge_request_title - ) - end - - subject { described_class.new(merge_request: merge_request) } - - it 'returns nil when template is not set in target project' do - expect(subject.message).to be_nil - end - - context 'when project has custom merge commit template' do - let(:merge_commit_template) { <<~MSG.rstrip } - %{title} - - See merge request %{reference} - MSG - - it 'uses custom template' do - expect(subject.message).to eq <<~MSG.rstrip - Bugfix - - See merge request #{merge_request.to_reference(full: true)} - MSG - end - end - - context 'when project has merge commit template with closed issues' do - let(:merge_commit_template) { <<~MSG.rstrip } - Merge branch '%{source_branch}' into '%{target_branch}' - - %{title} - - %{issues} - - See merge request %{reference} - MSG - - it 'omits issues and new lines when no issues are mentioned in description' do - expect(subject.message).to eq <<~MSG.rstrip - Merge branch 'feature' into 'master' - - Bugfix - - See merge request #{merge_request.to_reference(full: true)} - MSG - end - - context 'when MR closes issues' do - let(:issue_1) { create(:issue, project: project) } - let(:issue_2) { create(:issue, project: project) } - let(:merge_request_description) { "Description\n\nclosing #{issue_1.to_reference}, #{issue_2.to_reference}" } - - it 'includes them and keeps new line characters' do - expect(subject.message).to eq <<~MSG.rstrip - Merge branch 'feature' into 'master' - - Bugfix - - Closes #{issue_1.to_reference} and #{issue_2.to_reference} - - See merge request #{merge_request.to_reference(full: true)} - MSG - end - end - end - - context 'when project has merge commit template with description' do - let(:merge_commit_template) { <<~MSG.rstrip } - Merge branch '%{source_branch}' into '%{target_branch}' - - %{title} - - %{description} - - See merge request %{reference} - MSG - - it 'uses template' do - expect(subject.message).to eq <<~MSG.rstrip - Merge branch 'feature' into 'master' - - Bugfix - - Merge Request Description - Next line - - See merge request #{merge_request.to_reference(full: true)} - MSG - end - - context 'when description is empty string' do - let(:merge_request_description) { '' } - - it 'skips description placeholder and removes new line characters before it' do - expect(subject.message).to eq <<~MSG.rstrip - Merge branch 'feature' into 'master' - - Bugfix - - See merge request #{merge_request.to_reference(full: true)} - MSG - end - end - - context 'when description is nil' do - let(:merge_request_description) { nil } - - it 'skips description placeholder and removes new line characters before it' do - expect(subject.message).to eq <<~MSG.rstrip - Merge branch 'feature' into 'master' - - Bugfix - - See merge request #{merge_request.to_reference(full: true)} - MSG - end - end - - context 'when description is blank string' do - let(:merge_request_description) { "\n\r \n" } - - it 'skips description placeholder and removes new line characters before it' do - expect(subject.message).to eq <<~MSG.rstrip - Merge branch 'feature' into 'master' - - Bugfix - - See merge request #{merge_request.to_reference(full: true)} - MSG - end - end - end - - context 'when custom merge commit template contains placeholder in the middle or beginning of the line' do - let(:merge_commit_template) { <<~MSG.rstrip } - Merge branch '%{source_branch}' into '%{target_branch}' - - %{description} %{title} - - See merge request %{reference} - MSG - - it 'uses custom template' do - expect(subject.message).to eq <<~MSG.rstrip - Merge branch 'feature' into 'master' - - Merge Request Description - Next line Bugfix - - See merge request #{merge_request.to_reference(full: true)} - MSG - end - - context 'when description is empty string' do - let(:merge_request_description) { '' } - - it 'does not remove new line characters before empty placeholder' do - expect(subject.message).to eq <<~MSG.rstrip - Merge branch 'feature' into 'master' - - Bugfix - - See merge request #{merge_request.to_reference(full: true)} - MSG - end - end - end - - context 'when project has template with CRLF newlines' do - let(:merge_commit_template) do - "Merge branch '%{source_branch}' into '%{target_branch}'\r\n\r\n%{title}\r\n\r\n%{description}\r\n\r\nSee merge request %{reference}" - end - - it 'converts it to LF newlines' do - expect(subject.message).to eq <<~MSG.rstrip - Merge branch 'feature' into 'master' - - Bugfix - - Merge Request Description - Next line - - See merge request #{merge_request.to_reference(full: true)} - MSG - end - - context 'when description is empty string' do - let(:merge_request_description) { '' } - - it 'skips description placeholder and removes new line characters before it' do - expect(subject.message).to eq <<~MSG.rstrip - Merge branch 'feature' into 'master' - - Bugfix - - See merge request #{merge_request.to_reference(full: true)} - MSG - end - end - end -end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 92ef9aeb2d8c4a..102800bcca28a4 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -178,6 +178,13 @@ it 'returns the merge request title' do expect(subject.default_squash_commit_message).to eq(subject.title) end + + it 'uses template from target project' do + subject.target_project.squash_commit_template = 'Squashed branch %{source_branch} into %{target_branch}' + + expect(subject.default_squash_commit_message) + .to eq('Squashed branch master into feature') + end end describe 'modules' do -- GitLab From 17b9aa471e864734c7857f918e25da8147212c70 Mon Sep 17 00:00:00 2001 From: Piotr Stankowski Date: Thu, 11 Nov 2021 20:12:26 +0100 Subject: [PATCH 3/5] Add squash commit template field in project settings and docs This change allows editing project's squash_commit_template in project settings. Additionally it adds documentation on squash commit template feature. --- .../projects/_merge_request_settings.html.haml | 2 ++ ...ge_request_squash_commit_template.html.haml | 16 ++++++++++++++++ .../project/merge_requests/commit_templates.md | 17 ++++++++++++++++- .../squash_commit_message_template_v14_6.png | Bin 0 -> 26472 bytes .../project/merge_requests/squash_and_merge.md | 4 ++-- doc/user/project/settings/index.md | 2 +- .../projects/_merge_request_settings.html.haml | 2 ++ locale/gitlab.pot | 6 ++++++ spec/views/projects/edit.html.haml_spec.rb | 16 ++++++++++++++++ 9 files changed, 61 insertions(+), 4 deletions(-) create mode 100644 app/views/projects/_merge_request_squash_commit_template.html.haml create mode 100644 doc/user/project/merge_requests/img/squash_commit_message_template_v14_6.png diff --git a/app/views/projects/_merge_request_settings.html.haml b/app/views/projects/_merge_request_settings.html.haml index c5a25bec6eb3d5..728ff597860efb 100644 --- a/app/views/projects/_merge_request_settings.html.haml +++ b/app/views/projects/_merge_request_settings.html.haml @@ -12,5 +12,7 @@ = render 'projects/merge_request_merge_commit_template', project: @project, form: form += render 'projects/merge_request_squash_commit_template', project: @project, form: form + - if @project.forked? = render 'projects/merge_request_target_project_settings', project: @project, form: form diff --git a/app/views/projects/_merge_request_squash_commit_template.html.haml b/app/views/projects/_merge_request_squash_commit_template.html.haml new file mode 100644 index 00000000000000..51617bc027f1ad --- /dev/null +++ b/app/views/projects/_merge_request_squash_commit_template.html.haml @@ -0,0 +1,16 @@ +- form = local_assigns.fetch(:form) + +.form-group + %b= s_('ProjectSettings|Squash commit message template') + %p.text-secondary + - configure_the_squash_commit_message_help_link_url = help_page_path('user/project/merge_requests/commit_templates.md', anchor: 'squash-commit-message-template') + - configure_the_squash_commit_message_help_link_start = ''.html_safe % { url: configure_the_squash_commit_message_help_link_url } + = s_('ProjectSettings|The commit message used when squashing commits. %{link_start}Learn more about syntax and variables.%{link_end}').html_safe % { link_start: configure_the_squash_commit_message_help_link_start, link_end: ''.html_safe } + .mb-2 + = form.text_area :squash_commit_template, class: 'form-control gl-form-input', rows: 8, maxlength: 500, placeholder: '%{title}' + %p.form-text.text-muted + = s_('ProjectSettings|Maximum 500 characters.') + = s_('ProjectSettings|Supported variables:') + - Gitlab::MergeRequests::CommitMessageGenerator::PLACEHOLDERS.keys.each do |placeholder| + %code + = "%{#{placeholder}}".html_safe diff --git a/doc/user/project/merge_requests/commit_templates.md b/doc/user/project/merge_requests/commit_templates.md index b615c86288cdba..c1a8754b4a2e82 100644 --- a/doc/user/project/merge_requests/commit_templates.md +++ b/doc/user/project/merge_requests/commit_templates.md @@ -31,7 +31,7 @@ This commit message can be customized to follow any guidelines you might have. To do so, expand the **Merge requests** tab within your project's **General** settings and change the **Merge commit message template** text: -![Custom commit message for applied suggestions](img/merge_commit_message_template_v14_5.png) +![Custom commit message for merge commit](img/merge_commit_message_template_v14_5.png) You can use static text and following variables: @@ -49,3 +49,18 @@ Empty variables that are the only word in a line will be removed along with all Merge commit template field has a limit of 500 characters. This limit only applies to the template itself. + +## Squash commit message template + +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345275) in GitLab 14.6. + +As a project maintainer, you're able to configure squash commit message template. It will be used during merge with +squash to create squash commit message. It uses the same syntax and variables as merge commit message template. + +![Custom commit message for squash commit](img/squash_commit_message_template_v14_6.png) + +Default squash commit message can be recreated using following template: + +```plaintext +%{title} +``` diff --git a/doc/user/project/merge_requests/img/squash_commit_message_template_v14_6.png b/doc/user/project/merge_requests/img/squash_commit_message_template_v14_6.png new file mode 100644 index 0000000000000000000000000000000000000000..b4b982354672a66eda75221886a277080bb677b3 GIT binary patch literal 26472 zcmeAS@N?(olHy`uVBq!ia0y~yV9sD*V3gosVqjoccG9Vdfq{Xuz$3Dlfr0M`2s2LA z=96Y%P+;(MaSW-5dvmwE#`pVw_7C6pN`6m!Z>y@JsG{WJl=Ob)k|R1*M#IIpgz_>&NM;@9*9|dp2#x42^h31_p-r;>#aM2njuU z|Ni~Ump9kP?`P!}v#I$Ju6RZ3G_q(`$+?4z;8#_8WZfs0G{`h$RQ|*6Y2M->! zE`O(^r#Edq0|P@t+NBb;?w%f%MLUb1&$BAkQc_B~zpplSSBc`HQ>RX;dQYpUto-`= z`up$q>;FHQ?0@IZog+zxf|Zq(J9q9}x^$^wa6x%_d2#XSvu9NoUAc1QXty}OjKzlH z=jWW;cy4Y^=fAvc+qQ4Z=hwZuzJ7jt`|$&f%=`cUtL}6;6vEkhXu^aE`)YqzRac*% zZ@>Rwu$Ot>of#&XlcrA(|Nidoa^Klu0Rav!nVFfz&(D4R_U+xJrKfu&jpx}`A2YG5 z`m$p7?Atdtr;F>wymgw?IvesoPDk?rR3>FqYKlkt7zw7Jc)1&mFwybb&=llNt z{(OT(r{#Wg+xTRwetvrT;lqc8&h1r|mA-SWOl@uFX5L)xFVD~4f8@xKmX?+S2OKs= z)I2=IYHq%LWAbq$UWqeDzn8wgmV0~K+Vu1D;^N}C#r4jdJ)1ghN&No0-{0OQOW)s7 znEdC*$9ePSF|+f<>@Lf#th5ZS`|=_%K0cnGzdvqo)!Dh$=GNBH7hkltwDibYUpsz$ zdGz)?iT28m7~AtIN5^tRnPdsf%Vs_NF3 zOgT9@W%s_E4@b5w`kQ`wn(pyFS!JdA`g(Ko?W@Dr3k6nH{d#kA^WMFCtG~UmEPoeM z*3QDf(6Ie-Nr8!piOQnr?Rlc2q8}@4!o$Pm`rTb_Z_oe#_xpWSq0WsPKduU09TXJw z#LCRW+4=Fex3||uZI!YpNXX25IZZeE(4j*e8X6iJDk?6nuBi_WH2(VaOHxwO%gf7W z`kz04bfdRLL`Qq8O!A(tmwRu|&#jt5M~@siGt2b#!DeXK#(+*qH(b>6i>sDPYty?>bpC9QI*4EbM<>j3` zdGd6<*i&&O)4Y~8Ha1S3I`!DGW0I1R-D0{?yGm9zHa2#3d6krWxwSR>CZ|zXclXt+ zS36G}I&|pz_3i2B=N0jGXqcFoSXk_E?G`gNHGTN-VO?FFp`oE})Rq(H&)?sYDO}9? z&wTlUrkL-b@lbKygdXkZW;lS*GY5FVDPfgJbUmv%(?(eS!j?KsVWUVVdrSS6d zDp{4j5)oV*u`%hvfyRp$F9t97oBIByfV1=AZgKrjKWjQ&l2TK*mcG9B;>8QcW;Vm; zg1I?4&(6$TynMO3wAzuRk8Qltd-m=BBSXni-wd=oL4HwssvnhJQAw7?U>EOr5$G2|Ta;!)4^OKXp>FLko z>;G=uvSmy8`+KUYs*#bA+qP}<@fH`K9=p5DFm&tIt(TYk^YifZbac!xPWP*+`LlXy z#meC2et&;|@9*s`Eh|f#wtU~MTesrtel87O?q~o1kMZUTb-y_mu3V9^F3YI_fjyfy z7Z(&1#KzuTKEKW?<%Gb>5Ub2fOFn$~uxgc7+JY@xx2pThNXXBR4-Q_uYSpTYFVD`- zKK}gk(xA-T+}!W)?q0rp`Sa(`y*)iMa<>;fJ@xPJ@6&nKD?_S2JaAO{_3QQewQJXc z@?hz!E0>r1v$L=m@OET8IWf^Z@6L&H=j3cEKAf1S+^Jz|`t{Y-)m2qh_v?PIoiu6E z!i5i?ot=I7&>U911zP`S1-@Z+otH8j(P;kAZ zz{J$lw(QM~eYL;&!HwynB0o zo}8@yrZxQ8(Fuyqese4eKR-Lm$jtWR*Dt^McCn$MQ!^JVS#l&sX8Y#N%8P!LN7&W> zo6~Wl<=~tT$>$c8zPS+?89B3K&z?PcaeIDzc^SNI`sU4>r%e+Rym{-EUFD}EhYvR& zfBf>rix)3nii(JYgoJF+wY9ZPU2S7yGb7GL<#vyCYUw)$ z^XT#8nVD)TDgqb3y}i9X|NgnMGp9}!m6zw=+?1M>^r%BndC%Ux>1Ss>eemGIgBPAE zlX7ow`}^LkSeJW~nIM9=LeO+wliMw}WU0qu(w}%G>O`1R7-^DoX%!Ij` zzjryFStKSZ$}4TwqdVp8(b^Tax99u2I4#@|ySr?_Wk*EdeNdqfs5U~zQ1p;q?Gjk-(R1p&CQ2TPFDZ^?r!y`CnqmlxUjqY zy_{uH%gRevuB6QC5BxHPr@DCF8n$2^VI@hQu*Ko44;=CRwss9$^tJqBbKBb3F7KTf zX z_Wyn?o;ma8mzS4k_NbPhR}$QKaN^piy;WNSG*Yr}PUB6gij0UjGv9u`u8EmhTD-YM z!<*>HH|q}h1g~>c{_S6TMr2p{d%Kz+1=}Xra)O$X`3oOvsA=oytl6+(LdTacUuFn9 zO`ST`%F4<|YDe*NK0#GAwNv+OkA*ya@?%J{?WZxs-Em>E)I!>KFUA^zpzS?SDUfziwOIEG=^>X?Ad3LqCa&K=_ zn)u@1-`~7_a>dghJ~-I?`^(GZ%*>PXZd|^2G4b4g!`ph6mXRt#hM~K5?K-2oR!d9k zj8FBK7mBlEB#X39=wvxMI=;TPc4l1cu98H*W&2j%-k$HhG-#s7mj?%#l{RhJGG%RU z%6~;x@2Qg~KYsl9FyDgK*47+x1_p)-W#9RQclOo(zO>Z4x2NaI)vLk5!JR9@*T=b- z)cpAH;K2h`?`b=B>^NZ;>*#W(^q8o2Sjn3k6D#*kP0Ywxv2bBvVBp8U-|xSE@#2JC z>BhRhzuf!fEG;cNk3L$pN=sH&HaIx=+svoOe*XI9=H@ofuJ+f*$H#+Knpj%?Jk~4y z_xJbvd#k@+Uhe<*&z~z-ug(wRcp1f45@A3bWi>XXaXO4__ZQT`yM3Ja5kQ>EV@?KmY#zZk~7N z#j|H>x4XMntk9^h|9|S#sYQ#Fii?Z={rzv=a{DH)v}xP6zTV!yuU493v`jj} zAuBt#rG@32*4x=xmzSMg7rPr&b&HFa|NHav(xprDY^#MPZr!@IvC%Ol<;l&>>ApTb zVQZtV?kauF=AM6LMPTi(FO?r3xfT>`*uA?tAwj|Vqoi@#mv?t}uUn_5s`|8Be_sOE zOl!ttJ(8Pietr_ujf&Y>q^hLkXc~E(dyGo0GJ1GC=7u1c|upo4Gn5wF( zrKRPrUAtz@npOSnjig0ELSW#Pl9Uv_6_ zaeMWH=Kb9vTB-T+J>A{X_H}!9?)-Ukb9#LK*Erp%EiVqWa(9dC%b8|PsgY%1VEALK z@Z_&_?dP-Rl9H0wu3cNTYSo#&ESvaU{`by$n$5$&z_3S|qwl|)o}OO6oNZFwoD+BM z)cpDJvFQI{UIqpRhuX8nE2TjFNV_Uudl_aB%K_Ta6vW@rJb9b_xUzfSn;RRQ_kEFH zziCs^!$Yk*ckcWox?qNf3KtL0nKNgmtj{=gYO1z={63xdUk@#cpPgxBW`E-Se-e=cW3+`|Wh{_a-;5ssEAA*!MI4d5%xx z-6oHpK^ardud?1B_wz@;x-ZM|=Fz$=C92(_p{tww{oUQu)AgT!{^_!0^=j?=UziU*|7`#BiSV=1xH!3eZBELwY)yqG zHZ~qy;MhFl`pug+#l*y}UAwm4{NTKs8~Dp3jwF42e7yhe-MdeLH>o0^*5jO#gaFJ4yhLxY5 zwDZfy?Wx$fYu7H{*=EA(ej;40cV?fRYd!tvrJZ?qt(0b27OM&BMsGWF>Qqwym+T(aX#0c%SU<>hJ43RE$haWNa!nw70Vh&b6uh zbZ>99tE=m-UAy+}+b3&XmZDbu|KH!o$NR-aMYm>LTvY%6U-kEQvHR<63nik{c*564 z9o^I0-+zBs>1+M{f1G%Ar>46Ek|k9iWN6@mA;;=?r&mZQd3jY#v^&CL|;>r zlasTvzdt-Q^y{~8a%MRa^UwEpcJ8eBm^584_S_uH;IOc?++5qbKNZHt#xrjU39gOV zxhZ~sU0`6~`FXaNFJ6?iEL!5Az`@I_tD&)B%a%D7g-VxQFRoe}v(rdH;laDRy8{CQ znb~+GY$`S^x@cGQ#3L~9BB)KXH~Zh8p9>c*jEszIYip~kt9xU&U`2IvQc@D1jKzk! zzrQ9-nDF}g`tGBT4jgdc=kM1HUS`01>C&Yq-+YvU!ovD`dTgq{Ja!JL3 z$TQ!*e)wQf{LCjcRrT_-CnqOgym(PdOY82g(&Yc=zbsj@RG*Zs+hyY^u{&yKMd!0 zKajSxpYMHEYgO6SYcbZR4qlC#+`)A!Yr2g0o{0f}7VWuqGpP7*MEkaBvs|8ReszwY zajoB-v(_?Gjnf-^6ZpAzvrha!`}wvf8HXn8Ox)V|VSim&a}4j(w1=Btom*3Pt$lIP zI(3oRuQ>L}Ukr+!y8lZ=l9yv>wD;My!gFMNug;vtzAioAX8yFukgJCR-mX5lZGMS~ z+9IKeH}V`){`AK#dsA2Qy7J^n{n*{xF69<%Ol`e)?(RQ#H>Y(T&I?Sgoi5qAUN%@S z8xk|-w1bQUD0K$4(^oSF>mj~m{)(N&hweA?ce40_me@n_xHWm zH$9WDQky#IUXS)Ir=?eT?R=Pz?aPjwefY9*_bMLKQ2UoDHp};KeSAoI`Li8mxnDp1 zxia0+-?)2Kxqs&62Ml%|+VXq;*$GP4$~n4(PcO+yU%K;A$ff`9o~Mh2&VI=?jW_$@ z{fic>cihT8`gZV?9RGR3a zARut@a65m$jOC}7%je&^c~ezI<<0&5@fsrAvak1bED6#KUb=E+<%0u_rLV7T&A6EK z?#|B8(9ql4^Y<4$gt&@Z$3ReJ=-idYHQZi zS+j1fi{1U}+uPqq#p9<;ow{`C(v_=L#qF*7`tk8`ZZVyJ;9zfm|LYeoCO%8b%X|0k zU0y~;#qV!#Ykz;+ntMA;L*&`>=l_4bUhmw-^YYHl;;O1$Z*t3g`*M|E>5FR4Sm2^` zb6@T5wQJu#IXPKdOUud0>B(}}Cr_S~yuWArtTZQQ&AxsA7Q6S)cratagadmN7Ilm3 z_sQ8--Puv7si|pQ{x0X{rlpf72gkTho%H>dT^ znk9Ao-Pc}O>#+Fv{|^thbBpUu(OthkT}(`D+Vtu7cbC8K*54<yPlbgiuUN67TU_67mdVLer(XU0`&->_PQpKrl@}S)Rn^tA zGcrIe`c0dPZg0!g4qNl$-rnEl_iH9knzSkV`nqkow{P9NDJmwGcV|c8lM@ryty_0~ zzWx2J+0z>v8~5(D{hk+Z3Oqn{BU)Ji%gHx+kuKfAq$Bo;!uV1^CcYojCL#^EV_U*I(^WpIG`St&fipOus zyE|*9Yjbkx-(O$Z`h|2%O}B=vjpCKH3W<%i1r3?1xa2+0EZ_lPLk=H=m;ajeUZJ3{`5;r_z?cTfL+ zzw-I+Cbk~e6d0(aPE|6Z1%C%tESG~c;E)Q-!|*d@(+(Lw0upLG4CzE zQ)Za_YhQ)6@>br*GQC?vpR?G<7MXnfp(CzjB_92wG~>{*!alh&D|4al{T3fwyjNUV zzn0N>dZD!(YsHM3=lS<9xx`*fI(_b9MD$nd`s#G=-gHZ)|GxaYtY3aDxTL)3&ZSw! z8(a6E&D2+oDtK{o%H6`{&$r~)yyN|K=J=in6+8Lp1ARQNBl|J}GY)m$T<4c2b@J!b zqe>h1wRy>>|9xEkX~u&H^`#ryS+)PXnR)#y`-Q7fpPx)$_xIYp)Bl(5Hv0cm^U}3v zQg!c+U1K|xTwY{v|6xgfG;?w6!SBk^b+sl7%ccKr()#%7zbdK0i=zDIf42~(++Lj(!Hod>YMNd4uy}jAl*`=kWm8|Oj)qH<<*Ve{{hnIKno;|zD-b%^Iohy5Jd;9vibLVc{ zSlBOTd+OAwV|}vOX=&FMxpwo(S}j?=e1E2~UP)DQ&zF{Z*Z%raSX6ZB;zdcb9EoB_FRiIzp`n_ZnweQyvu4gTy#H+V z>eZbdv9Yny(YJ5joN4QSVP|oAc6N4YX{o&Y{9Ctf{rOX))Ya3oWy=;&LvZ23g|lbR z?mvI}eR|BXFhN)Es}EQnKYrZZ)pe{_Iz1&NB|H1|>(|=$mP+dC%eQPXN!y#0oLv3= zovgUHyNju*>B^NW`}+EPjKjmj{r&xIZEelX&8w?_U%3*ZRQ2smWL@3A8HULvrKPz! zIaOa?9GtUSL1|-zP2L>~ZEfx3E1c_U&qA8-o1Nw=IMP`uU`H8>udJS zO-na!EIdEY*4W6%Ew5kS-@kwIZ1?k7f*;OF8cQN_V*VT4<9?` zrXk|y;!^VZ+FIv!J~uZvkgXRNv0h&GkukmV=%c{JZZl`jym|9x>5B`F8X|nMR#z@u z*pPg@PfSeg%(m{Ho-NzAfB*RL;y7<&$%HFJ6wQ9C` zJ{t?mmh9{24jpQ;eI6bZWR!J9Y&M)9PqgGs6diDDC_qVs}AHST#D>rM#%j~Qy zDG7;^cXwv~?Yi_Jy>SiyqZ#*i$WAv(`?l@XvlZ!!*8ch`a9gVI?$hJ?9Uh@Gzt8O7 zId|f$#$~4@{4f7F!?mq@V_(AA6=AYDTXnv$_?&#R@7mqDU)FqSjpyGgdeuWnYS#g) z3r89vP?eL+qZrG^?73U=T|RT_UxaVi>Z*OY$oSaUXxI(Z5Q4>UgPh=IM>(j{`u)wJUd^c zB^72at?>9geR0G!o-fAEg@k)Zr+w&U7EGf_SWSuby<>vQ^URc?wyN$nEyU&?afOrxpyVl z?bhGad-s3x$@=S+%4un7d%_PdS+*=JG}P6lva&L1W5wxdx;|4wv{V;Onk1yFtGg|^ ztZdtz#SAV}CQrV+x4Qh>+O=!n-rZe(b9>tQbq=6@=c-k&Uhci~#A)G%1q&Yd&Mr*M z$~v_#EI2s%zR~oA8O#}JX=znezgoG)XRLbl>eZx4LUR4)rLV5+t^V%j>iYHN<>kr8 z`)cg=M{m#T?CkWOuJ`rRr&qVPuV1)O5!CiCetz!gks~1?d;U1DUAtCw(VaUn&kmg0 zuwg?&T!Cb?t5l4ZtMHlg=gkca0>bMneOqD~BClP$=Cshj&@eDMdiSnfyQ;sxTkb#q z+?g{v+S%uZh6HUetw?q&F#;gJzKhT>6I%Dn?piFzkdBX^SaZ* z33m(5M5lQe8ynBJtv+UF7#kbA_65((MbJwB`Sa)ADwBS(YlMb|TJN4d{ex8G%_U2g z6hA+gnwsjVGU?PQFSCf#L09%3T->r|(V|70)6c7^sfC4ycOOl9R-m~0pyrdOPrtsp z+MS!6n0W9`)fEoc_GJ^5-Sh76t9^NCscrSQH`mrmOUp?A(V6hoQRcJscZb_ZA``38= zsj?NExcKF1vokx_g{kS6hwGX?{S^Lo{?iV%^54bpOE+J%s#EW;)wYgz?bg-)Quc0N zcBG{Kst%3yTeijTp8lM#RjbDAlhv$M+Mg>={+#;lSXS@(zr9e@M9 z_rJ(~nz`QDg*ldIKTnm^$0aMmrTq8ZH(rpMQ5R?ls%UDT^M)$8VB9`B&{p5{0E^+cym-yU{i>gk6! zdbn?TPHbA~xily~KHl8id|S&;pQ6ITpC6CQ=il2?`RU0?&@Ada+h_%WGcyd8O-)U0 zY-)afdK$dkue7+>y6}-pOw64xFE5`xb7o19X7TmF+Eb_W&4s6LoT~i$Vo?1`5#J9t zH>aNo4(MUKKKJRb`R;!%*Ue#D*iU7C0<~bB)~}y(NB;fncXNF6 zER-gBEq!}?yS!l%OZ1|QYE3CIS9al*A-A6Non7PcmwCz?BaNt=j~+ehl{V*-GRe5L zB~wdFYq|e?KR>@T=?x;K;Xy&G>ZknJEPYXb|DU9s9Gm)oHM+WMm;2ApySFED+q4cT zn{x$`*=*H20!lKB3=Jdq)%lJlWu(Tr&d%>*p0(Y`pxs${PS<$_4MyNFa76p?KUs1shRF7 zs!7`t9~Y;GUOi==ec_SfHc%N4YO9{*UskgH$GQ7)D-OxON-I&aS|2X`*VvHter?iu zvu8#P+J35k1;0OXNQib1E?qh0OL|=5YOd4SM@uh#((>%|&?(beSaXo1B}bsqgM8Dm6M>SnS@Pzwf7-u5Rz&PCIGo*(-yWe|vrX{hyzoUA}yI z8LY3bpJtl6Uq)I|^5nm5GQTAhrf(GGYBe=AeG|RYo#W{=uZt|bJw04pTr)N|e^ozJ zR8-{U=hxTYUtUR)fMKuZ|RXS z`SQiC_LqTrp|s^(HFH*FW#yaQ+s^2U@!UMz>Y|jmeC3M^E-p55>uZ01>lV{BGBWDw z>6x-N>xg^7nR27c71MM#T@c>z--Oj`>C{IzzI;0WJ3=yRr9fa-;^A<=*>g89efIZC z(6_gLuNb*+p1%Ci6O$b`Ht{OW7c0+PdV6*6KB4*UE}5|^GQf2P_mMM8bCtSkqHJzmo>JMR5t>$-rFv=R@5kxiw|*)$pI5q}aej2r zl|aF4X2pK@H-DM0KH62nx7Uv8th`LC)57iB%QG@Ee0+Qo6BC1ig8t5Lcz0`Sc3RrA zmBGt5ZQdLl6f|S^re9xQm%qKWb$9uDF;UUGyGmdG`uh6s@9)!J-`F$5JFWcP9Zh}x zbUoYsEA483#q6*9dugfn&!0afB_-?sd~{DuT^geG_i?{{QBjeWw)W)7lU0Q}7cNYU ziHWJHsfmn?3=9km3lsZndiTHq1yxm7t*K#QVRF`GJ%8Qay?r}(&YYT;mzGYNG-)2J2}4~E!7pFax=3%|er|I>7P zq(;=%Un0F`?!6`Z3mzUiJKOw!&MC?7wwn*y2J91Lzq7rgzyJKz)!~mHJ@WGM%Dc1U z;6F3P?~M7A`uop!i|aQvH5nNj7Z(-f-QRci2=}|gi(VugNEpJzD?z% z*|V$P-?N?g;{190wnbA-4GaXjkNRw=`}1St8wn{1i97r2_wU)WXVD_3!0(`5^TL1? z>(-r{bLh+B9xk%Dk?61S~pSo-^}BGX3Utee*eE+2j{Em33ke-hZ_8Uv{|d; z#b>)P_VwxScg&eMC;q3#=A`3GdoFoxoE8x29&2f}b4tgg&)WBYh3z;0>KecL@5Gzx z@1CWKh05=oTK%Ew+w{Lr>aTWRe2{e7Y{MmSwP#&k+DA)+qP|yWm7SdbTsCA%1$V!S z%~?U)(}KEIpUxZ)ynZjDSLFQNqh8wU*F3AqQ4~~t(pBdA=V$M&TMiQQLj`BY%?tf= zD?ZO@-HRW0_k0n*>MHu^w&&7kyO>Nx?F1{WU#wcGk!vyE>cG)}$6uF3%NIBPIC6K- zp-;EEvO1^k`%sq^Id_WJYL(M{s+-bTuD34jP`&h%IXC>*ZTpj}ZdCjJ6H9cJ6}>2= zReR}T#m~;?8(%)vWDWK1Q@iGM@0s;U_UChB!nip&0>Z=XOI`#-M$X)}&FptHzmBo- zX8nCX9z8iZ`QgKdP74bzEO5-dwWYAMRMtH2&i{Yk?Vs5#DSUivZ+HB~$x9b6e*F4% z^`9Rf54ZE5pKb2{&Cj&&&fU8+XE*lAS}$9+EG<9({oA*`F8=duEQ_D{`1(6u<&Hf;AL}c zDnH%Wm~3Wdwzujl7dLnEs#!A8!orgclaIBuv|PDzMM7GdU)Ji&gM-aJp?{Yuu?mL- z1HyQgV8l?$)ha+uGXR_)D8DW(!}t&&_Rix7)7x9R-O$K0I7+{@f++{FL?6 z7H06*1mD^2aeZCv=g*%(i|(SMUq5QlkQ0Fmo|U*e@AH+IXe6WnNaZo1dSY zY+U!J;+y2+hK2^`c0S#xEg45T1Z}IoU7mn_ZPA&Ym58eO>HL=id4A?;mVt7uSzlvvA?Xb+Ojpo*xjA zofWsIqVUy~m7hKp>FVnG`=4JIyW8lg@2_+@(1NAEr=Rw0XlG62kDr(I^z({>=>CVY zJ*$rTNuOU{Tm5I}wpm5#)^k6`o~^LCWw`R|^@W_$=a$cWxoVyKYk>`4AHEK*QfJ#K zu}=Q=hsQIgOlk7cu6_7xBOkBbD@PYzy=^(oo_EjWt54riTkO5!(D&CFHk-fC?@#$! zY9{d@>A`NpWsATg=7(=gPiGN1yLV~ltz8%S;$`c;{cN z{{ET$Z%w@Of-fn);$>nra{0%X_UJdy6PmbsYTAV_)60!NSC||+T=BW}%Yp-K@AIdn zE?T=QC!#yw-zL69<>8k*-?aa{O@G&49#IkNy<$s-NB5meQc7LE`u8go-=48C4^5r7 zYSSnG#nET){9n?)dEeB3^Y=?%O*nq+ug$T8rIV*jt@!-wHc#DGf%C_Ezn7`M zeOL=xQupw1`}K9P+VPC<55&vlZmTXV{5VO~``Vhw!%r_R<-fkrxm`<3Ynk6%t$4=& zAqy8S)Q{T}P~vWS!2Zb18BLPE9}CyL()v+;$KnU$!EZkpmMmYsnSart-QUFBXTO~{ zXU?5C)E3kzRgTl@U{ z{C+vxsC|MA2cjol@$}YKH5clt-#uM^--+0LQvVnQWd3)uFgcc$eY>_c+Er=dym@i= z_tmD>zvY>AJznPR{u_Ur7cX2WC?;0+>r3Xwh@7OPPxJr(X@AV`#mr!`a)r~IJx|*9 zwVgVjEORT$kkn(?g@Bfs`*uE)=ntA2abkO4O z1MSb|vb($f|GZ0PUJb}7hCiE41ZDj1pZ{O;e{#$GU0k7YaaYP>#Y{JE4^8mz7F|B+ zU%9>g<`uVm!Z+kZaR2W+=2Bv|EaCXmo~QfDjK3XE-?PFm_WkBRM?Rl<^osd^q1vJg zw|CzDf5PL0XeP&$V^uTtzHd!V|6;K^>C^wCOGQE~qci@P+&XyH?O*+mwNpLzTTd;~ z&|1D~+q(Sdzom<1c}zl&lwL|bF68g_e0s-`Yix(sv)%0ZVleSF2gpNb7j-_kv41Y( ze0TB3aXgv%9xfL(Q)jwf^>NF|UH9;LUQVupO_}=l&&>Y6wwYcK(h>}G0uQiq*;gCK zHm>@Voi5b7HFW>y?d999R7_L;bpOkO-IL?^mrsALeEt8tU733(&s_KFo3~`$YPEvDa-P56=JN`99?=`pX*eX_A1pN&6lgRrL$LmE4+66e>P|j zz=wAxg~uj#FxmJV%jsUbX05Q+=lbq*zmHw->sWQPRQmk#xwQcSA#tZ43krsIzMk>z z)v7;!x?57!J}kd*d*|=P@Ytkr=iz6 zmNG`m8+~3iSxE5dlxsep4Ew!w3V*9*^-O>HeDB&Ep4Lr!L6NsW(o|Mvuk4qSD^vB4 zh|PM*6&G@augp|s5h#&$R2M32Si5-Tt`M(Z#_Kn{WnKTSZHn->4G&H)Sbp&G?XvPq z!H+(({jZ7NcT_uQk`ZIzvX+h)KXk%{Wka`Jh=>k#?G!iJo9h#G^V)}!e$nk^{)K6J zE15wHQ#NN?JURL3^NIb7Po8|S=l;8=ym|Rc6a$nNomhT!v8}JH@6}F?tsA`d`8%Vv z=+}R*`t+(?=4k09^+?feZCRVP>h@d-nOdpII#sNS>D&CSOQ%b2-ivzuf7zCqcdu7H z{uy#J4&=`ZJ+t`wTtpru7CrU6xU;>Las9l=ZI{IF`RlHp9F#dV<>p2gyY~3Y8_^n7 zp1nD5r+wJ6;<@Te{Z;#H|Lt*8`dVy0Y3)@ZE#DVe`@Tdxx;dk1%b``)CztMzJE^kB zUT*)6PfJ!TSYx(%g_YgvL-!))cAPsVvL(96_%G{n3vQ58WK3&U&gzI+W8%9tlv_Ki zde5)GW1|1Q8Gk=+t+QvHYDkpH64$(!*_s<1C-0hh-%qdb_p@mMt?#d2tVqlE-sa^V zTX~vg`Azou2KL8`i?*%4qkPq6OI3<@Z0XZhccD+GvwF6M&YXXD<+`=eb)PD$61VPZ zxW9SpzwgRXLKCNWoVmjaN_`Ax+tsHkC4(}D!)1%uRcs)KIiQwj1d2PlNV`PtTcFt4 z*=e4C@67r0>({NDwjPvz8u%wTC>R(Sefsn%>*l7VHFKG4R)%QBf7_LQe%@T`@^$Iw z=arR}Nl8h4x(f2x2gX$^R(N=M?W+0tsezGMMn>kzYUxW~B#+NF&j&5MEq#6MlPFl> z!Aln|WL#f2chja%XJ?yRvK?RczEDR;XUq2O)Ad!B3OF@q9%S=g)ut=8cXBm#>e{r_Y~FO-%G+cbOy{VAz>f zSy}n*+qbl|G)qfMM@L7OEoo<^lxEpf7I}JlMnv40V_97H`>pvio2R|f=22U-Op}gq z+|-;hefoU+dOJ(djPcUi-{0=;Dt-L1LP|J^ zXXnN1*SBxlRJB$xFg8|p{&{t!;NZ)K$;V_Y3KA|Za`oXX{y3|njbA=4GBPtQ?b_bz z@|*iyRHl0M%UCAm<;h7%M8w6_eR|><8XBsvudnpz^UpV-`5;eLh|hW+v~}gm%%Y-A z9xA6!pT2zc>fXA)yQWXSK2h0yTi)HOx3^5U%?!~>Rel!j;p?lbr}ysu{{7RYO`AD$ z=I-6U7rXc0h*H}OuIa5W*`&+t-LuDUu9c{?^zE(L*Kgd2U}tZ?awUY9mp4BC{@vZ> z))p2sX3Ur}eR_Io>dzlPPLw~>vA5s9ZClyrXJ^kuZ;V*8WXX{|W(EcdiF@9EE1~0#J z_ik)V%oFQkwW(fPw`@`KnQ>s+hUfaGCMJLW{`H@4_xH!gWP5x2Z6JdgOr}5E8FIF( zqvOSum6LbvvfAraT4NWlHTBl*+x;?@MtXYhetv%Lfe7{`}?3o4dQeXV-#4biquM^nx>|PkVcNzrM0kSa9a7S1n!a*RBl?4xV|;%F4>+%a4!A6FowrqqU7CFI~Or>*uGZt-X51iV3>g zH*PdEGP0`tl#-T~R#;dV6m;qGa{rmD_Uzen>eMN{*j*Xf*{30GfM=KH9Q=4`sW)hq z(mU&t;^Nb%Pv72Ky?yD@r8{?8UUu;G^i-N?u-??fWLNq7x|f$g!>8h6Vw;2&mHhqv z?dI>V{r&C3hk~0$cJuAi&Pd40&b@HqLhSCczaNjwzqzs`NOS)A|JwfdK>l-hJnOlf z`>_`n7vH?j)adX{*IDmI>B~z?y{GG)$qo(uV{5KVvjlhGwud z)UQ8qz~QITQApPMx0H#Q88o09ze)nU7W=^di?f%xIe{BtKg6rLuXw~XEp|{?v1ZK_ zXxn3QPSzZw)Gh~wioJ0Qmo+bLY;?@ZT2-S7&an+C!N~~;4|bKl=HcOq%U=|5`QXLV zr(Yjx!m05=hoie@8-6{b4Lj8hlwlJ zu01N8Q?FcFS}H0k`c2pD`2E+(cV*;XS80CAkG}uiKd)uyfu&gu{Velhe^;OEwMSR* zUbpSbd(OVX!oqHG{b~AtU+6?{d-LdM_ot;FwpWQur=Fc3ZSPdjq-=*C!)ss9)2L~VJY z`;Fn9i_*n+cX#jq@u*ufc-fix_Vxe&{r#kSZ~df6lR{UAot*wp_RihAYuBuK;{E?% z+@1=h$Aysi_uX-WF| zd8eo8PS)k&<(+Go+?I3l_jkMN(+!i4^<20R@bgl6^20-|yUX8C)30^jTlKZ-!-K{e zP4lz=rXPB_%tKUk>aJb8-23Hr?%1)RHGF-Xsfo#y%&%-byu9ta(rWR)_KAp#f3Daw zRXhAsh0QV-PtVSdqM{7Peop3RSN*E`L(dG1jDEdZz5WT;Yx8#(7CKi{{P_3xw`JWooeznL zu(bf}tgJ_m9rK%MlzMAR=BLuGrOTJEkJ-6t&6=E7S61$={yuHBub0=Zf`^BedQac7 zb!+Nr>yj4OPhFJGQ`OHZ0S`SSky|F^g2U;lsqf7!wZ(O-Oj~_xJbn^X=n9L$9uj-Mun+IajOGGV#Sr zmbiq6Z~v#c=)?&RYwO*L&TRr0KRi5~oRp-br1a;{ACal2OZz)Ie*FD@|MO?iGETeS zZ#IK=4hL3N{%qrwe)Q;(iK%JzmlqFTzdmik$IIK>*QckWbLH}7X~QI!=jY~jYFJrW z+1S)PIWcknzhBvji4Xf^tuI}=)F*2_ZPu))@bK%qN?)%GUcPGeYT>}ZK+uNu#qRw} zf>utRJbC{7`>(F9?#%e~5$&=Z6rH-tbzILr{v|akSISmaA-$XvG3SE6` zd%k_j34yfBpw;Qs-`*seoo(S1j@pvp=yLGjL8it9+qRi0DLI9PZuOq7r|jOB@@RL2 z4j(V?-onRjp`laf&b@nMWAe;OR(AI6jEomAU$(ZjMMXxIzPnTT|KH!Bl_igkbY^8` zS(m+0ShPBP{lEA1|FyNWHf`Ma^UG!bnSG+7qQAer?e6Rp!?$N%PP??k^DBS)(W6Iye}CWK-5nen`u5`D_Wk?q z=bxYMq0-JLyQ}v1Hpre0yP6%-r(d6IUCt+G16t~A_T#Cq*3@;;+d&IdT3cH|70>^_ z_y3Vp4pG*{;D=_pEgbGxB0z2I=Z^6SFd(e5-dLR zJn8SRua}qmmzR{xu_#QszApB4{fEr#?CbV#w&dL0R998^|K9Ci`|8gY)lTK*&wuOt zKS=7JriItip#1#z|Nnj8|Hf@^_4jo%XU=?cUBuMX)ZN|P-rl~n^y`EP6PVfgbfUMd zsr~&;*1GJ@_Wb*YTDkM@@B91a=4Sc29}myJ+$SU>V^jF($a%ZpZ~pxJY+e4&#L)2P zo6YC9Y~A|(-Cb!JnVA2-=g6!Kk&=wDI=`rDegy|>Pt%e%P9H8eDIwps43 zijPW@e?}}_y7cMOr^V0DtzEm;)ZAR&u4c#P&BlUDmo2lhu-I^R&YU@uCQXvkoL>6* z+1X}x{yu5*a|aJ9>g)IWE`5G}zPx?ipCg^ZlP68umUs8ng9jIu`AUQKXTG_!bMwN5 zi6J3Z*2V6QiH)5(*G^koTSFt`%8J0Ms;X0`PQ7~d3N%@Bb5m+=?pw&_Y_}c>#fcu* z*2S(~zI=PaL8jEyr@JX@bXS{fP=yUX6rFif^8c`;$agblg3 z&77SNgEr*NKdQg~k5R^j1wTJOpFVBcvL#D2yn0;ZapdLayWKi5(QE1M^7r#B z3Ky+g_wN1v|NkEM+pmk=9p>)d-f?cOwRysUhWGn^>*?#~AMca3kpu1Sb1C`q;v#4- z@+?#D@Njb_rKGd7Ojn1mPusWd?!MaS@bL0)Z*E3KMLm1=OjK0V(9rNrYKL|CyOy@L zb8{>&A3CIDV^ec?SE>EqFOz4@iV6q_aC19$X{mRs)5Dd)%Y%Z0cbC1@+T8Qx>C>%Q zS66M?WTdOBtF){1^|Yl+RX4MW=BnLPoj6xS@a2maJ#x0UPMy-Swyu7AYwNjl=g!;< z4GIzx7T#R(F==H;)q?|#xwp5Sy!$LVJpB9T^Y*uI-dr2GdD)H~6|b+Yby`?(VuE7$ z`ncLJFD_0}^}cfDN>Fg{%!Lm=|9tcF6WhlM8&y?Rx&Gt#5)Z9Y5bQjfwDbJYrlzL+ z{eR8O%%&ZGyfyQ38|NdJl21=g-u-|7-rVD+gAchVugZ6hU>a8 z!K})LM}Z%zjx=9ew16XQ<*lkh(db&QR(@SwrcaI{i#jw8DJN)ptz{SG=QCu<;BaS{ zDBy9={@L5I-71P-Hmv=(uYTgqo2lvP<)3cPyy+ViHHHb+%}T*Tr)!jhW*1_~YZ_3l}c@`}gm*ZQD+sJn8alZS;0NdAl=Di$X&}{`~o~ zE$3#Fz@hur)AeG7I$f46TUJm~5POCL<$b!R42RhKAR!UE^4|{eIo0b_=G!-K?p43v+tJbS;X?s8clVDUKb9?1YYC{Y-{028HqkdYI9Ng8gs1xMnxC6? zgTcy`EBEgGtG#|t)8)5s-^N$JHIb$567_V)hMFW~g~`FV3=7YGnkgXwhtG>Ux`}^hc`A*Kxy1Ke&&z?Q`nVlni zecW8D(p9^6@18bo+Tz8F*RIVC4Hb=Z`6n*a>7pTW>17EoFR!q$@K1BMg9i^DK781^ z?2Se7GoSEq@zRYv_wL;b3kz#&Yg-+*HpxK3g?shVrJ@1?2?+@a+1a~GUS9h2si0Z8eZC}+_tvFW&ftJ?R$jHctei>bl9wq7PuMdx}4fXauegA&E ziqO?-*ZBDO!a_rJP!V$c@!Gh(QPI)24;?ym z`SR!6`TJctc=`C0TGV`IELghKwD_5i%tb2;i<)0Amj~^Z7IZR^(zUUvd3>x_zWz^P zNXV71udgR3C2iWY>BFUk3l~Oj&kOu!qpO>HsD(2%H8p%)Oyu4wQ@=gc-``DCcIT6~ zi;0TLN=)3CeO>R9)+SKtc3OYGj;7|pi5ndhZfrEOdbv^l( z37wss?T0h7vz3FMNavUO%rsiMVugm0(WP6rcC7#b4tF_cXXknI=C!uAii(O(n>Niw z-g4UX>Eg1opvAynzLc1nf@-c4Cr)hGuwm1tP3h<73C2&3jEwyG^XJKvCskEdXU&@B z-Y4V8@$=_TQBhIdxIGpI1_J-xe+DWyy>j4CoHBWGbAGSLNu8-zUL7zo1%XbsV8MqE zA0F?QkKdkm_gJs=?c28>KYAo2EZjN$`jsnJ0*+aoJ$tshtE-^IsqDi8$N2dBr%s(} zYGO)HPoMK+|Np=17cFW!n-(4t^5*`2`F71sSFTJ=+aT)jH=M6;>6d39ywT&q$o4Gj$=BcbEP%l+s7dp0{?z=@TW^-0yuz181ecpDiT zZ{D@bN?rZ=o12@ze*OC7^M{Lz-8D5eD=RCnuaEz48{Q?u_0=LtPft%&R8&q*uEuWv z!i9=DIz4;*V`F1kSy}nz?PO$RWF#d8J6$Yl54CWdT6`bLXzv(Xzv<@UDcU zq@etK+iJIz$<2FM)h2t&B$=9S?d$7{|D@O=kYph7?c28>?=^#$nV6f$?=Cxg=up$A zFa0h`2N&*IQ?lvWwXmAgu8wwvSFc_zEOb!{OiX-ucDDKNU%x&q>o|Ng#pvW>wy$5m zK0iPIdC|@vQ7SnR2FAt zfsR<(vuDqpDX^HcXpz#NKn;N2!+wteuclMON4pZ4a>&csw+wa#!hlO3c zd9zdC)!VmQH*Y@tl83F?Lf6;F=S7KC|8Zr%(v|>k@9qE%9&YZ#@~%pP?`+D-%jnwpUt5*X*r*--e{&D`8vuHXDw$%?P9uRs3nT(fK1 zlqnL@()&N3v*u6~6&0;HH)Z;CVPpR_YuCoc#-2WPs-Q0N&)>g=3*Nkb9UT_t*2CX* z^wHYr?T_U2Yj(G$izu{&goI4fi7a}0YU=;{EKc6j^;|h3BO?tZc=V=c=jQ5`emAhU zxBnxbes0dq8#g>;#H6KXyLO9JR#sM4S9{AkN#^dksyMZ?)6>C$;h(jF!h9{nW1FgMzAR>&cX=>S|%zHJffJMLs=y`tHO=AlqMW- zoBwCii&w99?bxBvvS-hp0`(U&+NUg9vP8weKpR&4<;B$ z@JLI~zF)cYgi+sti#KmtT3hely7lX?udn&|_@+qrEq3phl8~tRaFBi0tXZX{rJS6c zN0jX5`!By-T~+1g=Jugnku~*2aF!g`BW)Swe8-7bPxkco7XIHbZCY4;z5T>{_wV;N z8%MrO*(7!UVsMP0)6Shc%irDMi32c zJ;v$hOiWA^ZvO91`u66gcGwyPJE7=hGP%{&zn3go($&>9XU?2UmoCX#mz|LBoM)DM zYfI+kx*reQ7rXa+%?7?&s5OxwqqL zzlN%+s@i-ES#@!$#zc>jl9Chr43BtLty&emJ+Jo9kHWyfz><y@^%v%9@5_xFp%{Wo&V`u^F+FF`RmH29m-~a(fW^k@3U#)%w;xS1-08a1=hm~&KV9y= zeECvPJvuu2_VW34UNzZ9GkqEz9C{{b@U42xuejf;Pu6-{{r|f7`1|o;6JDR2Ykj}w z^V#h6d&OjBWu>L1#l)UHdi%vVAtX1J7mxP1`t5&_* z{eEBfGb>-O#U_tnmRVJ9OYaX`KEN?BXPYkyt+ z`RCIOB#ziU77#v}*S%y~c}mKX#fzPh6i4!YU zuIzkqb^7{s>+%jZvF?~&Qqr!maOFx*9kJ@_>c`&QCo)q~u3Wt6DCuzAwl!&^ME%4W zvu54fQyCl`9X(MX*wyvv*X!}~?d$6*Dt`R^{e5X)ZSaj7Hxyc`s;hZ}^0 z#B=T1wNw7p)YjJ4eDBc@ndqVNRmjxL%*@z$^~#ky3m>z6EScAI)urtHJzH&U?MGh} z)~gD2T3K0rSbFc$B_*+L&D!?KYQC@Dy<2uMBgk2!xcKvh3m5k6+4K4Nd2Jz!Uq604 zc)ndpNy*9CdAZ-*T~%LSRepZ9a>a@jt5;iFS_ZbYsr${55Cws+FD@=FeSK}`u3hJ5 z8oRr@x4(Gz_T4)KS?cKOK7RaI(5bbx6*M}tr^0aZ$t4#ve!MNcd-v}9{r~IA%E~e_G$vh?l8}^) zjEod~tj@CXEl=E+^y#1tvnoP@=Q9O9pH1278g$xw`Q@))zgo)hy}P?yqW072OBXLH zigiz&GNr|7p@V|Lv4bDPmdvvev9hu{@|}DBdHLcz&R@RaT{>OUgo_IcHTCqCUCfx0 z7iwy1T3T9Kv+-la9&T~H2PK^@lcr63_T-64@v}2uUtgbZUG8_#vU_o4M8uVA*B(tg zZe9NF&fe44#Y&`k!!yUVKE4ReN#K_s##Y9BhxOT1V-JPA! zo~8YHuxm|;5kG(brAwDiojUdH+qdfP?@sD3TemJRIC!#)QsuWdk!5Au*4*$54-Z!n zdT2Ro>(;GD=JdS0y!`d6SId?xv8nsBW9ibRyLL&XyK#huhkyV2m9y2!+grIIN(rQ`}e~KgLnSFemw5id~YBvDS7hb$(_Z| z{k*-sYinzxqGoN|X7+B~_6-|WtXd_+)q42w;T*HyFE1~DBFfFpEpJ!TA`K?|(jcxu0#-myYw#pFew6R$BV-mZY!0|9bF32Fd*r zCr+PUy=Tv#OG~|N%PCcD_{R-jGM?$3xH`n#ojt~Lr>x_O%0 zx9YLrd7|K!7m4=2u^efs$) zQ=@{i^5MgWn`b3YxnHpKgwejWXJ!~WJ3AjfdbIVEs@LL+X1TYf%$!;I?@wiJ?%S=| z*VWY2lv>us?7VdCT3h=)U0vPS*x2CU;P3D5ddG!3a*OF?WM$pjo($!sC{QTU<3LCY_CzCcNB_$Qe_pe*`?%LYu&6_rzn`4=rks%=_*0xw9@@2}p zb?cg&n}7cN85((u4^SmV~3eg*rT(h!X{eHiH{jz1>-q-)1z2oab17qXQ-@p5N zcr3V>p(B=kcUS54b+OJIh7vs|PMqlJ>2Yr7yO?2OZEdZtuCA-QcKiLh-&a-!OG--a zE_-WaZr`7JbUmU zAUL@A#RWxC(W&R{e&?K%^VAcVEALt*opp$ck{$MT`Vmv=gyt0rKNRwx&QIY z`!B3mxqVa4%}q}a^V^GXwSrdeUCh}3ZjCF4sfme zb6H;A-izPYiR{|7>*L3dg@uKn>6}%et7rJAy*tCf#U<2zbhdf^yB9AmT)cR2@q`)k z=f{VJPF=oSeXig6lP4$MZ)dz37!gtN=SSg04-*rU7Iv%2lO}y=HMTOo*RdG{K2K8h zc5`#Pk+ZF_u@RKvKiuwm_Wu3;$W7xsafsMN?jXczF2f)2Dy`{&iYd zaB`9=4_ouuH0734r%oj$C%e13`S|#J5EpRrP!Ld2Q`@$E`|1@dcI?{4#>V!j*CUrV z2Yga*^^+5VeC>-DEqe6gMTfF9sGlWsN?KZacmDl-fBw`kGc*6$#I3ShcT(s*UTL!n z8Cxz~2zazIE9v)Q(8*)VmoHzv`t|Mn{kFBgwlp^%Ze(T`aGEx4T4`yio}OM>T3Tmk zr^`x7o@3Y6Mk^~RWtiNWoxg8q>FY4Q_U6N#>(;I7=-^PBytC}>t*57_v$L~*{`u$W z>FM1^AGLCe3+c;Gc>Fy^Q{=J4Y=(pwHa0dVXJ>v{E0ZcWkF>P3@^bUK)672>YY5#+ zVr6BGum4+GR<`Ym&O7G!Yd-(^`FXMQll{%pm+t|c&Ft%2o3}sOIpBj=;E^9~ zs~9EpBg@q`T{>9&;N{EAw6tv{FN0j`LU;vs#JpSN+MRc2N8|l1|J{F1Ud7zcuh3Fd zT%3M(*4Ia_z5my|l9;|=!Ge~SmbbUJ2aEsVTefUj_Vsl~|6aSIeq@#t0|VPPPZ!6K z)cUQ{*X}ugQ@(DgL%;Hm%@5>`H?#8x$!&J!NKH#CD=qDXggR^QqqT>l%tNNe{A=zcsl9n zQSJBfS08I|KjqM7VsihG-gw}n<>B)@LBR(a%*KV=9`|IcL@=08| za^<>p>$t^qR;*jMOz*e%(W6HzD=J#r+nswrJ4qU*U%pW+bJCu7=Co;HF)=xbi5t_; z%Yo*A^jZXN?JQnCablp03yW%O;oP}%yT$eQ6+CQeY-B9GdH=wJ2M?t4_k6tn|6lpD zGc&KGKK{QIyjQ28ep=xdi+lU)_b*$fW^H}@;K7GaPENjZl;i7{FFpTyClqGp=ZA-f z|F3rI#3?y1znp0z)$1nQebi#<`t|)Us$?W31A~5r zs%VLDfd-o%9BBOVrKHi}K+?vbpr9j3g;iBmadC0^`RgZ74)*csnKtd(=JfM_{{C%m zYpbfRo~{$wB=GCS;{Io6W;P3G>Fe9q{i(>#*6u!fYj5@TsZ&MoRTLC#m_0i>G*qe7b~6CWvqqUgZsWEtDOYuB!APCFa5G08PF^lF!=_PM#%;*ye; ze}9$k*|K@_=7kFn9y#*ly0&7=g`LIEK?gcdnl$Ofi;R|lu&}VYy1JsGB2UlGWy_vT zRCX6|%F4=eb#Z+on8I?ovwz)h0B*upE`A@bLGmF;p<{N{ruLgSaD-tt#u#A z(syzPj~rRDY+2fwkKlE~4dUl5nA}?~wDC&Y{r~fMW$FPsPT~n~mrC zegFDZwEJjjY3Y$5dHMM(gO@9{^z`;#y>jJA3g}40cd1*pZ_m%kInr5KS^0k7?{`m5 zPJUUkYwzB_Q#6AwMC|^4uloJQn{TeiRlogi!vM+z3?KYW_APwX(!w&)LqtO2#N&^B z^7iwlPW^h2T|Ocr!o}5z3W$FPEP@N%=GJWC|Bc zQ+Qlu>zwur_wIq#O-Y;QJ?Lj$cq-roz4uYLOR<|&`{ChX zO-)S>#o*w}nU|NDnwju{UO& z>v#S7_2&f^JI+s1sQ)e{Ep48AizN`8*5aHVORqW>sQm9@mfB>?#6v9y4>~4kHXjt| zblLx8nxcZjgKqtO4~lkjEWCE@TDQ1*3j_K5zD`~XXoO@&hGB%FE1^y7(6{g!8EWCR35BovarTdZ2KB4F|X4S4Cg z`?w$|pW9eibjUATrdPf5Vup#Ts%mIxXvG!qI&XcORwi(H$G~t*ZWVYl4+Dea15lyL zz#ziX2#yzq01lQukOLVU6a^rwI2l?UV2YUVDEcFR_Z)+dWbnyXAnl&6elF{r5}E*I C;=4ou literal 0 HcmV?d00001 diff --git a/doc/user/project/merge_requests/squash_and_merge.md b/doc/user/project/merge_requests/squash_and_merge.md index e551f65b75dda6..b6c1f29788ce17 100644 --- a/doc/user/project/merge_requests/squash_and_merge.md +++ b/doc/user/project/merge_requests/squash_and_merge.md @@ -28,9 +28,9 @@ NOTE: The squashed commit in this example is followed by a merge commit, because the merge method for this repository uses a merge commit. You can disable merge commits in **Project Settings > General > Merge requests > Merge method > Fast-forward merge**. -The squashed commit's default commit message is taken from the merge request title. +The squashed commit's default commit message is taken from the merge request title. It can be changed using [squash commit message template](commit_templates.md#squash-commit-message-template). -It can be customized before merging a merge request. +It can also be customized before merging a merge request. ![A squash commit message editor](img/squash_mr_message.png) diff --git a/doc/user/project/settings/index.md b/doc/user/project/settings/index.md index 0a3b9d5b2a5d88..3fcb18ca31aeaf 100644 --- a/doc/user/project/settings/index.md +++ b/doc/user/project/settings/index.md @@ -315,7 +315,7 @@ Set up your project's merge request settings: - Enable [require an associated issue from Jira](../../../integration/jira/issues.md#require-associated-jira-issue-for-merge-requests-to-be-merged). - Enable [`delete source branch after merge` option by default](../merge_requests/getting_started.md#deleting-the-source-branch). - Configure [suggested changes commit messages](../merge_requests/reviews/suggestions.md#configure-the-commit-message-for-applied-suggestions). -- Configure [merge commit message template](../merge_requests/commit_templates.md). +- Configure [merge and squash commit message templates](../merge_requests/commit_templates.md). - Configure [the default target project](../merge_requests/creating_merge_requests.md#set-the-default-target-project) for merge requests coming from forks. ### Service Desk diff --git a/ee/app/views/projects/_merge_request_settings.html.haml b/ee/app/views/projects/_merge_request_settings.html.haml index 8ffa06f99a4150..387b0dd7fdfed1 100644 --- a/ee/app/views/projects/_merge_request_settings.html.haml +++ b/ee/app/views/projects/_merge_request_settings.html.haml @@ -15,6 +15,8 @@ = render_ce 'projects/merge_request_merge_commit_template', project: @project, form: form += render_ce 'projects/merge_request_squash_commit_template', project: @project, form: form + - if @project.forked? = render_ce 'projects/merge_request_target_project_settings', project: @project, form: form diff --git a/locale/gitlab.pot b/locale/gitlab.pot index adc07db990ca99..509145382a93aa 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -27407,6 +27407,9 @@ msgstr "" msgid "ProjectSettings|Snippets" msgstr "" +msgid "ProjectSettings|Squash commit message template" +msgstr "" + msgid "ProjectSettings|Squash commits when merging" msgstr "" @@ -27431,6 +27434,9 @@ msgstr "" msgid "ProjectSettings|The commit message used when merging, if the merge method creates a merge commit. %{link_start}Learn more about syntax and variables.%{link_end}" msgstr "" +msgid "ProjectSettings|The commit message used when squashing commits. %{link_start}Learn more about syntax and variables.%{link_end}" +msgstr "" + msgid "ProjectSettings|The default target project for merge requests created in this fork project." msgstr "" diff --git a/spec/views/projects/edit.html.haml_spec.rb b/spec/views/projects/edit.html.haml_spec.rb index 60f4c1664f7549..8c96f286c79cb9 100644 --- a/spec/views/projects/edit.html.haml_spec.rb +++ b/spec/views/projects/edit.html.haml_spec.rb @@ -92,6 +92,22 @@ end end + context 'squash template' do + it 'displays a placeholder if none is set' do + render + + expect(rendered).to have_field('project[squash_commit_template]', placeholder: '%{title}') + end + + it 'displays the user entered value' do + project.update!(squash_commit_template: '%{first_multiline_commit}') + + render + + expect(rendered).to have_field('project[squash_commit_template]', with: '%{first_multiline_commit}') + end + end + context 'forking' do before do assign(:project, project) -- GitLab From b0aa21fb0dd3c8163872462cf3728a464313b244 Mon Sep 17 00:00:00 2001 From: Piotr Stankowski Date: Thu, 11 Nov 2021 20:29:03 +0100 Subject: [PATCH 4/5] Expose squashCommitTemplate field in APIs --- app/graphql/types/project_type.rb | 5 +++++ doc/api/graphql/reference/index.md | 1 + doc/api/projects.md | 8 ++++++++ lib/api/entities/project.rb | 1 + lib/api/helpers/projects_helpers.rb | 2 ++ spec/graphql/types/project_type_spec.rb | 2 +- 6 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index b6cb9cd330296b..8ef53ae001b1db 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -386,6 +386,11 @@ class ProjectType < BaseObject null: true, description: 'Template used to create merge commit message in merge requests.' + field :squash_commit_template, + GraphQL::Types::String, + null: true, + description: 'Template used to create squash commit message in merge requests.' + def label(title:) BatchLoader::GraphQL.for(title).batch(key: project) do |titles, loader, args| LabelsFinder diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 0400dc31128634..d49eee5d15c349 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -12867,6 +12867,7 @@ Represents vulnerability finding of a security report on the pipeline. | `serviceDeskEnabled` | [`Boolean`](#boolean) | Indicates if the project has service desk enabled. | | `sharedRunnersEnabled` | [`Boolean`](#boolean) | Indicates if shared runners are enabled for the project. | | `snippetsEnabled` | [`Boolean`](#boolean) | Indicates if Snippets are enabled for the current user. | +| `squashCommitTemplate` | [`String`](#string) | Template used to create squash commit message in merge requests. | | `squashReadOnly` | [`Boolean!`](#boolean) | Indicates if `squashReadOnly` is enabled. | | `sshUrlToRepo` | [`String`](#string) | URL to connect to the project via SSH. | | `starCount` | [`Int!`](#int) | Number of times the project has been starred. | diff --git a/doc/api/projects.md b/doc/api/projects.md index fb3099091ab10b..ee84b0b6a01d70 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -183,6 +183,7 @@ When the user is authenticated and `simple` is not set this returns something li "autoclose_referenced_issues": true, "suggestion_commit_message": null, "merge_commit_template": null, + "squash_commit_template": null, "marked_for_deletion_at": "2020-04-03", // Deprecated and will be removed in API v5 in favor of marked_for_deletion_on "marked_for_deletion_on": "2020-04-03", "statistics": { @@ -300,6 +301,7 @@ When the user is authenticated and `simple` is not set this returns something li "autoclose_referenced_issues": true, "suggestion_commit_message": null, "merge_commit_template": null, + "squash_commit_template": null, "statistics": { "commit_count": 12, "storage_size": 2066080, @@ -470,6 +472,7 @@ GET /users/:user_id/projects "autoclose_referenced_issues": true, "suggestion_commit_message": null, "merge_commit_template": null, + "squash_commit_template": null, "marked_for_deletion_at": "2020-04-03", // Deprecated and will be removed in API v5 in favor of marked_for_deletion_on "marked_for_deletion_on": "2020-04-03", "statistics": { @@ -587,6 +590,7 @@ GET /users/:user_id/projects "autoclose_referenced_issues": true, "suggestion_commit_message": null, "merge_commit_template": null, + "squash_commit_template": null, "statistics": { "commit_count": 12, "storage_size": 2066080, @@ -714,6 +718,7 @@ Example response: "autoclose_referenced_issues": true, "suggestion_commit_message": null, "merge_commit_template": null, + "squash_commit_template": null, "statistics": { "commit_count": 37, "storage_size": 1038090, @@ -826,6 +831,7 @@ Example response: "autoclose_referenced_issues": true, "suggestion_commit_message": null, "merge_commit_template": null, + "squash_commit_template": null, "statistics": { "commit_count": 12, "storage_size": 2066080, @@ -994,6 +1000,7 @@ GET /projects/:id "autoclose_referenced_issues": true, "suggestion_commit_message": null, "merge_commit_template": null, + "squash_commit_template": null, "marked_for_deletion_at": "2020-04-03", // Deprecated and will be removed in API v5 in favor of marked_for_deletion_on "marked_for_deletion_on": "2020-04-03", "compliance_frameworks": [ "sox" ], @@ -1307,6 +1314,7 @@ POST /projects/user/:user_id | `jobs_enabled` | boolean | **{dotted-circle}** No | _(Deprecated)_ Enable jobs for this project. Use `builds_access_level` instead. | | `lfs_enabled` | boolean | **{dotted-circle}** No | Enable LFS. | | `merge_commit_template` | string | **{dotted-circle}** No | [Template](../user/project/merge_requests/commit_templates.md) used to create merge commit message in merge requests. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/20263) in GitLab 14.5.)_ | +| `squash_commit_template` | string | **{dotted-circle}** No | [Template](../user/project/merge_requests/commit_templates.md#squash-commit-message-template) used to create squash commit message in merge requests. _([Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/345275) in GitLab 14.6.)_ | | `merge_method` | string | **{dotted-circle}** No | Set the [merge method](#project-merge-method) used. | | `merge_requests_access_level` | string | **{dotted-circle}** No | One of `disabled`, `private`, or `enabled`. | | `merge_requests_enabled` | boolean | **{dotted-circle}** No | _(Deprecated)_ Enable merge requests for this project. Use `merge_requests_access_level` instead. | diff --git a/lib/api/entities/project.rb b/lib/api/entities/project.rb index e3f1e90b80f0c6..d7600f8a9b5c4c 100644 --- a/lib/api/entities/project.rb +++ b/lib/api/entities/project.rb @@ -115,6 +115,7 @@ class Project < BasicProjectDetails expose :squash_option expose :suggestion_commit_message expose :merge_commit_template + expose :squash_commit_template expose :statistics, using: 'API::Entities::ProjectStatistics', if: -> (project, options) { options[:statistics] && Ability.allowed?(options[:current_user], :read_statistics, project) } diff --git a/lib/api/helpers/projects_helpers.rb b/lib/api/helpers/projects_helpers.rb index 2425982d405f1f..d7de8bd8b8b3e5 100644 --- a/lib/api/helpers/projects_helpers.rb +++ b/lib/api/helpers/projects_helpers.rb @@ -62,6 +62,7 @@ module ProjectsHelpers optional :merge_method, type: String, values: %w(ff rebase_merge merge), desc: 'The merge method used when merging merge requests' optional :suggestion_commit_message, type: String, desc: 'The commit message used to apply merge request suggestions' optional :merge_commit_template, type: String, desc: 'Template used to create merge commit message' + optional :squash_commit_template, type: String, desc: 'Template used to create squash commit message' optional :initialize_with_readme, type: Boolean, desc: "Initialize a project with a README.md" optional :ci_default_git_depth, type: Integer, desc: 'Default number of revisions for shallow cloning' optional :auto_devops_enabled, type: Boolean, desc: 'Flag indication if Auto DevOps is enabled' @@ -162,6 +163,7 @@ def self.update_params_at_least_one_of :avatar, :suggestion_commit_message, :merge_commit_template, + :squash_commit_template, :repository_storage, :compliance_framework_setting, :packages_enabled, diff --git a/spec/graphql/types/project_type_spec.rb b/spec/graphql/types/project_type_spec.rb index 4f205e861dd4b1..adf5507571b453 100644 --- a/spec/graphql/types/project_type_spec.rb +++ b/spec/graphql/types/project_type_spec.rb @@ -34,7 +34,7 @@ container_repositories container_repositories_count pipeline_analytics squash_read_only sast_ci_configuration cluster_agent cluster_agents agent_configurations - ci_template timelogs merge_commit_template + ci_template timelogs merge_commit_template squash_commit_template ] expect(described_class).to include_graphql_fields(*expected_fields) -- GitLab From c8e78181bfe5ddec921b1e5d4635b78bb6573bfb Mon Sep 17 00:00:00 2001 From: Piotr Stankowski Date: Thu, 11 Nov 2021 23:39:05 +0100 Subject: [PATCH 5/5] Add link to squash template help Show hint about commit templates regardless of merge commit editor visibility. Change hint message to mention squash template when squash is performed. --- .../components/states/commit_edit.vue | 1 - .../components/states/ready_to_merge.vue | 77 +++++++++++-------- locale/gitlab.pot | 6 ++ .../components/states/commit_edit_spec.js | 9 --- .../states/mr_widget_ready_to_merge_spec.js | 8 ++ 5 files changed, 58 insertions(+), 43 deletions(-) diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue index 3eda2828e97ec5..18761d04c2ed28 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue @@ -41,7 +41,6 @@ export default { rows="7" @input="$emit('input', $event.target.value)" > - diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue index 08a44d81bf0ee5..8830128b7d6ee2 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue @@ -181,9 +181,16 @@ export default { return this.mr.canRemoveSourceBranch; }, commitTemplateHelpPage() { - return helpPagePath('user/project/merge_requests/commit_templates.md', { - anchor: 'merge-commit-message-template', - }); + return helpPagePath('user/project/merge_requests/commit_templates.md'); + }, + commitTemplateHintText() { + if (this.shouldShowSquashEdit && this.shouldShowMergeEdit) { + return this.$options.i18n.mergeAndSquashCommitTemplatesHintText; + } + if (this.shouldShowSquashEdit) { + return this.$options.i18n.squashCommitTemplateHintText; + } + return this.$options.i18n.mergeCommitTemplateHintText; }, commits() { if (this.glFeatures.mergeRequestWidgetGraphql) { @@ -509,6 +516,12 @@ export default { mergeCommitTemplateHintText: s__( 'mrWidget|To change this default message, edit the template for merge commit messages. %{linkStart}Learn more.%{linkEnd}', ), + squashCommitTemplateHintText: s__( + 'mrWidget|To change this default message, edit the template for squash commit messages. %{linkStart}Learn more.%{linkEnd}', + ), + mergeAndSquashCommitTemplatesHintText: s__( + 'mrWidget|To change these default messages, edit the templates for both the merge and squash commit messages. %{linkStart}Learn more.%{linkEnd}', + ), }, }; @@ -674,23 +687,22 @@ export default { :label="__('Merge commit message')" input-id="merge-message-edit" class="gl-m-0! gl-p-0!" - > - - + /> +
  • +

    + + + +

    +
  • - - + /> +
  • +

    + + + +

    +
  • diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 509145382a93aa..b2fa528d3b63dd 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -41666,9 +41666,15 @@ msgstr "" msgid "mrWidget|To approve this merge request, please enter your password. This project requires all approvals to be authenticated." msgstr "" +msgid "mrWidget|To change these default messages, edit the templates for both the merge and squash commit messages. %{linkStart}Learn more.%{linkEnd}" +msgstr "" + msgid "mrWidget|To change this default message, edit the template for merge commit messages. %{linkStart}Learn more.%{linkEnd}" msgstr "" +msgid "mrWidget|To change this default message, edit the template for squash commit messages. %{linkStart}Learn more.%{linkEnd}" +msgstr "" + msgid "mrWidget|To merge, a Jira issue key must be mentioned in the title or description." msgstr "" diff --git a/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js b/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js index f965fc32dc12e9..c30f6f1dfd101a 100644 --- a/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js @@ -3,7 +3,6 @@ import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit const testCommitMessage = 'Test commit message'; const testLabel = 'Test label'; -const testTextMuted = 'Test text muted'; const testInputId = 'test-input-id'; describe('Commits edit component', () => { @@ -64,7 +63,6 @@ describe('Commits edit component', () => { beforeEach(() => { createComponent({ header: `
    ${testCommitMessage}
    `, - 'text-muted': `

    ${testTextMuted}

    `, }); }); @@ -74,12 +72,5 @@ describe('Commits edit component', () => { expect(headerSlotElement.exists()).toBe(true); expect(headerSlotElement.text()).toBe(testCommitMessage); }); - - it('renders text-muted slot correctly', () => { - const textMutedElement = wrapper.find('.test-text-muted'); - - expect(textMutedElement.exists()).toBe(true); - expect(textMutedElement.text()).toBe(testTextMuted); - }); }); }); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index a9cf085f84ca08..7082a19a8e72ab 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -1,5 +1,6 @@ import { shallowMount } from '@vue/test-utils'; import Vue from 'vue'; +import { GlSprintf } from '@gitlab/ui'; import simplePoll from '~/lib/utils/simple_poll'; import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue'; import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue'; @@ -487,6 +488,7 @@ describe('ReadyToMerge', () => { const findCommitEditElements = () => wrapper.findAll(CommitEdit); const findCommitDropdownElement = () => wrapper.find(CommitMessageDropdown); const findFirstCommitEditLabel = () => findCommitEditElements().at(0).props('label'); + const findTipLink = () => wrapper.find(GlSprintf); describe('squash checkbox', () => { it('should be rendered when squash before merge is enabled and there is more than 1 commit', () => { @@ -751,6 +753,12 @@ describe('ReadyToMerge', () => { expect(findCommitDropdownElement().exists()).toBeTruthy(); }); }); + + it('renders a tip including a link to docs on templates', () => { + createComponent(); + + expect(findTipLink().exists()).toBe(true); + }); }); describe('Merge request project settings', () => { -- GitLab