Commit 578c930f authored by Fatih Acet's avatar Fatih Acet

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into...

Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce into 41563-fix-branch-creation-from-issue-in-firefox
parents f4c2eeb2 6c2a35fe
...@@ -45,11 +45,9 @@ export default { ...@@ -45,11 +45,9 @@ export default {
onLeaveGroup() { onLeaveGroup() {
this.modalStatus = true; this.modalStatus = true;
}, },
leaveGroup(leaveConfirmed) { leaveGroup() {
this.modalStatus = false; this.modalStatus = false;
if (leaveConfirmed) { eventHub.$emit('leaveGroup', this.group, this.parentGroup);
eventHub.$emit('leaveGroup', this.group, this.parentGroup);
}
}, },
}, },
}; };
......
...@@ -42,28 +42,28 @@ export default { ...@@ -42,28 +42,28 @@ export default {
v-if="isGroup" v-if="isGroup"
css-class="number-subgroups" css-class="number-subgroups"
icon-name="folder" icon-name="folder"
:title="s__('Subgroups')" :title="__('Subgroups')"
:value=item.subgroupCount :value="item.subgroupCount"
/> />
<item-stats-value <item-stats-value
v-if="isGroup" v-if="isGroup"
css-class="number-projects" css-class="number-projects"
icon-name="bookmark" icon-name="bookmark"
:title="s__('Projects')" :title="__('Projects')"
:value=item.projectCount :value="item.projectCount"
/> />
<item-stats-value <item-stats-value
v-if="isGroup" v-if="isGroup"
css-class="number-users" css-class="number-users"
icon-name="users" icon-name="users"
:title="s__('Members')" :title="__('Members')"
:value=item.memberCount :value="item.memberCount"
/> />
<item-stats-value <item-stats-value
v-if="isProject" v-if="isProject"
css-class="project-stars" css-class="project-stars"
icon-name="star" icon-name="star"
:value=item.starCount :value="item.starCount"
/> />
<item-stats-value <item-stats-value
css-class="item-visibility" css-class="item-visibility"
......
...@@ -32,10 +32,10 @@ ...@@ -32,10 +32,10 @@
methods: { methods: {
createNewItem(type) { createNewItem(type) {
this.modalType = type; this.modalType = type;
this.toggleModalOpen(); this.openModal = true;
}, },
toggleModalOpen() { hideModal() {
this.openModal = !this.openModal; this.openModal = false;
}, },
}, },
}; };
...@@ -95,7 +95,7 @@ ...@@ -95,7 +95,7 @@
:branch-id="branch" :branch-id="branch"
:path="path" :path="path"
:parent="parent" :parent="parent"
@toggle="toggleModalOpen" @hide="hideModal"
/> />
</div> </div>
</template> </template>
...@@ -43,10 +43,10 @@ ...@@ -43,10 +43,10 @@
type: this.type, type: this.type,
}); });
this.toggleModalOpen(); this.hideModal();
}, },
toggleModalOpen() { hideModal() {
this.$emit('toggle'); this.$emit('hide');
}, },
}, },
computed: { computed: {
...@@ -86,7 +86,7 @@ ...@@ -86,7 +86,7 @@
:title="modalTitle" :title="modalTitle"
:primary-button-label="buttonLabel" :primary-button-label="buttonLabel"
kind="success" kind="success"
@toggle="toggleModalOpen" @cancel="hideModal"
@submit="createEntryInStore" @submit="createEntryInStore"
> >
<form <form
......
...@@ -110,7 +110,7 @@ export default { ...@@ -110,7 +110,7 @@ export default {
kind="primary" kind="primary"
:title="__('Branch has changed')" :title="__('Branch has changed')"
:text="__('This branch has changed since you started editing. Would you like to create a new branch?')" :text="__('This branch has changed since you started editing. Would you like to create a new branch?')"
@toggle="showNewBranchModal = false" @cancel="showNewBranchModal = false"
@submit="makeCommit(true)" @submit="makeCommit(true)"
/> />
<commit-files-list <commit-files-list
......
...@@ -50,7 +50,7 @@ export default { ...@@ -50,7 +50,7 @@ export default {
kind="warning" kind="warning"
:title="__('Are you sure?')" :title="__('Are you sure?')"
:text="__('Are you sure you want to discard your changes?')" :text="__('Are you sure you want to discard your changes?')"
@toggle="closeDiscardPopup" @cancel="closeDiscardPopup"
@submit="toggleEditMode(true)" @submit="toggleEditMode(true)"
/> />
</div> </div>
......
<script> <script>
import modal from '../../../vue_shared/components/modal.vue'; import modal from '~/vue_shared/components/modal.vue';
import { __, s__, sprintf } from '../../../locale'; import { __, s__, sprintf } from '~/locale';
import csrf from '../../../lib/utils/csrf'; import csrf from '~/lib/utils/csrf';
export default { export default {
props: { props: {
...@@ -22,7 +22,6 @@ ...@@ -22,7 +22,6 @@
return { return {
enteredPassword: '', enteredPassword: '',
enteredUsername: '', enteredUsername: '',
isOpen: false,
}; };
}, },
components: { components: {
...@@ -69,78 +68,58 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`), ...@@ -69,78 +68,58 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`),
return this.enteredUsername === this.username; return this.enteredUsername === this.username;
}, },
onSubmit(status) { onSubmit() {
if (status) { this.$refs.form.submit();
if (!this.canSubmit()) {
return;
}
this.$refs.form.submit();
}
this.toggleOpen(false);
},
toggleOpen(isOpen) {
this.isOpen = isOpen;
}, },
}, },
}; };
</script> </script>
<template> <template>
<div> <modal
<modal id="delete-account-modal"
v-if="isOpen" :title="s__('Profiles|Delete your account?')"
:title="s__('Profiles|Delete your account?')" :text="text"
:text="text" kind="danger"
:kind="`danger ${!canSubmit() && 'disabled'}`" :primary-button-label="s__('Profiles|Delete account')"
:primary-button-label="s__('Profiles|Delete account')" @submit="onSubmit"
@toggle="toggleOpen" :submit-disabled="!canSubmit()">
@submit="onSubmit">
<template slot="body" slot-scope="props">
<p v-html="props.text"></p>
<form <template slot="body" slot-scope="props">
ref="form" <p v-html="props.text"></p>
:action="actionUrl"
method="post">
<input <form
type="hidden" ref="form"
name="_method" :action="actionUrl"
value="delete" /> method="post">
<input
type="hidden"
name="authenticity_token"
:value="csrfToken" />
<p id="input-label" v-html="inputLabel"></p> <input
type="hidden"
name="_method"
value="delete" />
<input
type="hidden"
name="authenticity_token"
:value="csrfToken" />
<input <p id="input-label" v-html="inputLabel"></p>
v-if="confirmWithPassword"
name="password"
class="form-control"
type="password"
v-model="enteredPassword"
aria-labelledby="input-label" />
<input
v-else
name="username"
class="form-control"
type="text"
v-model="enteredUsername"
aria-labelledby="input-label" />
</form>
</template>
</modal> <input
v-if="confirmWithPassword"
name="password"
class="form-control"
type="password"
v-model="enteredPassword"
aria-labelledby="input-label" />
<input
v-else
name="username"
class="form-control"
type="text"
v-model="enteredUsername"
aria-labelledby="input-label" />
</form>
</template>
<button </modal>
type="button"
class="btn btn-danger"
@click="toggleOpen(true)">
{{ s__('Profiles|Delete account') }}
</button>
</div>
</template> </template>
import Vue from 'vue'; import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import deleteAccountModal from './components/delete_account_modal.vue'; import deleteAccountModal from './components/delete_account_modal.vue';
Vue.use(Translate);
const deleteAccountButton = document.getElementById('delete-account-button');
const deleteAccountModalEl = document.getElementById('delete-account-modal'); const deleteAccountModalEl = document.getElementById('delete-account-modal');
// eslint-disable-next-line no-new // eslint-disable-next-line no-new
new Vue({ new Vue({
...@@ -9,6 +14,9 @@ new Vue({ ...@@ -9,6 +14,9 @@ new Vue({
components: { components: {
deleteAccountModal, deleteAccountModal,
}, },
mounted() {
deleteAccountButton.classList.remove('disabled');
},
render(createElement) { render(createElement) {
return createElement('delete-account-modal', { return createElement('delete-account-modal', {
props: { props: {
......
let hasUserDefinedProjectPath = false; let hasUserDefinedProjectPath = false;
const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => { const deriveProjectPathFromUrl = ($projectImportUrl) => {
const $currentProjectPath = $projectImportUrl.parents('.toggle-import-form').find('#project_path');
if (hasUserDefinedProjectPath) { if (hasUserDefinedProjectPath) {
return; return;
} }
...@@ -21,7 +22,7 @@ const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => { ...@@ -21,7 +22,7 @@ const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => {
// extract everything after the last slash // extract everything after the last slash
const pathMatch = /\/([^/]+)$/.exec(importUrl); const pathMatch = /\/([^/]+)$/.exec(importUrl);
if (pathMatch) { if (pathMatch) {
$projectPath.val(pathMatch[1]); $currentProjectPath.val(pathMatch[1]);
} }
}; };
...@@ -96,7 +97,7 @@ const bindEvents = () => { ...@@ -96,7 +97,7 @@ const bindEvents = () => {
hasUserDefinedProjectPath = $projectPath.val().trim().length > 0; hasUserDefinedProjectPath = $projectPath.val().trim().length > 0;
}); });
$projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl, $projectPath)); $projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl));
}; };
document.addEventListener('DOMContentLoaded', bindEvents); document.addEventListener('DOMContentLoaded', bindEvents);
......
...@@ -3,6 +3,10 @@ export default { ...@@ -3,6 +3,10 @@ export default {
name: 'modal', name: 'modal',
props: { props: {
id: {
type: String,
required: false,
},
title: { title: {
type: String, type: String,
required: false, required: false,
...@@ -62,11 +66,11 @@ export default { ...@@ -62,11 +66,11 @@ export default {
}, },
methods: { methods: {
close() { emitCancel(event) {
this.$emit('toggle', false); this.$emit('cancel', event);
}, },
emitSubmit(status) { emitSubmit(event) {
this.$emit('submit', status); this.$emit('submit', event);
}, },
}, },
}; };
...@@ -75,7 +79,9 @@ export default { ...@@ -75,7 +79,9 @@ export default {
<template> <template>
<div class="modal-open"> <div class="modal-open">
<div <div
class="modal show" :id="id"
class="modal"
:class="id ? '' : 'show'"
role="dialog" role="dialog"
tabindex="-1" tabindex="-1"
> >
...@@ -93,7 +99,8 @@ export default { ...@@ -93,7 +99,8 @@ export default {
<button <button
type="button" type="button"
class="close pull-right" class="close pull-right"
@click="close" @click="emitCancel($event)"
data-dismiss="modal"
aria-label="Close" aria-label="Close"
> >
<span aria-hidden="true">&times;</span> <span aria-hidden="true">&times;</span>
...@@ -110,7 +117,8 @@ export default { ...@@ -110,7 +117,8 @@ export default {
type="button" type="button"
class="btn pull-left" class="btn pull-left"
:class="btnCancelKindClass" :class="btnCancelKindClass"
@click="close"> @click="emitCancel($event)"
data-dismiss="modal">
{{ closeButtonLabel }} {{ closeButtonLabel }}
</button> </button>
<button <button
...@@ -119,13 +127,17 @@ export default { ...@@ -119,13 +127,17 @@ export default {
class="btn pull-right js-primary-button" class="btn pull-right js-primary-button"
:disabled="submitDisabled" :disabled="submitDisabled"
:class="btnKindClass" :class="btnKindClass"
@click="emitSubmit(true)"> @click="emitSubmit($event)"
data-dismiss="modal">
{{ primaryButtonLabel }} {{ primaryButtonLabel }}
</button> </button>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<div class="modal-backdrop fade in" /> <div
v-if="!id"
class="modal-backdrop fade in">
</div>
</div> </div>
</template> </template>
...@@ -70,7 +70,7 @@ export default { ...@@ -70,7 +70,7 @@ export default {
class="recaptcha-modal js-recaptcha-modal" class="recaptcha-modal js-recaptcha-modal"
:hide-footer="true" :hide-footer="true"
:title="__('Please solve the reCAPTCHA')" :title="__('Please solve the reCAPTCHA')"
@toggle="close" @cancel="close"
> >
<div slot="body"> <div slot="body">
<p> <p>
......
module DeploymentPlatform module DeploymentPlatform
def deployment_platform def deployment_platform
@deployment_platform ||= find_cluster_platform_kubernetes @deployment_platform ||=
@deployment_platform ||= find_kubernetes_service_integration find_cluster_platform_kubernetes ||
@deployment_platform ||= build_cluster_and_deployment_platform find_kubernetes_service_integration ||
build_cluster_and_deployment_platform
end end
private private
......
...@@ -84,11 +84,13 @@ ...@@ -84,11 +84,13 @@
= s_('Profiles|Deleting an account has the following effects:') = s_('Profiles|Deleting an account has the following effects:')
= render 'users/deletion_guidance', user: current_user = render 'users/deletion_guidance', user: current_user
%button#delete-account-button.btn.btn-danger.disabled{ data: { toggle: 'modal',
target: '#delete-account-modal' } }
= s_('Profiles|Delete account')
#delete-account-modal{ data: { action_url: user_registration_path, #delete-account-modal{ data: { action_url: user_registration_path,
confirm_with_password: ('true' if current_user.confirm_deletion_with_password?), confirm_with_password: ('true' if current_user.confirm_deletion_with_password?),
username: current_user.username } } username: current_user.username } }
%button.btn.btn-danger.disabled
= s_('Profiles|Delete account')
- else - else
- if @user.solo_owned_groups.present? - if @user.solo_owned_groups.present?
%p %p
......
...@@ -11,5 +11,5 @@ ...@@ -11,5 +11,5 @@
%label.text-danger %label.text-danger
= s_('ClusterIntegration|Remove cluster integration') = s_('ClusterIntegration|Remove cluster integration')
%p %p
= s_('ClusterIntegration|Removing cluster integration will remove the cluster configuration you have added to this project. It will not delete your cluster on Google Kubernetes Engine.') = s_("ClusterIntegration|Remove this cluster's configuration from this project. This will not delete your actual cluster.")
= link_to(s_('ClusterIntegration|Remove integration'), namespace_project_cluster_path(@project.namespace, @project, @cluster.id), method: :delete, class: 'btn btn-danger', data: { confirm: "Are you sure you want to remove cluster integration from this project? This will not delete your cluster on Google Kubernetes Engine"}) = link_to(s_('ClusterIntegration|Remove integration'), namespace_project_cluster_path(@project.namespace, @project, @cluster.id), method: :delete, class: 'btn btn-danger', data: { confirm: s_("ClusterIntegration|Are you sure you want to remove this cluster's integration? This will not delete your actual cluster.")})
%h4= s_('ClusterIntegration|Enable cluster integration') %h4= s_('ClusterIntegration|Cluster integration')
.settings-content
.settings-content
.hidden.js-cluster-error.alert.alert-danger.alert-block.append-bottom-10{ role: 'alert' } .hidden.js-cluster-error.alert.alert-danger.alert-block.append-bottom-10{ role: 'alert' }
= s_('ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine') = s_('ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine')
%p.js-error-reason %p.js-error-reason
...@@ -11,11 +11,4 @@ ...@@ -11,11 +11,4 @@
.hidden.js-cluster-success.alert.alert-success.alert-block.append-bottom-10{ role: 'alert' } .hidden.js-cluster-success.alert.alert-success.alert-block.append-bottom-10{ role: 'alert' }
= s_('ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster\'s details') = s_('ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster\'s details')
%p %p= s_('ClusterIntegration|Control how your cluster integrates with GitLab')
- if @cluster.enabled?
- if can?(current_user, :update_cluster, @cluster)
= s_('ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab\'s connection to it.')
- else
= s_('ClusterIntegration|Cluster integration is enabled for this project.')
- else
= s_('ClusterIntegration|Cluster integration is disabled for this project.')
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
.table-mobile-content .table-mobile-content
= link_to cluster.name, namespace_project_cluster_path(@project.namespace, @project, cluster) = link_to cluster.name, namespace_project_cluster_path(@project.namespace, @project, cluster)
.table-section.section-30 .table-section.section-30
.table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Environment pattern") .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Environment scope")
.table-mobile-content= cluster.environment_scope .table-mobile-content= cluster.environment_scope
.table-section.section-30 .table-section.section-30
.table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Project namespace") .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Project namespace")
......
= form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field| = form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field|
= form_errors(@cluster) = form_errors(@cluster)
.form-group.append-bottom-20 .form-group.append-bottom-20
%h5= s_('ClusterIntegration|Integration status')
%p
- if @cluster.enabled?
- if can?(current_user, :update_cluster, @cluster)
= s_('ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab\'s connection to it.')
- else
= s_('ClusterIntegration|Cluster integration is enabled for this project.')
- else
= s_('ClusterIntegration|Cluster integration is disabled for this project.')
%label.append-bottom-10 %label.append-bottom-10
= field.hidden_field :enabled, { class: 'js-toggle-input'} = field.hidden_field :enabled, { class: 'js-toggle-input'}
...@@ -12,6 +21,13 @@ ...@@ -12,6 +21,13 @@
= sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked') = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked')
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked') = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
- if can?(current_user, :update_cluster, @cluster) .form-group
.form-group %h5= s_('ClusterIntegration|Environment scope')
= field.submit _('Save'), class: 'btn btn-success' %p
= s_("ClusterIntegration|Choose which of your project's environments will use this cluster.")
= link_to s_("ClusterIntegration|Learn more about environments"), help_page_path('ci/environments')
= field.text_field :environment_scope, class: 'form-control js-select-on-focus', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope')
- if can?(current_user, :update_cluster, @cluster)
.form-group
= field.submit _('Save changes'), class: 'btn btn-success'
...@@ -9,10 +9,6 @@ ...@@ -9,10 +9,6 @@
= form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field| = form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field|
= form_errors(@cluster) = form_errors(@cluster)
.form-group
= field.label :environment_scope, s_('ClusterIntegration|Environment scope')
= field.text_field :environment_scope, class: 'form-control js-select-on-focus', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope')
= field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field| = field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field|
.form-group .form-group
= platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL') = platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL')
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
.table-section.section-30{ role: "rowheader" } .table-section.section-30{ role: "rowheader" }
= s_("ClusterIntegration|Cluster") = s_("ClusterIntegration|Cluster")
.table-section.section-30{ role: "rowheader" } .table-section.section-30{ role: "rowheader" }
= s_("ClusterIntegration|Environment pattern") = s_("ClusterIntegration|Environment scope")
.table-section.section-30{ role: "rowheader" } .table-section.section-30{ role: "rowheader" }
= s_("ClusterIntegration|Project namespace") = s_("ClusterIntegration|Project namespace")
.table-section.section-10{ role: "rowheader" } .table-section.section-10{ role: "rowheader" }
......
...@@ -18,9 +18,9 @@ ...@@ -18,9 +18,9 @@
.js-cluster-application-notice .js-cluster-application-notice
.flash-container .flash-container
%section.settings.no-animate.expanded %section.settings.no-animate.expanded#cluster-integration
= render 'banner' = render 'banner'
= render 'enabled' = render 'integration_form'
.cluster-applications-table#js-cluster-applications .cluster-applications-table#js-cluster-applications
...@@ -41,6 +41,6 @@ ...@@ -41,6 +41,6 @@
%h4= _('Advanced settings') %h4= _('Advanced settings')
%button.btn.js-settings-toggle %button.btn.js-settings-toggle
= expanded ? 'Collapse' : 'Expand' = expanded ? 'Collapse' : 'Expand'
%p= s_('ClusterIntegration|Manage cluster integration on your GitLab project') %p= s_("ClusterIntegration|Advanced options on this cluster's integration")
.settings-content .settings-content
= render 'advanced_settings' = render 'advanced_settings'
...@@ -4,10 +4,6 @@ ...@@ -4,10 +4,6 @@
= field.label :name, s_('ClusterIntegration|Cluster name') = field.label :name, s_('ClusterIntegration|Cluster name')
= field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name') = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name')
.form-group
= field.label :environment_scope, s_('ClusterIntegration|Environment scope')
= field.text_field :environment_scope, class: 'form-control js-select-on-focus', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope')
= field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field| = field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field|
.form-group .form-group
= platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL') = platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL')
......
---
title: Fix gitlab-rake gitlab:import:repos import schedule
merge_request: 15931
author:
type: fixed
---
title: Add online and status attribute to runner api entity
merge_request: 11750
author:
type: added
---
title: Fix import project url not updating project name
merge_request: 16120
author:
type: fixed
---
title: Modify `LDAP::Person` to return username value based on attributes
merge_request:
author:
type: fixed
---
title: Add id to modal.vue to support data-toggle="modal"
merge_request: 16189
author:
type: other
# See http://doc.gitlab.com/ce/development/migration_style_guide.html # See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab. # for more information on how to write migrations for GitLab.
# rubocop:disable Migration/Datetime
class ScheduleIssuesClosedAtTypeChange < ActiveRecord::Migration class ScheduleIssuesClosedAtTypeChange < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
......
...@@ -28,19 +28,25 @@ exactly which repositories are causing the trouble. ...@@ -28,19 +28,25 @@ exactly which repositories are causing the trouble.
### Check all GitLab repositories ### Check all GitLab repositories
>**Note:**
>
> - `gitlab:repo:check` has been deprecated in favor of `gitlab:git:fsck`
> - [Deprecated][ce-15931] in GitLab 10.4.
> - `gitlab:repo:check` will be removed in the future. [Removal issue][ce-41699]
This task loops through all repositories on the GitLab server and runs the This task loops through all repositories on the GitLab server and runs the
3 integrity checks described previously. 3 integrity checks described previously.
**Omnibus Installation** **Omnibus Installation**
``` ```
sudo gitlab-rake gitlab:repo:check sudo gitlab-rake gitlab:git:fsck
``` ```
**Source Installation** **Source Installation**
```bash ```bash
sudo -u git -H bundle exec rake gitlab:repo:check RAILS_ENV=production sudo -u git -H bundle exec rake gitlab:git:fsck RAILS_ENV=production
``` ```
### Check repositories for a specific user ### Check repositories for a specific user
...@@ -76,3 +82,6 @@ The LDAP check Rake task will test the bind_dn and password credentials ...@@ -76,3 +82,6 @@ The LDAP check Rake task will test the bind_dn and password credentials
(if configured) and will list a sample of LDAP users. This task is also (if configured) and will list a sample of LDAP users. This task is also
executed as part of the `gitlab:check` task, but can run independently. executed as part of the `gitlab:check` task, but can run independently.
See [LDAP Rake Tasks - LDAP Check](ldap.md#check) for details. See [LDAP Rake Tasks - LDAP Check](ldap.md#check) for details.
[ce-15931]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15931
[ce-41699]: https://gitlab.com/gitlab-org/gitlab-ce/issues/41699
...@@ -30,14 +30,18 @@ Example response: ...@@ -30,14 +30,18 @@ Example response:
"description": "test-1-20150125", "description": "test-1-20150125",
"id": 6, "id": 6,
"is_shared": false, "is_shared": false,
"name": null "name": null,
"online": true,
"status": "online"
}, },
{ {
"active": true, "active": true,
"description": "test-2-20150125", "description": "test-2-20150125",
"id": 8, "id": 8,
"is_shared": false, "is_shared": false,
"name": null "name": null,
"online": false,
"status": "offline"
} }
] ]
``` ```
...@@ -69,28 +73,36 @@ Example response: ...@@ -69,28 +73,36 @@ Example response:
"description": "shared-runner-1", "description": "shared-runner-1",
"id": 1, "id": 1,
"is_shared": true, "is_shared": true,
"name": null "name": null,
"online": true,
"status": "online"
}, },
{ {
"active": true, "active": true,
"description": "shared-runner-2", "description": "shared-runner-2",
"id": 3, "id": 3,
"is_shared": true, "is_shared": true,
"name": null "name": null,
"online": false
"status": "offline"
}, },
{ {
"active": true, "active": true,
"description": "test-1-20150125", "description": "test-1-20150125",
"id": 6, "id": 6,
"is_shared": false, "is_shared": false,
"name": null "name": null,
"online": true
"status": "paused"
}, },
{ {
"active": true, "active": true,
"description": "test-2-20150125", "description": "test-2-20150125",
"id": 8, "id": 8,
"is_shared": false, "is_shared": false,
"name": null "name": null,
"online": false,
"status": "offline"
} }
] ]
``` ```
...@@ -122,6 +134,8 @@ Example response: ...@@ -122,6 +134,8 @@ Example response:
"is_shared": false, "is_shared": false,
"contacted_at": "2016-01-25T16:39:48.066Z", "contacted_at": "2016-01-25T16:39:48.066Z",
"name": null, "name": null,
"online": true,
"status": "online",
"platform": null, "platform": null,
"projects": [ "projects": [
{ {
...@@ -176,6 +190,8 @@ Example response: ...@@ -176,6 +190,8 @@ Example response:
"is_shared": false, "is_shared": false,
"contacted_at": "2016-01-25T16:39:48.066Z", "contacted_at": "2016-01-25T16:39:48.066Z",
"name": null, "name": null,
"online": true,
"status": "online",
"platform": null, "platform": null,
"projects": [ "projects": [
{ {
...@@ -327,14 +343,18 @@ Example response: ...@@ -327,14 +343,18 @@ Example response:
"description": "test-2-20150125", "description": "test-2-20150125",
"id": 8, "id": 8,
"is_shared": false, "is_shared": false,
"name": null "name": null,
"online": false,
"status": "offline"
}, },
{ {
"active": true, "active": true,
"description": "development_runner", "description": "development_runner",
"id": 5, "id": 5,
"is_shared": true, "is_shared": true,
"name": null "name": null,
"online": true
"status": "paused"
} }
] ]
``` ```
...@@ -364,7 +384,9 @@ Example response: ...@@ -364,7 +384,9 @@ Example response:
"description": "test-2016-02-01", "description": "test-2016-02-01",
"id": 9, "id": 9,
"is_shared": false, "is_shared": false,
"name": null "name": null,
"online": true,
"status": "online"
} }
``` ```
......
...@@ -97,6 +97,29 @@ describe 'Gitaly Request count tests' do ...@@ -97,6 +97,29 @@ describe 'Gitaly Request count tests' do
end end
``` ```
## Running tests with a locally modified version of Gitaly
Normally, gitlab-ce/ee tests use a local clone of Gitaly in `tmp/tests/gitaly`
pinned at the version specified in GITALY_SERVER_VERSION. If you want
to run tests locally against a modified version of Gitaly you can
replace `tmp/tests/gitaly` with a symlink.
```shell
rm -rf tmp/tests/gitaly
ln -s /path/to/gitaly tmp/tests/gitaly
```
Make sure you run `make` in your local Gitaly directory before running
tests. Otherwise, Gitaly will fail to boot.
If you make changes to your local Gitaly in between test runs you need
to manually run `make` again.
Note that CI tests will not use your locally modified version of
Gitaly. To use a custom Gitaly version in CI you need to update
GITALY_SERVER_VERSION. You can use the format `=revision` to use a
non-tagged commit from https://gitlab.com/gitlab-org/gitaly in CI.
--- ---
[Return to Development documentation](README.md) [Return to Development documentation](README.md)
...@@ -862,6 +862,8 @@ module API ...@@ -862,6 +862,8 @@ module API
expose :active expose :active
expose :is_shared expose :is_shared
expose :name expose :name
expose :online?, as: :online
expose :status
end end
class RunnerDetails < Runner class RunnerDetails < Runner
......
...@@ -14,14 +14,7 @@ module Gitlab ...@@ -14,14 +14,7 @@ module Gitlab
ENCODING_CONFIDENCE_THRESHOLD = 50 ENCODING_CONFIDENCE_THRESHOLD = 50
def encode!(message) def encode!(message)
return nil unless message.respond_to?(:force_encoding) message = force_encode_utf8(message)
return message if message.encoding == Encoding::UTF_8 && message.valid_encoding?
if message.respond_to?(:frozen?) && message.frozen?
message = message.dup
end
message.force_encoding("UTF-8")
return message if message.valid_encoding? return message if message.valid_encoding?
# return message if message type is binary # return message if message type is binary
...@@ -35,6 +28,8 @@ module Gitlab ...@@ -35,6 +28,8 @@ module Gitlab
# encode and clean the bad chars # encode and clean the bad chars
message.replace clean(message) message.replace clean(message)
rescue ArgumentError
return nil
rescue rescue
encoding = detect ? detect[:encoding] : "unknown" encoding = detect ? detect[:encoding] : "unknown"
"--broken encoding: #{encoding}" "--broken encoding: #{encoding}"
...@@ -54,8 +49,8 @@ module Gitlab ...@@ -54,8 +49,8 @@ module Gitlab
end end
def encode_utf8(message) def encode_utf8(message)
return nil unless message.is_a?(String) message = force_encode_utf8(message)
return message if message.encoding == Encoding::UTF_8 && message.valid_encoding? return message if message.valid_encoding?
detect = CharlockHolmes::EncodingDetector.detect(message) detect = CharlockHolmes::EncodingDetector.detect(message)
if detect && detect[:encoding] if detect && detect[:encoding]
...@@ -69,6 +64,8 @@ module Gitlab ...@@ -69,6 +64,8 @@ module Gitlab
else else
clean(message) clean(message)
end end
rescue ArgumentError
return nil
end end
def encode_binary(s) def encode_binary(s)
...@@ -83,6 +80,15 @@ module Gitlab ...@@ -83,6 +80,15 @@ module Gitlab
private private
def force_encode_utf8(message)
raise ArgumentError unless message.respond_to?(:force_encoding)
return message if message.encoding == Encoding::UTF_8 && message.valid_encoding?
message = message.dup if message.respond_to?(:frozen?) && message.frozen?
message.force_encoding("UTF-8")
end
def clean(message) def clean(message)
message.encode("UTF-16BE", undef: :replace, invalid: :replace, replace: "") message.encode("UTF-16BE", undef: :replace, invalid: :replace, replace: "")
.encode("UTF-8") .encode("UTF-8")
......
...@@ -11,7 +11,7 @@ module Gitlab ...@@ -11,7 +11,7 @@ module Gitlab
include Gitlab::EncodingHelper include Gitlab::EncodingHelper
def ref_name(ref) def ref_name(ref)
encode_utf8(ref).sub(/\Arefs\/(tags|heads|remotes)\//, '') encode!(ref).sub(/\Arefs\/(tags|heads|remotes)\//, '')
end end
def branch_name(ref) def branch_name(ref)
......
...@@ -50,10 +50,19 @@ module Gitlab ...@@ -50,10 +50,19 @@ module Gitlab
# to the caller to limit the number of blobs and blob_size_limit. # to the caller to limit the number of blobs and blob_size_limit.
# #
# Gitaly migration issue: https://gitlab.com/gitlab-org/gitaly/issues/798 # Gitaly migration issue: https://gitlab.com/gitlab-org/gitaly/issues/798
def batch(repository, blob_references, blob_size_limit: nil) def batch(repository, blob_references, blob_size_limit: MAX_DATA_DISPLAY_SIZE)
blob_size_limit ||= MAX_DATA_DISPLAY_SIZE Gitlab::GitalyClient.migrate(:list_blobs_by_sha_path) do |is_enabled|
blob_references.map do |sha, path| if is_enabled
find_by_rugged(repository, sha, path, limit: blob_size_limit) Gitlab::GitalyClient.allow_n_plus_1_calls do
blob_references.map do |sha, path|
find_by_gitaly(repository, sha, path, limit: blob_size_limit)
end
end
else
blob_references.map do |sha, path|
find_by_rugged(repository, sha, path, limit: blob_size_limit)
end
end
end end
end end
...@@ -122,13 +131,23 @@ module Gitlab ...@@ -122,13 +131,23 @@ module Gitlab
) )
end end
def find_by_gitaly(repository, sha, path) def find_by_gitaly(repository, sha, path, limit: MAX_DATA_DISPLAY_SIZE)
path = path.sub(/\A\/*/, '') path = path.sub(/\A\/*/, '')
path = '/' if path.empty? path = '/' if path.empty?
name = File.basename(path) name = File.basename(path)
entry = Gitlab::GitalyClient::CommitService.new(repository).tree_entry(sha, path, MAX_DATA_DISPLAY_SIZE)
# Gitaly will think that setting the limit to 0 means unlimited, while
# the client might only need the metadata and thus set the limit to 0.
# In this method we'll then set the limit to 1, but clear the byte of data
# that we got back so for the outside world it looks like the limit was
# actually 0.
req_limit = limit == 0 ? 1 : limit
entry = Gitlab::GitalyClient::CommitService.new(repository).tree_entry(sha, path, req_limit)
return unless entry return unless entry
entry.data = "" if limit == 0
case entry.type case entry.type
when :COMMIT when :COMMIT
new( new(
......
...@@ -97,6 +97,11 @@ module Gitlab ...@@ -97,6 +97,11 @@ module Gitlab
end end
end end
def update_branch(branch_name, newrev, oldrev)
ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name
update_ref_in_hooks(ref, newrev, oldrev)
end
private private
# Returns [newrev, should_run_after_create, should_run_after_create_branch] # Returns [newrev, should_run_after_create, should_run_after_create_branch]
......
...@@ -1219,9 +1219,16 @@ module Gitlab ...@@ -1219,9 +1219,16 @@ module Gitlab
rebase_path = worktree_path(REBASE_WORKTREE_PREFIX, rebase_id) rebase_path = worktree_path(REBASE_WORKTREE_PREFIX, rebase_id)
env = git_env_for_user(user) env = git_env_for_user(user)
if remote_repository.is_a?(RemoteRepository)
env.merge!(remote_repository.fetch_env)
remote_repo_path = GITALY_INTERNAL_URL
else
remote_repo_path = remote_repository.path
end
with_worktree(rebase_path, branch, env: env) do with_worktree(rebase_path, branch, env: env) do
run_git!( run_git!(
%W(pull --rebase #{remote_repository.path} #{remote_branch}), %W(pull --rebase #{remote_repo_path} #{remote_branch}),
chdir: rebase_path, env: env chdir: rebase_path, env: env
) )
......
...@@ -74,7 +74,7 @@ module Gitlab ...@@ -74,7 +74,7 @@ module Gitlab
def user_options(fields, value, limit) def user_options(fields, value, limit)
options = { options = {
attributes: Gitlab::LDAP::Person.ldap_attributes(config).compact.uniq, attributes: Gitlab::LDAP::Person.ldap_attributes(config),
base: config.base base: config.base
} }
......
...@@ -148,7 +148,7 @@ module Gitlab ...@@ -148,7 +148,7 @@ module Gitlab
def default_attributes def default_attributes
{ {
'username' => %w(uid userid sAMAccountName), 'username' => %w(uid sAMAccountName userid),
'email' => %w(mail email userPrincipalName), 'email' => %w(mail email userPrincipalName),
'name' => 'cn', 'name' => 'cn',
'first_name' => 'givenName', 'first_name' => 'givenName',
......
...@@ -6,6 +6,8 @@ module Gitlab ...@@ -6,6 +6,8 @@ module Gitlab
# Source: http://ctogonewild.com/2009/09/03/bitmask-searches-in-ldap/ # Source: http://ctogonewild.com/2009/09/03/bitmask-searches-in-ldap/
AD_USER_DISABLED = Net::LDAP::Filter.ex("userAccountControl:1.2.840.113556.1.4.803", "2") AD_USER_DISABLED = Net::LDAP::Filter.ex("userAccountControl:1.2.840.113556.1.4.803", "2")
InvalidEntryError = Class.new(StandardError)
attr_accessor :entry, :provider attr_accessor :entry, :provider
def self.find_by_uid(uid, adapter) def self.find_by_uid(uid, adapter)
...@@ -29,11 +31,12 @@ module Gitlab ...@@ -29,11 +31,12 @@ module Gitlab
def self.ldap_attributes(config) def self.ldap_attributes(config)
[ [
'dn', # Used in `dn` 'dn',
config.uid, # Used in `uid` config.uid,
*config.attributes['name'], # Used in `name` *config.attributes['name'],
*config.attributes['email'] # Used in `email` *config.attributes['email'],
] *config.attributes['username']
].compact.uniq
end end
def self.normalize_dn(dn) def self.normalize_dn(dn)
...@@ -60,6 +63,8 @@ module Gitlab ...@@ -60,6 +63,8 @@ module Gitlab
Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" } Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" }
@entry = entry @entry = entry
@provider = provider @provider = provider
validate_entry
end end
def name def name
...@@ -71,7 +76,13 @@ module Gitlab ...@@ -71,7 +76,13 @@ module Gitlab
end end
def username def username
uid username = attribute_value(:username)
# Depending on the attribute, multiple values may
# be returned. We need only one for username.
# Ex. `uid` returns only one value but `mail` may
# return an array of multiple email addresses.
[username].flatten.first
end end
def email def email
...@@ -104,6 +115,19 @@ module Gitlab ...@@ -104,6 +115,19 @@ module Gitlab
entry.public_send(selected_attr) # rubocop:disable GitlabSecurity/PublicSend entry.public_send(selected_attr) # rubocop:disable GitlabSecurity/PublicSend
end end
def validate_entry
allowed_attrs = self.class.ldap_attributes(config).map(&:downcase)
# Net::LDAP::Entry transforms keys to symbols. Change to strings to compare.
entry_attrs = entry.attribute_names.map { |n| n.to_s.downcase }
invalid_attrs = entry_attrs - allowed_attrs
if invalid_attrs.any?
raise InvalidEntryError,
"#{self.class.name} initialized with Net::LDAP::Entry containing invalid attributes(s): #{invalid_attrs}"
end
end
end end
end end
end end
module Gitlab
module SetupHelper
class << self
# We cannot create config.toml files for all possible Gitaly configuations.
# For instance, if Gitaly is running on another machine then it makes no
# sense to write a config.toml file on the current machine. This method will
# only generate a configuration for the most common and simplest case: when
# we have exactly one Gitaly process and we are sure it is running locally
# because it uses a Unix socket.
# For development and testing purposes, an extra storage is added to gitaly,
# which is not known to Rails, but must be explicitly stubbed.
def gitaly_configuration_toml(gitaly_dir, gitaly_ruby: true)
storages = []
address = nil
Gitlab.config.repositories.storages.each do |key, val|
if address
if address != val['gitaly_address']
raise ArgumentError, "Your gitlab.yml contains more than one gitaly_address."
end
elsif URI(val['gitaly_address']).scheme != 'unix'
raise ArgumentError, "Automatic config.toml generation only supports 'unix:' addresses."
else
address = val['gitaly_address']
end
storages << { name: key, path: val['path'] }
end
if Rails.env.test?
storages << { name: 'test_second_storage', path: Rails.root.join('tmp', 'tests', 'second_storage').to_s }
end
config = { socket_path: address.sub(%r{\Aunix:}, ''), storage: storages }
config[:auth] = { token: 'secret' } if Rails.env.test?
config[:'gitaly-ruby'] = { dir: File.join(gitaly_dir, 'ruby') } if gitaly_ruby
config[:'gitlab-shell'] = { dir: Gitlab.config.gitlab_shell.path }
config[:bin_dir] = Gitlab.config.gitaly.client_path
TOML.dump(config)
end
# rubocop:disable Rails/Output
def create_gitaly_configuration(dir, force: false)
config_path = File.join(dir, 'config.toml')
FileUtils.rm_f(config_path) if force
File.open(config_path, File::WRONLY | File::CREAT | File::EXCL) do |f|
f.puts gitaly_configuration_toml(dir)
end
rescue Errno::EEXIST
puts "Skipping config.toml generation:"
puts "A configuration file already exists."
rescue ArgumentError => e
puts "Skipping config.toml generation:"
puts e.message
end
# rubocop:enable Rails/Output
end
end
end
...@@ -71,7 +71,6 @@ module Gitlab ...@@ -71,7 +71,6 @@ module Gitlab
# Ex. # Ex.
# add_repository("/path/to/storage", "gitlab/gitlab-ci") # add_repository("/path/to/storage", "gitlab/gitlab-ci")
# #
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387
def add_repository(storage, name) def add_repository(storage, name)
relative_path = name.dup relative_path = name.dup
relative_path << '.git' unless relative_path.end_with?('.git') relative_path << '.git' unless relative_path.end_with?('.git')
...@@ -100,7 +99,7 @@ module Gitlab ...@@ -100,7 +99,7 @@ module Gitlab
# Ex. # Ex.
# import_repository("/path/to/storage", "gitlab/gitlab-ci", "https://gitlab.com/gitlab-org/gitlab-test.git") # import_repository("/path/to/storage", "gitlab/gitlab-ci", "https://gitlab.com/gitlab-org/gitlab-test.git")
# #
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/874
def import_repository(storage, name, url) def import_repository(storage, name, url)
# The timeout ensures the subprocess won't hang forever # The timeout ensures the subprocess won't hang forever
cmd = gitlab_projects(storage, "#{name}.git") cmd = gitlab_projects(storage, "#{name}.git")
...@@ -122,7 +121,6 @@ module Gitlab ...@@ -122,7 +121,6 @@ module Gitlab
# Ex. # Ex.
# fetch_remote(my_repo, "upstream") # fetch_remote(my_repo, "upstream")
# #
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387
def fetch_remote(repository, remote, ssh_auth: nil, forced: false, no_tags: false) def fetch_remote(repository, remote, ssh_auth: nil, forced: false, no_tags: false)
gitaly_migrate(:fetch_remote) do |is_enabled| gitaly_migrate(:fetch_remote) do |is_enabled|
if is_enabled if is_enabled
...@@ -142,7 +140,7 @@ module Gitlab ...@@ -142,7 +140,7 @@ module Gitlab
# Ex. # Ex.
# mv_repository("/path/to/storage", "gitlab/gitlab-ci", "randx/gitlab-ci-new") # mv_repository("/path/to/storage", "gitlab/gitlab-ci", "randx/gitlab-ci-new")
# #
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/873
def mv_repository(storage, path, new_path) def mv_repository(storage, path, new_path)
gitlab_projects(storage, "#{path}.git").mv_project("#{new_path}.git") gitlab_projects(storage, "#{path}.git").mv_project("#{new_path}.git")
end end
...@@ -156,7 +154,7 @@ module Gitlab ...@@ -156,7 +154,7 @@ module Gitlab
# Ex. # Ex.
# fork_repository("/path/to/forked_from/storage", "gitlab/gitlab-ci", "/path/to/forked_to/storage", "new-namespace/gitlab-ci") # fork_repository("/path/to/forked_from/storage", "gitlab/gitlab-ci", "/path/to/forked_to/storage", "new-namespace/gitlab-ci")
# #
# Gitaly note: JV: not easy to migrate because this involves two Gitaly servers, not one. # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/817
def fork_repository(forked_from_storage, forked_from_disk_path, forked_to_storage, forked_to_disk_path) def fork_repository(forked_from_storage, forked_from_disk_path, forked_to_storage, forked_to_disk_path)
gitlab_projects(forked_from_storage, "#{forked_from_disk_path}.git") gitlab_projects(forked_from_storage, "#{forked_from_disk_path}.git")
.fork_repository(forked_to_storage, "#{forked_to_disk_path}.git") .fork_repository(forked_to_storage, "#{forked_to_disk_path}.git")
...@@ -170,7 +168,7 @@ module Gitlab ...@@ -170,7 +168,7 @@ module Gitlab
# Ex. # Ex.
# remove_repository("/path/to/storage", "gitlab/gitlab-ci") # remove_repository("/path/to/storage", "gitlab/gitlab-ci")
# #
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/873
def remove_repository(storage, name) def remove_repository(storage, name)
gitlab_projects(storage, "#{name}.git").rm_project gitlab_projects(storage, "#{name}.git").rm_project
end end
...@@ -221,7 +219,6 @@ module Gitlab ...@@ -221,7 +219,6 @@ module Gitlab
# Ex. # Ex.
# add_namespace("/path/to/storage", "gitlab") # add_namespace("/path/to/storage", "gitlab")
# #
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385
def add_namespace(storage, name) def add_namespace(storage, name)
Gitlab::GitalyClient.migrate(:add_namespace) do |enabled| Gitlab::GitalyClient.migrate(:add_namespace) do |enabled|
if enabled if enabled
...@@ -243,7 +240,6 @@ module Gitlab ...@@ -243,7 +240,6 @@ module Gitlab
# Ex. # Ex.
# rm_namespace("/path/to/storage", "gitlab") # rm_namespace("/path/to/storage", "gitlab")
# #
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385
def rm_namespace(storage, name) def rm_namespace(storage, name)
Gitlab::GitalyClient.migrate(:remove_namespace) do |enabled| Gitlab::GitalyClient.migrate(:remove_namespace) do |enabled|
if enabled if enabled
...@@ -261,7 +257,6 @@ module Gitlab ...@@ -261,7 +257,6 @@ module Gitlab
# Ex. # Ex.
# mv_namespace("/path/to/storage", "gitlab", "gitlabhq") # mv_namespace("/path/to/storage", "gitlab", "gitlabhq")
# #
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385
def mv_namespace(storage, old_name, new_name) def mv_namespace(storage, old_name, new_name)
Gitlab::GitalyClient.migrate(:rename_namespace) do |enabled| Gitlab::GitalyClient.migrate(:rename_namespace) do |enabled|
if enabled if enabled
......
...@@ -387,14 +387,8 @@ namespace :gitlab do ...@@ -387,14 +387,8 @@ namespace :gitlab do
namespace :repo do namespace :repo do
desc "GitLab | Check the integrity of the repositories managed by GitLab" desc "GitLab | Check the integrity of the repositories managed by GitLab"
task check: :environment do task check: :environment do
Gitlab.config.repositories.storages.each do |name, repository_storage| puts "This task is deprecated. Please use gitlab:git:fsck instead".color(:red)
namespace_dirs = Dir.glob(File.join(repository_storage['path'], '*')) Rake::Task["gitlab:git:fsck"].execute
namespace_dirs.each do |namespace_dir|
repo_dirs = Dir.glob(File.join(namespace_dir, '*'))
repo_dirs.each { |repo_dir| check_repo_integrity(repo_dir) }
end
end
end end
end end
...@@ -461,35 +455,4 @@ namespace :gitlab do ...@@ -461,35 +455,4 @@ namespace :gitlab do
puts "FAIL. Please update gitlab-shell to #{required_version} from #{current_version}".color(:red) puts "FAIL. Please update gitlab-shell to #{required_version} from #{current_version}".color(:red)
end end
end end
def check_repo_integrity(repo_dir)
puts "\nChecking repo at #{repo_dir.color(:yellow)}"
git_fsck(repo_dir)
check_config_lock(repo_dir)
check_ref_locks(repo_dir)
end
def git_fsck(repo_dir)
puts "Running `git fsck`".color(:yellow)
system(*%W(#{Gitlab.config.git.bin_path} fsck), chdir: repo_dir)
end
def check_config_lock(repo_dir)
config_exists = File.exist?(File.join(repo_dir, 'config.lock'))
config_output = config_exists ? 'yes'.color(:red) : 'no'.color(:green)
puts "'config.lock' file exists?".color(:yellow) + " ... #{config_output}"
end
def check_ref_locks(repo_dir)
lock_files = Dir.glob(File.join(repo_dir, 'refs/heads/*.lock'))
if lock_files.present?
puts "Ref lock files exist:".color(:red)
lock_files.each do |lock_file|
puts " #{lock_file}"
end
else
puts "No ref lock files exist".color(:green)
end
end
end end
...@@ -30,6 +30,20 @@ namespace :gitlab do ...@@ -30,6 +30,20 @@ namespace :gitlab do
end end
end end
desc 'GitLab | Git | Check all repos integrity'
task fsck: :environment do
failures = perform_git_cmd(%W(#{Gitlab.config.git.bin_path} fsck --name-objects --no-progress), "Checking integrity") do |repo|
check_config_lock(repo)
check_ref_locks(repo)
end
if failures.empty?
puts "Done".color(:green)
else
output_failures(failures)
end
end
def perform_git_cmd(cmd, message) def perform_git_cmd(cmd, message)
puts "Starting #{message} on all repositories" puts "Starting #{message} on all repositories"
...@@ -40,6 +54,8 @@ namespace :gitlab do ...@@ -40,6 +54,8 @@ namespace :gitlab do
else else
failures << repo failures << repo
end end
yield(repo) if block_given?
end end
failures failures
...@@ -49,5 +65,24 @@ namespace :gitlab do ...@@ -49,5 +65,24 @@ namespace :gitlab do
puts "The following repositories reported errors:".color(:red) puts "The following repositories reported errors:".color(:red)
failures.each { |f| puts "- #{f}" } failures.each { |f| puts "- #{f}" }
end end
def check_config_lock(repo_dir)
config_exists = File.exist?(File.join(repo_dir, 'config.lock'))
config_output = config_exists ? 'yes'.color(:red) : 'no'.color(:green)
puts "'config.lock' file exists?".color(:yellow) + " ... #{config_output}"
end
def check_ref_locks(repo_dir)
lock_files = Dir.glob(File.join(repo_dir, 'refs/heads/*.lock'))
if lock_files.present?
puts "Ref lock files exist:".color(:red)
lock_files.each { |lock_file| puts " #{lock_file}" }
else
puts "No ref lock files exist".color(:green)
end
end
end end
end end
...@@ -21,8 +21,8 @@ namespace :gitlab do ...@@ -21,8 +21,8 @@ namespace :gitlab do
command << 'BUNDLE_FLAGS=--no-deployment' if Rails.env.test? command << 'BUNDLE_FLAGS=--no-deployment' if Rails.env.test?
Gitlab::SetupHelper.create_gitaly_configuration(args.dir)
Dir.chdir(args.dir) do Dir.chdir(args.dir) do
create_gitaly_configuration
# In CI we run scripts/gitaly-test-build instead of this command # In CI we run scripts/gitaly-test-build instead of this command
unless ENV['CI'].present? unless ENV['CI'].present?
Bundler.with_original_env { run_command!(command) } Bundler.with_original_env { run_command!(command) }
...@@ -39,60 +39,7 @@ namespace :gitlab do ...@@ -39,60 +39,7 @@ namespace :gitlab do
# Exclude gitaly-ruby configuration because that depends on the gitaly # Exclude gitaly-ruby configuration because that depends on the gitaly
# installation directory. # installation directory.
puts gitaly_configuration_toml(gitaly_ruby: false) puts Gitlab::SetupHelper.gitaly_configuration_toml('', gitaly_ruby: false)
end
private
# We cannot create config.toml files for all possible Gitaly configuations.
# For instance, if Gitaly is running on another machine then it makes no
# sense to write a config.toml file on the current machine. This method will
# only generate a configuration for the most common and simplest case: when
# we have exactly one Gitaly process and we are sure it is running locally
# because it uses a Unix socket.
# For development and testing purposes, an extra storage is added to gitaly,
# which is not known to Rails, but must be explicitly stubbed.
def gitaly_configuration_toml(gitaly_ruby: true)
storages = []
address = nil
Gitlab.config.repositories.storages.each do |key, val|
if address
if address != val['gitaly_address']
raise ArgumentError, "Your gitlab.yml contains more than one gitaly_address."
end
elsif URI(val['gitaly_address']).scheme != 'unix'
raise ArgumentError, "Automatic config.toml generation only supports 'unix:' addresses."
else
address = val['gitaly_address']
end
storages << { name: key, path: val['path'] }
end
if Rails.env.test?
storages << { name: 'test_second_storage', path: Rails.root.join('tmp', 'tests', 'second_storage').to_s }
end
config = { socket_path: address.sub(%r{\Aunix:}, ''), storage: storages }
config[:auth] = { token: 'secret' } if Rails.env.test?
config[:'gitaly-ruby'] = { dir: File.join(Dir.pwd, 'ruby') } if gitaly_ruby
config[:'gitlab-shell'] = { dir: Gitlab.config.gitlab_shell.path }
config[:bin_dir] = Gitlab.config.gitaly.client_path
TOML.dump(config)
end
def create_gitaly_configuration
File.open("config.toml", File::WRONLY | File::CREAT | File::EXCL) do |f|
f.puts gitaly_configuration_toml
end
rescue Errno::EEXIST
puts "Skipping config.toml generation:"
puts "A configuration file already exists."
rescue ArgumentError => e
puts "Skipping config.toml generation:"
puts e.message
end end
end end
end end
...@@ -130,7 +130,7 @@ module Gitlab ...@@ -130,7 +130,7 @@ module Gitlab
def all_repos def all_repos
Gitlab.config.repositories.storages.each_value do |repository_storage| Gitlab.config.repositories.storages.each_value do |repository_storage|
IO.popen(%W(find #{repository_storage['path']} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find| IO.popen(%W(find #{repository_storage['path']} -mindepth 2 -type d -name *.git)) do |find|
find.each_line do |path| find.each_line do |path|
yield path.chomp yield path.chomp
end end
......
...@@ -81,14 +81,14 @@ feature 'Gcp Cluster', :js do ...@@ -81,14 +81,14 @@ feature 'Gcp Cluster', :js do
end end
it 'user sees a cluster details page' do it 'user sees a cluster details page' do
expect(page).to have_button('Save') expect(page).to have_button('Save changes')
expect(page.find(:css, '.cluster-name').value).to eq(cluster.name) expect(page.find(:css, '.cluster-name').value).to eq(cluster.name)
end end
context 'when user disables the cluster' do context 'when user disables the cluster' do
before do before do
page.find(:css, '.js-toggle-cluster').click page.find(:css, '.js-toggle-cluster').click
click_button 'Save' page.within('#cluster-integration') { click_button 'Save changes' }
end end
it 'user sees the successful message' do it 'user sees the successful message' do
...@@ -99,7 +99,7 @@ feature 'Gcp Cluster', :js do ...@@ -99,7 +99,7 @@ feature 'Gcp Cluster', :js do
context 'when user changes cluster parameters' do context 'when user changes cluster parameters' do
before do before do
fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace' fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace'
click_button 'Save changes' page.within('#js-cluster-details') { click_button 'Save changes' }
end end
it 'user sees the successful message' do it 'user sees the successful message' do
......
...@@ -29,7 +29,7 @@ feature 'User Cluster', :js do ...@@ -29,7 +29,7 @@ feature 'User Cluster', :js do
end end
it 'user sees a cluster details page' do it 'user sees a cluster details page' do
expect(page).to have_content('Enable cluster integration') expect(page).to have_content('Cluster integration')
expect(page.find_field('cluster[name]').value).to eq('dev-cluster') expect(page.find_field('cluster[name]').value).to eq('dev-cluster')
expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value) expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value)
.to have_content('http://example.com') .to have_content('http://example.com')
...@@ -57,14 +57,14 @@ feature 'User Cluster', :js do ...@@ -57,14 +57,14 @@ feature 'User Cluster', :js do
end end
it 'user sees a cluster details page' do it 'user sees a cluster details page' do
expect(page).to have_button('Save') expect(page).to have_button('Save changes')
end end
context 'when user disables the cluster' do context 'when user disables the cluster' do
before do before do
page.find(:css, '.js-toggle-cluster').click page.find(:css, '.js-toggle-cluster').click
fill_in 'cluster_name', with: 'dev-cluster' fill_in 'cluster_name', with: 'dev-cluster'
click_button 'Save' page.within('#cluster-integration') { click_button 'Save changes' }
end end
it 'user sees the successful message' do it 'user sees the successful message' do
...@@ -76,7 +76,7 @@ feature 'User Cluster', :js do ...@@ -76,7 +76,7 @@ feature 'User Cluster', :js do
before do before do
fill_in 'cluster_name', with: 'my-dev-cluster' fill_in 'cluster_name', with: 'my-dev-cluster'
fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace' fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace'
click_button 'Save changes' page.within('#js-cluster-details') { click_button 'Save changes' }
end end
it 'user sees the successful message' do it 'user sees the successful message' do
......
...@@ -47,17 +47,11 @@ describe('ItemActionsComponent', () => { ...@@ -47,17 +47,11 @@ describe('ItemActionsComponent', () => {
it('should change `modalStatus` prop to `false` and emit `leaveGroup` event with required params when called with `leaveConfirmed` as `true`', () => { it('should change `modalStatus` prop to `false` and emit `leaveGroup` event with required params when called with `leaveConfirmed` as `true`', () => {
spyOn(eventHub, '$emit'); spyOn(eventHub, '$emit');
vm.modalStatus = true; vm.modalStatus = true;
vm.leaveGroup(true);
expect(vm.modalStatus).toBeFalsy();
expect(eventHub.$emit).toHaveBeenCalledWith('leaveGroup', vm.group, vm.parentGroup);
});
it('should change `modalStatus` prop to `false` and should NOT emit `leaveGroup` event when called with `leaveConfirmed` as `false`', () => { vm.leaveGroup();
spyOn(eventHub, '$emit');
vm.modalStatus = true;
vm.leaveGroup(false);
expect(vm.modalStatus).toBeFalsy(); expect(vm.modalStatus).toBeFalsy();
expect(eventHub.$emit).not.toHaveBeenCalled(); expect(eventHub.$emit).toHaveBeenCalledWith('leaveGroup', vm.group, vm.parentGroup);
}); });
}); });
}); });
......
...@@ -51,7 +51,7 @@ describe('DeleteAccountModal component', () => { ...@@ -51,7 +51,7 @@ describe('DeleteAccountModal component', () => {
Vue.nextTick() Vue.nextTick()
.then(() => { .then(() => {
expect(vm.enteredPassword).toBe(input.value); expect(vm.enteredPassword).toBe(input.value);
expect(submitButton).toHaveClass('disabled'); expect(submitButton).toHaveAttr('disabled', 'disabled');
submitButton.click(); submitButton.click();
expect(form.submit).not.toHaveBeenCalled(); expect(form.submit).not.toHaveBeenCalled();
}) })
...@@ -68,7 +68,7 @@ describe('DeleteAccountModal component', () => { ...@@ -68,7 +68,7 @@ describe('DeleteAccountModal component', () => {
Vue.nextTick() Vue.nextTick()
.then(() => { .then(() => {
expect(vm.enteredPassword).toBe(input.value); expect(vm.enteredPassword).toBe(input.value);
expect(submitButton).not.toHaveClass('disabled'); expect(submitButton).not.toHaveAttr('disabled', 'disabled');
submitButton.click(); submitButton.click();
expect(form.submit).toHaveBeenCalled(); expect(form.submit).toHaveBeenCalled();
}) })
...@@ -101,7 +101,7 @@ describe('DeleteAccountModal component', () => { ...@@ -101,7 +101,7 @@ describe('DeleteAccountModal component', () => {
Vue.nextTick() Vue.nextTick()
.then(() => { .then(() => {
expect(vm.enteredUsername).toBe(input.value); expect(vm.enteredUsername).toBe(input.value);
expect(submitButton).toHaveClass('disabled'); expect(submitButton).toHaveAttr('disabled', 'disabled');
submitButton.click(); submitButton.click();
expect(form.submit).not.toHaveBeenCalled(); expect(form.submit).not.toHaveBeenCalled();
}) })
...@@ -118,7 +118,7 @@ describe('DeleteAccountModal component', () => { ...@@ -118,7 +118,7 @@ describe('DeleteAccountModal component', () => {
Vue.nextTick() Vue.nextTick()
.then(() => { .then(() => {
expect(vm.enteredUsername).toBe(input.value); expect(vm.enteredUsername).toBe(input.value);
expect(submitButton).not.toHaveClass('disabled'); expect(submitButton).not.toHaveAttr('disabled', 'disabled');
submitButton.click(); submitButton.click();
expect(form.submit).toHaveBeenCalled(); expect(form.submit).toHaveBeenCalled();
}) })
......
...@@ -6,8 +6,12 @@ describe('New Project', () => { ...@@ -6,8 +6,12 @@ describe('New Project', () => {
beforeEach(() => { beforeEach(() => {
setFixtures(` setFixtures(`
<input id="project_import_url" /> <div class='toggle-import-form'>
<input id="project_path" /> <div class='import-url-data'>
<input id="project_import_url" />
<input id="project_path" />
</div>
</div>
`); `);
$projectImportUrl = $('#project_import_url'); $projectImportUrl = $('#project_import_url');
...@@ -25,7 +29,7 @@ describe('New Project', () => { ...@@ -25,7 +29,7 @@ describe('New Project', () => {
it('does not change project path for disabled $projectImportUrl', () => { it('does not change project path for disabled $projectImportUrl', () => {
$projectImportUrl.attr('disabled', true); $projectImportUrl.attr('disabled', true);
projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual(dummyImportUrl); expect($projectPath.val()).toEqual(dummyImportUrl);
}); });
...@@ -38,7 +42,7 @@ describe('New Project', () => { ...@@ -38,7 +42,7 @@ describe('New Project', () => {
it('does not change project path if it is set by user', () => { it('does not change project path if it is set by user', () => {
$projectPath.keyup(); $projectPath.keyup();
projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual(dummyImportUrl); expect($projectPath.val()).toEqual(dummyImportUrl);
}); });
...@@ -46,7 +50,7 @@ describe('New Project', () => { ...@@ -46,7 +50,7 @@ describe('New Project', () => {
it('does not change project path for empty $projectImportUrl', () => { it('does not change project path for empty $projectImportUrl', () => {
$projectImportUrl.val(''); $projectImportUrl.val('');
projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual(dummyImportUrl); expect($projectPath.val()).toEqual(dummyImportUrl);
}); });
...@@ -54,7 +58,7 @@ describe('New Project', () => { ...@@ -54,7 +58,7 @@ describe('New Project', () => {
it('does not change project path for whitespace $projectImportUrl', () => { it('does not change project path for whitespace $projectImportUrl', () => {
$projectImportUrl.val(' '); $projectImportUrl.val(' ');
projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual(dummyImportUrl); expect($projectPath.val()).toEqual(dummyImportUrl);
}); });
...@@ -62,7 +66,7 @@ describe('New Project', () => { ...@@ -62,7 +66,7 @@ describe('New Project', () => {
it('does not change project path for $projectImportUrl without slashes', () => { it('does not change project path for $projectImportUrl without slashes', () => {
$projectImportUrl.val('has-no-slash'); $projectImportUrl.val('has-no-slash');
projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual(dummyImportUrl); expect($projectPath.val()).toEqual(dummyImportUrl);
}); });
...@@ -70,7 +74,7 @@ describe('New Project', () => { ...@@ -70,7 +74,7 @@ describe('New Project', () => {
it('changes project path to last $projectImportUrl component', () => { it('changes project path to last $projectImportUrl component', () => {
$projectImportUrl.val('/this/is/last'); $projectImportUrl.val('/this/is/last');
projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual('last'); expect($projectPath.val()).toEqual('last');
}); });
...@@ -78,7 +82,7 @@ describe('New Project', () => { ...@@ -78,7 +82,7 @@ describe('New Project', () => {
it('ignores trailing slashes in $projectImportUrl', () => { it('ignores trailing slashes in $projectImportUrl', () => {
$projectImportUrl.val('/has/trailing/slash/'); $projectImportUrl.val('/has/trailing/slash/');
projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual('slash'); expect($projectPath.val()).toEqual('slash');
}); });
...@@ -86,7 +90,7 @@ describe('New Project', () => { ...@@ -86,7 +90,7 @@ describe('New Project', () => {
it('ignores fragment identifier in $projectImportUrl', () => { it('ignores fragment identifier in $projectImportUrl', () => {
$projectImportUrl.val('/this/has/a#fragment-identifier/'); $projectImportUrl.val('/this/has/a#fragment-identifier/');
projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual('a'); expect($projectPath.val()).toEqual('a');
}); });
...@@ -94,7 +98,7 @@ describe('New Project', () => { ...@@ -94,7 +98,7 @@ describe('New Project', () => {
it('ignores query string in $projectImportUrl', () => { it('ignores query string in $projectImportUrl', () => {
$projectImportUrl.val('/url/with?query=string'); $projectImportUrl.val('/url/with?query=string');
projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual('with'); expect($projectPath.val()).toEqual('with');
}); });
...@@ -102,7 +106,7 @@ describe('New Project', () => { ...@@ -102,7 +106,7 @@ describe('New Project', () => {
it('ignores trailing .git in $projectImportUrl', () => { it('ignores trailing .git in $projectImportUrl', () => {
$projectImportUrl.val('/repository.git'); $projectImportUrl.val('/repository.git');
projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual('repository'); expect($projectPath.val()).toEqual('repository');
}); });
...@@ -110,7 +114,7 @@ describe('New Project', () => { ...@@ -110,7 +114,7 @@ describe('New Project', () => {
it('changes project path for HTTPS URL in $projectImportUrl', () => { it('changes project path for HTTPS URL in $projectImportUrl', () => {
$projectImportUrl.val('https://username:password@gitlab.company.com/group/project.git'); $projectImportUrl.val('https://username:password@gitlab.company.com/group/project.git');
projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual('project'); expect($projectPath.val()).toEqual('project');
}); });
...@@ -118,7 +122,7 @@ describe('New Project', () => { ...@@ -118,7 +122,7 @@ describe('New Project', () => {
it('changes project path for SSH URL in $projectImportUrl', () => { it('changes project path for SSH URL in $projectImportUrl', () => {
$projectImportUrl.val('git@gitlab.com:gitlab-org/gitlab-ce.git'); $projectImportUrl.val('git@gitlab.com:gitlab-org/gitlab-ce.git');
projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); projectNew.deriveProjectPathFromUrl($projectImportUrl);
expect($projectPath.val()).toEqual('gitlab-ce'); expect($projectPath.val()).toEqual('gitlab-ce');
}); });
......
...@@ -57,15 +57,16 @@ describe('new dropdown component', () => { ...@@ -57,15 +57,16 @@ describe('new dropdown component', () => {
}); });
}); });
describe('toggleModalOpen', () => { describe('hideModal', () => {
beforeAll((done) => {
vm.openModal = true;
Vue.nextTick(done);
});
it('closes modal after toggling', (done) => { it('closes modal after toggling', (done) => {
vm.toggleModalOpen(); vm.hideModal();
Vue.nextTick() Vue.nextTick()
.then(() => {
expect(vm.$el.querySelector('.modal')).not.toBeNull();
})
.then(vm.toggleModalOpen)
.then(() => { .then(() => {
expect(vm.$el.querySelector('.modal')).toBeNull(); expect(vm.$el.querySelector('.modal')).toBeNull();
}) })
......
...@@ -2,11 +2,65 @@ import Vue from 'vue'; ...@@ -2,11 +2,65 @@ import Vue from 'vue';
import modal from '~/vue_shared/components/modal.vue'; import modal from '~/vue_shared/components/modal.vue';
import mountComponent from '../../helpers/vue_mount_component_helper'; import mountComponent from '../../helpers/vue_mount_component_helper';
const modalComponent = Vue.extend(modal);
describe('Modal', () => { describe('Modal', () => {
it('does not render a primary button if no primaryButtonLabel', () => { let vm;
const modalComponent = Vue.extend(modal);
const vm = mountComponent(modalComponent); afterEach(() => {
vm.$destroy();
});
describe('props', () => {
describe('without primaryButtonLabel', () => {
beforeEach(() => {
vm = mountComponent(modalComponent, {
primaryButtonLabel: null,
});
});
it('does not render a primary button', () => {
expect(vm.$el.querySelector('.js-primary-button')).toBeNull();
});
});
describe('with id', () => {
it('does not render a primary button', () => {
beforeEach(() => {
vm = mountComponent(modalComponent, {
id: 'my-modal',
});
});
it('assigns the id to the modal', () => {
expect(vm.$el.querySelector('#my-modal.modal')).not.toBeNull();
});
it('does not show the modal immediately', () => {
expect(vm.$el.querySelector('#my-modal.modal')).not.toHaveClass('show');
});
it('does not show a backdrop', () => {
expect(vm.$el.querySelector('modal-backdrop')).toBeNull();
});
});
});
it('works with data-toggle="modal"', (done) => {
setFixtures(`
<button id="modal-button" data-toggle="modal" data-target="#my-modal"></button>
<div id="modal-container"></div>
`);
const modalContainer = document.getElementById('modal-container');
const modalButton = document.getElementById('modal-button');
vm = mountComponent(modalComponent, {
id: 'my-modal',
}, modalContainer);
const modalElement = vm.$el.querySelector('#my-modal');
$(modalElement).on('shown.bs.modal', () => done());
expect(vm.$el.querySelector('.js-primary-button')).toBeNull(); modalButton.click();
});
}); });
}); });
...@@ -120,6 +120,24 @@ describe Gitlab::EncodingHelper do ...@@ -120,6 +120,24 @@ describe Gitlab::EncodingHelper do
it 'returns empty string on conversion errors' do it 'returns empty string on conversion errors' do
expect { ext_class.encode_utf8('') }.not_to raise_error(ArgumentError) expect { ext_class.encode_utf8('') }.not_to raise_error(ArgumentError)
end end
context 'with strings that can be forcefully encoded into utf8' do
let(:test_string) do
"refs/heads/FixSymbolsTitleDropdown".encode("ASCII-8BIT")
end
let(:expected_string) do
"refs/heads/FixSymbolsTitleDropdown".encode("UTF-8")
end
subject { ext_class.encode_utf8(test_string) }
it "doesn't use CharlockHolmes if the encoding can be forced into utf_8" do
expect(CharlockHolmes::EncodingDetector).not_to receive(:detect)
expect(subject).to eq(expected_string)
expect(subject.encoding.name).to eq('UTF-8')
end
end
end end
describe '#clean' do describe '#clean' do
......
...@@ -202,16 +202,6 @@ describe Gitlab::Git::Blob, seed_helper: true do ...@@ -202,16 +202,6 @@ describe Gitlab::Git::Blob, seed_helper: true do
context 'limiting' do context 'limiting' do
subject { described_class.batch(repository, blob_references, blob_size_limit: blob_size_limit) } subject { described_class.batch(repository, blob_references, blob_size_limit: blob_size_limit) }
context 'default' do
let(:blob_size_limit) { nil }
it 'limits to MAX_DATA_DISPLAY_SIZE' do
stub_const('Gitlab::Git::Blob::MAX_DATA_DISPLAY_SIZE', 100)
expect(subject.first.data.size).to eq(100)
end
end
context 'positive' do context 'positive' do
let(:blob_size_limit) { 10 } let(:blob_size_limit) { 10 }
...@@ -221,7 +211,10 @@ describe Gitlab::Git::Blob, seed_helper: true do ...@@ -221,7 +211,10 @@ describe Gitlab::Git::Blob, seed_helper: true do
context 'zero' do context 'zero' do
let(:blob_size_limit) { 0 } let(:blob_size_limit) { 0 }
it { expect(subject.first.data).to eq('') } it 'only loads the metadata' do
expect(subject.first.size).not_to be(0)
expect(subject.first.data).to eq('')
end
end end
context 'negative' do context 'negative' do
......
...@@ -16,7 +16,7 @@ describe Gitlab::LDAP::Adapter do ...@@ -16,7 +16,7 @@ describe Gitlab::LDAP::Adapter do
expect(adapter).to receive(:ldap_search) do |arg| expect(adapter).to receive(:ldap_search) do |arg|
expect(arg[:filter].to_s).to eq('(uid=johndoe)') expect(arg[:filter].to_s).to eq('(uid=johndoe)')
expect(arg[:base]).to eq('dc=example,dc=com') expect(arg[:base]).to eq('dc=example,dc=com')
expect(arg[:attributes]).to match(%w{dn uid cn mail email userPrincipalName}) expect(arg[:attributes]).to match(ldap_attributes)
end.and_return({}) end.and_return({})
adapter.users('uid', 'johndoe') adapter.users('uid', 'johndoe')
...@@ -26,7 +26,7 @@ describe Gitlab::LDAP::Adapter do ...@@ -26,7 +26,7 @@ describe Gitlab::LDAP::Adapter do
expect(adapter).to receive(:ldap_search).with( expect(adapter).to receive(:ldap_search).with(
base: 'uid=johndoe,ou=users,dc=example,dc=com', base: 'uid=johndoe,ou=users,dc=example,dc=com',
scope: Net::LDAP::SearchScope_BaseObject, scope: Net::LDAP::SearchScope_BaseObject,
attributes: %w{dn uid cn mail email userPrincipalName}, attributes: ldap_attributes,
filter: nil filter: nil
).and_return({}) ).and_return({})
...@@ -63,7 +63,7 @@ describe Gitlab::LDAP::Adapter do ...@@ -63,7 +63,7 @@ describe Gitlab::LDAP::Adapter do
it 'uses the right uid attribute when non-default' do it 'uses the right uid attribute when non-default' do
stub_ldap_config(uid: 'sAMAccountName') stub_ldap_config(uid: 'sAMAccountName')
expect(adapter).to receive(:ldap_search).with( expect(adapter).to receive(:ldap_search).with(
hash_including(attributes: %w{dn sAMAccountName cn mail email userPrincipalName}) hash_including(attributes: ldap_attributes)
).and_return({}) ).and_return({})
adapter.users('sAMAccountName', 'johndoe') adapter.users('sAMAccountName', 'johndoe')
...@@ -137,4 +137,8 @@ describe Gitlab::LDAP::Adapter do ...@@ -137,4 +137,8 @@ describe Gitlab::LDAP::Adapter do
end end
end end
end end
def ldap_attributes
Gitlab::LDAP::Person.ldap_attributes(Gitlab::LDAP::Config.new('ldapmain'))
end
end end
...@@ -8,13 +8,16 @@ describe Gitlab::LDAP::Person do ...@@ -8,13 +8,16 @@ describe Gitlab::LDAP::Person do
before do before do
stub_ldap_config( stub_ldap_config(
options: { options: {
'uid' => 'uid',
'attributes' => { 'attributes' => {
'name' => 'cn', 'name' => 'cn',
'email' => %w(mail email userPrincipalName) 'email' => %w(mail email userPrincipalName),
'username' => username_attribute
} }
} }
) )
end end
let(:username_attribute) { %w(uid sAMAccountName userid) }
describe '.normalize_dn' do describe '.normalize_dn' do
subject { described_class.normalize_dn(given) } subject { described_class.normalize_dn(given) }
...@@ -44,6 +47,34 @@ describe Gitlab::LDAP::Person do ...@@ -44,6 +47,34 @@ describe Gitlab::LDAP::Person do
end end
end end
describe '.ldap_attributes' do
it 'returns a compact and unique array' do
stub_ldap_config(
options: {
'uid' => nil,
'attributes' => {
'name' => 'cn',
'email' => 'mail',
'username' => %w(uid mail memberof)
}
}
)
config = Gitlab::LDAP::Config.new('ldapmain')
ldap_attributes = described_class.ldap_attributes(config)
expect(ldap_attributes).to match_array(%w(dn uid cn mail memberof))
end
end
describe '.validate_entry' do
it 'raises InvalidEntryError' do
entry['foo'] = 'bar'
expect { described_class.new(entry, 'ldapmain') }
.to raise_error(Gitlab::LDAP::Person::InvalidEntryError)
end
end
describe '#name' do describe '#name' do
it 'uses the configured name attribute and handles values as an array' do it 'uses the configured name attribute and handles values as an array' do
name = 'John Doe' name = 'John Doe'
...@@ -72,6 +103,44 @@ describe Gitlab::LDAP::Person do ...@@ -72,6 +103,44 @@ describe Gitlab::LDAP::Person do
end end
end end
describe '#username' do
context 'with default uid username attribute' do
let(:username_attribute) { 'uid' }
it 'returns the proper username value' do
attr_value = 'johndoe'
entry[username_attribute] = attr_value
person = described_class.new(entry, 'ldapmain')
expect(person.username).to eq(attr_value)
end
end
context 'with a different username attribute' do
let(:username_attribute) { 'sAMAccountName' }
it 'returns the proper username value' do
attr_value = 'johndoe'
entry[username_attribute] = attr_value
person = described_class.new(entry, 'ldapmain')
expect(person.username).to eq(attr_value)
end
end
context 'with a non-standard username attribute' do
let(:username_attribute) { 'mail' }
it 'returns the proper username value' do
attr_value = 'john.doe@example.com'
entry[username_attribute] = attr_value
person = described_class.new(entry, 'ldapmain')
expect(person.username).to eq(attr_value)
end
end
end
def assert_generic_test(test_description, got, expected) def assert_generic_test(test_description, got, expected)
test_failure_message = "Failed test description: '#{test_description}'\n\n expected: #{expected}\n got: #{got}" test_failure_message = "Failed test description: '#{test_description}'\n\n expected: #{expected}\n got: #{got}"
expect(got).to eq(expected), test_failure_message expect(got).to eq(expected), test_failure_message
......
...@@ -275,6 +275,26 @@ describe Gitlab::OAuth::User do ...@@ -275,6 +275,26 @@ describe Gitlab::OAuth::User do
end end
end end
context 'and a corresponding LDAP person with a non-default username' do
before do
allow(ldap_user).to receive(:uid) { uid }
allow(ldap_user).to receive(:username) { 'johndoe@example.com' }
allow(ldap_user).to receive(:email) { %w(johndoe@example.com john2@example.com) }
allow(ldap_user).to receive(:dn) { dn }
end
context 'and no account for the LDAP user' do
it 'creates a user favoring the LDAP username and strips email domain' do
allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
oauth_user.save
expect(gl_user).to be_valid
expect(gl_user.username).to eql 'johndoe'
end
end
end
context "and no corresponding LDAP person" do context "and no corresponding LDAP person" do
before do before do
allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(nil) allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(nil)
......
require 'rspec/mocks' require 'rspec/mocks'
require 'toml'
module TestEnv module TestEnv
extend self extend self
...@@ -147,6 +148,9 @@ module TestEnv ...@@ -147,6 +148,9 @@ module TestEnv
version: Gitlab::GitalyClient.expected_server_version, version: Gitlab::GitalyClient.expected_server_version,
task: "gitlab:gitaly:install[#{gitaly_dir}]") do task: "gitlab:gitaly:install[#{gitaly_dir}]") do
# Always re-create config, in case it's outdated. This is fast anyway.
Gitlab::SetupHelper.create_gitaly_configuration(gitaly_dir, force: true)
start_gitaly(gitaly_dir) start_gitaly(gitaly_dir)
end end
end end
...@@ -347,6 +351,9 @@ module TestEnv ...@@ -347,6 +351,9 @@ module TestEnv
end end
def component_needs_update?(component_folder, expected_version) def component_needs_update?(component_folder, expected_version)
# Allow local overrides of the component for tests during development
return false if Rails.env.test? && File.symlink?(component_folder)
version = File.read(File.join(component_folder, 'VERSION')).strip version = File.read(File.join(component_folder, 'VERSION')).strip
# Notice that this will always yield true when using branch versions # Notice that this will always yield true when using branch versions
......
require 'rake_helper'
describe 'gitlab:git rake tasks' do
before do
Rake.application.rake_require 'tasks/gitlab/git'
storages = { 'default' => { 'path' => Settings.absolute('tmp/tests/default_storage') } }
FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/@hashed/1/2/test.git'))
allow(Gitlab.config.repositories).to receive(:storages).and_return(storages)
allow_any_instance_of(String).to receive(:color) { |string, _color| string }
stub_warn_user_is_not_gitlab
end
after do
FileUtils.rm_rf(Settings.absolute('tmp/tests/default_storage'))
end
describe 'fsck' do
it 'outputs the integrity check for a repo' do
expect { run_rake_task('gitlab:git:fsck') }.to output(/Performed Checking integrity at .*@hashed\/1\/2\/test.git/).to_stdout
end
it 'errors out about config.lock issues' do
FileUtils.touch(Settings.absolute('tmp/tests/default_storage/@hashed/1/2/test.git/config.lock'))
expect { run_rake_task('gitlab:git:fsck') }.to output(/file exists\? ... yes/).to_stdout
end
it 'errors out about ref lock issues' do
FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/@hashed/1/2/test.git/refs/heads'))
FileUtils.touch(Settings.absolute('tmp/tests/default_storage/@hashed/1/2/test.git/refs/heads/blah.lock'))
expect { run_rake_task('gitlab:git:fsck') }.to output(/Ref lock files exist:/).to_stdout
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