diff options
93 files changed, 555 insertions, 351 deletions
diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 00000000000..62fe6a45b95 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,6 @@ +/config/ +/node_modules/ +/public/ +/vendor/ +karma.config.js +webpack.config.js diff --git a/app/assets/javascripts/groups/components/app.vue b/app/assets/javascripts/groups/components/app.vue index 63bb5832bd0..22eb7bd44c5 100644 --- a/app/assets/javascripts/groups/components/app.vue +++ b/app/assets/javascripts/groups/components/app.vue @@ -4,7 +4,7 @@ import $ from 'jquery'; import { s__ } from '~/locale'; import loadingIcon from '~/vue_shared/components/loading_icon.vue'; -import modal from '~/vue_shared/components/modal.vue'; +import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; import { getParameterByName } from '~/lib/utils/common_utils'; import { mergeUrlParams } from '~/lib/utils/url_utility'; @@ -15,7 +15,7 @@ import groupsComponent from './groups.vue'; export default { components: { loadingIcon, - modal, + DeprecatedModal, groupsComponent, }, props: { @@ -52,8 +52,9 @@ export default { }, }, created() { - this.searchEmptyMessage = this.hideProjects ? - COMMON_STR.GROUP_SEARCH_EMPTY : COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY; + this.searchEmptyMessage = this.hideProjects + ? COMMON_STR.GROUP_SEARCH_EMPTY + : COMMON_STR.GROUP_PROJECT_SEARCH_EMPTY; eventHub.$on('fetchPage', this.fetchPage); eventHub.$on('toggleChildren', this.toggleChildren); @@ -72,22 +73,30 @@ export default { eventHub.$off('updateGroups', this.updateGroups); }, methods: { - fetchGroups({ parentId, page, filterGroupsBy, sortBy, archived, updatePagination }) { - return this.service.getGroups(parentId, page, filterGroupsBy, sortBy, archived) - .then((res) => { - if (updatePagination) { - this.updatePagination(res.headers); - } + fetchGroups({ + parentId, + page, + filterGroupsBy, + sortBy, + archived, + updatePagination, + }) { + return this.service + .getGroups(parentId, page, filterGroupsBy, sortBy, archived) + .then(res => { + if (updatePagination) { + this.updatePagination(res.headers); + } - return res; - }) - .then(res => res.json()) - .catch(() => { - this.isLoading = false; - $.scrollTo(0); + return res; + }) + .then(res => res.json()) + .catch(() => { + this.isLoading = false; + $.scrollTo(0); - Flash(COMMON_STR.FAILURE); - }); + Flash(COMMON_STR.FAILURE); + }); }, fetchAllGroups() { const page = getParameterByName('page') || null; @@ -103,7 +112,7 @@ export default { sortBy, archived, updatePagination: true, - }).then((res) => { + }).then(res => { this.isLoading = false; this.updateGroups(res, Boolean(filterGroupsBy)); }); @@ -118,14 +127,18 @@ export default { sortBy, archived, updatePagination: true, - }).then((res) => { + }).then(res => { this.isLoading = false; $.scrollTo(0); const currentPath = mergeUrlParams({ page }, window.location.href); - window.history.replaceState({ - page: currentPath, - }, document.title, currentPath); + window.history.replaceState( + { + page: currentPath, + }, + document.title, + currentPath, + ); this.updateGroups(res); }); @@ -138,11 +151,13 @@ export default { // eslint-disable-next-line promise/catch-or-return this.fetchGroups({ parentId: parentGroup.id, - }).then((res) => { - this.store.setGroupChildren(parentGroup, res); - }).catch(() => { - parentGroup.isChildrenLoading = false; - }); + }) + .then(res => { + this.store.setGroupChildren(parentGroup, res); + }) + .catch(() => { + parentGroup.isChildrenLoading = false; + }); } else { parentGroup.isOpen = true; } @@ -154,7 +169,11 @@ export default { this.targetGroup = group; this.targetParentGroup = parentGroup; this.showModal = true; - this.groupLeaveConfirmationMessage = s__(`GroupsTree|Are you sure you want to leave the "${group.fullName}" group?`); + this.groupLeaveConfirmationMessage = s__( + `GroupsTree|Are you sure you want to leave the "${ + group.fullName + }" group?`, + ); }, hideLeaveGroupModal() { this.showModal = false; @@ -162,14 +181,15 @@ export default { leaveGroup() { this.showModal = false; this.targetGroup.isBeingRemoved = true; - this.service.leaveGroup(this.targetGroup.leavePath) + this.service + .leaveGroup(this.targetGroup.leavePath) .then(res => res.json()) - .then((res) => { + .then(res => { $.scrollTo(0); this.store.removeGroup(this.targetGroup, this.targetParentGroup); Flash(res.notice, 'notice'); }) - .catch((err) => { + .catch(err => { let message = COMMON_STR.FAILURE; if (err.status === 403) { message = COMMON_STR.LEAVE_FORBIDDEN; @@ -208,8 +228,8 @@ export default { :search-empty-message="searchEmptyMessage" :page-info="pageInfo" /> - <modal - v-if="showModal" + <deprecated-modal + v-show="showModal" kind="warning" :primary-button-label="__('Leave')" :title="__('Are you sure?')" diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue index 5723891d130..4b5a50785b6 100644 --- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue +++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue @@ -1,75 +1,75 @@ <script> - import { __ } from '~/locale'; - import modal from '~/vue_shared/components/modal.vue'; +import { __ } from '~/locale'; +import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; - export default { - components: { - modal, +export default { + components: { + DeprecatedModal, + }, + props: { + branchId: { + type: String, + required: true, }, - props: { - branchId: { - type: String, - required: true, - }, - type: { - type: String, - required: true, - }, - path: { - type: String, - required: true, - }, + type: { + type: String, + required: true, }, - data() { - return { - entryName: this.path !== '' ? `${this.path}/` : '', - }; + path: { + type: String, + required: true, }, - computed: { - modalTitle() { - if (this.type === 'tree') { - return __('Create new directory'); - } + }, + data() { + return { + entryName: this.path !== '' ? `${this.path}/` : '', + }; + }, + computed: { + modalTitle() { + if (this.type === 'tree') { + return __('Create new directory'); + } - return __('Create new file'); - }, - buttonLabel() { - if (this.type === 'tree') { - return __('Create directory'); - } - - return __('Create file'); - }, - formLabelName() { - if (this.type === 'tree') { - return __('Directory name'); - } + return __('Create new file'); + }, + buttonLabel() { + if (this.type === 'tree') { + return __('Create directory'); + } - return __('File name'); - }, + return __('Create file'); }, - mounted() { - this.$refs.fieldName.focus(); + formLabelName() { + if (this.type === 'tree') { + return __('Directory name'); + } + + return __('File name'); }, - methods: { - createEntryInStore() { - this.$emit('create', { - branchId: this.branchId, - name: this.entryName, - type: this.type, - }); + }, + mounted() { + this.$refs.fieldName.focus(); + }, + methods: { + createEntryInStore() { + this.$emit('create', { + branchId: this.branchId, + name: this.entryName, + type: this.type, + }); - this.hideModal(); - }, - hideModal() { - this.$emit('hide'); - }, + this.hideModal(); + }, + hideModal() { + this.$emit('hide'); }, - }; + }, +}; </script> <template> - <modal + <deprecated-modal :title="modalTitle" :primary-button-label="buttonLabel" kind="success" @@ -95,5 +95,5 @@ </div> </fieldset> </form> - </modal> + </deprecated-modal> </template> diff --git a/app/assets/javascripts/ide/components/repo_commit_section.vue b/app/assets/javascripts/ide/components/repo_commit_section.vue index d772cab2d0e..d885ed5e301 100644 --- a/app/assets/javascripts/ide/components/repo_commit_section.vue +++ b/app/assets/javascripts/ide/components/repo_commit_section.vue @@ -2,7 +2,7 @@ import { mapState, mapActions, mapGetters } from 'vuex'; import tooltip from '~/vue_shared/directives/tooltip'; import icon from '~/vue_shared/components/icon.vue'; -import modal from '~/vue_shared/components/modal.vue'; +import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; import LoadingButton from '~/vue_shared/components/loading_button.vue'; import commitFilesList from './commit_sidebar/list.vue'; import * as consts from '../stores/modules/commit/constants'; @@ -10,7 +10,7 @@ import Actions from './commit_sidebar/actions.vue'; export default { components: { - modal, + DeprecatedModal, icon, commitFilesList, Actions, @@ -37,23 +37,20 @@ export default { 'lastCommitMsg', 'changedFiles', ]), - ...mapState('commit', [ - 'commitMessage', - 'submitCommitLoading', - ]), + ...mapState('commit', ['commitMessage', 'submitCommitLoading']), ...mapGetters('commit', [ 'commitButtonDisabled', 'discardDraftButtonDisabled', 'branchName', ]), statusSvg() { - return this.lastCommitMsg ? this.committedStateSvgPath : this.noChangesStateSvgPath; + return this.lastCommitMsg + ? this.committedStateSvgPath + : this.noChangesStateSvgPath; }, }, methods: { - ...mapActions([ - 'setPanelCollapsedStatus', - ]), + ...mapActions(['setPanelCollapsedStatus']), ...mapActions('commit', [ 'updateCommitMessage', 'discardDraft', @@ -67,8 +64,9 @@ export default { }); }, forceCreateNewBranch() { - return this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH) - .then(() => this.commitChanges()); + return this.updateCommitAction(consts.COMMIT_TO_NEW_BRANCH).then(() => + this.commitChanges(), + ); }, }, }; @@ -81,7 +79,7 @@ export default { 'multi-file-commit-empty-state-container': !changedFiles.length }" > - <modal + <deprecated-modal id="ide-create-branch-modal" :primary-button-label="__('Create new branch')" kind="success" @@ -92,7 +90,7 @@ export default { {{ __(`This branch has changed since you started editing. Would you like to create a new branch?`) }} </template> - </modal> + </deprecated-modal> <commit-files-list title="Staged" :file-list="changedFiles" diff --git a/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue b/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue index 14315d5492e..343c65edb37 100644 --- a/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue +++ b/app/assets/javascripts/pages/admin/projects/index/components/delete_project_modal.vue @@ -1,11 +1,11 @@ <script> import _ from 'underscore'; - import modal from '~/vue_shared/components/modal.vue'; + import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; import { s__, sprintf } from '~/locale'; export default { components: { - modal, + DeprecatedModal, }, props: { deleteProjectUrl: { @@ -79,7 +79,7 @@ </script> <template> - <modal + <deprecated-modal id="delete-project-modal" :title="title" :text="text" @@ -121,5 +121,5 @@ /> </form> </template> - </modal> + </deprecated-modal> </template> diff --git a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue index 7b5e333011e..0e3ac636661 100644 --- a/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue +++ b/app/assets/javascripts/pages/admin/users/components/delete_user_modal.vue @@ -1,11 +1,11 @@ <script> import _ from 'underscore'; - import modal from '~/vue_shared/components/modal.vue'; + import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; import { s__, sprintf } from '~/locale'; export default { components: { - modal, + DeprecatedModal, }, props: { deleteUserUrl: { @@ -113,7 +113,7 @@ </script> <template> - <modal + <deprecated-modal id="delete-user-modal" :title="title" :text="text" @@ -170,5 +170,5 @@ {{ secondaryButtonLabel }} </button> </template> - </modal> + </deprecated-modal> </template> diff --git a/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue b/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue index c43e0a0490f..16f792d635a 100644 --- a/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue +++ b/app/assets/javascripts/pages/milestones/shared/components/delete_milestone_modal.vue @@ -2,14 +2,14 @@ import axios from '~/lib/utils/axios_utils'; import Flash from '~/flash'; - import modal from '~/vue_shared/components/modal.vue'; + import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; import { n__, s__, sprintf } from '~/locale'; import { redirectTo } from '~/lib/utils/url_utility'; import eventHub from '../event_hub'; export default { components: { - modal, + DeprecatedModal, }, props: { issueCount: { @@ -92,7 +92,7 @@ Once deleted, it cannot be undone or recovered.`), </script> <template> - <modal + <deprecated-modal id="delete-milestone-modal" :title="title" :text="text" @@ -106,5 +106,5 @@ Once deleted, it cannot be undone or recovered.`), <p v-html="props.text"></p> </template> - </modal> + </deprecated-modal> </template> diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue index c9028952ddd..714aed1333e 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue @@ -1,5 +1,5 @@ <script> - import modal from '~/vue_shared/components/modal.vue'; + import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; import { s__, sprintf } from '~/locale'; import pipelinesTableRowComponent from './pipelines_table_row.vue'; import eventHub from '../event_hub'; @@ -12,7 +12,7 @@ export default { components: { pipelinesTableRowComponent, - modal, + DeprecatedModal, }, props: { pipelines: { @@ -120,7 +120,7 @@ :auto-devops-help-path="autoDevopsHelpPath" :view-type="viewType" /> - <modal + <deprecated-modal id="confirmation-modal" :title="modalTitle" :text="modalText" @@ -134,6 +134,6 @@ > <p v-html="props.text"></p> </template> - </modal> + </deprecated-modal> </div> </template> diff --git a/app/assets/javascripts/profile/account/components/delete_account_modal.vue b/app/assets/javascripts/profile/account/components/delete_account_modal.vue index 1ffe482d782..f50002afbf2 100644 --- a/app/assets/javascripts/profile/account/components/delete_account_modal.vue +++ b/app/assets/javascripts/profile/account/components/delete_account_modal.vue @@ -1,11 +1,11 @@ <script> - import modal from '~/vue_shared/components/modal.vue'; + import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; import { __, s__, sprintf } from '~/locale'; import csrf from '~/lib/utils/csrf'; export default { components: { - modal, + DeprecatedModal, }, props: { actionUrl: { @@ -76,7 +76,7 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`), </script> <template> - <modal + <deprecated-modal id="delete-account-modal" :title="s__('Profiles|Delete your account?')" :text="text" @@ -131,5 +131,5 @@ Once you confirm %{deleteAccount}, it cannot be undone or recovered.`), </form> </template> - </modal> + </deprecated-modal> </template> diff --git a/app/assets/javascripts/vue_shared/components/modal.vue b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue index 5f1364421aa..dcf1489b37c 100644 --- a/app/assets/javascripts/vue_shared/components/modal.vue +++ b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue @@ -1,7 +1,7 @@ <script> /* eslint-disable vue/require-default-prop */ export default { - name: 'Modal', + name: 'DeprecatedModal', // use GlModal instead props: { id: { diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue index c35621c9ef3..21ffdc1dc86 100644 --- a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue +++ b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue @@ -1,11 +1,11 @@ <script> - import modal from './modal.vue'; + import DeprecatedModal from './deprecated_modal.vue'; export default { name: 'RecaptchaModal', components: { - modal, + DeprecatedModal, }, props: { @@ -65,7 +65,7 @@ </script> <template> - <modal + <deprecated-modal kind="warning" class="recaptcha-modal js-recaptcha-modal" :hide-footer="true" @@ -82,5 +82,5 @@ > </div> </div> - </modal> + </deprecated-modal> </template> diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss index 6397757bf88..cc74cb72795 100644 --- a/app/assets/stylesheets/framework/dropdowns.scss +++ b/app/assets/stylesheets/framework/dropdowns.scss @@ -622,7 +622,7 @@ } .dropdown-content { - max-height: $dropdown-max-height; + max-height: 252px; overflow-y: auto; } @@ -699,6 +699,31 @@ border-radius: $border-radius-base; } +.git-revision-dropdown { + .dropdown-content { + max-height: 215px; + } +} + +.sidebar-move-issue-dropdown { + .dropdown-content { + max-height: 160px; + } +} + +.dropdown-menu-author { + .dropdown-content { + max-height: 215px; + } +} + +.dropdown-menu-labels { + .dropdown-content { + max-height: 128px; + } +} + + .dropdown-menu-due-date { .dropdown-content { max-height: 230px; diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index d1d98270ad9..3dd4a613789 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -152,3 +152,4 @@ .sidebar-collapsed-icon .sidebar-collapsed-value { font-size: 12px; } + diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss index c03d4c2eebf..318d3ddaece 100644 --- a/app/assets/stylesheets/pages/boards.scss +++ b/app/assets/stylesheets/pages/boards.scss @@ -31,8 +31,12 @@ .dropdown-menu-issues-board-new { width: 320px; + .open & { + max-height: 400px; + } + .dropdown-content { - max-height: 150px; + max-height: 162px; } } diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 8871a069d5d..d9267f5cdf3 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -162,17 +162,14 @@ * Last push widget */ .event-last-push { - overflow: auto; width: 100%; + display: flex; + align-items: center; .event-last-push-text { @include str-truncated(100%); - padding: 4px 0; font-size: 13px; - float: left; - margin-right: -150px; - padding-right: 150px; - line-height: 20px; + margin-right: $gl-padding; } } diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 0f49d15203b..b0852adb459 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -26,9 +26,15 @@ } } +.dropdown-menu-labels { + .dropdown-content { + max-height: 135px; + } +} + .dropdown-new-label { .dropdown-content { - max-height: 260px; + max-height: 136px; } } diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss index c9363188505..dbde0720993 100644 --- a/app/assets/stylesheets/pages/search.scss +++ b/app/assets/stylesheets/pages/search.scss @@ -112,7 +112,7 @@ input[type="checkbox"]:hover { } .dropdown-content { - max-height: 350px; + max-height: 302px; } } diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb index c27f2ee3c09..a4648b33cfa 100644 --- a/app/controllers/admin/application_controller.rb +++ b/app/controllers/admin/application_controller.rb @@ -3,23 +3,9 @@ # Automatically sets the layout and ensures an administrator is logged in class Admin::ApplicationController < ApplicationController before_action :authenticate_admin! - before_action :display_read_only_information layout 'admin' def authenticate_admin! render_404 unless current_user.admin? end - - def display_read_only_information - return unless Gitlab::Database.read_only? - - flash.now[:notice] = read_only_message - end - - private - - # Overridden in EE - def read_only_message - _('You are on a read-only GitLab instance.') - end end diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb index 06ce7328fb5..557671ab186 100644 --- a/app/controllers/projects/pipelines_settings_controller.rb +++ b/app/controllers/projects/pipelines_settings_controller.rb @@ -10,10 +10,7 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController if service.execute flash[:notice] = "Pipelines settings for '#{@project.name}' were successfully updated." - if service.run_auto_devops_pipeline? - CreatePipelineWorker.perform_async(project.id, current_user.id, project.default_branch, :web, ignore_skip_ci: true, save_on_errors: false) - flash[:success] = "A new Auto DevOps pipeline has been created, go to <a href=\"#{project_pipelines_path(@project)}\">Pipelines page</a> for details".html_safe - end + run_autodevops_pipeline(service) redirect_to project_settings_ci_cd_path(@project) else @@ -24,6 +21,18 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController private + def run_autodevops_pipeline(service) + return unless service.run_auto_devops_pipeline? + + if @project.empty_repo? + flash[:warning] = "This repository is currently empty. A new Auto DevOps pipeline will be created after a new file has been pushed to a branch." + return + end + + CreatePipelineWorker.perform_async(project.id, current_user.id, project.default_branch, :web, ignore_skip_ci: true, save_on_errors: false) + flash[:success] = "A new Auto DevOps pipeline has been created, go to <a href=\"#{project_pipelines_path(@project)}\">Pipelines page</a> for details".html_safe + end + def update_params params.require(:project).permit( :runners_token, :builds_enabled, :build_allow_git_fetch, diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 3ddf8eb3369..701be97ee96 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -323,4 +323,11 @@ module ApplicationHelper def locale_path asset_path("locale/#{Gitlab::I18n.locale}/app.js") end + + # Overridden in EE + def read_only_message + return unless Gitlab::Database.read_only? + + _('You are on a read-only GitLab instance.') + end end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 63e4a5dc45c..b3b080e6dcf 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -96,7 +96,7 @@ module ApplicationSettingsHelper def repository_storages_options_for_select(selected) options = Gitlab.config.repositories.storages.map do |name, storage| - ["#{name} - #{storage['path']}", name] + ["#{name} - #{storage['gitaly_address']}", name] end options_for_select(options, selected) diff --git a/app/models/concerns/avatarable.rb b/app/models/concerns/avatarable.rb index d35e37935fb..318df11727e 100644 --- a/app/models/concerns/avatarable.rb +++ b/app/models/concerns/avatarable.rb @@ -21,7 +21,7 @@ module Avatarable def avatar_type unless self.avatar.image? - self.errors.add :avatar, "only images allowed" + errors.add :avatar, "file format is not supported. Please try one of the following supported formats: #{AvatarUploader::IMAGE_EXT.join(', ')}" end end diff --git a/app/models/group.rb b/app/models/group.rb index f669b1a7009..d99af79b5fe 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -189,12 +189,6 @@ class Group < Namespace owners.include?(user) && owners.size == 1 end - def avatar_type - unless self.avatar.image? - self.errors.add :avatar, "only images allowed" - end - end - def post_create_hook Gitlab::AppLogger.info("Group \"#{name}\" was created") diff --git a/app/models/project.rb b/app/models/project.rb index 250680e2a2c..ed5f8b00ba2 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -503,7 +503,7 @@ class Project < ActiveRecord::Base end def repository_storage_path - Gitlab.config.repositories.storages[repository_storage].try(:[], 'path') + Gitlab.config.repositories.storages[repository_storage]&.legacy_disk_path end def team diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 3b3d9239086..ad27f320853 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -65,7 +65,7 @@ module Ci project.pipelines .where(ref: pipeline.ref) .where.not(id: pipeline.id) - .where.not(sha: project.repository.sha_from_ref(pipeline.ref)) + .where.not(sha: project.commit(pipeline.ref).try(:id)) .created_or_pending end diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index f0963cf9da8..f67a8878c80 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -6,6 +6,7 @@ .mobile-overlay .alert-wrapper = render "layouts/broadcast" + = render 'layouts/header/read_only_banner' = yield :flash_message - unless @hide_breadcrumbs = render "layouts/nav/breadcrumbs" diff --git a/app/views/layouts/header/_read_only_banner.html.haml b/app/views/layouts/header/_read_only_banner.html.haml new file mode 100644 index 00000000000..f3d563c362f --- /dev/null +++ b/app/views/layouts/header/_read_only_banner.html.haml @@ -0,0 +1,7 @@ +- message = read_only_message +- if message + .flash-container.flash-container-page + .flash-notice + %div{ class: (container_class) } + %span + = message diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml index 6f5eb828902..6a1035d2dc7 100644 --- a/app/views/projects/_last_push.html.haml +++ b/app/views/projects/_last_push.html.haml @@ -13,6 +13,6 @@ #{time_ago_with_tooltip(event.created_at)} - .pull-right + .flex-right = link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm qa-create-merge-request" do #{ _('Create merge request') } diff --git a/changelogs/unreleased/43482-enabling-auto-devops-on-an-empty-project-gives-you-wrong-information.yml b/changelogs/unreleased/43482-enabling-auto-devops-on-an-empty-project-gives-you-wrong-information.yml new file mode 100644 index 00000000000..889fd008bad --- /dev/null +++ b/changelogs/unreleased/43482-enabling-auto-devops-on-an-empty-project-gives-you-wrong-information.yml @@ -0,0 +1,5 @@ +--- +title: Add empty repo check before running AutoDevOps pipeline +merge_request: 17605 +author: +type: changed diff --git a/changelogs/unreleased/43771-improve-avatar-error-message.yml b/changelogs/unreleased/43771-improve-avatar-error-message.yml new file mode 100644 index 00000000000..1fae10f4d1f --- /dev/null +++ b/changelogs/unreleased/43771-improve-avatar-error-message.yml @@ -0,0 +1,5 @@ +--- +title: Change avatar error message to include allowed file formats +merge_request: 17747 +author: Fabian Schneider +type: changed diff --git a/changelogs/unreleased/44382-ui-breakdown-for-create-merge-request.yml b/changelogs/unreleased/44382-ui-breakdown-for-create-merge-request.yml new file mode 100644 index 00000000000..dd8c0b19d5f --- /dev/null +++ b/changelogs/unreleased/44382-ui-breakdown-for-create-merge-request.yml @@ -0,0 +1,5 @@ +--- +title: Fix UI breakdown for Create merge request button +merge_request: 17821 +author: Takuya Noguchi +type: fixed diff --git a/changelogs/unreleased/ci-pipeline-commit-lookup.yml b/changelogs/unreleased/ci-pipeline-commit-lookup.yml new file mode 100644 index 00000000000..b2a1e4c2163 --- /dev/null +++ b/changelogs/unreleased/ci-pipeline-commit-lookup.yml @@ -0,0 +1,5 @@ +--- +title: Use porcelain commit lookup method on CI::CreatePipelineService +merge_request: 17911 +author: +type: fixed diff --git a/changelogs/unreleased/increase-unicorn-memory-killer-limits.yml b/changelogs/unreleased/increase-unicorn-memory-killer-limits.yml new file mode 100644 index 00000000000..6d7d2df4f4a --- /dev/null +++ b/changelogs/unreleased/increase-unicorn-memory-killer-limits.yml @@ -0,0 +1,5 @@ +--- +title: Increase the memory limits used in the unicorn killer +merge_request: 17948 +author: +type: other diff --git a/changelogs/unreleased/jprovazn-issueref.yml b/changelogs/unreleased/jprovazn-issueref.yml new file mode 100644 index 00000000000..ee19cac7b19 --- /dev/null +++ b/changelogs/unreleased/jprovazn-issueref.yml @@ -0,0 +1,6 @@ +--- +title: Display state indicator for issuable references in non-project scope (e.g. + when referencing issuables from group scope). +merge_request: +author: +type: fixed diff --git a/changelogs/unreleased/tc-re-add-read-only-banner.yml b/changelogs/unreleased/tc-re-add-read-only-banner.yml new file mode 100644 index 00000000000..35bcd7e184e --- /dev/null +++ b/changelogs/unreleased/tc-re-add-read-only-banner.yml @@ -0,0 +1,5 @@ +--- +title: Add read-only banner to all pages +merge_request: 17798 +author: +type: fixed diff --git a/changelogs/unreleased/winh-deprecate-old-modal.yml b/changelogs/unreleased/winh-deprecate-old-modal.yml new file mode 100644 index 00000000000..4fae1fafbea --- /dev/null +++ b/changelogs/unreleased/winh-deprecate-old-modal.yml @@ -0,0 +1,5 @@ +--- +title: Rename modal.vue to deprecated_modal.vue +merge_request: 17438 +author: +type: other diff --git a/config.ru b/config.ru index 7b15939c6ff..405d01863ac 100644 --- a/config.ru +++ b/config.ru @@ -7,8 +7,8 @@ if defined?(Unicorn) # Unicorn self-process killer require 'unicorn/worker_killer' - min = (ENV['GITLAB_UNICORN_MEMORY_MIN'] || 300 * 1 << 20).to_i - max = (ENV['GITLAB_UNICORN_MEMORY_MAX'] || 350 * 1 << 20).to_i + min = (ENV['GITLAB_UNICORN_MEMORY_MIN'] || 400 * 1 << 20).to_i + max = (ENV['GITLAB_UNICORN_MEMORY_MAX'] || 650 * 1 << 20).to_i # Max memory size (RSS) per worker use Unicorn::WorkerKiller::Oom, min, max diff --git a/config/application.rb b/config/application.rb index 0ff95e33a9c..13501d4bdb5 100644 --- a/config/application.rb +++ b/config/application.rb @@ -170,7 +170,7 @@ module Gitlab ENV['GIT_TERMINAL_PROMPT'] = '0' # Gitlab Read-only middleware support - config.middleware.insert_after ActionDispatch::Flash, 'Gitlab::Middleware::ReadOnly' + config.middleware.insert_after ActionDispatch::Flash, '::Gitlab::Middleware::ReadOnly' config.generators do |g| g.factory_bot false diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index ea0dee7af53..53cf0010d8e 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -467,12 +467,7 @@ unless Settings.repositories.storages['default'] end Settings.repositories.storages.each do |key, storage| - storage = Settingslogic.new(storage) - - # Expand relative paths - storage['path'] = Settings.absolute(storage['path']) - - Settings.repositories.storages[key] = storage + Settings.repositories.storages[key] = Gitlab::GitalyClient::StorageSettings.new(storage) end # @@ -486,7 +481,7 @@ repositories_storages = Settings.repositories.storages.values repository_downloads_path = Settings.gitlab['repository_downloads_path'].to_s.gsub(%r{/$}, '') repository_downloads_full_path = File.expand_path(repository_downloads_path, Settings.gitlab['user_home']) -if repository_downloads_path.blank? || repositories_storages.any? { |rs| [repository_downloads_path, repository_downloads_full_path].include?(rs['path'].gsub(%r{/$}, '')) } +if repository_downloads_path.blank? || repositories_storages.any? { |rs| [repository_downloads_path, repository_downloads_full_path].include?(rs.legacy_disk_path.gsub(%r{/$}, '')) } Settings.gitlab['repository_downloads_path'] = File.join(Settings.shared['path'], 'cache/archive') end diff --git a/config/initializers/6_validations.rb b/config/initializers/6_validations.rb index f8e67ce04c9..d92cdb97766 100644 --- a/config/initializers/6_validations.rb +++ b/config/initializers/6_validations.rb @@ -5,7 +5,7 @@ end def find_parent_path(name, path) parent = Pathname.new(path).realpath.parent Gitlab.config.repositories.storages.detect do |n, rs| - name != n && Pathname.new(rs['path']).realpath == parent + name != n && Pathname.new(rs.legacy_disk_path).realpath == parent end rescue Errno::EIO, Errno::ENOENT => e warning = "WARNING: couldn't verify #{path} (#{name}). "\ @@ -33,7 +33,7 @@ def validate_storages_config "If you're using the Gitlab Development Kit, you can update your configuration running `gdk reconfigure`.\n" end - if !repository_storage.is_a?(Hash) || repository_storage['path'].nil? + if !repository_storage.is_a?(Gitlab::GitalyClient::StorageSettings) || repository_storage.legacy_disk_path.nil? storage_validation_error("#{name} is not a valid storage, because it has no `path` key. Refer to gitlab.yml.example for an updated example") end @@ -50,7 +50,7 @@ end def validate_storages_paths Gitlab.config.repositories.storages.each do |name, repository_storage| - parent_name, _parent_path = find_parent_path(name, repository_storage['path']) + parent_name, _parent_path = find_parent_path(name, repository_storage.legacy_disk_path) if parent_name storage_validation_error("#{name} is a nested path of #{parent_name}. Nested paths are not supported for repository storages") end diff --git a/config/webpack.config.js b/config/webpack.config.js index f5fb7de6176..42fe4b345e1 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -9,14 +9,12 @@ const StatsWriterPlugin = require('webpack-stats-plugin').StatsWriterPlugin; const CopyWebpackPlugin = require('copy-webpack-plugin'); const CompressionPlugin = require('compression-webpack-plugin'); const NameAllModulesPlugin = require('name-all-modules-plugin'); -const BundleAnalyzerPlugin = require('webpack-bundle-analyzer') - .BundleAnalyzerPlugin; +const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin; const WatchMissingNodeModulesPlugin = require('react-dev-utils/WatchMissingNodeModulesPlugin'); const ROOT_PATH = path.resolve(__dirname, '..'); const IS_PRODUCTION = process.env.NODE_ENV === 'production'; -const IS_DEV_SERVER = - process.argv.join(' ').indexOf('webpack-dev-server') !== -1; +const IS_DEV_SERVER = process.argv.join(' ').indexOf('webpack-dev-server') !== -1; const DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost'; const DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10) || 3808; const DEV_SERVER_LIVERELOAD = process.env.DEV_SERVER_LIVERELOAD !== 'false'; @@ -29,10 +27,10 @@ let watchAutoEntries = []; function generateEntries() { // generate automatic entry points const autoEntries = {}; - const pageEntries = glob.sync('pages/**/index.js', { - cwd: path.join(ROOT_PATH, 'app/assets/javascripts'), - }); - watchAutoEntries = [path.join(ROOT_PATH, 'app/assets/javascripts/pages/')]; + const pageEntries = glob.sync('pages/**/index.js', { cwd: path.join(ROOT_PATH, 'app/assets/javascripts') }); + watchAutoEntries = [ + path.join(ROOT_PATH, 'app/assets/javascripts/pages/'), + ]; function generateAutoEntries(path, prefix = '.') { const chunkPath = path.replace(/\/index\.js$/, ''); @@ -40,16 +38,16 @@ function generateEntries() { autoEntries[chunkName] = `${prefix}/${path}`; } - pageEntries.forEach(path => generateAutoEntries(path)); + pageEntries.forEach(( path ) => generateAutoEntries(path)); autoEntriesCount = Object.keys(autoEntries).length; const manualEntries = { - common: './commons/index.js', - main: './main.js', - raven: './raven/index.js', - webpack_runtime: './webpack.js', - ide: './ide/index.js', + common: './commons/index.js', + main: './main.js', + raven: './raven/index.js', + webpack_runtime: './webpack.js', + ide: './ide/index.js', }; return Object.assign(manualEntries, autoEntries); @@ -63,12 +61,8 @@ const config = { output: { path: path.join(ROOT_PATH, 'public/assets/webpack'), publicPath: '/assets/webpack/', - filename: IS_PRODUCTION - ? '[name].[chunkhash].bundle.js' - : '[name].bundle.js', - chunkFilename: IS_PRODUCTION - ? '[name].[chunkhash].chunk.js' - : '[name].chunk.js', + filename: IS_PRODUCTION ? '[name].[chunkhash].bundle.js' : '[name].bundle.js', + chunkFilename: IS_PRODUCTION ? '[name].[chunkhash].chunk.js' : '[name].chunk.js', }, module: { @@ -97,8 +91,8 @@ const config = { { loader: 'worker-loader', options: { - inline: true, - }, + inline: true + } }, { loader: 'babel-loader' }, ], @@ -109,7 +103,7 @@ const config = { loader: 'file-loader', options: { name: '[name].[hash].[ext]', - }, + } }, { test: /katex.css$/, @@ -119,8 +113,8 @@ const config = { { loader: 'css-loader', options: { - name: '[name].[hash].[ext]', - }, + name: '[name].[hash].[ext]' + } }, ], }, @@ -130,18 +124,15 @@ const config = { loader: 'file-loader', options: { name: '[name].[hash].[ext]', - }, + } }, { test: /monaco-editor\/\w+\/vs\/loader\.js$/, use: [ { loader: 'exports-loader', options: 'l.global' }, - { - loader: 'imports-loader', - options: 'l=>{},this=>l,AMDLoader=>this,module=>undefined', - }, + { loader: 'imports-loader', options: 'l=>{},this=>l,AMDLoader=>this,module=>undefined' }, ], - }, + } ], noParse: [/monaco-editor\/\w+\/vs\//], @@ -159,10 +150,10 @@ const config = { source: false, chunks: false, modules: false, - assets: true, + assets: true }); return JSON.stringify(stats, null, 2); - }, + } }), // prevent pikaday from including moment.js @@ -179,7 +170,7 @@ const config = { new NameAllModulesPlugin(), // assign deterministic chunk ids - new webpack.NamedChunksPlugin(chunk => { + new webpack.NamedChunksPlugin((chunk) => { if (chunk.name) { return chunk.name; } @@ -196,12 +187,9 @@ const config = { const pagesBase = path.join(ROOT_PATH, 'app/assets/javascripts/pages'); if (m.resource.indexOf(pagesBase) === 0) { - moduleNames.push( - path - .relative(pagesBase, m.resource) - .replace(/\/index\.[a-z]+$/, '') - .replace(/\//g, '__'), - ); + moduleNames.push(path.relative(pagesBase, m.resource) + .replace(/\/index\.[a-z]+$/, '') + .replace(/\//g, '__')); } else { moduleNames.push(path.relative(m.context, m.resource)); } @@ -209,8 +197,7 @@ const config = { chunk.forEachModule(collectModuleNames); - const hash = crypto - .createHash('sha256') + const hash = crypto.createHash('sha256') .update(moduleNames.join('_')) .digest('hex'); @@ -222,23 +209,13 @@ const config = { names: ['main', 'common', 'webpack_runtime'], }), - // enable scope hoisting - new webpack.optimize.ModuleConcatenationPlugin(), - // copy pre-compiled vendor libraries verbatim new CopyWebpackPlugin([ { - from: path.join( - ROOT_PATH, - `node_modules/monaco-editor/${IS_PRODUCTION ? 'min' : 'dev'}/vs`, - ), + from: path.join(ROOT_PATH, `node_modules/monaco-editor/${IS_PRODUCTION ? 'min' : 'dev'}/vs`), to: 'monaco-editor/vs', transform: function(content, path) { - if ( - /\.js$/.test(path) && - !/worker/i.test(path) && - !/typescript/i.test(path) - ) { + if (/\.js$/.test(path) && !/worker/i.test(path) && !/typescript/i.test(path)) { return ( '(function(){\n' + 'var define = this.define, require = this.require;\n' + @@ -248,23 +225,23 @@ const config = { ); } return content; - }, - }, + } + } ]), ], resolve: { extensions: ['.js'], alias: { - '~': path.join(ROOT_PATH, 'app/assets/javascripts'), - emojis: path.join(ROOT_PATH, 'fixtures/emojis'), - empty_states: path.join(ROOT_PATH, 'app/views/shared/empty_states'), - icons: path.join(ROOT_PATH, 'app/views/shared/icons'), - images: path.join(ROOT_PATH, 'app/assets/images'), - vendor: path.join(ROOT_PATH, 'vendor/assets/javascripts'), - vue$: 'vue/dist/vue.esm.js', - spec: path.join(ROOT_PATH, 'spec/javascripts'), - }, + '~': path.join(ROOT_PATH, 'app/assets/javascripts'), + 'emojis': path.join(ROOT_PATH, 'fixtures/emojis'), + 'empty_states': path.join(ROOT_PATH, 'app/views/shared/empty_states'), + 'icons': path.join(ROOT_PATH, 'app/views/shared/icons'), + 'images': path.join(ROOT_PATH, 'app/assets/images'), + 'vendor': path.join(ROOT_PATH, 'vendor/assets/javascripts'), + 'vue$': 'vue/dist/vue.esm.js', + 'spec': path.join(ROOT_PATH, 'spec/javascripts'), + } }, // sqljs requires fs @@ -279,14 +256,15 @@ if (IS_PRODUCTION) { new webpack.NoEmitOnErrorsPlugin(), new webpack.LoaderOptionsPlugin({ minimize: true, - debug: false, + debug: false }), + new webpack.optimize.ModuleConcatenationPlugin(), new webpack.optimize.UglifyJsPlugin({ - sourceMap: true, + sourceMap: true }), new webpack.DefinePlugin({ - 'process.env': { NODE_ENV: JSON.stringify('production') }, - }), + 'process.env': { NODE_ENV: JSON.stringify('production') } + }) ); // compression can require a lot of compute time and is disabled in CI @@ -304,7 +282,7 @@ if (IS_DEV_SERVER) { headers: { 'Access-Control-Allow-Origin': '*' }, stats: 'errors-only', hot: DEV_SERVER_LIVERELOAD, - inline: DEV_SERVER_LIVERELOAD, + inline: DEV_SERVER_LIVERELOAD }; config.plugins.push( // watch node_modules for changes if we encounter a missing module compile error @@ -320,14 +298,12 @@ if (IS_DEV_SERVER) { ]; // report our auto-generated bundle count - console.log( - `${autoEntriesCount} entries from '/pages' automatically added to webpack output.`, - ); + console.log(`${autoEntriesCount} entries from '/pages' automatically added to webpack output.`); callback(); - }); + }) }, - }, + } ); if (DEV_SERVER_LIVERELOAD) { config.plugins.push(new webpack.HotModuleReplacementPlugin()); @@ -342,7 +318,7 @@ if (WEBPACK_REPORT) { openAnalyzer: false, reportFilename: path.join(ROOT_PATH, 'webpack-report/index.html'), statsFilename: path.join(ROOT_PATH, 'webpack-report/stats.json'), - }), + }) ); } diff --git a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb index bcdae272209..a96ea7d9db4 100644 --- a/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb +++ b/db/migrate/20161124141322_migrate_process_commit_worker_jobs.rb @@ -12,7 +12,7 @@ class MigrateProcessCommitWorkerJobs < ActiveRecord::Migration end def repository_storage_path - Gitlab.config.repositories.storages[repository_storage]['path'] + Gitlab.config.repositories.storages[repository_storage].legacy_disk_path end def repository_path diff --git a/db/migrate/20161220141214_remove_dot_git_from_group_names.rb b/db/migrate/20161220141214_remove_dot_git_from_group_names.rb index 8fb1f9d5e73..bddc234db25 100644 --- a/db/migrate/20161220141214_remove_dot_git_from_group_names.rb +++ b/db/migrate/20161220141214_remove_dot_git_from_group_names.rb @@ -60,7 +60,7 @@ class RemoveDotGitFromGroupNames < ActiveRecord::Migration def move_namespace(group_id, path_was, path) repository_storage_paths = select_all("SELECT distinct(repository_storage) FROM projects WHERE namespace_id = #{group_id}").map do |row| - Gitlab.config.repositories.storages[row['repository_storage']]['path'] + Gitlab.config.repositories.storages[row['repository_storage']].legacy_disk_path end.compact # Move the namespace directory in all storages paths used by member projects diff --git a/db/migrate/20161226122833_remove_dot_git_from_usernames.rb b/db/migrate/20161226122833_remove_dot_git_from_usernames.rb index 61dcc8c54f5..7c28d934c29 100644 --- a/db/migrate/20161226122833_remove_dot_git_from_usernames.rb +++ b/db/migrate/20161226122833_remove_dot_git_from_usernames.rb @@ -71,7 +71,7 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration route_exists = route_exists?(path) Gitlab.config.repositories.storages.each_value do |storage| - if route_exists || path_exists?(path, storage['path']) + if route_exists || path_exists?(path, storage.legacy_disk_path) counter += 1 path = "#{base}#{counter}" @@ -84,7 +84,7 @@ class RemoveDotGitFromUsernames < ActiveRecord::Migration def move_namespace(namespace_id, path_was, path) repository_storage_paths = select_all("SELECT distinct(repository_storage) FROM projects WHERE namespace_id = #{namespace_id}").map do |row| - Gitlab.config.repositories.storages[row['repository_storage']]['path'] + Gitlab.config.repositories.storages[row['repository_storage']].legacy_disk_path end.compact # Move the namespace directory in all storages paths used by member projects diff --git a/doc/development/i18n/proofreader.md b/doc/development/i18n/proofreader.md index 960eabd5538..cf62314bc29 100644 --- a/doc/development/i18n/proofreader.md +++ b/doc/development/i18n/proofreader.md @@ -10,6 +10,7 @@ are very appreciative of the work done by translators and proofreaders! - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve) - Chinese Traditional - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve) + - Weizhe Ding - [GitLab](https://gitlab.com/d.weizhe), [Crowdin](https://crowdin.com/profile/d.weizhe) - Chinese Traditional, Hong Kong - Huang Tao - [GitLab](https://gitlab.com/htve), [Crowdin](https://crowdin.com/profile/htve) - Dutch diff --git a/doc/development/migration_style_guide.md b/doc/development/migration_style_guide.md index 1e060ffd941..a211effdfa7 100644 --- a/doc/development/migration_style_guide.md +++ b/doc/development/migration_style_guide.md @@ -23,10 +23,6 @@ When downtime is necessary the migration has to be approved by: An up-to-date list of people holding these titles can be found at <https://about.gitlab.com/team/>. -The document ["What Requires Downtime?"](what_requires_downtime.md) specifies -various database operations, whether they require downtime and how to -work around that whenever possible. - When writing your migrations, also consider that databases might have stale data or inconsistencies and guard for that. Try to make as few assumptions as possible about the state of the database. @@ -41,6 +37,18 @@ Migrations that make changes to the database schema (e.g. adding a column) can only be added in the monthly release, patch releases may only contain data migrations _unless_ schema changes are absolutely required to solve a problem. +## What Requires Downtime? + +The document ["What Requires Downtime?"](what_requires_downtime.md) specifies +various database operations, such as + +- [adding, dropping, and renaming columns](what_requires_downtime.md#adding-columns) +- [changing column constraints and types](what_requires_downtime.md#changing-column-constraints) +- [adding and dropping indexes, tables, and foreign keys](what_requires_downtime.md#adding-indexes) + +and whether they require downtime and how to work around that whenever possible. + + ## Downtime Tagging Every migration must specify if it requires downtime or not, and if it should diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index 6715159a1aa..88a7f2a4235 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -65,7 +65,7 @@ module Backup def restore Gitlab.config.repositories.storages.each do |name, repository_storage| - path = repository_storage['path'] + path = repository_storage.legacy_disk_path next unless File.exist?(path) # Move repos dir to 'repositories.old' dir @@ -200,7 +200,7 @@ module Backup end def repository_storage_paths_args - Gitlab.config.repositories.storages.values.map { |rs| rs['path'] } + Gitlab.config.repositories.storages.values.map { |rs| rs.legacy_disk_path } end def progress diff --git a/lib/banzai/filter/issuable_state_filter.rb b/lib/banzai/filter/issuable_state_filter.rb index 77299abe324..8f541dcfdb2 100644 --- a/lib/banzai/filter/issuable_state_filter.rb +++ b/lib/banzai/filter/issuable_state_filter.rb @@ -17,7 +17,7 @@ module Banzai issuables.each do |node, issuable| next if !can_read_cross_project? && issuable.project != project - if VISIBLE_STATES.include?(issuable.state) && node.inner_html == issuable.reference_link_text(project) + if VISIBLE_STATES.include?(issuable.state) && issuable_reference?(node.inner_html, issuable) node.content += " (#{issuable.state})" end end @@ -27,6 +27,10 @@ module Banzai private + def issuable_reference?(text, issuable) + text == issuable.reference_link_text(project || group) + end + def can_read_cross_project? Ability.allowed?(current_user, :read_cross_project) end @@ -38,6 +42,10 @@ module Banzai def project context[:project] end + + def group + context[:group] + end end end end diff --git a/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb b/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb index fd4a8832ec2..62d4d0a92a6 100644 --- a/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb +++ b/lib/gitlab/database/rename_reserved_paths_migration/v1/migration_classes.rb @@ -74,7 +74,7 @@ module Gitlab }.freeze def repository_storage_path - Gitlab.config.repositories.storages[repository_storage]['path'] + Gitlab.config.repositories.storages[repository_storage].legacy_disk_path end # Overridden to have the correct `source_type` for the `route` relation diff --git a/lib/gitlab/git/gitlab_projects.rb b/lib/gitlab/git/gitlab_projects.rb index a142ed6b2ef..dc0bc8518bc 100644 --- a/lib/gitlab/git/gitlab_projects.rb +++ b/lib/gitlab/git/gitlab_projects.rb @@ -212,7 +212,7 @@ module Gitlab end def shard_name_from_shard_path(shard_path) - Gitlab.config.repositories.storages.find { |_, info| info['path'] == shard_path }&.first || + Gitlab.config.repositories.storages.find { |_, info| info.legacy_disk_path == shard_path }&.first || raise(ShardNameNotFoundError, "no shard found for path '#{shard_path}'") end diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 208710b0935..20b0647fce9 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -93,7 +93,7 @@ module Gitlab @relative_path = relative_path @gl_repository = gl_repository - storage_path = Gitlab.config.repositories.storages[@storage]['path'] + storage_path = Gitlab.config.repositories.storages[@storage].legacy_disk_path @gitlab_projects = Gitlab::Git::GitlabProjects.new( storage_path, relative_path, @@ -516,10 +516,6 @@ module Gitlab end end - def sha_from_ref(ref) - rev_parse_target(ref).oid - end - # Return the object that +revspec+ points to. If +revspec+ is an # annotated tag, then return the tag's target instead. def rev_parse_target(revspec) @@ -2409,6 +2405,10 @@ module Gitlab def rev_list_param(spec) spec == :all ? ['--all'] : spec end + + def sha_from_ref(ref) + rev_parse_target(ref).oid + end end end end diff --git a/lib/gitlab/git/storage/checker.rb b/lib/gitlab/git/storage/checker.rb index d3c37f82101..2f611cef37b 100644 --- a/lib/gitlab/git/storage/checker.rb +++ b/lib/gitlab/git/storage/checker.rb @@ -35,7 +35,7 @@ module Gitlab def initialize(storage, logger = Rails.logger) @storage = storage config = Gitlab.config.repositories.storages[@storage] - @storage_path = config['path'] + @storage_path = config.legacy_disk_path @logger = logger @hostname = Gitlab::Environment.hostname diff --git a/lib/gitlab/git/storage/circuit_breaker.rb b/lib/gitlab/git/storage/circuit_breaker.rb index 898bb1b65be..e35054466ff 100644 --- a/lib/gitlab/git/storage/circuit_breaker.rb +++ b/lib/gitlab/git/storage/circuit_breaker.rb @@ -25,7 +25,7 @@ module Gitlab if !config.present? NullCircuitBreaker.new(storage, hostname, error: Misconfiguration.new("Storage '#{storage}' is not configured")) - elsif !config['path'].present? + elsif !config.legacy_disk_path.present? NullCircuitBreaker.new(storage, hostname, error: Misconfiguration.new("Path for storage '#{storage}' is not configured")) else new(storage, hostname) diff --git a/lib/gitlab/gitaly_client/storage_settings.rb b/lib/gitlab/gitaly_client/storage_settings.rb new file mode 100644 index 00000000000..8668caf0c55 --- /dev/null +++ b/lib/gitlab/gitaly_client/storage_settings.rb @@ -0,0 +1,35 @@ +module Gitlab + module GitalyClient + # This is a chokepoint that is meant to help us stop remove all places + # where production code (app, config, db, lib) touches Git repositories + # directly. + class StorageSettings + DirectPathAccessError = Class.new(StandardError) + + # This class will give easily recognizable NoMethodErrors + Deprecated = Class.new + + attr_reader :legacy_disk_path + + def initialize(storage) + raise "expected a Hash, got a #{storage.class.name}" unless storage.is_a?(Hash) + + # Support a nil 'path' field because some of the circuit breaker tests use it. + @legacy_disk_path = File.expand_path(storage['path'], Rails.root) if storage['path'] + + storage['path'] = Deprecated + @hash = storage + end + + def gitaly_address + @hash.fetch(:gitaly_address) + end + + private + + def method_missing(m, *args, &block) + @hash.public_send(m, *args, &block) # rubocop:disable GitlabSecurity/PublicSend + end + end + end +end diff --git a/lib/gitlab/health_checks/fs_shards_check.rb b/lib/gitlab/health_checks/fs_shards_check.rb index afaa59b1018..6e554383270 100644 --- a/lib/gitlab/health_checks/fs_shards_check.rb +++ b/lib/gitlab/health_checks/fs_shards_check.rb @@ -77,7 +77,7 @@ module Gitlab end def storage_path(storage_name) - storages_paths&.dig(storage_name, 'path') + storages_paths[storage_name]&.legacy_disk_path end # All below test methods use shell commands to perform actions on storage volumes. diff --git a/lib/gitlab/repo_path.rb b/lib/gitlab/repo_path.rb index 79265cf952d..1fa2a19b0af 100644 --- a/lib/gitlab/repo_path.rb +++ b/lib/gitlab/repo_path.rb @@ -21,11 +21,11 @@ module Gitlab result = repo_path storage = Gitlab.config.repositories.storages.values.find do |params| - repo_path.start_with?(params['path']) + repo_path.start_with?(params.legacy_disk_path) end if storage - result = result.sub(storage['path'], '') + result = result.sub(storage.legacy_disk_path, '') elsif fail_on_not_found raise NotFoundError.new("No known storage path matches #{repo_path.inspect}") end diff --git a/lib/gitlab/setup_helper.rb b/lib/gitlab/setup_helper.rb index 07d7c91cb5d..e5c02dd8ecc 100644 --- a/lib/gitlab/setup_helper.rb +++ b/lib/gitlab/setup_helper.rb @@ -24,7 +24,7 @@ module Gitlab address = val['gitaly_address'] end - storages << { name: key, path: val['path'] } + storages << { name: key, path: val.legacy_disk_path } end if Rails.env.test? diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 3a8f5826818..c8c15b9684a 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -82,7 +82,7 @@ module Gitlab repository.gitaly_repository_client.create_repository true else - repo_path = File.join(Gitlab.config.repositories.storages[storage]['path'], relative_path) + repo_path = File.join(Gitlab.config.repositories.storages[storage].legacy_disk_path, relative_path) Gitlab::Git::Repository.create(repo_path, bare: true, symlink_hooks_to: gitlab_shell_hooks_path) end end @@ -131,7 +131,7 @@ module Gitlab if is_enabled repository.gitaly_repository_client.fetch_remote(remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags, timeout: git_timeout, prune: prune) else - storage_path = Gitlab.config.repositories.storages[repository.storage]["path"] + storage_path = Gitlab.config.repositories.storages[repository.storage].legacy_disk_path local_fetch_remote(storage_path, repository.relative_path, remote, ssh_auth: ssh_auth, forced: forced, no_tags: no_tags, prune: prune) end end @@ -478,7 +478,7 @@ module Gitlab def gitaly_namespace_client(storage_path) storage, _value = Gitlab.config.repositories.storages.find do |storage, value| - value['path'] == storage_path + value.legacy_disk_path == storage_path end Gitlab::GitalyClient::NamespaceService.new(storage) diff --git a/lib/gitlab/task_helpers.rb b/lib/gitlab/task_helpers.rb index 34bee6fecbe..42be301fd9b 100644 --- a/lib/gitlab/task_helpers.rb +++ b/lib/gitlab/task_helpers.rb @@ -129,7 +129,7 @@ module Gitlab def all_repos Gitlab.config.repositories.storages.each_value do |repository_storage| - IO.popen(%W(find #{repository_storage['path']} -mindepth 2 -type d -name *.git)) do |find| + IO.popen(%W(find #{repository_storage.legacy_disk_path} -mindepth 2 -type d -name *.git)) do |find| find.each_line do |path| yield path.chomp end @@ -138,7 +138,7 @@ module Gitlab end def repository_storage_paths_args - Gitlab.config.repositories.storages.values.map { |rs| rs['path'] } + Gitlab.config.repositories.storages.values.map { |rs| rs.legacy_disk_path } end def user_home diff --git a/lib/system_check/orphans/namespace_check.rb b/lib/system_check/orphans/namespace_check.rb index b8446300f72..b5f443abe06 100644 --- a/lib/system_check/orphans/namespace_check.rb +++ b/lib/system_check/orphans/namespace_check.rb @@ -6,8 +6,8 @@ module SystemCheck def multi_check Gitlab.config.repositories.storages.each do |storage_name, repository_storage| $stdout.puts - $stdout.puts "* Storage: #{storage_name} (#{repository_storage['path']})".color(:yellow) - toplevel_namespace_dirs = disk_namespaces(repository_storage['path']) + $stdout.puts "* Storage: #{storage_name} (#{repository_storage.legacy_disk_path})".color(:yellow) + toplevel_namespace_dirs = disk_namespaces(repository_storage.legacy_disk_path) orphans = (toplevel_namespace_dirs - existing_namespaces) print_orphans(orphans, storage_name) diff --git a/lib/system_check/orphans/repository_check.rb b/lib/system_check/orphans/repository_check.rb index 9b6b2429783..5ef0b93ad08 100644 --- a/lib/system_check/orphans/repository_check.rb +++ b/lib/system_check/orphans/repository_check.rb @@ -6,10 +6,12 @@ module SystemCheck def multi_check Gitlab.config.repositories.storages.each do |storage_name, repository_storage| + storage_path = repository_storage.legacy_disk_path + $stdout.puts - $stdout.puts "* Storage: #{storage_name} (#{repository_storage['path']})".color(:yellow) + $stdout.puts "* Storage: #{storage_name} (#{storage_path})".color(:yellow) - repositories = disk_repositories(repository_storage['path']) + repositories = disk_repositories(storage_path) orphans = (repositories - fetch_repositories(storage_name)) print_orphans(orphans, storage_name) diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 2403f57f05a..abef8cd2bcc 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -61,7 +61,7 @@ namespace :gitlab do puts "Repo base directory exists?" Gitlab.config.repositories.storages.each do |name, repository_storage| - repo_base_path = repository_storage['path'] + repo_base_path = repository_storage.legacy_disk_path print "#{name}... " if File.exist?(repo_base_path) @@ -86,7 +86,7 @@ namespace :gitlab do puts "Repo storage directories are symlinks?" Gitlab.config.repositories.storages.each do |name, repository_storage| - repo_base_path = repository_storage['path'] + repo_base_path = repository_storage.legacy_disk_path print "#{name}... " unless File.exist?(repo_base_path) @@ -110,7 +110,7 @@ namespace :gitlab do puts "Repo paths access is drwxrws---?" Gitlab.config.repositories.storages.each do |name, repository_storage| - repo_base_path = repository_storage['path'] + repo_base_path = repository_storage.legacy_disk_path print "#{name}... " unless File.exist?(repo_base_path) @@ -140,7 +140,7 @@ namespace :gitlab do puts "Repo paths owned by #{gitlab_shell_ssh_user}:root, or #{gitlab_shell_ssh_user}:#{Gitlab.config.gitlab_shell.owner_group}?" Gitlab.config.repositories.storages.each do |name, repository_storage| - repo_base_path = repository_storage['path'] + repo_base_path = repository_storage.legacy_disk_path print "#{name}... " unless File.exist?(repo_base_path) diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake index 2453079911d..d6d15285489 100644 --- a/lib/tasks/gitlab/cleanup.rake +++ b/lib/tasks/gitlab/cleanup.rake @@ -12,7 +12,7 @@ namespace :gitlab do namespaces = Namespace.pluck(:path) namespaces << HASHED_REPOSITORY_NAME # add so that it will be ignored Gitlab.config.repositories.storages.each do |name, repository_storage| - git_base_path = repository_storage['path'] + git_base_path = repository_storage.legacy_disk_path all_dirs = Dir.glob(git_base_path + '/*') puts git_base_path.color(:yellow) @@ -54,7 +54,7 @@ namespace :gitlab do move_suffix = "+orphaned+#{Time.now.to_i}" Gitlab.config.repositories.storages.each do |name, repository_storage| - repo_root = repository_storage['path'] + repo_root = repository_storage.legacy_disk_path # Look for global repos (legacy, depth 1) and normal repos (depth 2) IO.popen(%W(find #{repo_root} -mindepth 1 -maxdepth 2 -name *.git)) do |find| find.each_line do |path| diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake index 45e9a1a1c72..47ed522aec3 100644 --- a/lib/tasks/gitlab/info.rake +++ b/lib/tasks/gitlab/info.rake @@ -68,7 +68,7 @@ namespace :gitlab do puts "Version:\t#{gitlab_shell_version || "unknown".color(:red)}" puts "Repository storage paths:" Gitlab.config.repositories.storages.each do |name, repository_storage| - puts "- #{name}: \t#{repository_storage['path']}" + puts "- #{name}: \t#{repository_storage.legacy_disk_path}" end puts "Hooks:\t\t#{Gitlab.config.gitlab_shell.hooks_path}" puts "Git:\t\t#{Gitlab.config.git.bin_path}" diff --git a/public/robots.txt b/public/robots.txt index 123272a9834..1f9d42f4adc 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -20,6 +20,7 @@ Disallow: /projects/new Disallow: /groups/new Disallow: /groups/*/edit Disallow: /users +Disallow: /help # Global snippets User-Agent: * diff --git a/spec/controllers/projects/pipelines_settings_controller_spec.rb b/spec/controllers/projects/pipelines_settings_controller_spec.rb index 1cc488bef32..913b9bd804a 100644 --- a/spec/controllers/projects/pipelines_settings_controller_spec.rb +++ b/spec/controllers/projects/pipelines_settings_controller_spec.rb @@ -47,10 +47,32 @@ describe Projects::PipelinesSettingsController do expect_any_instance_of(Projects::UpdateService).to receive(:run_auto_devops_pipeline?).and_return(true) end - it 'queues a CreatePipelineWorker' do - expect(CreatePipelineWorker).to receive(:perform_async).with(project.id, user.id, project.default_branch, :web, any_args) + context 'when the project repository is empty' do + it 'sets a warning flash' do + expect(subject).to set_flash[:warning] + end - subject + it 'does not queue a CreatePipelineWorker' do + expect(CreatePipelineWorker).not_to receive(:perform_async).with(project.id, user.id, project.default_branch, :web, any_args) + + subject + end + end + + context 'when the project repository is not empty' do + let(:project) { create(:project, :repository) } + + it 'sets a success flash' do + allow(CreatePipelineWorker).to receive(:perform_async).with(project.id, user.id, project.default_branch, :web, any_args) + + expect(subject).to set_flash[:success] + end + + it 'queues a CreatePipelineWorker' do + expect(CreatePipelineWorker).to receive(:perform_async).with(project.id, user.id, project.default_branch, :web, any_args) + + subject + end end end diff --git a/spec/features/read_only_spec.rb b/spec/features/read_only_spec.rb new file mode 100644 index 00000000000..8bfaf558466 --- /dev/null +++ b/spec/features/read_only_spec.rb @@ -0,0 +1,25 @@ +require 'rails_helper' + +describe 'read-only message' do + set(:user) { create(:user) } + + before do + sign_in(user) + end + + it 'shows read-only banner when database is read-only' do + allow(Gitlab::Database).to receive(:read_only?).and_return(true) + + visit root_dashboard_path + + expect(page).to have_content('You are on a read-only GitLab instance.') + end + + it 'does not show read-only banner when database is able to read-write' do + allow(Gitlab::Database).to receive(:read_only?).and_return(false) + + visit root_dashboard_path + + expect(page).not_to have_content('You are on a read-only GitLab instance.') + end +end diff --git a/spec/initializers/6_validations_spec.rb b/spec/initializers/6_validations_spec.rb index 83283f03940..1dc307ea922 100644 --- a/spec/initializers/6_validations_spec.rb +++ b/spec/initializers/6_validations_spec.rb @@ -15,7 +15,7 @@ describe '6_validations' do describe 'validate_storages_config' do context 'with correct settings' do before do - mock_storages('foo' => { 'path' => 'tmp/tests/paths/a/b/c' }, 'bar' => { 'path' => 'tmp/tests/paths/a/b/d' }) + mock_storages('foo' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c'), 'bar' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/d')) end it 'passes through' do @@ -25,7 +25,7 @@ describe '6_validations' do context 'when one of the settings is incorrect' do before do - mock_storages('foo' => { 'path' => 'tmp/tests/paths/a/b/c', 'failure_count_threshold' => 'not a number' }) + mock_storages('foo' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c', 'failure_count_threshold' => 'not a number')) end it 'throws an error' do @@ -35,7 +35,7 @@ describe '6_validations' do context 'with invalid storage names' do before do - mock_storages('name with spaces' => { 'path' => 'tmp/tests/paths/a/b/c' }) + mock_storages('name with spaces' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c')) end it 'throws an error' do @@ -67,7 +67,7 @@ describe '6_validations' do describe 'validate_storages_paths' do context 'with correct settings' do before do - mock_storages('foo' => { 'path' => 'tmp/tests/paths/a/b/c' }, 'bar' => { 'path' => 'tmp/tests/paths/a/b/d' }) + mock_storages('foo' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c'), 'bar' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/d')) end it 'passes through' do @@ -77,7 +77,7 @@ describe '6_validations' do context 'with nested storage paths' do before do - mock_storages('foo' => { 'path' => 'tmp/tests/paths/a/b/c' }, 'bar' => { 'path' => 'tmp/tests/paths/a/b/c/d' }) + mock_storages('foo' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c'), 'bar' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c/d')) end it 'throws an error' do @@ -87,7 +87,7 @@ describe '6_validations' do context 'with similar but un-nested storage paths' do before do - mock_storages('foo' => { 'path' => 'tmp/tests/paths/a/b/c' }, 'bar' => { 'path' => 'tmp/tests/paths/a/b/c2' }) + mock_storages('foo' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c'), 'bar' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/paths/a/b/c2')) end it 'passes through' do @@ -97,7 +97,7 @@ describe '6_validations' do describe 'inaccessible storage' do before do - mock_storages('foo' => { 'path' => 'tmp/tests/a/path/that/does/not/exist' }) + mock_storages('foo' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/a/path/that/does/not/exist')) end it 'passes through with a warning' do diff --git a/spec/initializers/settings_spec.rb b/spec/initializers/settings_spec.rb index 838ca9fabef..57f5adbbc40 100644 --- a/spec/initializers/settings_spec.rb +++ b/spec/initializers/settings_spec.rb @@ -1,5 +1,5 @@ require 'spec_helper' -require_relative '../../config/initializers/1_settings' +require_relative '../../config/initializers/1_settings' unless defined?(Settings) describe Settings do describe '#ldap' do diff --git a/spec/javascripts/vue_shared/components/modal_spec.js b/spec/javascripts/vue_shared/components/deprecated_modal_spec.js index d01a94c25e5..59d4e549a91 100644 --- a/spec/javascripts/vue_shared/components/modal_spec.js +++ b/spec/javascripts/vue_shared/components/deprecated_modal_spec.js @@ -1,11 +1,11 @@ import $ from 'jquery'; import Vue from 'vue'; -import modal from '~/vue_shared/components/modal.vue'; +import DeprecatedModal from '~/vue_shared/components/deprecated_modal.vue'; import mountComponent from 'spec/helpers/vue_mount_component_helper'; -const modalComponent = Vue.extend(modal); +const modalComponent = Vue.extend(DeprecatedModal); -describe('Modal', () => { +describe('DeprecatedModal', () => { let vm; afterEach(() => { diff --git a/spec/lib/backup/repository_spec.rb b/spec/lib/backup/repository_spec.rb index a9b5ed1112a..03573c304aa 100644 --- a/spec/lib/backup/repository_spec.rb +++ b/spec/lib/backup/repository_spec.rb @@ -33,7 +33,7 @@ describe Backup::Repository do let(:timestamp) { Time.utc(2017, 3, 22) } let(:temp_dirs) do Gitlab.config.repositories.storages.map do |name, storage| - File.join(storage['path'], '..', 'repositories.old.' + timestamp.to_i.to_s) + File.join(storage.legacy_disk_path, '..', 'repositories.old.' + timestamp.to_i.to_s) end end diff --git a/spec/lib/banzai/filter/issuable_state_filter_spec.rb b/spec/lib/banzai/filter/issuable_state_filter_spec.rb index 17347768a49..a5373517ac8 100644 --- a/spec/lib/banzai/filter/issuable_state_filter_spec.rb +++ b/spec/lib/banzai/filter/issuable_state_filter_spec.rb @@ -8,6 +8,7 @@ describe Banzai::Filter::IssuableStateFilter do let(:context) { { current_user: user, issuable_state_filter_enabled: true } } let(:closed_issue) { create_issue(:closed) } let(:project) { create(:project, :public) } + let(:group) { create(:group) } let(:other_project) { create(:project, :public) } def create_link(text, data) @@ -77,6 +78,13 @@ describe Banzai::Filter::IssuableStateFilter do expect(doc.css('a').last.text).to eq("#{closed_issue.to_reference(other_project)} (closed)") end + it 'handles references from group scopes' do + link = create_link(closed_issue.to_reference(other_project), issue: closed_issue.id, reference_type: 'issue') + doc = filter(link, context.merge(project: nil, group: group)) + + expect(doc.css('a').last.text).to eq("#{closed_issue.to_reference(other_project)} (closed)") + end + it 'skips cross project references if the user cannot read cross project' do expect(Ability).to receive(:allowed?).with(user, :read_cross_project) { false } link = create_link(closed_issue.to_reference(other_project), issue: closed_issue.id, reference_type: 'issue') diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb index 5cb1f4deb5f..0dc3705825d 100644 --- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb @@ -54,7 +54,7 @@ describe ::Gitlab::BareRepositoryImport::Repository do context 'hashed storage' do let(:gitlab_shell) { Gitlab::Shell.new } let(:repository_storage) { 'default' } - let(:root_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } + let(:root_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path } let(:hash) { '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' } let(:hashed_path) { "@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b" } let(:repo_path) { File.join(root_path, "#{hashed_path}.git") } diff --git a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb index 4c1ca4349ea..9dcf272d25e 100644 --- a/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb +++ b/spec/lib/gitlab/health_checks/fs_shards_check_spec.rb @@ -26,7 +26,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do let(:storages_paths) do { - default: { path: tmp_dir } + default: Gitlab::GitalyClient::StorageSettings.new('path' => tmp_dir) }.with_indifferent_access end @@ -56,7 +56,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do context 'storage points to not existing folder' do let(:storages_paths) do { - default: { path: 'tmp/this/path/doesnt/exist' } + default: Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/this/path/doesnt/exist') }.with_indifferent_access end @@ -102,7 +102,7 @@ describe Gitlab::HealthChecks::FsShardsCheck do context 'storage points to not existing folder' do let(:storages_paths) do { - default: { path: 'tmp/this/path/doesnt/exist' } + default: Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/this/path/doesnt/exist') }.with_indifferent_access end diff --git a/spec/lib/gitlab/repo_path_spec.rb b/spec/lib/gitlab/repo_path_spec.rb index b67bcc77bd4..f030f371372 100644 --- a/spec/lib/gitlab/repo_path_spec.rb +++ b/spec/lib/gitlab/repo_path_spec.rb @@ -48,8 +48,8 @@ describe ::Gitlab::RepoPath do describe '.strip_storage_path' do before do allow(Gitlab.config.repositories).to receive(:storages).and_return({ - 'storage1' => { 'path' => '/foo' }, - 'storage2' => { 'path' => '/bar' } + 'storage1' => Gitlab::GitalyClient::StorageSettings.new('path' => '/foo'), + 'storage2' => Gitlab::GitalyClient::StorageSettings.new('path' => '/bar') }) end diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb index 14b59c5e945..ea5ce58e34b 100644 --- a/spec/lib/gitlab/shell_spec.rb +++ b/spec/lib/gitlab/shell_spec.rb @@ -405,7 +405,7 @@ describe Gitlab::Shell do describe '#create_repository' do shared_examples '#create_repository' do let(:repository_storage) { 'default' } - let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } + let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path } let(:repo_name) { 'project/path' } let(:created_path) { File.join(repository_storage_path, repo_name + '.git') } @@ -679,7 +679,7 @@ describe Gitlab::Shell do describe 'namespace actions' do subject { described_class.new } - let(:storage_path) { Gitlab.config.repositories.storages.default.path } + let(:storage_path) { Gitlab.config.repositories.storages.default.legacy_disk_path } describe '#add_namespace' do it 'creates a namespace' do diff --git a/spec/migrations/remove_dot_git_from_usernames_spec.rb b/spec/migrations/remove_dot_git_from_usernames_spec.rb index 129374cb38c..3a88a66a476 100644 --- a/spec/migrations/remove_dot_git_from_usernames_spec.rb +++ b/spec/migrations/remove_dot_git_from_usernames_spec.rb @@ -29,7 +29,9 @@ describe RemoveDotGitFromUsernames do update_namespace(user, 'test.git') update_namespace(user2, 'test_git') - storages = { 'default' => 'tmp/tests/custom_repositories' } + default_hash = Gitlab.config.repositories.storages.default.to_h + default_hash['path'] = 'tmp/tests/custom_repositories' + storages = { 'default' => Gitlab::GitalyClient::StorageSettings.new(default_hash) } allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) allow(migration).to receive(:route_exists?).with('test_git').and_return(true) diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index abfc0896a41..d620943693c 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -240,7 +240,7 @@ describe Group do it "is false if avatar is html page" do group.update_attribute(:avatar, 'uploads/avatar.html') - expect(group.avatar_type).to eq(["only images allowed"]) + expect(group.avatar_type).to eq(["file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff"]) end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index ee142718f7e..62e95a622eb 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -305,7 +305,7 @@ describe Namespace do end describe '#rm_dir', 'callback' do - let(:repository_storage_path) { Gitlab.config.repositories.storages.default['path'] } + let(:repository_storage_path) { Gitlab.config.repositories.storages.default.legacy_disk_path } let(:path_in_dir) { File.join(repository_storage_path, namespace.full_path) } let(:deleted_path) { namespace.full_path.gsub(namespace.path, "#{namespace.full_path}+#{namespace.id}+deleted") } let(:deleted_path_in_dir) { File.join(repository_storage_path, deleted_path) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 4cf8d861595..61aadddceb5 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -922,7 +922,7 @@ describe Project do it 'is false if avatar is html page' do project.update_attribute(:avatar, 'uploads/avatar.html') - expect(project.avatar_type).to eq(['only images allowed']) + expect(project.avatar_type).to eq(['file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff']) end end @@ -1101,8 +1101,8 @@ describe Project do before do storages = { - 'default' => { 'path' => 'tmp/tests/repositories' }, - 'picked' => { 'path' => 'tmp/tests/repositories' } + 'default' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/repositories'), + 'picked' => Gitlab::GitalyClient::StorageSettings.new('path' => 'tmp/tests/repositories') } allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 5680eb24985..c61674fff13 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -1222,7 +1222,7 @@ describe User do it 'is false if avatar is html page' do user.update_attribute(:avatar, 'uploads/avatar.html') - expect(user.avatar_type).to eq(['only images allowed']) + expect(user.avatar_type).to eq(['file format is not supported. Please try one of the following supported formats: png, jpg, jpeg, gif, bmp, tiff']) end end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 8471467d2fa..4413c6ef83e 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -153,7 +153,7 @@ describe Projects::CreateService, '#execute' do context 'when another repository already exists on disk' do let(:repository_storage) { 'default' } - let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } + let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path } let(:opts) do { diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index d1011b07db6..0f7c46367d0 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -105,7 +105,7 @@ describe Projects::ForkService do context 'repository already exists' do let(:repository_storage) { 'default' } - let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } + let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path } before do gitlab_shell.create_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}") diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index ce567fe3879..95a6771c59d 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -146,7 +146,7 @@ describe Projects::TransferService do context 'namespace which contains orphan repository with same projects path name' do let(:repository_storage) { 'default' } - let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } + let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path } before do group.add_owner(user) diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index f3f97b6b921..e3b49a6242d 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -190,7 +190,7 @@ describe Projects::UpdateService do context 'when renaming a project' do let(:repository_storage) { 'default' } - let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } + let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path } context 'with legacy storage' do let(:project) { create(:project, :legacy_storage, :repository, creator: user, namespace: user.namespace) } diff --git a/spec/support/stored_repositories.rb b/spec/support/stored_repositories.rb index 52e47ae2d34..21995c89a6e 100644 --- a/spec/support/stored_repositories.rb +++ b/spec/support/stored_repositories.rb @@ -4,7 +4,7 @@ RSpec.configure do |config| end config.before(:all, :broken_storage) do - FileUtils.rm_rf Gitlab.config.repositories.storages.broken['path'] + FileUtils.rm_rf Gitlab.config.repositories.storages.broken.legacy_disk_path end config.before(:each, :broken_storage) do diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb index 9f08c139322..bad1d34df3a 100644 --- a/spec/support/stub_configuration.rb +++ b/spec/support/stub_configuration.rb @@ -50,8 +50,12 @@ module StubConfiguration # Default storage is always required messages['default'] ||= Gitlab.config.repositories.storages.default - messages.each do |storage_name, storage_settings| - storage_settings['path'] = TestEnv.repos_path unless storage_settings.key?('path') + messages.each do |storage_name, storage_hash| + if !storage_hash.key?('path') || storage_hash['path'] == Gitlab::GitalyClient::StorageSettings::Deprecated + storage_hash['path'] = TestEnv.repos_path + end + + messages[storage_name] = Gitlab::GitalyClient::StorageSettings.new(storage_hash.to_h) end allow(Gitlab.config.repositories).to receive(:storages).and_return(Settingslogic.new(messages)) diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 01321989f01..f14e69b1041 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -225,7 +225,7 @@ module TestEnv end def repos_path - Gitlab.config.repositories.storages.default['path'] + Gitlab.config.repositories.storages.default.legacy_disk_path end def backup_path diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 168facd51a6..0d24782f317 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -195,14 +195,23 @@ describe 'gitlab:app namespace rake task' do end context 'multiple repository storages' do - let(:gitaly_address) { Gitlab.config.repositories.storages.default.gitaly_address } + let(:storage_default) do + Gitlab::GitalyClient::StorageSettings.new(@default_storage_hash.merge('path' => 'tmp/tests/default_storage')) + end + let(:test_second_storage) do + Gitlab::GitalyClient::StorageSettings.new(@default_storage_hash.merge('path' => 'tmp/tests/custom_storage')) + end let(:storages) do { - 'default' => { 'path' => Settings.absolute('tmp/tests/default_storage'), 'gitaly_address' => gitaly_address }, - 'test_second_storage' => { 'path' => Settings.absolute('tmp/tests/custom_storage'), 'gitaly_address' => gitaly_address } + 'default' => storage_default, + 'test_second_storage' => test_second_storage } end + before(:all) do + @default_storage_hash = Gitlab.config.repositories.storages.default.to_h + end + before do # We only need a backup of the repositories for this test stub_env('SKIP', 'db,uploads,builds,artifacts,lfs,registry') diff --git a/spec/tasks/gitlab/cleanup_rake_spec.rb b/spec/tasks/gitlab/cleanup_rake_spec.rb index 9e746ceddd6..2bf873c923f 100644 --- a/spec/tasks/gitlab/cleanup_rake_spec.rb +++ b/spec/tasks/gitlab/cleanup_rake_spec.rb @@ -6,13 +6,16 @@ describe 'gitlab:cleanup rake tasks' do end describe 'cleanup' do - let(:gitaly_address) { Gitlab.config.repositories.storages.default.gitaly_address } let(:storages) do { - 'default' => { 'path' => Settings.absolute('tmp/tests/default_storage'), 'gitaly_address' => gitaly_address } + 'default' => Gitlab::GitalyClient::StorageSettings.new(@default_storage_hash.merge('path' => 'tmp/tests/default_storage')) } end + before(:all) do + @default_storage_hash = Gitlab.config.repositories.storages.default.to_h + end + before do FileUtils.mkdir(Settings.absolute('tmp/tests/default_storage')) allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) diff --git a/spec/tasks/gitlab/git_rake_spec.rb b/spec/tasks/gitlab/git_rake_spec.rb index 9aebf7b0b4a..1efaecc63a5 100644 --- a/spec/tasks/gitlab/git_rake_spec.rb +++ b/spec/tasks/gitlab/git_rake_spec.rb @@ -1,10 +1,13 @@ require 'rake_helper' describe 'gitlab:git rake tasks' do + before(:all) do + @default_storage_hash = Gitlab.config.repositories.storages.default.to_h + end + before do Rake.application.rake_require 'tasks/gitlab/git' - - storages = { 'default' => { 'path' => Settings.absolute('tmp/tests/default_storage') } } + storages = { 'default' => Gitlab::GitalyClient::StorageSettings.new(@default_storage_hash.merge('path' => '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) diff --git a/spec/tasks/gitlab/gitaly_rake_spec.rb b/spec/tasks/gitlab/gitaly_rake_spec.rb index 1f4053ff9ad..1e507c0236e 100644 --- a/spec/tasks/gitlab/gitaly_rake_spec.rb +++ b/spec/tasks/gitlab/gitaly_rake_spec.rb @@ -99,14 +99,14 @@ describe 'gitlab:gitaly namespace rake task' do describe 'storage_config' do it 'prints storage configuration in a TOML format' do config = { - 'default' => { + 'default' => Gitlab::GitalyClient::StorageSettings.new( 'path' => '/path/to/default', 'gitaly_address' => 'unix:/path/to/my.socket' - }, - 'nfs_01' => { + ), + 'nfs_01' => Gitlab::GitalyClient::StorageSettings.new( 'path' => '/path/to/nfs_01', 'gitaly_address' => 'unix:/path/to/my.socket' - } + ) } allow(Gitlab.config.repositories).to receive(:storages).and_return(config) allow(Rails.env).to receive(:test?).and_return(false) @@ -134,7 +134,7 @@ describe 'gitlab:gitaly namespace rake task' do parsed_output = TomlRB.parse(expected_output) config.each do |name, params| - expect(parsed_output['storage']).to include({ 'name' => name, 'path' => params['path'] }) + expect(parsed_output['storage']).to include({ 'name' => name, 'path' => params.legacy_disk_path }) end end end diff --git a/spec/tasks/gitlab/shell_rake_spec.rb b/spec/tasks/gitlab/shell_rake_spec.rb index 65155cb044d..4a756c5742d 100644 --- a/spec/tasks/gitlab/shell_rake_spec.rb +++ b/spec/tasks/gitlab/shell_rake_spec.rb @@ -11,7 +11,7 @@ describe 'gitlab:shell rake tasks' do it 'invokes create_hooks task' do expect(Rake::Task['gitlab:shell:create_hooks']).to receive(:invoke) - storages = Gitlab.config.repositories.storages.values.map { |rs| rs['path'] } + storages = Gitlab.config.repositories.storages.values.map(&:legacy_disk_path) expect(Kernel).to receive(:system).with('bin/install', *storages).and_call_original expect(Kernel).to receive(:system).with('bin/compile').and_call_original |