From 4ffd764d483859882ab4ce378d470c4478955be1 Mon Sep 17 00:00:00 2001 From: Kun Qian Date: Mon, 21 Nov 2022 00:27:22 +0800 Subject: [PATCH] Add mirror branch setting radio and regex input Instead of a checkbox indicates whether mirror only protected branches, we add three radio buttons and a text input, user now can choose to mirror all/protected branches or branches with name hit a customized regular expression. Changelog: added EE: true --- .../projects/mirrors/_branch_filter.html.haml | 6 + .../projects/mirrors/_mirror_repos.html.haml | 7 +- .../mirrors/_mirror_repos_list.html.haml | 4 +- .../mirrors/_mirror_repos_push.html.haml | 1 + .../repository/show/ee_mirror_repos.js | 35 +++++ ee/app/helpers/ee/mirror_helper.rb | 18 +++ .../projects/mirrors/_branch_filter.html.haml | 32 ++++ .../mirrors/_branch_name_regex.html.haml | 2 + .../_mirror_branches_setting_badge.html.haml | 3 + .../mirrors/_mirror_repos_form.html.haml | 2 + .../mirrors/_table_pull_row.html.haml | 4 +- .../ee/repository_mirrors_settings_spec.rb | 143 +++++++++++++++++- locale/gitlab.pot | 24 +++ .../settings/repository_settings_spec.rb | 1 + 14 files changed, 273 insertions(+), 9 deletions(-) create mode 100644 app/views/projects/mirrors/_branch_filter.html.haml create mode 100644 ee/app/views/projects/mirrors/_branch_filter.html.haml create mode 100644 ee/app/views/projects/mirrors/_branch_name_regex.html.haml create mode 100644 ee/app/views/projects/mirrors/_mirror_branches_setting_badge.html.haml diff --git a/app/views/projects/mirrors/_branch_filter.html.haml b/app/views/projects/mirrors/_branch_filter.html.haml new file mode 100644 index 00000000000000..b9db9898d496fc --- /dev/null +++ b/app/views/projects/mirrors/_branch_filter.html.haml @@ -0,0 +1,6 @@ +.form-check.gl-mb-3 + = check_box_tag :only_protected_branches, '1', false, class: 'js-mirror-protected form-check-input' + = label_tag :only_protected_branches, _('Mirror only protected branches'), class: 'form-check-label' + .form-text.text-muted + = _('If enabled, only protected branches will be mirrored.') + = link_to _('Learn more.'), help_page_path('user/project/repository/mirror/index.md', anchor: 'mirror-only-protected-branches'), target: '_blank', rel: 'noopener noreferrer' diff --git a/app/views/projects/mirrors/_mirror_repos.html.haml b/app/views/projects/mirrors/_mirror_repos.html.haml index f4e57450aa1f59..4cfe463fa389e5 100644 --- a/app/views/projects/mirrors/_mirror_repos.html.haml +++ b/app/views/projects/mirrors/_mirror_repos.html.haml @@ -27,12 +27,7 @@ = render 'projects/mirrors/mirror_repos_form', f: f - .form-check.gl-mb-3 - = check_box_tag :only_protected_branches, '1', false, class: 'js-mirror-protected form-check-input' - = label_tag :only_protected_branches, _('Mirror only protected branches'), class: 'form-check-label' - .form-text.text-muted - = _('If enabled, only protected branches will be mirrored.') - = link_to _('Learn more.'), help_page_path('user/project/repository/mirror/index.md', anchor: 'mirror-only-protected-branches'), target: '_blank', rel: 'noopener noreferrer' + = render 'projects/mirrors/branch_filter' .panel-footer = f.submit _('Mirror repository'), class: 'js-mirror-submit', name: :update_remote_mirror, pajamas_button: true, data: { qa_selector: 'mirror_repository_button' } diff --git a/app/views/projects/mirrors/_mirror_repos_list.html.haml b/app/views/projects/mirrors/_mirror_repos_list.html.haml index fb8133e6de8bd0..46833b5986be13 100644 --- a/app/views/projects/mirrors/_mirror_repos_list.html.haml +++ b/app/views/projects/mirrors/_mirror_repos_list.html.haml @@ -26,7 +26,9 @@ - @project.remote_mirrors.each_with_index do |mirror, index| - next if mirror.new_record? %tr.rspec-mirrored-repository-row{ class: ('bg-secondary' if mirror.disabled?), data: { qa_selector: 'mirrored_repository_row_container' } } - %td{ data: { qa_selector: 'mirror_repository_url_content' } }= mirror.safe_url || _('Invalid URL') + %td{ data: { qa_selector: 'mirror_repository_url_content' } } + = mirror.safe_url || _('Invalid URL') + = render_if_exists 'projects/mirrors/mirror_branches_setting_badge', record: mirror %td= _('Push') %td = mirror.last_update_started_at.present? ? time_ago_with_tooltip(mirror.last_update_started_at) : _('Never') diff --git a/app/views/projects/mirrors/_mirror_repos_push.html.haml b/app/views/projects/mirrors/_mirror_repos_push.html.haml index 339c5d82919cd9..136f504084e370 100644 --- a/app/views/projects/mirrors/_mirror_repos_push.html.haml +++ b/app/views/projects/mirrors/_mirror_repos_push.html.haml @@ -4,6 +4,7 @@ = rm_f.hidden_field :enabled, value: '1' = rm_f.hidden_field :url, class: 'js-mirror-url-hidden', required: true, pattern: "(#{protocols}):\/\/.+" = rm_f.hidden_field :only_protected_branches, class: 'js-mirror-protected-hidden' + = render_if_exists partial: 'projects/mirrors/branch_name_regex', locals: { f: rm_f } = rm_f.hidden_field :keep_divergent_refs, class: 'js-mirror-keep-divergent-refs-hidden' = render partial: 'projects/mirrors/ssh_host_keys', locals: { f: rm_f } = render partial: 'projects/mirrors/authentication_method', locals: { f: rm_f } diff --git a/ee/app/assets/javascripts/pages/projects/settings/repository/show/ee_mirror_repos.js b/ee/app/assets/javascripts/pages/projects/settings/repository/show/ee_mirror_repos.js index de5f6d3bbbed1e..785a5ab45a4212 100644 --- a/ee/app/assets/javascripts/pages/projects/settings/repository/show/ee_mirror_repos.js +++ b/ee/app/assets/javascripts/pages/projects/settings/repository/show/ee_mirror_repos.js @@ -7,6 +7,11 @@ export default class EEMirrorRepos extends MirrorRepos { constructor(...args) { super(...args); + this.$mirrorBranchSettingInput = $('.js-mirror-branch-setting', this.$form); + this.$mirrorBranchRegexInput = $('.js-mirror-branch-regex', this.$form); + this.mirrorOnlyBranchesMatchRegexEnabled = this.$form.data( + 'mirrorOnlyBranchesMatchRegexEnabled', + ); this.$mirrorDirectionSelect = $('.js-mirror-direction', this.$form); this.$insertionPoint = $('.js-form-insertion-point', this.$form); this.$repoCount = $('.js-mirrored-repo-count', this.$container); @@ -79,9 +84,39 @@ export default class EEMirrorRepos extends MirrorRepos { this.initMirrorSSH(); } + updateProtectedBranches() { + if (this.mirrorOnlyBranchesMatchRegexEnabled) { + return; + } + super.updateProtectedBranches(); + } + updateMirrorBranchSetting(event) { + if (!this.mirrorOnlyBranchesMatchRegexEnabled) { + return; + } + const mirrorBy = $(event.target).val(); + const isOnlyProtectedBranches = mirrorBy === 'protected' ? '1' : '0'; + const isNotByRegex = mirrorBy !== 'regex'; + this.$mirrorBranchRegexInput.attr('disabled', isNotByRegex); + + $('.js-mirror-protected-hidden', this.$form).val(isOnlyProtectedBranches); + } + + updateMirrorBranchRegex() { + if (!this.mirrorOnlyBranchesMatchRegexEnabled) { + return; + } + const isRegexInputDisabled = this.$mirrorBranchRegexInput.attr('disabled'); + const regexValue = isRegexInputDisabled ? '' : this.$mirrorBranchRegexInput.val(); + $('.js-mirror-branch-regex-hidden', this.$form).val(regexValue); + } + registerUpdateListeners() { super.registerUpdateListeners(); this.$mirrorDirectionSelect.on('change', () => this.handleUpdate()); + this.$mirrorBranchSettingInput.on('change', (event) => this.updateMirrorBranchSetting(event)); + this.$mirrorBranchRegexInput.on('change', () => this.updateMirrorBranchRegex()); + this.$form.on('submit', () => this.updateMirrorBranchRegex()); } deleteMirror(event) { diff --git a/ee/app/helpers/ee/mirror_helper.rb b/ee/app/helpers/ee/mirror_helper.rb index eb0874af2d98a7..97e65b3794af95 100644 --- a/ee/app/helpers/ee/mirror_helper.rb +++ b/ee/app/helpers/ee/mirror_helper.rb @@ -2,6 +2,13 @@ module EE module MirrorHelper + extend ::Gitlab::Utils::Override + + override :mirrors_form_data_attributes + def mirrors_form_data_attributes + super.merge(mirror_only_branches_match_regex_enabled: ::Feature.enabled?(:mirror_only_branches_match_regex, @project) && @project.licensed_feature_available?(:repository_mirrors)) + end + def render_mirror_failed_message(raw_message:) mirror_last_update_at = @project.import_state.last_update_at message = "Pull mirroring failed #{time_ago_with_tooltip(mirror_last_update_at)}.".html_safe @@ -32,5 +39,16 @@ def mirrored_repositories_count(project = @project) count = project.mirror == true ? 1 : 0 count + @project.remote_mirrors.to_a.count { |mirror| mirror.enabled } end + + def mirror_branches_text(record) + case record.mirror_branches_setting + when 'all' + _('All branches') + when 'protected' + _('All protected branches') + when 'regex' + _('Specific branches') + end + end end end diff --git a/ee/app/views/projects/mirrors/_branch_filter.html.haml b/ee/app/views/projects/mirrors/_branch_filter.html.haml new file mode 100644 index 00000000000000..9c4566677e10b8 --- /dev/null +++ b/ee/app/views/projects/mirrors/_branch_filter.html.haml @@ -0,0 +1,32 @@ +- wiki_syntax_link_url = 'https://github.com/google/re2/wiki/Syntax' +- wiki_syntax_link_start = ''.html_safe % { url: wiki_syntax_link_url } +- if Feature.enabled?(:mirror_only_branches_match_regex, @project) && @project.licensed_feature_available?(:repository_mirrors) + .form-group.gl-mt-5 + = label_tag do + = _('Mirror branches') + .help-block.gl-font-weight-normal + = _('Choose which branches should be mirrored') + .form-check + = radio_button_tag :mirror_branch_setting, :all, true, class: 'form-check-input js-mirror-branch-setting' + = label_tag :mirror_branch_setting_all, class: 'form-check-label' do + = _('Mirror all branches') + .form-text.text-muted + = _("If enabled, all branches will be mirrored.") + = link_to _('Learn more.'), help_page_path('user/project/repository/mirror/index.md'), target: '_blank', rel: 'noopener noreferrer' + .form-check + = radio_button_tag :mirror_branch_setting, :protected, false, class: 'form-check-input js-mirror-branch-setting' + = label_tag :mirror_branch_setting_protected, class: 'form-check-label' do + = _('Mirror only protected branches') + .form-text.text-muted + = _('If enabled, only protected branches will be mirrored.') + = link_to _('Learn more.'), help_page_path('user/project/repository/mirror/index.md', anchor: 'mirror-only-protected-branches'), target: '_blank', rel: 'noopener noreferrer' + .form-check + = radio_button_tag :mirror_branch_setting, :regex, false, class: 'form-check-input js-mirror-branch-setting' + = label_tag :mirror_branch_setting_regex, class: 'form-check-label' do + = _('Mirror specific branches') + = text_field_tag :mirror_branch_regex, nil, class: 'form-control gl-form-input gl-mt-2 js-mirror-branch-regex', placeholder: _('Example') + ':(feature|hotfix)\/*', disabled: true + .form-text.text-muted + = _('All branch names must match %{link_start}this regular expression%{link_end}. If empty, any branch name is allowed.').html_safe % { link_start: wiki_syntax_link_start, link_end: ''.html_safe } + +- else + = render_ce 'projects/mirrors/branch_filter' diff --git a/ee/app/views/projects/mirrors/_branch_name_regex.html.haml b/ee/app/views/projects/mirrors/_branch_name_regex.html.haml new file mode 100644 index 00000000000000..08b77b2f43214b --- /dev/null +++ b/ee/app/views/projects/mirrors/_branch_name_regex.html.haml @@ -0,0 +1,2 @@ +- if Feature.enabled?(:mirror_only_branches_match_regex, @project) && @project.licensed_feature_available?(:repository_mirrors) + = f.hidden_field :mirror_branch_regex, class: 'js-mirror-branch-regex-hidden' diff --git a/ee/app/views/projects/mirrors/_mirror_branches_setting_badge.html.haml b/ee/app/views/projects/mirrors/_mirror_branches_setting_badge.html.haml new file mode 100644 index 00000000000000..84c5bcfdd43e4c --- /dev/null +++ b/ee/app/views/projects/mirrors/_mirror_branches_setting_badge.html.haml @@ -0,0 +1,3 @@ +- if Feature.enabled?(:mirror_only_branches_match_regex, @project) && @project.licensed_feature_available?(:repository_mirrors) + %div + = gl_badge_tag mirror_branches_text(record) diff --git a/ee/app/views/projects/mirrors/_mirror_repos_form.html.haml b/ee/app/views/projects/mirrors/_mirror_repos_form.html.haml index b16b60224e9a8e..aabbaab9210eb0 100644 --- a/ee/app/views/projects/mirrors/_mirror_repos_form.html.haml +++ b/ee/app/views/projects/mirrors/_mirror_repos_form.html.haml @@ -20,6 +20,8 @@ = f.hidden_field :mirror, value: '1' = f.hidden_field :username_only_import_url, class: 'js-mirror-url-hidden', required: true, pattern: "(#{protocols}):\/\/.+", value: '' = f.hidden_field :only_mirror_protected_branches, class: 'js-mirror-protected-hidden' + - if Feature.enabled?(:mirror_only_branches_match_regex, @project) && @project.licensed_feature_available?(:repository_mirrors) + = f.hidden_field :mirror_branch_regex, class: 'js-mirror-branch-regex-hidden' = f.fields_for :import_data, import_data, include_id: false do |import_form| = render partial: 'projects/mirrors/ssh_host_keys', locals: { f: import_form } diff --git a/ee/app/views/projects/mirrors/_table_pull_row.html.haml b/ee/app/views/projects/mirrors/_table_pull_row.html.haml index a719a2a137b99d..7de4670761a4ab 100644 --- a/ee/app/views/projects/mirrors/_table_pull_row.html.haml +++ b/ee/app/views/projects/mirrors/_table_pull_row.html.haml @@ -7,7 +7,9 @@ - return unless import_state %tr{ data: { qa_selector: 'mirrored_repository_row_container' } } - %td.mirror-url{ data: { qa_selector: 'mirror_repository_url_content' } }= @project.safe_import_url + %td.mirror-url{ data: { qa_selector: 'mirror_repository_url_content' } } + = @project.safe_import_url + = render_if_exists 'projects/mirrors/mirror_branches_setting_badge', record: @project %td= _('Pull') %td = import_state.last_update_started_at.present? ? time_ago_with_tooltip(import_state.last_update_started_at) : _('Never') diff --git a/ee/spec/features/projects/settings/ee/repository_mirrors_settings_spec.rb b/ee/spec/features/projects/settings/ee/repository_mirrors_settings_spec.rb index bfa83ecd5b4aef..348d61baeedd03 100644 --- a/ee/spec/features/projects/settings/ee/repository_mirrors_settings_spec.rb +++ b/ee/spec/features/projects/settings/ee/repository_mirrors_settings_spec.rb @@ -8,7 +8,7 @@ before do project.add_maintainer(user) - gitlab_sign_in(user) + sign_in(user) end context 'unlicensed' do @@ -107,5 +107,146 @@ expect(page).to have_select('Mirror direction', options: %w[Pull Push]) end end + + context 'when create a push mirror' do + let(:ssh_url) { 'ssh://user@localhost/project.git' } + + before do + visit project_settings_repository_path(project) + end + + it 'that mirrors all branches', :js do + fill_in 'url', with: ssh_url + expect(page).to have_css(".js-mirror-url-hidden[value=\"#{ssh_url}\"]", visible: false) + + select 'SSH public key', from: 'Authentication method' + select_direction + + expect(page).to have_css('#mirror_branch_setting_all') + find('#mirror_branch_setting_all').click + Sidekiq::Testing.fake! do + click_button 'Mirror repository' + end + + project.reload + + expect(page).to have_content('Mirroring settings were successfully updated') + expect(project.remote_mirrors.first.only_protected_branches).to eq(false) + expect(project.remote_mirrors.first.mirror_branch_regex).to be_nil + end + + it 'that mirrors protected branches', :js do + select_direction + find('#mirror_branch_setting_protected').click + + fill_in 'url', with: ssh_url + expect(page).to have_css(".js-mirror-url-hidden[value=\"#{ssh_url}\"]", visible: false) + + select 'SSH public key', from: 'Authentication method' + + Sidekiq::Testing.fake! do + click_button 'Mirror repository' + end + + project.reload + + expect(page).to have_content('Mirroring settings were successfully updated') + expect(project.remote_mirrors.first.only_protected_branches).to eq(true) + end + + it 'that mirrors branches match regex', :js do + fill_in 'url', with: ssh_url + expect(page).to have_css(".js-mirror-url-hidden[value=\"#{ssh_url}\"]", visible: false) + + select 'SSH public key', from: 'Authentication method' + select_direction + + find('#mirror_branch_setting_regex').click + fill_in 'mirror_branch_regex', with: 'text' + + Sidekiq::Testing.fake! do + click_button 'Mirror repository' + end + + project.reload + + expect(page).to have_content('Mirroring settings were successfully updated') + expect(project.remote_mirrors.first.only_protected_branches).to be_falsey + expect(project.remote_mirrors.first.mirror_branch_regex).to eq('text') + end + end + + context 'when create a pull mirror' do + let(:ssh_url) { 'ssh://user@localhost/project.git' } + + before do + visit project_settings_repository_path(project) + end + + it 'that mirrors all branches', :js do + fill_in 'url', with: ssh_url + expect(page).to have_css(".js-mirror-url-hidden[value=\"#{ssh_url}\"]", visible: false) + + select 'SSH public key', from: 'Authentication method' + select_direction('pull') + + expect(page).to have_css('#mirror_branch_setting_all') + find('#mirror_branch_setting_all').click + Sidekiq::Testing.fake! do + click_button 'Mirror repository' + end + + project.reload + + expect(page).to have_content('Mirroring settings were successfully updated') + expect(project.mirror_branches_setting).to eq('all') + expect(project.mirror_branch_regex).to be_nil + end + + it 'that only mirrors protected branches', :js do + select_direction('pull') + find('#mirror_branch_setting_protected').click + + fill_in 'url', with: ssh_url + expect(page).to have_css(".js-mirror-url-hidden[value=\"#{ssh_url}\"]", visible: false) + + select 'SSH public key', from: 'Authentication method' + + Sidekiq::Testing.fake! do + click_button 'Mirror repository' + end + + project.reload + + expect(page).to have_content('Mirroring settings were successfully updated') + expect(project.only_mirror_protected_branches).to eq(true) + end + + it 'that mirrors branches match regex', :js do + fill_in 'url', with: ssh_url + expect(page).to have_css(".js-mirror-url-hidden[value=\"#{ssh_url}\"]", visible: false) + + select 'SSH public key', from: 'Authentication method' + select_direction('pull') + + find('#mirror_branch_setting_regex').click + fill_in 'mirror_branch_regex', with: 'text' + + Sidekiq::Testing.fake! do + click_button 'Mirror repository' + end + + project.reload + + expect(page).to have_content('Mirroring settings were successfully updated') + expect(project.mirror_branches_setting).to eq('regex') + expect(project.mirror_branch_regex).to eq('text') + end + end + + def select_direction(direction = 'push') + direction_select = find('#mirror_direction') + direction_select.select(direction.capitalize) + end end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 914037a5557c24..d1a877a8a3783f 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4102,6 +4102,9 @@ msgstr "" msgid "All Members" msgstr "" +msgid "All branch names must match %{link_start}this regular expression%{link_end}. If empty, any branch name is allowed." +msgstr "" + msgid "All branches" msgstr "" @@ -8729,6 +8732,9 @@ msgstr "" msgid "Choose which Git strategy to use when fetching the project." msgstr "" +msgid "Choose which branches should be mirrored" +msgstr "" + msgid "Choose which repositories you want to connect and run CI/CD pipelines." msgstr "" @@ -16756,6 +16762,9 @@ msgstr "" msgid "Exactly one of %{attributes} is required" msgstr "" +msgid "Example" +msgstr "" + msgid "Example: (feature|hotfix)\\/*" msgstr "" @@ -21218,6 +21227,9 @@ msgstr "" msgid "If disabled, only administrators can configure repository mirroring." msgstr "" +msgid "If enabled, all branches will be mirrored." +msgstr "" + msgid "If enabled, only protected branches will be mirrored." msgstr "" @@ -27306,6 +27318,12 @@ msgstr "" msgid "Minutes" msgstr "" +msgid "Mirror all branches" +msgstr "" + +msgid "Mirror branches" +msgstr "" + msgid "Mirror direction" msgstr "" @@ -27318,6 +27336,9 @@ msgstr "" msgid "Mirror settings are only available to GitLab administrators." msgstr "" +msgid "Mirror specific branches" +msgstr "" + msgid "Mirror user" msgstr "" @@ -40752,6 +40773,9 @@ msgstr "" msgid "Spam log successfully submitted as ham." msgstr "" +msgid "Specific branches" +msgstr "" + msgid "Specified URL cannot be used: \"%{reason}\"" msgstr "" diff --git a/spec/features/projects/settings/repository_settings_spec.rb b/spec/features/projects/settings/repository_settings_spec.rb index 6f0a3094849f85..a0625c93b1ab28 100644 --- a/spec/features/projects/settings/repository_settings_spec.rb +++ b/spec/features/projects/settings/repository_settings_spec.rb @@ -9,6 +9,7 @@ before do stub_feature_flags(branch_rules: false) + stub_feature_flags(mirror_only_branches_match_regex: false) project.add_role(user, role) sign_in(user) end -- GitLab