Commit 9b649a36 authored by Tim Zallmann's avatar Tim Zallmann

Merge branch '3959-mirroring-interface-improvements' into 'master'

Resolve "Mirroring interface improvements"

Closes #3959

See merge request gitlab-org/gitlab-ee!4503
parents 001c41e2 c5ea333a
import initForm from '../form';
import MirrorRepos from './mirror_repos';
document.addEventListener('DOMContentLoaded', initForm);
document.addEventListener('DOMContentLoaded', () => {
initForm();
const mirrorReposContainer = document.querySelector('.js-mirror-settings');
if (mirrorReposContainer) new MirrorRepos(mirrorReposContainer).init();
});
import $ from 'jquery';
import _ from 'underscore';
import { __ } from '~/locale';
import Flash from '~/flash';
import axios from '~/lib/utils/axios_utils';
export default class MirrorRepos {
constructor(container) {
this.$container = $(container);
this.$form = $('.js-mirror-form', this.$container);
this.$urlInput = $('.js-mirror-url', this.$form);
this.$protectedBranchesInput = $('.js-mirror-protected', this.$form);
this.$table = $('.js-mirrors-table-body', this.$container);
this.mirrorEndpoint = this.$form.data('projectMirrorEndpoint');
}
init() {
this.initMirrorPush();
this.registerUpdateListeners();
}
initMirrorPush() {
this.$passwordGroup = $('.js-password-group', this.$container);
this.$password = $('.js-password', this.$passwordGroup);
this.$authMethod = $('.js-auth-method', this.$form);
this.$authMethod.on('change', () => this.togglePassword());
this.$password.on('input.updateUrl', () => this.debouncedUpdateUrl());
}
updateUrl() {
let val = this.$urlInput.val();
if (this.$password) {
const password = this.$password.val();
if (password) val = val.replace('@', `:${password}@`);
}
$('.js-mirror-url-hidden', this.$form).val(val);
}
updateProtectedBranches() {
const val = this.$protectedBranchesInput.get(0).checked
? this.$protectedBranchesInput.val()
: '0';
$('.js-mirror-protected-hidden', this.$form).val(val);
}
registerUpdateListeners() {
this.debouncedUpdateUrl = _.debounce(() => this.updateUrl(), 200);
this.$urlInput.on('input', () => this.debouncedUpdateUrl());
this.$protectedBranchesInput.on('change', () => this.updateProtectedBranches());
this.$table.on('click', '.js-delete-mirror', event => this.deleteMirror(event));
}
togglePassword() {
const isPassword = this.$authMethod.val() === 'password';
if (!isPassword) {
this.$password.val('');
this.updateUrl();
}
this.$passwordGroup.collapse(isPassword ? 'show' : 'hide');
}
deleteMirror(event, existingPayload) {
const $target = $(event.currentTarget);
let payload = existingPayload;
if (!payload) {
payload = {
project: {
remote_mirrors_attributes: {
id: $target.data('mirrorId'),
enabled: 0,
},
},
};
}
return axios
.put(this.mirrorEndpoint, payload)
.then(() => this.removeRow($target))
.catch(() => Flash(__('Failed to remove mirror.')));
}
/* eslint-disable class-methods-use-this */
removeRow($target) {
const row = $target.closest('tr');
$('.js-delete-mirror', row).tooltip('hide');
row.remove();
}
/* eslint-enable class-methods-use-this */
}
......@@ -201,7 +201,7 @@ label {
}
.gl-show-field-errors {
.form-control {
.form-control:not(textarea) {
height: 34px;
}
......
......@@ -342,3 +342,17 @@
margin-bottom: 0;
}
}
.mirror-error-badge {
background-color: $error-bg;
border-radius: $border-radius-default;
color: $white-light;
}
.push-pull-table {
margin-top: 1em;
.mirror-action-buttons {
padding-right: 0;
}
}
.account-well.prepend-top-default.append-bottom-default
%ul
%li
The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> or <code>git://</code>.
%li
Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>.
%li
The update action will time out after 10 minutes. For big repositories, use a clone/push combination.
%li
The Git LFS objects will <strong>not</strong> be synced.
= _('The repository must be accessible over <code>http://</code>,
<code>https://</code>, <code>ssh://</code> and <code>git://</code>.').html_safe
%li= _('Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>.').html_safe
%li= _("The update action will time out after #{import_will_timeout_message(Gitlab.config.gitlab_shell.git_timeout)} minutes. For big repositories, use a clone/push combination.")
%li= _('The Git LFS objects will <strong>not</strong> be synced.').html_safe
%li
= _('This user will be the author of all events in the activity feed that are the result of an update,
like new branches being created or new commits being pushed to existing branches.')
- expanded = Rails.env.test?
- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
%section.settings.project-mirror-settings.js-mirror-settings.no-animate{ class: ('expanded' if expanded) }
.settings-header
%h4= _('Mirroring repositories')
%button.btn.js-settings-toggle
= expanded ? _('Collapse') : _('Expand')
%p
= _('Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically.')
= link_to _('Read more'), help_page_path('workflow/repository_mirroring'), target: '_blank'
.settings-content
= form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'false', data: mirrors_form_data_attributes } do |f|
.card
.card-header
%h3.card-title= _('Mirror a repository')
.card-body
%div= form_errors(@project)
.form-group.has-feedback
= label_tag :url, _('Git repository URL'), class: 'label-light'
= text_field_tag :url, nil, class: 'form-control js-mirror-url js-repo-url', placeholder: _('Input your repository URL'), required: true, pattern: "(#{protocols}):\/\/.+", title: 'A valid repository URL is required'
= render 'projects/mirrors/instructions'
= render 'projects/mirrors/mirror_repos_form', f: f
.form-check.append-bottom-10
= check_box_tag :only_protected_branches, '1', false, class: 'js-mirror-protected form-check-input'
= label_tag :only_protected_branches, _('Only mirror protected branches'), class: 'form-check-label'
= link_to icon('question-circle'), help_page_path('user/project/protected_branches'), target: '_blank'
.card-footer
= f.submit _('Mirror repository'), class: 'btn btn-create', name: :update_remote_mirror
.card
.table-responsive
%table.table.push-pull-table
%thead
%tr
%th
= _('Mirrored repositories')
= render_if_exists 'projects/mirrors/mirrored_repositories_count'
%th= _('Direction')
%th= _('Last update')
%th
%th
%tbody.js-mirrors-table-body
= render_if_exists 'projects/mirrors/table_pull_row'
- @project.remote_mirrors.each_with_index do |mirror, index|
- if mirror.enabled
%tr
%td= mirror.safe_url
%td= _('Push')
%td= mirror.last_update_at.present? ? time_ago_with_tooltip(mirror.last_update_at) : _('Never')
%td
- if mirror.last_error.present?
.badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error')
%td.mirror-action-buttons
.btn-group.mirror-actions-group.pull-right{ role: 'group' }
= render 'shared/remote_mirror_update_button', remote_mirror: mirror
%button.js-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o')
- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
.form-group
= label_tag :mirror_direction, _('Mirror direction'), class: 'label-light'
= select_tag :mirror_direction, options_for_select([[_('Push'), 'push']]), class: 'form-control js-mirror-direction', disabled: true
= f.fields_for :remote_mirrors, @remote_mirror do |rm_f|
= 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'
.form-group
= label_tag :auth_method, _('Authentication method'), class: 'label-bold'
= select_tag :auth_method, options_for_select([[[_('None'), 'none'], [_('Password'), 'password']], 'none'), { class: "form-control js-auth-method", disabled: true }
.form-group.js-password-group.collapse
= label_tag :password, _('Password'), class: 'label-bold'
= text_field_tag :password, '', class: 'form-control js-password'
- expanded = Rails.env.test?
%section.settings.no-animate#js-push-remote-settings{ class: ('expanded' if expanded) }
.settings-header
%h4
Push to a remote repository
%button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand'
%p
Set up the remote repository that you want to update with the content of the current repository
every time someone pushes to it.
= link_to 'Read more', help_page_path('workflow/repository_mirroring', anchor: 'pushing-to-a-remote-repository'), target: '_blank'
.settings-content
= form_for @project, url: project_mirror_path(@project) do |f|
%div
= form_errors(@project)
= render "shared/remote_mirror_update_button", remote_mirror: @remote_mirror
- if @remote_mirror.last_error.present?
.card.bg-danger
.card-header
- if @remote_mirror.last_update_at
The remote repository failed to update #{time_ago_with_tooltip(@remote_mirror.last_update_at)}.
- else
The remote repository failed to update.
- if @remote_mirror.last_successful_update_at
Last successful update #{time_ago_with_tooltip(@remote_mirror.last_successful_update_at)}.
.card-body
%pre
:preserve
#{h(@remote_mirror.last_error.strip)}
= f.fields_for :remote_mirrors, @remote_mirror do |rm_form|
.form-group
= rm_form.check_box :enabled, class: "float-left"
.prepend-left-20
= rm_form.label :enabled, "Remote mirror repository", class: "label-bold append-bottom-0"
%p.light.append-bottom-0
Automatically update the remote mirror's branches, tags, and commits from this repository every time someone pushes to it.
.form-group.has-feedback
= rm_form.label :url, "Git repository URL", class: "label-bold"
= rm_form.text_field :url, class: "form-control", placeholder: 'https://username:password@gitlab.company.com/group/project.git'
= render "projects/mirrors/instructions"
.form-group
= rm_form.check_box :only_protected_branches, class: 'float-left'
.prepend-left-20
= rm_form.label :only_protected_branches, class: 'label-bold'
= link_to icon('question-circle'), help_page_path('user/project/protected_branches')
= f.submit 'Save changes', class: 'btn btn-create', name: 'update_remote_mirror'
- if can?(current_user, :admin_mirror, @project)
= render 'projects/mirrors/pull'
- if can?(current_user, :admin_remote_mirror, @project)
= render 'projects/mirrors/push'
= render 'projects/mirrors/mirror_repos'
- if @project.has_remote_mirror?
.append-bottom-default
- if remote_mirror.update_in_progress?
%span.btn.disabled
= icon("refresh spin")
Updating&hellip;
- else
= link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn" do
= icon("refresh")
Update Now
- if @remote_mirror.last_successful_update_at
%p.inline.prepend-left-10
Successfully updated #{time_ago_with_tooltip(@remote_mirror.last_successful_update_at)}.
- if remote_mirror.update_in_progress?
%button.btn.disabled{ type: 'button', data: { toggle: 'tooltip', container: 'body' }, title: _('Updating') }
= icon("refresh spin")
- else
= link_to update_now_project_mirror_path(@project, sync_remote: true), method: :post, class: "btn", data: { toggle: 'tooltip', container: 'body' }, title: _('Update now') do
= icon("refresh")
import $ from 'jquery';
import { __ } from '~/locale';
import Flash from '~/flash';
import MirrorRepos from '~/pages/projects/settings/repository/show/mirror_repos';
import MirrorPull from 'ee/mirrors/mirror_pull';
export default class EEMirrorRepos extends MirrorRepos {
constructor(...args) {
super(...args);
this.$password = undefined;
this.$mirrorDirectionSelect = $('.js-mirror-direction', this.$form);
this.$insertionPoint = $('.js-form-insertion-point', this.$form);
this.$repoCount = $('.js-mirrored-repo-count', this.$container);
this.directionFormMap = {
push: $('.js-push-mirrors-form', this.$form).html(),
pull: $('.js-pull-mirrors-form', this.$form).html(),
};
}
init() {
this.$insertionPoint.collapse({
toggle: false,
});
this.handleUpdate();
super.init();
}
handleUpdate() {
return this.hideForm()
.then(() => {
this.updateForm();
this.showForm();
})
.catch(() => {
Flash(__('Something went wrong on our end.'));
});
}
hideForm() {
return new Promise(resolve => {
if (!this.$insertionPoint.html()) return resolve();
this.$insertionPoint.one('hidden.bs.collapse', () => {
resolve();
});
return this.$insertionPoint.collapse('hide');
});
}
showForm() {
return new Promise(resolve => {
this.$insertionPoint.one('shown.bs.collapse', () => {
resolve();
});
this.$insertionPoint.collapse('show');
});
}
updateForm() {
const direction = this.$mirrorDirectionSelect.val();
this.$insertionPoint.collapse('hide');
this.$insertionPoint.html(this.directionFormMap[direction]);
this.$insertionPoint.collapse('show');
this.updateUrl();
this.updateProtectedBranches();
if (direction === 'pull') return this.initMirrorPull();
if (this.mirrorPull) this.mirrorPull.destroy();
return this.initMirrorPush();
}
initMirrorPull() {
this.$password.off('input.updateUrl');
this.$password = undefined;
this.mirrorPull = new MirrorPull('.js-mirror-form');
this.mirrorPull.init();
this.initSelect2();
}
initSelect2() {
$('.js-mirror-user', this.$form).select2({
width: 'resolve',
dropdownAutoWidth: true,
});
}
registerUpdateListeners() {
super.registerUpdateListeners();
this.$mirrorDirectionSelect.on('change', () => this.handleUpdate());
}
deleteMirror(event) {
const $target = $(event.currentTarget);
const isPullMirror = $target.hasClass('js-delete-pull-mirror');
let payload;
if (isPullMirror) {
payload = {
project: {
mirror: false,
},
};
}
return super.deleteMirror(event, payload)
.then(() => {
if (isPullMirror) this.$mirrorDirectionSelect.removeAttr('disabled');
});
}
removeRow($target) {
super.removeRow($target);
const currentCount = parseInt(this.$repoCount.text().replace(/(\(|\))/, ''), 10);
this.$repoCount.text(`(${currentCount - 1})`);
}
}
/* eslint-disable no-new */
import ProtectedBranchCreate from 'ee/protected_branches/protected_branch_create';
import ProtectedBranchEditList from 'ee/protected_branches/protected_branch_edit_list';
import ProtectedTagCreate from 'ee/protected_tags/protected_tag_create';
import ProtectedTagEditList from 'ee/protected_tags/protected_tag_edit_list';
import UsersSelect from '~/users_select';
import UserCallout from '~/user_callout';
import initSettingsPanels from '~/settings_panels';
......@@ -7,13 +12,8 @@ import CEProtectedBranchCreate from '~/protected_branches/protected_branch_creat
import CEProtectedBranchEditList from '~/protected_branches/protected_branch_edit_list';
import CEProtectedTagCreate from '~/protected_tags/protected_tag_create';
import CEProtectedTagEditList from '~/protected_tags/protected_tag_edit_list';
import MirrorPull from 'ee/mirrors/mirror_pull';
import DueDateSelectors from '~/due_date_select';
import ProtectedBranchCreate from 'ee/protected_branches/protected_branch_create';
import ProtectedBranchEditList from 'ee/protected_branches/protected_branch_edit_list';
import ProtectedTagCreate from 'ee/protected_tags/protected_tag_create';
import ProtectedTagEditList from 'ee/protected_tags/protected_tag_edit_list';
import EEMirrorRepos from './ee_mirror_repos';
document.addEventListener('DOMContentLoaded', () => {
new UsersSelect();
......@@ -36,11 +36,8 @@ document.addEventListener('DOMContentLoaded', () => {
new CEProtectedTagEditList();
}
const mirrorPull = new MirrorPull('.js-project-mirror-push-form');
if (mirrorPull) {
mirrorPull.init();
}
const pushPullContainer = document.querySelector('.js-mirror-settings');
if (pushPullContainer) new EEMirrorRepos(pushPullContainer).init();
new DueDateSelectors();
});
......@@ -16,7 +16,7 @@
}
.ssh-public-key {
width: 95%;
width: 94%;
word-wrap: break-word;
word-break: break-all;
}
......
......@@ -28,4 +28,14 @@ module MirrorHelper
def options_for_mirror_user
options_from_collection_for_select(default_mirror_users, :id, :name, @project.mirror_user_id || current_user.id)
end
def mirrored_repositories_count(project = @project)
count = project.mirror == true ? 1 : 0
count + @project.remote_mirrors.to_a.count { |mirror| mirror.enabled }
end
def mirrors_form_data_attributes
{ project_mirror_ssh_endpoint: ssh_host_keys_project_mirror_path(@project, :json),
project_mirror_endpoint: project_mirror_path(@project) }
end
end
- import_data = @project.import_data || @project.build_import_data
- is_one_user_option = default_mirror_users.count == 1
- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
- can_push = can?(current_user, :admin_remote_mirror, @project)
- can_pull = can?(current_user, :admin_mirror, @project)
- options = []
- options.push([_('Push'), 'push']) if can_push
- if can_pull
- has_existing_pull_mirror = can_pull && @project.mirror
- pull_addition_method = has_existing_pull_mirror ? options.method(:push) : options.method(:unshift)
- pull_addition_method.call([_('Pull'), 'pull']) if can_pull
.form-group
= label_tag :mirror_direction, _('Mirror direction'), class: 'label-light'
= select_tag :mirror_direction, options_for_select(options), class: 'form-control js-mirror-direction', disabled: (options.count == 1) || has_existing_pull_mirror
.js-form-insertion-point
- if can_push
%template.js-push-mirrors-form
= f.fields_for :remote_mirrors, @project.remote_mirrors.build do |rm_f|
= 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'
.form-group
= label_tag :auth_method, _('Authentication method'), class: 'label-bold'
= select_tag :auth_method, options_for_select([[_('None'), 'none'], [_('Password'), 'password']], 'none'), { class: "form-control js-auth-method" }
.form-group.js-password-group.collapse
= label_tag :password, _('Password'), class: 'label-bold'
= text_field_tag :password, '', type: 'password', class: 'form-control js-password'
- if can_pull
%template.js-pull-mirrors-form
= f.hidden_field :mirror, value: '1'
= f.hidden_field :username_only_import_url, class: 'js-mirror-url-hidden', required: true, pattern: "(#{protocols}):\/\/.+"
= f.hidden_field :only_mirror_protected_branches, class: 'js-mirror-protected-hidden'
= f.fields_for :import_data, import_data do |import_form|
= render partial: 'projects/mirrors/pull/ssh_host_keys', locals: { f: import_form }
= render partial: 'projects/mirrors/pull/authentication_method', locals: { f: import_form }
.form-group
= f.label :mirror_user_id, _('Mirror user'), class: 'label-light'
- if is_one_user_option
= select_tag(:mirror_user_id_select, options_for_mirror_user, class: 'js-mirror-user select2 lg append-bottom-5', required: true, disabled: true)
= f.hidden_field :mirror_user_id, value: default_mirror_users.first.id if is_one_user_option
- else
= f.select(:mirror_user_id, options_for_mirror_user, {}, class: 'js-mirror-user select2 lg append-bottom-5', required: true)
.help-block
= _('This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches. Upon creation or when reassigning you can only assign yourself to be the mirror user.')
.form-check.append-bottom-10
= f.check_box :mirror_overwrites_diverged_branches, class: 'form-check-input', checked: false
= f.label :mirror_overwrites_diverged_branches, _('Overwrite diverged branches'), class: 'form-check-label'
.form-text.text-muted
= _("If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored.") % { default_branch: @project.default_branch }
- if @project.builds_enabled?
= render 'shared/mirror_trigger_builds_setting', f: f, checked: false
%span.js-mirrored-repo-count (#{mirrored_repositories_count})
- expanded = Rails.env.test?
- import_data = @project.import_data || @project.build_import_data
- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
%section.settings.project-mirror-settings.no-animate#js-pull-remote-repository{ class: ('expanded' if expanded) }
.settings-header
%h4
Pull from a remote repository
%button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand'
%p
Set up your project to automatically have its branches, tags, and commits
updated from an upstream repository.
= link_to 'Read more', help_page_path('workflow/repository_mirroring', anchor: 'pulling-from-a-remote-repository'), target: '_blank'
.settings-content
= form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors project-mirror-push-form js-project-mirror-push-form', autocomplete: 'false', data: { project_mirror_endpoint: ssh_host_keys_project_mirror_path(@project, :json) } } do |f|
%div
= form_errors(@project)
= render "shared/mirror_update_button"
= render "projects/mirrors/pull/mirror_update_fail"
.form-group
= f.check_box :mirror, class: "float-left"
.prepend-left-20
= f.label :mirror, "Mirror repository", class: "label-bold append-bottom-0"
.form-group
= f.label :username_only_import_url, "Git repository URL", class: "label-bold"
= f.text_field :username_only_import_url, class: 'form-control js-repo-url', placeholder: 'https://username@gitlab.company.com/group/project.git', required: 'required', pattern: "(#{protocols}):\/\/.+", title: 'URL must have protocol present (eg; ssh://...)'
= render "projects/mirrors/instructions"
= f.fields_for :import_data, import_data do |import_form|
= render partial: "projects/mirrors/pull/ssh_host_keys", locals: { f: import_form }
= render partial: "projects/mirrors/pull/authentication_method", locals: { f: import_form }
.form-group
= f.label :mirror_user_id, "Mirror user", class: "label-bold"
= select_tag('project[mirror_user_id]', options_for_mirror_user, class: "select2 lg", required: true)
.form-text.text-muted
This user will be the author of all events in the activity feed that are the result of an update,
like new branches being created or new commits being pushed to existing branches.
You can only assign yourself to be the mirror user.
.form-group
= f.check_box :only_mirror_protected_branches, class: 'float-left'
.prepend-left-20
= f.label :only_mirror_protected_branches, class: 'label-bold'
= link_to icon('question-circle'), help_page_path('user/project/protected_branches')
.form-group
= f.check_box :mirror_overwrites_diverged_branches, class: 'float-left'
.prepend-left-20
= f.label :mirror_overwrites_diverged_branches, "Overwrite diverged branches", class: 'label-bold'
.form-text.text-muted
If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart,
to prevent local data loss. If the default branch (#{@project.default_branch}) has diverged and cannot be updated,
mirroring will fail. Other diverged branches are silently ignored.
- if @project.builds_enabled?
= render "shared/mirror_trigger_builds_setting", f: f
= f.submit 'Save changes', class: 'btn btn-create', name: 'update_remote_mirror'
- if @project.mirror
%tr
%td= @project.username_only_import_url
%td= _('Pull')
%td= @project.mirror_last_update_at.present? ? time_ago_with_tooltip(@project.mirror_last_update_at) : _('Never')
%td
- if @project.import_error.present?
.badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true' }, title: html_escape(@project.import_error.try(:strip)) }= _('Error')
%td.mirror-action-buttons
.btn-group.mirror-actions-group.pull-right{ role: 'group' }
- if @project.mirror?
- if @project.mirror_about_to_update? || @project.updating_mirror?
%button.btn.disabled{ type: 'button', data: { container: 'body', toggle: 'tooltip' }, title: _('Updating') }= icon("refresh spin")
- else
= link_to update_now_project_mirror_path(@project), method: :post, class: 'btn js-force-update-mirror', data: { container: 'body', toggle: 'tooltip' }, title: _('Update now') do
= icon("refresh")
%button.js-delete-mirror.js-delete-pull-mirror.btn.btn-danger{ type: 'button', data: { toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o')
......@@ -4,31 +4,31 @@
- ssh_public_key_present = import_data.ssh_public_key.present?
.form-group
= f.label :auth_method, 'Authentication method', class: 'label-bold'
= f.label :auth_method, _('Authentication method'), class: 'label-bold'
= f.select :auth_method,
options_for_select([['Password authentication', 'password'], ['SSH public key authentication', 'ssh_public_key']], import_data.auth_method),
{}, { class: "form-control js-pull-mirror-auth-type #{'hidden' unless import_data.ssh_import?}" }
options_for_select([[_('Password'), 'password'], [_('SSH public key'), 'ssh_public_key']], import_data.auth_method),
{}, { class: "form-control js-pull-mirror-auth-type" }
.form-group
.account-well.changing-auth-method.hidden.js-well-changing-auth
= icon('spinner spin lg')
.account-well.well-password-auth.hidden.js-well-password-auth
= f.label :password, "Password", class: "label-bold"
.collapse.js-well-changing-auth
.changing-auth-method= icon('spinner spin lg')
.well-password-auth.collapse.js-well-password-auth
= f.label :password, _("Password"), class: "label-bold"
= f.password_field :password, value: import_data.password, class: 'form-control'
.account-well.well-ssh-auth.hidden.js-well-ssh-auth
%p.js-ssh-public-key-present{ class: ('hidden' unless ssh_public_key_present) }
Here is the public SSH key that needs to be added to the remote
server. For more information, please refer to the documentation.
%p.js-ssh-public-key-pending{ class: ('hidden' if ssh_public_key_present) }
An SSH key will be automatically generated when the form is
submitted. For more information, please refer to the documentation.
.well-ssh-auth.collapse.js-well-ssh-auth
%p.js-ssh-public-key-present{ class: ('collapse' unless ssh_public_key_present) }
= _('Here is the public SSH key that needs to be added to the remote server. For more information, please refer to the documentation.')
%p.js-ssh-public-key-pending{ class: ('collapse' if ssh_public_key_present) }
= _('An SSH key will be automatically generated when the form is submitted. For more information, please refer to the documentation.')
.clearfix.js-ssh-public-key-wrap{ class: ('hidden' unless ssh_public_key_present) }
.clearfix.js-ssh-public-key-wrap{ class: ('collapse' unless ssh_public_key_present) }
%code.prepend-top-10.ssh-public-key
= import_data.ssh_public_key
= clipboard_button(text: import_data.ssh_public_key, title: _("Copy SSH public key to clipboard"), class: 'prepend-top-10 btn-copy-ssh-public-key')
= link_to 'Regenerate key', project_mirror_path(@project, project: { import_data_attributes: regen_data }),
method: :patch,
data: { confirm: 'Are you sure you want to regenerate public key? You will have to update the public key on the remote server before mirroring will work again.' },
class: "btn btn-inverted btn-warning prepend-top-10 js-btn-regenerate-ssh-key #{ 'hidden' unless ssh_public_key_present }"
= button_tag type: 'button',
data: { endpoint: project_mirror_path(@project, project: { import_data_attributes: regen_data }) },
class: "btn btn-inverted btn-warning prepend-top-10 js-btn-regenerate-ssh-key#{ ' collapse' unless ssh_public_key_present }" do
%i.fa.fa-spinner.fa-spin.js-spinner
= _('Regenerate key')
= render 'projects/mirrors/pull/regenerate_public_ssh_key_confirm_modal'
.modal.js-regenerate-public-ssh-key-confirm-modal{ tabindex: -1 }
.modal-dialog
.modal-content
.modal-header
%h3.modal-title.page-title
Regenerate public SSH key?
%button.close.js-cancel{ type: 'button', 'data-dismiss': 'modal', 'aria-label' => _('Close') }
%span{ 'aria-hidden': true } &times;
.modal-body
%p= _('Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again.')
.form-actions.modal-footer
= button_tag _('Cancel'), type: 'button', class: 'btn js-cancel'
= button_tag _('Regenerate key'), type: 'button', class: 'btn btn-inverted btn-warning js-confirm'
......@@ -2,13 +2,13 @@
- verified_by = import_data.ssh_known_hosts_verified_by
- verified_at = import_data.ssh_known_hosts_verified_at
.form-group.js-ssh-host-keys-section{ class: ('hidden' unless import_data.ssh_import?) }
%button.btn.btn-inverted.btn-success.append-bottom-15.js-detect-host-keys{ type: 'button' }
.form-group.js-ssh-host-keys-section{ class: ('collapse' unless import_data.ssh_import?) }
%button.btn.btn-inverted.btn-success.inline.js-detect-host-keys.append-right-10{ type: 'button' }
= icon('spinner spin', class: 'detect-host-keys-load-spinner hidden')
Detect host keys
.fingerprint-ssh-info.js-fingerprint-ssh-info{ class: ('hidden' unless import_data.ssh_import?) }
= _('Detect host keys')
.fingerprint-ssh-info.js-fingerprint-ssh-info.prepend-top-10.append-bottom-10{ class: ('collapse' unless import_data.ssh_import?) }
%label.label-bold
Fingerprints
= _('Fingerprints')
.fingerprints-list.js-fingerprints-list
- import_data.ssh_known_hosts_fingerprints.each do |fp|
%code= fp.fingerprint
......@@ -19,16 +19,15 @@
- if verified_by
= link_to verified_by.name, user_path(verified_by)
- else
a deleted user
= _('a deleted user')
#{time_ago_in_words(verified_at)} ago
.js-ssh-hosts-advanced
%button.btn.btn-sm.btn-default.prepend-top-10.append-bottom-15.btn-show-advanced.show-advanced{ type: 'button' }
.js-ssh-hosts-advanced.inline
%button.btn.btn-default.btn-show-advanced.show-advanced{ type: 'button' }
%span.label-show
Show advanced
= _('Input host keys manually')
%span.label-hide
Hide advanced
= icon('chevron')
.js-ssh-known-hosts.hidden
= f.label :ssh_known_hosts, 'SSH host keys', class: 'label-bold'
= f.text_area :ssh_known_hosts, class: 'form-control known-hosts js-known-hosts', rows: '10'
= _('Hide host keys manual input')
.js-ssh-known-hosts.collapse.prepend-top-default
= f.label :ssh_known_hosts, _('SSH host keys'), class: 'label-bold'
= f.text_area :ssh_known_hosts, class: 'form-control known-hosts js-known-hosts', rows: '10'
.form-group
= f.check_box :mirror_trigger_builds, class: "float-left"
.prepend-left-20
= f.label :mirror_trigger_builds, "Trigger pipelines for mirror updates", class: "label-bold"
%p.light.append-bottom-0
Trigger pipelines when branches or tags are updated from the upstream repository.
Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners.
Only enable this if you know they can handle the load.
<strong>CI will run using the credentials assigned above.</strong>
- checked = local_assigns.fetch(:checked, nil)
- check_box_options = {}
- check_box_options[:checked] = checked unless checked.nil?
.form-check.append-bottom-10
= f.check_box :mirror_trigger_builds, check_box_options.merge(class: "form-check-input")
= f.label :mirror_trigger_builds, _("Trigger pipelines for mirror updates"), class: "form-check-label"
.form-text.text-muted
= _('Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load.')
%strong= _('CI will run using the credentials assigned above.')
......@@ -30,7 +30,7 @@ describe 'Project mirror', :js do
visit project_mirror_path(project)
end
Sidekiq::Testing.fake! { click_link('Update Now') }
Sidekiq::Testing.fake! { find('.js-force-update-mirror').click }
end
end
......@@ -44,7 +44,7 @@ describe 'Project mirror', :js do
visit project_mirror_path(project)
end
expect(page).to have_content('Update Now')
expect(page).to have_selector('.js-force-update-mirror')
expect(page).to have_selector('.btn.disabled')
end
end
......@@ -66,10 +66,10 @@ describe 'Project mirror', :js do
visit project_settings_repository_path(project)
page.within('.project-mirror-settings') do
check 'Mirror repository'
fill_in 'Git repository URL', with: 'http://user@example.com'
select 'Pull', from: 'Mirror direction'
fill_in 'Password', with: 'foo'
click_without_sidekiq 'Save changes'
click_without_sidekiq 'Mirror repository'
end
expect(page).to have_content('Mirroring settings were successfully updated')
......@@ -87,8 +87,9 @@ describe 'Project mirror', :js do
page.within('.project-mirror-settings') do
fill_in 'Git repository URL', with: 'http://2.example.com'
select('Pull', from: 'Mirror direction')
fill_in 'Password', with: ''
click_without_sidekiq 'Save changes'
click_without_sidekiq 'Mirror repository'
end
expect(page).to have_content('Mirroring settings were successfully updated')
......@@ -104,9 +105,9 @@ describe 'Project mirror', :js do
visit project_settings_repository_path(project)
page.within('.project-mirror-settings') do
check 'Mirror repository'
fill_in 'Git repository URL', with: 'ssh://user@example.com'
select 'SSH public key authentication', from: 'Authentication method'
select('Pull', from: 'Mirror direction')
select 'SSH public key', from: 'Authentication method'
# Generates an SSH public key with an asynchronous PUT and displays it
wait_for_requests
......@@ -114,7 +115,7 @@ describe 'Project mirror', :js do
expect(import_data.ssh_public_key).not_to be_nil
expect(page).to have_content(import_data.ssh_public_key)
click_without_sidekiq 'Save changes'
click_without_sidekiq 'Mirror repository'
end
# We didn't set any host keys
......@@ -128,11 +129,16 @@ describe 'Project mirror', :js do
expect(import_data.auth_method).to eq('ssh_public_key')
expect(import_data.password).to be_blank
find('.js-delete-mirror').click
fill_in 'Git repository URL', with: 'ssh://user@example.com'
select('Pull', from: 'Mirror direction')
first_key = import_data.ssh_public_key
expect(page).to have_content(first_key)
# Check regenerating the public key works
accept_confirm { click_without_sidekiq 'Regenerate key' }
click_without_sidekiq 'Regenerate key'
find('.js-regenerate-public-ssh-key-confirm-modal .js-confirm').click
wait_for_requests
expect(page).not_to have_content(first_key)
......@@ -151,12 +157,13 @@ describe 'Project mirror', :js do
page.within('.project-mirror-settings') do
fill_in 'Git repository URL', with: 'ssh://example.com'
select('Pull', from: 'Mirror direction')
click_on 'Detect host keys'
wait_for_requests
expect(page).to have_content(key.fingerprint)
click_on 'Show advanced'
click_on 'Input host keys manually'
expect(page).to have_field('SSH host keys', with: key.key_text)
end
......@@ -169,6 +176,7 @@ describe 'Project mirror', :js do
page.within('.project-mirror-settings') do
fill_in 'Git repository URL', with: 'ssh://example.com'
select('Pull', from: 'Mirror direction')
click_on 'Detect host keys'
wait_for_requests
end
......@@ -182,9 +190,14 @@ describe 'Project mirror', :js do
page.within('.project-mirror-settings') do
fill_in 'Git repository URL', with: 'ssh://example.com'
click_on 'Show advanced'
select('Pull', from: 'Mirror direction')
click_on 'Input host keys manually'
fill_in 'SSH host keys', with: "example.com #{key.key_text}"
click_without_sidekiq 'Save changes'
click_without_sidekiq 'Mirror repository'
find('.js-delete-mirror').click
fill_in 'Git repository URL', with: 'ssh://example.com'
select('Pull', from: 'Mirror direction')
expect(page).to have_content(key.fingerprint)
expect(page).to have_content("Verified by #{h(user.name)} less than a minute ago")
......@@ -198,20 +211,22 @@ describe 'Project mirror', :js do
page.within('.project-mirror-settings') do
fill_in 'Git repository URL', with: 'ssh://example.com'
select('Pull', from: 'Mirror direction')
execute_script 'document.querySelector("html").scrollTop = 1000;'
expect(page).to have_select('Authentication method')
# SSH can use password authentication but needs host keys
select 'Password authentication', from: 'Authentication method'
select 'Password', from: 'Authentication method'
expect(page).to have_field('Password')
expect(page).to have_button('Detect host keys')
expect(page).to have_button('Show advanced')
expect(page).to have_button('Input host keys manually')
# SSH public key authentication also needs host keys but no password
select 'SSH public key authentication', from: 'Authentication method'
select 'SSH public key', from: 'Authentication method'
expect(page).not_to have_field('Password')
expect(page).to have_button('Detect host keys')
expect(page).to have_button('Show advanced')
expect(page).to have_button('Input host keys manually')
end
end
......@@ -220,12 +235,13 @@ describe 'Project mirror', :js do
page.within('.project-mirror-settings') do
fill_in 'Git repository URL', with: 'https://example.com'
select('Pull', from: 'Mirror direction')
# HTTPS can't use public key authentication and doesn't need host keys
expect(page).to have_field('Password')
expect(page).not_to have_select('Authentication method')
expect(page).not_to have_button('Detect host keys')
expect(page).not_to have_button('Show advanced')
expect(page).not_to have_button('Input host keys manually')
end
end
end
......
......@@ -18,7 +18,7 @@ describe 'Project settings > [EE] repository' do
visit project_settings_repository_path(project)
end
it 'does not show pull mirror settings' do
it 'does not show pull mirror settings', :js do
expect(page).to have_no_selector('#project_mirror')
expect(page).to have_no_selector('#project_import_url')
expect(page).to have_no_selector('#project_mirror_user_id', visible: false)
......
......@@ -495,6 +495,9 @@ msgstr ""
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
msgstr ""
msgid "An SSH key will be automatically generated when the form is submitted. For more information, please refer to the documentation."
msgstr ""
msgid "An application called %{link_to_client} is requesting access to your GitLab account."
msgstr ""
......@@ -666,6 +669,9 @@ msgstr ""
msgid "Are you sure you want to lose unsaved changes?"
msgstr ""
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr ""
msgid "Are you sure you want to remove %{group_name}?"
msgstr ""
......@@ -750,6 +756,9 @@ msgstr ""
msgid "Authentication log"
msgstr ""
msgid "Authentication method"
msgstr ""
msgid "Author"
msgstr ""
......@@ -1178,6 +1187,9 @@ msgstr ""
msgid "CI / CD Settings"
msgstr ""
msgid "CI will run using the credentials assigned above."
msgstr ""
msgid "CI/CD"
msgstr ""
......@@ -2477,12 +2489,18 @@ msgstr ""
msgid "Details"
msgstr ""
msgid "Detect host keys"
msgstr ""
msgid "Diffs|No file name available"
msgstr ""
msgid "Diffs|Something went wrong while fetching diff lines."
msgstr ""
msgid "Direction"
msgstr ""
msgid "Directory name"
msgstr ""
......@@ -2759,6 +2777,9 @@ msgstr ""
msgid "Epics let you manage your portfolio of projects more efficiently and with less effort"
msgstr ""
msgid "Error"
msgstr ""
msgid "Error Reporting and Logging"
msgstr ""
......@@ -2906,6 +2927,9 @@ msgstr ""
msgid "Failed to remove issue from board, please try again."
msgstr ""
msgid "Failed to remove mirror."
msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
......@@ -2954,6 +2978,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
msgid "Fingerprints"
msgstr ""
msgid "Finished"
msgstr ""
......@@ -3610,6 +3637,12 @@ msgstr ""
msgid "Help page text and support page url."
msgstr ""
msgid "Here is the public SSH key that needs to be added to the remote server. For more information, please refer to the documentation."
msgstr ""
msgid "Hide host keys manual input"
msgstr ""
msgid "Hide value"
msgid_plural "Hide values"
msgstr[0] ""
......@@ -3678,6 +3711,9 @@ msgstr ""
msgid "Identity provider single sign on URL"
msgstr ""
msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored."
msgstr ""
msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
......@@ -3774,12 +3810,21 @@ msgstr ""
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
msgid "Incompatible Project"
msgstr ""
msgid "Inline"
msgstr ""
msgid "Input host keys manually"
msgstr ""
msgid "Input your repository URL"
msgstr ""
msgid "Install GitLab Runner"
msgstr ""
......@@ -4387,6 +4432,24 @@ msgstr ""
msgid "Milestones|This action cannot be reversed."
msgstr ""
msgid "Mirror a repository"
msgstr ""
msgid "Mirror direction"
msgstr ""
msgid "Mirror repository"
msgstr ""
msgid "Mirror user"
msgstr ""
msgid "Mirrored repositories"
msgstr ""
msgid "Mirroring repositories"
msgstr ""
msgid "MissingSSHKeyWarningLink|add an SSH key"
msgstr ""
......@@ -4456,6 +4519,9 @@ msgstr ""
msgid "Network"
msgstr ""
msgid "Never"
msgstr ""
msgid "New"
msgstr ""
......@@ -4746,6 +4812,9 @@ msgstr ""
msgid "Only comments from the following commit are shown below"
msgstr ""
msgid "Only mirror protected branches"
msgstr ""
msgid "Only project members can comment."
msgstr ""
......@@ -4809,6 +4878,9 @@ msgstr ""
msgid "Overview"
msgstr ""
msgid "Overwrite diverged branches"
msgstr ""
msgid "Owner"
msgstr ""
......@@ -5460,6 +5532,12 @@ msgstr ""
msgid "Public pipelines"
msgstr ""
msgid "Pull"
msgstr ""
msgid "Push"
msgstr ""
msgid "Push Rules"
msgstr ""
......@@ -5505,6 +5583,9 @@ msgstr ""
msgid "Refresh"
msgstr ""
msgid "Regenerate key"
msgstr ""
msgid "Register / Sign In"
msgstr ""
......@@ -5732,6 +5813,12 @@ msgstr ""
msgid "SSH Keys"
msgstr ""
msgid "SSH host keys"
msgstr ""
msgid "SSH public key"
msgstr ""
msgid "SSL Verification"
msgstr ""
......@@ -5948,6 +6035,9 @@ msgstr ""
msgid "Set up assertions/attributes/claims (email, first_name, last_name) and NameID according to %{docsLinkStart}the documentation %{icon}%{docsLinkEnd}"
msgstr ""
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
msgstr ""
msgid "SetPasswordToCloneLink|set a password"
msgstr ""
......@@ -6426,6 +6516,9 @@ msgstr ""
msgid "The Advanced Global Search in GitLab is a powerful search service that saves you time. Instead of creating duplicate code and wasting time, you can now search for code within other teams that can help your own project."
msgstr ""
msgid "The Git LFS objects will <strong>not</strong> be synced."
msgstr ""
msgid "The Issue Tracker is the place to add things that need to be improved or solved in a project"
msgstr ""
......@@ -6498,6 +6591,9 @@ msgstr ""
msgid "The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>."
msgstr ""
msgid "The repository must be accessible over <code>http://</code>, <code>https://</code>, <code>ssh://</code> and <code>git://</code>."
msgstr ""
msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request."
msgstr ""
......@@ -6675,6 +6771,12 @@ msgstr ""
msgid "This user has no identities"
msgstr ""
msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
msgstr ""
msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches. Upon creation or when reassigning you can only assign yourself to be the mirror user."
msgstr ""
msgid "This will delete the custom metric, Are you sure?"
msgstr ""
......@@ -6967,6 +7069,12 @@ msgstr ""
msgid "Trending"
msgstr ""
msgid "Trigger pipelines for mirror updates"
msgstr ""
msgid "Trigger pipelines when branches or tags are updated from the upstream repository. Depending on the activity of the upstream repository, this may greatly increase the load on your CI runners. Only enable this if you know they can handle the load."
msgstr ""
msgid "Trigger this manual action"
msgstr ""
......@@ -7036,9 +7144,15 @@ msgstr ""
msgid "Update"
msgstr ""
msgid "Update now"
msgstr ""
msgid "Update your group name, description, avatar, and other general settings."
msgstr ""
msgid "Updating"
msgstr ""
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr ""
......@@ -7552,6 +7666,9 @@ msgstr ""
msgid "Your projects"
msgstr ""
msgid "a deleted user"
msgstr ""
msgid "ago"
msgstr ""
......
......@@ -17,7 +17,7 @@ describe 'Project remote mirror', :feature do
visit project_mirror_path(project)
expect(page).to have_content('The remote repository failed to update.')
expect_mirror_to_have_error_and_timeago('Never')
end
end
......@@ -27,8 +27,14 @@ describe 'Project remote mirror', :feature do
visit project_mirror_path(project)
expect(page).to have_content('The remote repository failed to update 5 minutes ago.')
expect_mirror_to_have_error_and_timeago('5 minutes ago')
end
end
def expect_mirror_to_have_error_and_timeago(timeago)
row = first('.js-mirrors-table-body tr')
expect(row).to have_content('Error')
expect(row).to have_content(timeago)
end
end
end
......@@ -129,9 +129,8 @@ describe 'Projects > Settings > Repository settings' do
visit project_settings_repository_path(project)
end
it 'shows push mirror settings' do
expect(page).to have_selector('#project_remote_mirrors_attributes_0_enabled')
expect(page).to have_selector('#project_remote_mirrors_attributes_0_url')
it 'shows push mirror settings', :js do
expect(page).to have_selector('#mirror_direction')
end
end
end
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment