diff options
376 files changed, 8872 insertions, 4481 deletions
diff --git a/.gitlab/ci/rails.gitlab-ci.yml b/.gitlab/ci/rails.gitlab-ci.yml index da4541ab5c4..68280506da2 100644 --- a/.gitlab/ci/rails.gitlab-ci.yml +++ b/.gitlab/ci/rails.gitlab-ci.yml @@ -111,7 +111,7 @@ variables: SETUP_DB: "false" script: - - git fetch https://gitlab.com/gitlab-org/gitlab-ce.git v9.3.0 + - git fetch https://gitlab.com/gitlab-org/gitlab-ce.git v11.11.0 - git checkout -f FETCH_HEAD - sed -i "s/gem 'oj', '~> 2.17.4'//" Gemfile - sed -i "s/gem 'bootsnap', '~> 1.0.0'/gem 'bootsnap'/" Gemfile diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 63b1685feda..698570efb07 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -799,7 +799,6 @@ Style/SelfAssignment: Exclude: - 'app/models/concerns/bulk_member_access_load.rb' - 'app/serializers/base_serializer.rb' - - 'spec/features/projects/clusters/interchangeability_spec.rb' - 'spec/support/import_export/configuration_helper.rb' # Offense count: 50 diff --git a/README.md b/README.md index 133c15a83a7..e64f04cea59 100644 --- a/README.md +++ b/README.md @@ -54,8 +54,6 @@ Just select your operating system, download the respective package (Debian or RP There are various other options to install GitLab, please refer to the [installation page on the GitLab website](https://about.gitlab.com/installation/) for more information. -You can access a new installation with the login **`root`** and password **`5iveL!fe`**, after login you are required to set a unique password. - ## Contributing GitLab is an open source project and we are very happy to accept community contributions. Please refer to [Contributing to GitLab page](https://about.gitlab.com/contributing/) for more details. @@ -125,4 +123,3 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on ## Is it awesome? [These people](https://twitter.com/gitlab/likes) seem to like it. - diff --git a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue b/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue deleted file mode 100644 index 6c256fa6736..00000000000 --- a/app/assets/javascripts/cycle_analytics/components/stage_plan_component.vue +++ /dev/null @@ -1,59 +0,0 @@ -<script> -import userAvatarImage from '../../vue_shared/components/user_avatar/user_avatar_image.vue'; -import iconCommit from '../svg/icon_commit.svg'; -import limitWarning from './limit_warning_component.vue'; -import totalTime from './total_time_component.vue'; - -export default { - components: { - userAvatarImage, - totalTime, - limitWarning, - }, - props: { - items: { - type: Array, - default: () => [], - }, - stage: { - type: Object, - default: () => ({}), - }, - }, - computed: { - iconCommit() { - return iconCommit; - }, - }, -}; -</script> -<template> - <div> - <div class="events-description"> - {{ stage.description }} - <limit-warning :count="items.length" /> - </div> - <ul class="stage-event-list"> - <li v-for="(commit, i) in items" :key="i" class="stage-event-item"> - <div class="item-details item-conmmit-component"> - <!-- FIXME: Pass an alt attribute here for accessibility --> - <user-avatar-image :img-src="commit.author.avatarUrl" /> - <h5 class="item-title commit-title"> - <a :href="commit.commitUrl"> {{ commit.title }} </a> - </h5> - <span> - {{ s__('FirstPushedBy|First') }} <span class="commit-icon" v-html="iconCommit"> </span> - <a :href="commit.commitUrl" class="commit-hash-link commit-sha">{{ - commit.shortSha - }}</a> - {{ s__('FirstPushedBy|pushed by') }} - <a :href="commit.author.webUrl" class="commit-author-link"> - {{ commit.author.name }} - </a> - </span> - </div> - <div class="item-time"><total-time :time="commit.totalTime" /></div> - </li> - </ul> - </div> -</template> diff --git a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js index 3f0a9f2602c..b56e08175cc 100644 --- a/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js +++ b/app/assets/javascripts/cycle_analytics/cycle_analytics_bundle.js @@ -5,7 +5,6 @@ import Flash from '../flash'; import Translate from '../vue_shared/translate'; import banner from './components/banner.vue'; import stageCodeComponent from './components/stage_code_component.vue'; -import stagePlanComponent from './components/stage_plan_component.vue'; import stageComponent from './components/stage_component.vue'; import stageReviewComponent from './components/stage_review_component.vue'; import stageStagingComponent from './components/stage_staging_component.vue'; @@ -26,7 +25,7 @@ export default () => { components: { banner, 'stage-issue-component': stageComponent, - 'stage-plan-component': stagePlanComponent, + 'stage-plan-component': stageComponent, 'stage-code-component': stageCodeComponent, 'stage-test-component': stageTestComponent, 'stage-review-component': stageReviewComponent, diff --git a/app/assets/javascripts/filtered_search/constants.js b/app/assets/javascripts/filtered_search/constants.js new file mode 100644 index 00000000000..8ca0e94cfeb --- /dev/null +++ b/app/assets/javascripts/filtered_search/constants.js @@ -0,0 +1,2 @@ +/* eslint-disable import/prefer-default-export */ +export const TOKEN_TYPES = ['author', 'assignee']; diff --git a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js index 315cd6f64da..7f6457242ef 100644 --- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js +++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js @@ -1,4 +1,4 @@ -import VisualTokenValue from 'ee_else_ce/filtered_search/visual_token_value'; +import VisualTokenValue from './visual_token_value'; import { objectToQueryString } from '~/lib/utils/common_utils'; import FilteredSearchContainer from './container'; diff --git a/app/assets/javascripts/filtered_search/visual_token_value.js b/app/assets/javascripts/filtered_search/visual_token_value.js index a54b445fb0a..018207541b3 100644 --- a/app/assets/javascripts/filtered_search/visual_token_value.js +++ b/app/assets/javascripts/filtered_search/visual_token_value.js @@ -6,6 +6,7 @@ import DropdownUtils from '~/filtered_search/dropdown_utils'; import Flash from '~/flash'; import UsersCache from '~/lib/utils/users_cache'; import { __ } from '~/locale'; +import { TOKEN_TYPES } from 'ee_else_ce/filtered_search/constants'; export default class VisualTokenValue { constructor(tokenValue, tokenType) { @@ -22,7 +23,7 @@ export default class VisualTokenValue { if (tokenType === 'label') { this.updateLabelTokenColor(tokenValueContainer); - } else if (tokenType === 'author' || tokenType === 'assignee') { + } else if (TOKEN_TYPES.includes(tokenType)) { this.updateUserTokenAppearance(tokenValueContainer, tokenValueElement); } else if (tokenType === 'my-reaction') { this.updateEmojiTokenAppearance(tokenValueContainer, tokenValueElement); diff --git a/app/assets/javascripts/jobs/components/job_log.vue b/app/assets/javascripts/jobs/components/job_log.vue index 92e20e92d66..d611b370ab9 100644 --- a/app/assets/javascripts/jobs/components/job_log.vue +++ b/app/assets/javascripts/jobs/components/job_log.vue @@ -17,10 +17,19 @@ export default { ...mapState(['isScrolledToBottomBeforeReceivingTrace']), }, updated() { - this.$nextTick(() => this.handleScrollDown()); + this.$nextTick(() => { + this.handleScrollDown(); + this.handleCollapsibleRows(); + }); }, mounted() { - this.$nextTick(() => this.handleScrollDown()); + this.$nextTick(() => { + this.handleScrollDown(); + this.handleCollapsibleRows(); + }); + }, + destroyed() { + this.removeEventListener(); }, methods: { ...mapActions(['scrollBottom']), @@ -38,21 +47,45 @@ export default { }, 0); } }, + removeEventListener() { + this.$el + .querySelectorAll('.js-section-start') + .forEach(el => el.removeEventListener('click', this.handleSectionClick)); + }, + /** + * The collapsible rows are sent in HTML from the backend + * We need tos add a onclick handler for the divs that match `.js-section-start` + * + */ + handleCollapsibleRows() { + this.$el + .querySelectorAll('.js-section-start') + .forEach(el => el.addEventListener('click', this.handleSectionClick)); + }, + /** + * On click, we toggle the hidden class of + * all the rows that match the `data-section` selector + */ + handleSectionClick(evt) { + const clickedArrow = evt.currentTarget; + // toggle the arrow class + clickedArrow.classList.toggle('fa-caret-right'); + clickedArrow.classList.toggle('fa-caret-down'); + + const { section } = clickedArrow.dataset; + const sibilings = this.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`); + + sibilings.forEach(row => row.classList.toggle('hidden')); + }, }, }; </script> <template> <pre class="js-build-trace build-trace qa-build-trace"> - <code - class="bash" - v-html="trace" - > + <code class="bash" v-html="trace"> </code> - <div - v-if="!isComplete" - class="js-log-animation build-loader-animation" - > + <div v-if="!isComplete" class="js-log-animation build-loader-animation"> <div class="dot"></div> <div class="dot"></div> <div class="dot"></div> diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue index cb073a9b04d..6e92b599b0a 100644 --- a/app/assets/javascripts/jobs/components/stages_dropdown.vue +++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue @@ -2,7 +2,6 @@ import _ from 'underscore'; import { GlLink } from '@gitlab/ui'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; -import PipelineLink from '~/vue_shared/components/ci_pipeline_link.vue'; import Icon from '~/vue_shared/components/icon.vue'; export default { @@ -10,7 +9,6 @@ export default { CiIcon, Icon, GlLink, - PipelineLink, }, props: { pipeline: { @@ -50,12 +48,9 @@ export default { <ci-icon :status="pipeline.details.status" class="vertical-align-middle" /> <span class="font-weight-bold">{{ s__('Job|Pipeline') }}</span> - <pipeline-link - :href="pipeline.path" - :pipeline-id="pipeline.id" - :pipeline-iid="pipeline.iid" - class="js-pipeline-path link-commit qa-pipeline-path" - /> + <gl-link :href="pipeline.path" class="js-pipeline-path link-commit qa-pipeline-path" + >#{{ pipeline.id }}</gl-link + > <template v-if="hasRef"> {{ s__('Job|for') }} diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue index f3a71ee434c..b2e365e5cde 100644 --- a/app/assets/javascripts/pipelines/components/header_component.vue +++ b/app/assets/javascripts/pipelines/components/header_component.vue @@ -83,8 +83,6 @@ export default { v-if="shouldRenderContent" :status="status" :item-id="pipeline.id" - :item-iid="pipeline.iid" - :item-id-tooltip="__('Pipeline ID (IID)')" :time="pipeline.created_at" :user="pipeline.user" :actions="actions" diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue index 00c02e15562..c41ecab1294 100644 --- a/app/assets/javascripts/pipelines/components/pipeline_url.vue +++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue @@ -2,7 +2,6 @@ import { GlLink, GlTooltipDirective } from '@gitlab/ui'; import _ from 'underscore'; import { __, sprintf } from '~/locale'; -import PipelineLink from '~/vue_shared/components/ci_pipeline_link.vue'; import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue'; import popover from '~/vue_shared/directives/popover'; @@ -20,7 +19,6 @@ export default { components: { UserAvatarLink, GlLink, - PipelineLink, }, directives: { GlTooltip: GlTooltipDirective, @@ -61,13 +59,10 @@ export default { }; </script> <template> - <div class="table-section section-10 d-none d-sm-none d-md-block pipeline-tags section-wrap"> - <pipeline-link - :href="pipeline.path" - :pipeline-id="pipeline.id" - :pipeline-iid="pipeline.iid" - class="js-pipeline-url-link" - /> + <div class="table-section section-10 d-none d-sm-none d-md-block pipeline-tags"> + <gl-link :href="pipeline.path" class="js-pipeline-url-link"> + <span class="pipeline-id">#{{ pipeline.id }}</span> + </gl-link> <div class="label-container"> <span v-if="pipeline.flags.latest" diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue index 1e4dfe76b26..f535b2ae9f2 100644 --- a/app/assets/javascripts/registry/components/table_registry.vue +++ b/app/assets/javascripts/registry/components/table_registry.vue @@ -64,7 +64,7 @@ export default { <th>{{ s__('ContainerRegistry|Tag') }}</th> <th>{{ s__('ContainerRegistry|Tag ID') }}</th> <th>{{ s__('ContainerRegistry|Size') }}</th> - <th>{{ s__('ContainerRegistry|Created') }}</th> + <th>{{ s__('ContainerRegistry|Last Updated') }}</th> <th></th> </tr> </thead> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue index abe5bdd2901..34cdb70ce14 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/deployment.vue @@ -49,6 +49,7 @@ export default { required: false, default: () => ({ sourceProjectId: '', + sourceProjectPath: '', mergeRequestId: '', appUrl: '', }), @@ -184,11 +185,6 @@ export default { :link="deploymentExternalUrl" :css-class="`deploy-link js-deploy-url inline ${slotProps.className}`" /> - <visual-review-app-link - v-if="showVisualReviewApp" - :link="deploymentExternalUrl" - :app-metadata="visualReviewAppMeta" - /> </template> <template slot="result" slot-scope="slotProps"> @@ -213,12 +209,12 @@ export default { :link="deploymentExternalUrl" css-class="js-deploy-url js-deploy-url-feature-flag deploy-link btn btn-default btn-sm inline" /> - <visual-review-app-link - v-if="showVisualReviewApp" - :link="deploymentExternalUrl" - :app-metadata="visualReviewAppMeta" - /> </template> + <visual-review-app-link + v-if="showVisualReviewApp" + :link="deploymentExternalUrl" + :app-metadata="visualReviewAppMeta" + /> </template> <span v-if="deployment.stop_url" diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue index c377c16fb13..f5fa68308bc 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue @@ -5,7 +5,6 @@ import { sprintf, __ } from '~/locale'; import PipelineStage from '~/pipelines/components/stage.vue'; import CiIcon from '~/vue_shared/components/ci_icon.vue'; import Icon from '~/vue_shared/components/icon.vue'; -import PipelineLink from '~/vue_shared/components/ci_pipeline_link.vue'; import TooltipOnTruncate from '~/vue_shared/components/tooltip_on_truncate.vue'; import mrWidgetPipelineMixin from 'ee_else_ce/vue_merge_request_widget/mixins/mr_widget_pipeline'; @@ -17,7 +16,6 @@ export default { Icon, TooltipOnTruncate, GlLink, - PipelineLink, LinkedPipelinesMiniList: () => import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'), }, @@ -114,12 +112,9 @@ export default { <div class="media-body"> <div class="font-weight-bold js-pipeline-info-container"> {{ s__('Pipeline|Pipeline') }} - <pipeline-link - :href="pipeline.path" - :pipeline-id="pipeline.id" - :pipeline-iid="pipeline.iid" - class="pipeline-id pipeline-iid font-weight-normal" - /> + <gl-link :href="pipeline.path" class="pipeline-id font-weight-normal pipeline-number" + >#{{ pipeline.id }}</gl-link + > {{ pipeline.details.status.label }} <template v-if="hasCommitInfo"> {{ s__('Pipeline|for') }} diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue index 03a15ba81ed..17ac8ada32d 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue @@ -1,4 +1,5 @@ <script> +import _ from 'underscore'; import Deployment from './deployment.vue'; import MrWidgetContainer from './mr_widget_container.vue'; import MrWidgetPipeline from './mr_widget_pipeline.vue'; @@ -17,6 +18,8 @@ export default { Deployment, MrWidgetContainer, MrWidgetPipeline, + MergeTrainInfo: () => + import('ee_component/vue_merge_request_widget/components/merge_train_info.vue'), }, props: { mr: { @@ -50,6 +53,7 @@ export default { appUrl: this.mr.appUrl, mergeRequestId: this.mr.iid, sourceProjectId: this.mr.sourceProjectId, + sourceProjectPath: this.mr.sourceProjectFullPath, }; }, pipeline() { @@ -58,6 +62,9 @@ export default { showVisualReviewAppLink() { return Boolean(this.mr.visualReviewFF && this.mr.visualReviewAppAvailable); }, + showMergeTrainInfo() { + return _.isNumber(this.mr.mergeTrainIndex); + }, }, }; </script> @@ -79,10 +86,15 @@ export default { :class="deploymentClass" :deployment="deployment" :show-metrics="hasDeploymentMetrics" - :show-visual-review-app="true" + :show-visual-review-app="showVisualReviewAppLink" :visual-review-app-meta="visualReviewAppMeta" /> </div> + <merge-train-info + v-if="showMergeTrainInfo" + class="mr-widget-extension" + :merge-train-index="mr.mergeTrainIndex" + /> </template> </mr-widget-container> </template> diff --git a/app/assets/javascripts/vue_shared/components/ci_pipeline_link.vue b/app/assets/javascripts/vue_shared/components/ci_pipeline_link.vue deleted file mode 100644 index eae4c06467c..00000000000 --- a/app/assets/javascripts/vue_shared/components/ci_pipeline_link.vue +++ /dev/null @@ -1,32 +0,0 @@ -<script> -import { GlLink, GlTooltipDirective } from '@gitlab/ui'; - -export default { - components: { - GlLink, - }, - directives: { - GlTooltip: GlTooltipDirective, - }, - props: { - href: { - type: String, - required: true, - }, - pipelineId: { - type: Number, - required: true, - }, - pipelineIid: { - type: Number, - required: true, - }, - }, -}; -</script> -<template> - <gl-link v-gl-tooltip :href="href" :title="__('Pipeline ID (IID)')"> - <span class="pipeline-id">#{{ pipelineId }}</span> - <span class="pipeline-iid">(#{{ pipelineIid }})</span> - </gl-link> -</template> diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue index 0bac63b1062..3f45dc7853b 100644 --- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue +++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue @@ -37,16 +37,6 @@ export default { type: Number, required: true, }, - itemIid: { - type: Number, - required: false, - default: null, - }, - itemIdTooltip: { - type: String, - required: false, - default: '', - }, time: { type: String, required: true, @@ -95,12 +85,7 @@ export default { <section class="header-main-content"> <ci-icon-badge :status="status" /> - <strong v-gl-tooltip :title="itemIdTooltip"> - {{ itemName }} #{{ itemId }} - <template v-if="itemIid" - >(#{{ itemIid }})</template - > - </strong> + <strong> {{ itemName }} #{{ itemId }} </strong> <template v-if="shouldRenderTriggeredLabel"> triggered @@ -111,8 +96,9 @@ export default { <timeago-tooltip :time="time" /> + by + <template v-if="user"> - by <gl-link v-gl-tooltip :href="user.path" diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 2a601afff53..821e6691fe4 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -248,14 +248,24 @@ label { .gl-form-checkbox { align-items: baseline; + margin-right: 1rem; + margin-bottom: 0.25rem; + + .form-check-input { + margin-right: 0; + } + + .form-check-label { + padding-left: $gl-padding-8; + } &.form-check-inline .form-check-input { align-self: flex-start; - margin-right: $gl-padding-8; height: 1.5 * $gl-font-size; } - .help-text { - margin-bottom: 0; + .form-check-input:disabled, + .form-check-input:disabled ~ .form-check-label { + cursor: not-allowed; } } diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss index 6fc742871e7..6e98908eeed 100644 --- a/app/assets/stylesheets/pages/builds.scss +++ b/app/assets/stylesheets/pages/builds.scss @@ -124,6 +124,10 @@ float: left; padding-left: $gl-padding-8; } + + .section-header ~ .section.line { + margin-left: $gl-padding; + } } .build-header { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 8cb3fab74e0..3917937f4af 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -904,7 +904,8 @@ margin-right: -5px; } -.deploy-heading { +.deploy-heading, +.merge-train-info { @include media-breakpoint-up(md) { padding: $gl-padding-8 $gl-padding; } diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index 0a9c56f5625..3b62121eb0d 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -340,6 +340,11 @@ .deprecated-service { cursor: default; + + a { + font-weight: $gl-font-weight-bold; + color: $white-light; + } } .personal-access-tokens-never-expires-label { diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb index 80ee7c35906..ec8077d18e3 100644 --- a/app/controllers/clusters/clusters_controller.rb +++ b/app/controllers/clusters/clusters_controller.rb @@ -128,6 +128,7 @@ class Clusters::ClustersController < Clusters::BaseController :enabled, :name, :environment_scope, + :managed, :base_domain, platform_kubernetes_attributes: [ :api_url, @@ -140,6 +141,7 @@ class Clusters::ClustersController < Clusters::BaseController params.require(:cluster).permit( :enabled, :environment_scope, + :managed, :base_domain, platform_kubernetes_attributes: [ :namespace diff --git a/app/controllers/projects/registry/tags_controller.rb b/app/controllers/projects/registry/tags_controller.rb index 567d750caae..bf1d8d8b5fc 100644 --- a/app/controllers/projects/registry/tags_controller.rb +++ b/app/controllers/projects/registry/tags_controller.rb @@ -3,7 +3,7 @@ module Projects module Registry class TagsController < ::Projects::Registry::ApplicationController - before_action :authorize_update_container_image!, only: [:destroy] + before_action :authorize_destroy_container_image!, only: [:destroy] def index respond_to do |format| diff --git a/app/graphql/types/issue_type.rb b/app/graphql/types/issue_type.rb index c762aa69e43..f2365499eee 100644 --- a/app/graphql/types/issue_type.rb +++ b/app/graphql/types/issue_type.rb @@ -51,5 +51,7 @@ module Types field :created_at, Types::TimeType, null: false field :updated_at, Types::TimeType, null: false + + field :task_completion_status, Types::TaskCompletionStatus, null: false end end diff --git a/app/graphql/types/merge_request_type.rb b/app/graphql/types/merge_request_type.rb index 662503d447b..dac4c24cf10 100644 --- a/app/graphql/types/merge_request_type.rb +++ b/app/graphql/types/merge_request_type.rb @@ -55,5 +55,7 @@ module Types field :head_pipeline, Types::Ci::PipelineType, null: true, method: :actual_head_pipeline field :pipelines, Types::Ci::PipelineType.connection_type, resolver: Resolvers::MergeRequestPipelinesResolver + + field :task_completion_status, Types::TaskCompletionStatus, null: false end end diff --git a/app/graphql/types/task_completion_status.rb b/app/graphql/types/task_completion_status.rb new file mode 100644 index 00000000000..c289802509d --- /dev/null +++ b/app/graphql/types/task_completion_status.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Types + class TaskCompletionStatus < BaseObject + graphql_name 'TaskCompletionStatus' + description 'Completion status of tasks' + + field :count, GraphQL::INT_TYPE, null: false + field :completed_count, GraphQL::INT_TYPE, null: false + end +end diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb index d4b50b7ecfb..01ccf163b45 100644 --- a/app/helpers/services_helper.rb +++ b/app/helpers/services_helper.rb @@ -39,7 +39,7 @@ module ServicesHelper end def disable_fields_service?(service) - !current_controller?("admin/services") && service.deprecated? + service.is_a?(KubernetesService) || (!current_controller?("admin/services") && service.deprecated?) end extend self diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb index ccc877fb924..8c044c86c47 100644 --- a/app/models/clusters/cluster.rb +++ b/app/models/clusters/cluster.rb @@ -193,21 +193,40 @@ module Clusters platform_kubernetes.kubeclient if kubernetes? end + ## + # This is subtly different to #find_or_initialize_kubernetes_namespace_for_project + # below because it will ignore any namespaces that have not got a service account + # token. This provides a guarantee that any namespace selected here can be used + # for cluster operations - a namespace needs to have a service account configured + # before it it can be used. + # + # This is used for selecting a namespace to use when querying a cluster, or + # generating variables to pass to CI. def kubernetes_namespace_for(project) - find_or_initialize_kubernetes_namespace_for_project(project).namespace + find_or_initialize_kubernetes_namespace_for_project( + project, scope: kubernetes_namespaces.has_service_account_token + ).namespace end - def find_or_initialize_kubernetes_namespace_for_project(project) + ## + # This is subtly different to #kubernetes_namespace_for because it will include + # namespaces that have yet to receive a service account token. This allows + # the namespace configuration process to be repeatable - if a namespace has + # already been created without a token we don't need to create another + # record entirely, just set the token on the pre-existing namespace. + # + # This is used for configuring cluster namespaces. + def find_or_initialize_kubernetes_namespace_for_project(project, scope: kubernetes_namespaces) attributes = { project: project } attributes[:cluster_project] = cluster_project if project_type? - kubernetes_namespaces.find_or_initialize_by(attributes).tap do |namespace| + scope.find_or_initialize_by(attributes).tap do |namespace| namespace.set_defaults end end def allow_user_defined_namespace? - project_type? + project_type? || !managed? end def kube_ingress_domain diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb index 8e06156c73d..272861cacf0 100644 --- a/app/models/clusters/platforms/kubernetes.rb +++ b/app/models/clusters/platforms/kubernetes.rb @@ -80,9 +80,18 @@ module Clusters .append(key: 'KUBE_CA_PEM_FILE', value: ca_pem, file: true) end - if kubernetes_namespace = cluster.kubernetes_namespaces.has_service_account_token.find_by(project: project) + if !cluster.managed? + project_namespace = namespace.presence || "#{project.path}-#{project.id}".downcase + + variables + .append(key: 'KUBE_URL', value: api_url) + .append(key: 'KUBE_TOKEN', value: token, public: false, masked: true) + .append(key: 'KUBE_NAMESPACE', value: project_namespace) + .append(key: 'KUBECONFIG', value: kubeconfig(project_namespace), public: false, file: true) + + elsif kubernetes_namespace = cluster.kubernetes_namespaces.has_service_account_token.find_by(project: project) variables.concat(kubernetes_namespace.predefined_variables) - elsif cluster.project_type? || !cluster.managed? + elsif cluster.project_type? # As of 11.11 a user can create a cluster that they manage themselves, # which replicates the existing project-level cluster behaviour. # Once we have marked all project-level clusters that make use of this diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb index 5245dbc8d15..79a376ff0fd 100644 --- a/app/models/lfs_object.rb +++ b/app/models/lfs_object.rb @@ -5,7 +5,7 @@ class LfsObject < ApplicationRecord include ObjectStorage::BackgroundMove has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent - has_many :projects, through: :lfs_objects_projects + has_many :projects, -> { distinct }, through: :lfs_objects_projects scope :with_files_stored_locally, -> { where(file_store: LfsObjectUploader::Store::LOCAL) } diff --git a/app/models/lfs_objects_project.rb b/app/models/lfs_objects_project.rb index f9afb18c1d7..e45c56b6394 100644 --- a/app/models/lfs_objects_project.rb +++ b/app/models/lfs_objects_project.rb @@ -5,11 +5,17 @@ class LfsObjectsProject < ApplicationRecord belongs_to :lfs_object validates :lfs_object_id, presence: true - validates :lfs_object_id, uniqueness: { scope: [:project_id], message: "already exists in project" } + validates :lfs_object_id, uniqueness: { scope: [:project_id, :repository_type], message: "already exists in repository" } validates :project_id, presence: true after_commit :update_project_statistics, on: [:create, :destroy] + enum repository_type: { + project: 0, + wiki: 1, + design: 2 ## EE-specific + } + private def update_project_statistics diff --git a/app/models/project.rb b/app/models/project.rb index 9d17d68eee2..351d08eaf63 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -72,7 +72,6 @@ class Project < ApplicationRecord delegate :no_import?, to: :import_state, allow_nil: true default_value_for :archived, false - default_value_for(:visibility_level) { Gitlab::CurrentSettings.default_project_visibility } default_value_for :resolve_outdated_diff_discussions, false default_value_for :container_registry_enabled, gitlab_config_features.container_registry default_value_for(:repository_storage) { Gitlab::CurrentSettings.pick_repository_storage } @@ -223,7 +222,7 @@ class Project < ApplicationRecord has_many :starrers, through: :users_star_projects, source: :user has_many :releases has_many :lfs_objects_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent - has_many :lfs_objects, through: :lfs_objects_projects + has_many :lfs_objects, -> { distinct }, through: :lfs_objects_projects has_many :lfs_file_locks has_many :project_group_links has_many :invited_groups, through: :project_group_links, source: :group @@ -613,6 +612,23 @@ class Project < ApplicationRecord end end + def initialize(attributes = {}) + # We can't use default_value_for because the database has a default + # value of 0 for visibility_level. If someone attempts to create a + # private project, default_value_for will assume that the + # visibility_level hasn't changed and will use the application + # setting default, which could be internal or public. For projects + # inside a private group, those levels are invalid. + # + # To fix the problem, we assign the actual default in the application if + # no explicit visibility has been initialized. + unless visibility_attribute_present?(attributes) + attributes[:visibility_level] = Gitlab::CurrentSettings.default_project_visibility + end + + super + end + def all_pipelines if builds_enabled? super diff --git a/app/models/project_services/data_fields.rb b/app/models/project_services/data_fields.rb new file mode 100644 index 00000000000..438d85098c8 --- /dev/null +++ b/app/models/project_services/data_fields.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +module DataFields + extend ActiveSupport::Concern + + included do + has_one :issue_tracker_data + has_one :jira_tracker_data + end +end diff --git a/app/models/project_services/deployment_service.rb b/app/models/project_services/deployment_service.rb deleted file mode 100644 index 80aa2101509..00000000000 --- a/app/models/project_services/deployment_service.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -# Base class for deployment services -# -# These services integrate with a deployment solution like Kubernetes/OpenShift, -# Mesosphere, etc, to provide additional features to environments. -class DeploymentService < Service - default_value_for :category, 'deployment' - - def self.supported_events - %w() - end - - def predefined_variables(project:) - [] - end - - # Environments may have a number of terminals. Should return an array of - # hashes describing them, e.g.: - # - # [{ - # :selectors => {"a" => "b", "foo" => "bar"}, - # :url => "wss://external.example.com/exec", - # :headers => {"Authorization" => "Token xxx"}, - # :subprotocols => ["foo"], - # :ca_pem => "----BEGIN CERTIFICATE...", # optional - # :created_at => Time.now.utc - # }] - # - # Selectors should be a set of values that uniquely identify a particular - # terminal - def terminals(environment) - raise NotImplementedError - end - - def can_test? - false - end -end diff --git a/app/models/project_services/issue_tracker_data.rb b/app/models/project_services/issue_tracker_data.rb new file mode 100644 index 00000000000..2c1d28ed421 --- /dev/null +++ b/app/models/project_services/issue_tracker_data.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +class IssueTrackerData < ApplicationRecord + belongs_to :service + + delegate :activated?, to: :service, allow_nil: true + + validates :service, presence: true + validates :project_url, presence: true, public_url: { enforce_sanitization: true }, if: :activated? + validates :issues_url, presence: true, public_url: { enforce_sanitization: true }, if: :activated? + validates :new_issue_url, public_url: { enforce_sanitization: true }, if: :activated? + + def self.encryption_options + { + key: Settings.attr_encrypted_db_key_base_32, + encode: true, + mode: :per_attribute_iv, + algorithm: 'aes-256-gcm' + } + end + + attr_encrypted :project_url, encryption_options + attr_encrypted :issues_url, encryption_options + attr_encrypted :new_issue_url, encryption_options +end diff --git a/app/models/project_services/jira_tracker_data.rb b/app/models/project_services/jira_tracker_data.rb new file mode 100644 index 00000000000..4f528e3d81b --- /dev/null +++ b/app/models/project_services/jira_tracker_data.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +class JiraTrackerData < ApplicationRecord + belongs_to :service + + delegate :activated?, to: :service, allow_nil: true + + validates :service, presence: true + validates :url, public_url: { enforce_sanitization: true }, presence: true, if: :activated? + validates :api_url, public_url: { enforce_sanitization: true }, allow_blank: true + validates :username, presence: true, if: :activated? + validates :password, presence: true, if: :activated? + validates :jira_issue_transition_id, + format: { with: Gitlab::Regex.jira_transition_id_regex, message: s_("JiraService|transition ids can have only numbers which can be split with , or ;") }, + allow_blank: true + + def self.encryption_options + { + key: Settings.attr_encrypted_db_key_base_32, + encode: true, + mode: :per_attribute_iv, + algorithm: 'aes-256-gcm' + } + end + + attr_encrypted :url, encryption_options + attr_encrypted :api_url, encryption_options + attr_encrypted :username, encryption_options + attr_encrypted :password, encryption_options +end diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index aa6b4aa1d5e..edf7e886e77 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -5,10 +5,12 @@ # We'll move this class to Clusters::Platforms::Kubernetes, which contains exactly the same logic. # After we've migrated data, we'll remove KubernetesService. This would happen in a few months. # If you're modyfiyng this class, please note that you should update the same change in Clusters::Platforms::Kubernetes. -class KubernetesService < DeploymentService +class KubernetesService < Service include Gitlab::Kubernetes include ReactiveCaching + default_value_for :category, 'deployment' + self.reactive_cache_key = ->(service) { [service.class.model_name.singular, service.project_id] } # Namespace defaults to the project path, but can be overridden in case that @@ -32,7 +34,10 @@ class KubernetesService < DeploymentService before_validation :enforce_namespace_to_lower_case - validate :deprecation_validation, unless: :template? + attr_accessor :skip_deprecation_validation + + validate :deprecation_validation, unless: :skip_deprecation_validation + validates :namespace, allow_blank: true, length: 1..63, @@ -44,6 +49,14 @@ class KubernetesService < DeploymentService after_save :clear_reactive_cache! + def self.supported_events + %w() + end + + def can_test? + false + end + def initialize_properties self.properties = {} if properties.nil? end @@ -56,11 +69,6 @@ class KubernetesService < DeploymentService 'Kubernetes / OpenShift integration' end - def help - 'To enable terminal access to Kubernetes environments, label your ' \ - 'deployments with `app=$CI_ENVIRONMENT_SLUG`' - end - def self.to_param 'kubernetes' end @@ -153,14 +161,25 @@ class KubernetesService < DeploymentService end def deprecated? - !active + true + end + + def editable? + false end def deprecation_message - content = _("Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page") % { - deprecated_message_content: deprecated_message_content, - url: Gitlab::Routing.url_helpers.project_clusters_path(project) - } + content = if project + _("Kubernetes service integration has been deprecated. %{deprecated_message_content} your Kubernetes clusters using the new <a href=\"%{url}\"/>Kubernetes Clusters</a> page") % { + deprecated_message_content: deprecated_message_content, + url: Gitlab::Routing.url_helpers.project_clusters_path(project) + } + else + _("The instance-level Kubernetes service integration is deprecated. Your data has been migrated to an <a href=\"%{url}\"/>instance-level cluster</a>.") % { + url: Gitlab::Routing.url_helpers.admin_clusters_path + } + end + content.html_safe end @@ -243,10 +262,6 @@ class KubernetesService < DeploymentService end def deprecated_message_content - if active? - _("Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure") - else - _("Fields on this page are now uneditable, you can configure") - end + _("Fields on this page are now uneditable, you can configure") end end diff --git a/app/models/project_services/mock_deployment_service.rb b/app/models/project_services/mock_deployment_service.rb index 7ab1687f8ba..1103cb11e73 100644 --- a/app/models/project_services/mock_deployment_service.rb +++ b/app/models/project_services/mock_deployment_service.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true -class MockDeploymentService < DeploymentService +class MockDeploymentService < Service + default_value_for :category, 'deployment' + def title 'Mock deployment' end @@ -17,4 +19,16 @@ class MockDeploymentService < DeploymentService def terminals(environment) [] end + + def self.supported_events + %w() + end + + def predefined_variables(project:) + [] + end + + def can_test? + false + end end diff --git a/app/models/service.rb b/app/models/service.rb index 9896aa12e90..40033003f3b 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -6,6 +6,7 @@ class Service < ApplicationRecord include Sortable include Importable include ProjectServicesLoggable + include DataFields serialize :properties, JSON # rubocop:disable Cop/ActiveRecordSerialize @@ -119,7 +120,7 @@ class Service < ApplicationRecord end def self.event_names - self.supported_events.map { |event| "#{event}_events" } + self.supported_events.map { |event| ServicesHelper.service_event_field_name(event) } end def event_field(event) @@ -151,7 +152,7 @@ class Service < ApplicationRecord end def self.supported_events - %w(push tag_push issue confidential_issue merge_request wiki_page) + %w(commit push tag_push issue confidential_issue merge_request wiki_page) end def execute(data) diff --git a/app/policies/base_policy.rb b/app/policies/base_policy.rb index 5dd2279ef99..82bf9bf8bf6 100644 --- a/app/policies/base_policy.rb +++ b/app/policies/base_policy.rb @@ -7,6 +7,10 @@ class BasePolicy < DeclarativePolicy::Base with_options scope: :user, score: 0 condition(:admin) { @user&.admin? } + desc "User is blocked" + with_options scope: :user, score: 0 + condition(:blocked) { @user&.blocked? } + desc "User has access to all private groups & projects" with_options scope: :user, score: 0 condition(:full_private_access) { @user&.full_private_access? } diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb index e85397422e6..134de1c9ace 100644 --- a/app/policies/global_policy.rb +++ b/app/policies/global_policy.rb @@ -1,10 +1,6 @@ # frozen_string_literal: true class GlobalPolicy < BasePolicy - desc "User is blocked" - with_options scope: :user, score: 0 - condition(:blocked) { @user&.blocked? } - desc "User is an internal user" with_options scope: :user, score: 0 condition(:internal) { @user&.internal? } diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 728a3040227..b3e29e775fc 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -258,6 +258,7 @@ class ProjectPolicy < BasePolicy enable :resolve_note enable :create_container_image enable :update_container_image + enable :destroy_container_image enable :create_environment enable :create_deployment enable :create_release @@ -446,6 +447,10 @@ class ProjectPolicy < BasePolicy prevent :owner_access end + rule { blocked }.policy do + prevent :create_pipeline + end + private def team_member? diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb index ec2698ecbe3..9ef93b2387f 100644 --- a/app/serializers/pipeline_entity.rb +++ b/app/serializers/pipeline_entity.rb @@ -4,7 +4,6 @@ class PipelineEntity < Grape::Entity include RequestAwareEntity expose :id - expose :iid expose :user, using: UserEntity expose :active?, as: :active diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index fd5442a6c28..f2cd51ef4d0 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -3,7 +3,7 @@ module Files class CreateService < Files::BaseService def create_commit! - transformer = Lfs::FileTransformer.new(project, @branch_name) + transformer = Lfs::FileTransformer.new(project, repository, @branch_name) result = transformer.new_file(@file_path, @file_content) diff --git a/app/services/files/multi_service.rb b/app/services/files/multi_service.rb index c1bc26c330a..d8c4e5bc5e8 100644 --- a/app/services/files/multi_service.rb +++ b/app/services/files/multi_service.rb @@ -5,7 +5,7 @@ module Files UPDATE_FILE_ACTIONS = %w(update move delete chmod).freeze def create_commit! - transformer = Lfs::FileTransformer.new(project, @branch_name) + transformer = Lfs::FileTransformer.new(project, repository, @branch_name) actions = actions_after_lfs_transformation(transformer, params[:actions]) actions = transform_move_actions(actions) diff --git a/app/services/lfs/file_transformer.rb b/app/services/lfs/file_transformer.rb index 5239fe1b6e3..d1746399908 100644 --- a/app/services/lfs/file_transformer.rb +++ b/app/services/lfs/file_transformer.rb @@ -8,17 +8,17 @@ module Lfs # pointer returned. If the file isn't in LFS the untransformed content # is returned to save in the commit. # - # transformer = Lfs::FileTransformer.new(project, @branch_name) + # transformer = Lfs::FileTransformer.new(project, repository, @branch_name) # content_or_lfs_pointer = transformer.new_file(file_path, content).content # create_transformed_commit(content_or_lfs_pointer) # class FileTransformer - attr_reader :project, :branch_name + attr_reader :project, :repository, :repository_type, :branch_name - delegate :repository, to: :project - - def initialize(project, branch_name) + def initialize(project, repository, branch_name) @project = project + @repository = repository + @repository_type = repository.repo_type.name @branch_name = branch_name end @@ -64,7 +64,11 @@ module Lfs # rubocop: enable CodeReuse/ActiveRecord def link_lfs_object!(lfs_object) - project.lfs_objects << lfs_object + LfsObjectsProject.safe_find_or_create_by!( + project: project, + lfs_object: lfs_object, + repository_type: repository_type + ) end def parse_file_content(file_content, encoding: nil) diff --git a/app/views/admin/application_settings/ci_cd.html.haml b/app/views/admin/application_settings/ci_cd.html.haml index db24c9982f7..a2aa1687f80 100644 --- a/app/views/admin/application_settings/ci_cd.html.haml +++ b/app/views/admin/application_settings/ci_cd.html.haml @@ -13,6 +13,8 @@ .settings-content = render 'ci_cd' += render_if_exists 'admin/application_settings/required_instance_ci_setting', expanded: expanded_by_default? + - if Gitlab.config.registry.enabled %section.settings.as-registry.no-animate#js-registry-settings{ class: ('expanded' if expanded_by_default?) } .settings-header diff --git a/app/views/admin/services/_deprecated_message.html.haml b/app/views/admin/services/_deprecated_message.html.haml new file mode 100644 index 00000000000..fea9506a4bb --- /dev/null +++ b/app/views/admin/services/_deprecated_message.html.haml @@ -0,0 +1,3 @@ +.flash-container.flash-container-page + .flash-alert.deprecated-service + %span= @service.deprecation_message diff --git a/app/views/admin/services/_form.html.haml b/app/views/admin/services/_form.html.haml index 1798b44bbb7..97373a3c350 100644 --- a/app/views/admin/services/_form.html.haml +++ b/app/views/admin/services/_form.html.haml @@ -6,5 +6,6 @@ = form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'fieldset-form' } do |form| = render 'shared/service_settings', form: form, subject: @service - .footer-block.row-content-block - = form.submit 'Save', class: 'btn btn-success' + - unless @service.is_a?(KubernetesService) + .footer-block.row-content-block + = form.submit 'Save', class: 'btn btn-success' diff --git a/app/views/admin/services/edit.html.haml b/app/views/admin/services/edit.html.haml index 512176649e6..79f5ab0d77d 100644 --- a/app/views/admin/services/edit.html.haml +++ b/app/views/admin/services/edit.html.haml @@ -1,4 +1,7 @@ - add_to_breadcrumbs "Service Templates", admin_application_settings_services_path - breadcrumb_title @service.title - page_title @service.title, "Service Templates" + += render 'deprecated_message' if @service.deprecation_message + = render 'form' diff --git a/app/views/clusters/platforms/kubernetes/_form.html.haml b/app/views/clusters/platforms/kubernetes/_form.html.haml index c1727cf9079..f2e44462226 100644 --- a/app/views/clusters/platforms/kubernetes/_form.html.haml +++ b/app/views/clusters/platforms/kubernetes/_form.html.haml @@ -48,7 +48,7 @@ = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') .form-group - = field.check_box :managed, { disabled: true, label: s_('ClusterIntegration|GitLab-managed cluster'), + = field.check_box :managed, { label: s_('ClusterIntegration|GitLab-managed cluster'), label_class: 'label-bold' } .form-text.text-muted = s_('ClusterIntegration|Allow GitLab to manage namespace and service accounts for this cluster.') diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml index 0a14830c666..0da1f1ba7f5 100644 --- a/app/views/groups/settings/_permissions.html.haml +++ b/app/views/groups/settings/_permissions.html.haml @@ -17,6 +17,7 @@ %br %span.descr.text-muted= share_with_group_lock_help_text(@group) + = render_if_exists 'groups/settings/ip_restriction', f: f, group: @group = render 'groups/settings/lfs', f: f = render 'groups/settings/project_creation_level', f: f, group: @group = render 'groups/settings/two_factor_auth', f: f diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index bdf7b933ab8..f4560404c03 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -53,10 +53,9 @@ %span.badge.badge-info= _('manual') - if pipeline_link - %td.pipeline-link - = link_to pipeline_path(pipeline), class: 'has-tooltip', title: _('Pipeline ID (IID)') do + %td + = link_to pipeline_path(pipeline) do %span.pipeline-id ##{pipeline.id} - %span.pipeline-iid (##{pipeline.iid}) %span by - if pipeline.user = user_avatar(user: pipeline.user, size: 20) diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 77ea2c04b28..a766dd51463 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -81,7 +81,7 @@ = link_to project_pipeline_path(@project, last_pipeline.id), class: "ci-status-icon-#{last_pipeline.status}" do = ci_icon_for_status(last_pipeline.status) #{ _('Pipeline') } - = link_to "##{last_pipeline.id} (##{last_pipeline.iid})", project_pipeline_path(@project, last_pipeline.id), class: "has-tooltip", title: _('Pipeline ID (IID)') + = link_to "##{last_pipeline.id}", project_pipeline_path(@project, last_pipeline.id) = ci_label_for_status(last_pipeline.status) - if last_pipeline.stages_count.nonzero? #{ n_(s_('Pipeline|with stage'), s_('Pipeline|with stages'), last_pipeline.stages_count) } diff --git a/app/views/shared/projects/_search_form.html.haml b/app/views/shared/projects/_search_form.html.haml index 7c7c0a363ac..4365e3f6877 100644 --- a/app/views/shared/projects/_search_form.html.haml +++ b/app/views/shared/projects/_search_form.html.haml @@ -1,7 +1,7 @@ - form_field_classes = local_assigns[:admin_view] || !Feature.enabled?(:project_list_filter_bar) ? 'input-short js-projects-list-filter' : '' - placeholder = local_assigns[:search_form_placeholder] ? search_form_placeholder : 'Filter by name...' -= form_tag filter_projects_path, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f| += form_tag filter_projects_path, method: :get, class: 'project-filter-form qa-project-filter-form', id: 'project-filter-form' do |f| = search_field_tag :name, params[:name], placeholder: placeholder, class: "project-filter-form-field form-control #{form_field_classes}", diff --git a/changelogs/unreleased/30138-display-cycle-analytics-issue-logic-fixes.yml b/changelogs/unreleased/30138-display-cycle-analytics-issue-logic-fixes.yml new file mode 100644 index 00000000000..574995f20fa --- /dev/null +++ b/changelogs/unreleased/30138-display-cycle-analytics-issue-logic-fixes.yml @@ -0,0 +1,5 @@ +--- +title: Change logic behind cycle analytics +merge_request: 29018 +author: +type: changed diff --git a/changelogs/unreleased/35428-docker-registry-date-not-accurate.yml b/changelogs/unreleased/35428-docker-registry-date-not-accurate.yml new file mode 100644 index 00000000000..f51ecdf83a6 --- /dev/null +++ b/changelogs/unreleased/35428-docker-registry-date-not-accurate.yml @@ -0,0 +1,6 @@ +--- +title: Changed the 'Created' label to 'Last Updated' on the container registry table + to more accurately reflect what the date represents. +merge_request: 29464 +author: +type: other diff --git a/changelogs/unreleased/55362-refresh-blank-service-account-token.yml b/changelogs/unreleased/55362-refresh-blank-service-account-token.yml new file mode 100644 index 00000000000..3189de97e8b --- /dev/null +++ b/changelogs/unreleased/55362-refresh-blank-service-account-token.yml @@ -0,0 +1,5 @@ +--- +title: Refresh service_account_token for kubernetes_namespaces +merge_request: 29657 +author: +type: fixed diff --git a/changelogs/unreleased/56737-commits-and-mr-events-on-jira-api.yml b/changelogs/unreleased/56737-commits-and-mr-events-on-jira-api.yml new file mode 100644 index 00000000000..ee2ce8acaeb --- /dev/null +++ b/changelogs/unreleased/56737-commits-and-mr-events-on-jira-api.yml @@ -0,0 +1,5 @@ +--- +title: Expose all current events properly on services API +merge_request: 29736 +author: Zsolt Kovari +type: fixed diff --git a/changelogs/unreleased/57414-show-pipeline-iid.yml b/changelogs/unreleased/57414-show-pipeline-iid.yml deleted file mode 100644 index 596ae00e5a3..00000000000 --- a/changelogs/unreleased/57414-show-pipeline-iid.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Show Pipeline IID everywhere Pipeline ID is shown -merge_request: 57414 -author: Mike Scott -type: added diff --git a/changelogs/unreleased/60617-allow-switching-from-gitlab-managed-to-unmanaged-clusters.yml b/changelogs/unreleased/60617-allow-switching-from-gitlab-managed-to-unmanaged-clusters.yml new file mode 100644 index 00000000000..1127dde4fcf --- /dev/null +++ b/changelogs/unreleased/60617-allow-switching-from-gitlab-managed-to-unmanaged-clusters.yml @@ -0,0 +1,5 @@ +--- +title: Allow switching clusters between managed and unmanaged +merge_request: 29322 +author: +type: added diff --git a/changelogs/unreleased/62685-add-index-invite-email-to-members.yml b/changelogs/unreleased/62685-add-index-invite-email-to-members.yml new file mode 100644 index 00000000000..80bb05e1709 --- /dev/null +++ b/changelogs/unreleased/62685-add-index-invite-email-to-members.yml @@ -0,0 +1,5 @@ +--- +title: Add index on invite_email for members +merge_request: 29768 +author: +type: performance diff --git a/changelogs/unreleased/62910-task-completion-status-gql-pderichs.yml b/changelogs/unreleased/62910-task-completion-status-gql-pderichs.yml new file mode 100644 index 00000000000..dcbfa7c185d --- /dev/null +++ b/changelogs/unreleased/62910-task-completion-status-gql-pderichs.yml @@ -0,0 +1,5 @@ +--- +title: Make task completion status available via GraphQL +merge_request: +author: +type: added diff --git a/changelogs/unreleased/63079-exclude-k8s-namespaces-with-no-service-account-token.yml b/changelogs/unreleased/63079-exclude-k8s-namespaces-with-no-service-account-token.yml new file mode 100644 index 00000000000..9dc99c8a62f --- /dev/null +++ b/changelogs/unreleased/63079-exclude-k8s-namespaces-with-no-service-account-token.yml @@ -0,0 +1,6 @@ +--- +title: Ensure a Kubernetes namespace is not used for deployments if there is no service + account token associated with it +merge_request: 29643 +author: +type: fixed diff --git a/changelogs/unreleased/backport-schema-changes.yml b/changelogs/unreleased/backport-schema-changes.yml new file mode 100644 index 00000000000..58f3ca72b0b --- /dev/null +++ b/changelogs/unreleased/backport-schema-changes.yml @@ -0,0 +1,5 @@ +--- +title: Backport the EE schema and migrations to CE +merge_request: 26940 +author: Yorick Peterse +type: other diff --git a/changelogs/unreleased/container-registry-api-perms-58271.yml b/changelogs/unreleased/container-registry-api-perms-58271.yml new file mode 100644 index 00000000000..0d1036a7788 --- /dev/null +++ b/changelogs/unreleased/container-registry-api-perms-58271.yml @@ -0,0 +1,5 @@ +--- +title: Allow developer role to delete docker tags via container registry API +merge_request: 29512 +author: +type: fixed diff --git a/changelogs/unreleased/error-pipelines-for-blocked-users.yml b/changelogs/unreleased/error-pipelines-for-blocked-users.yml new file mode 100644 index 00000000000..3ace28b6cfd --- /dev/null +++ b/changelogs/unreleased/error-pipelines-for-blocked-users.yml @@ -0,0 +1,5 @@ +--- +title: preventing blocked users and their PipelineSchdules from creating new Pipelines +merge_request: 27318 +author: +type: fixed diff --git a/changelogs/unreleased/generate-spans-for-sections.yml b/changelogs/unreleased/generate-spans-for-sections.yml new file mode 100644 index 00000000000..e167d66490f --- /dev/null +++ b/changelogs/unreleased/generate-spans-for-sections.yml @@ -0,0 +1,5 @@ +--- +title: Adds collapsible sections for job log +merge_request: 28642 +author: +type: added diff --git a/changelogs/unreleased/hashed-storage-enabled-default.yml b/changelogs/unreleased/hashed-storage-enabled-default.yml new file mode 100644 index 00000000000..9a34505785c --- /dev/null +++ b/changelogs/unreleased/hashed-storage-enabled-default.yml @@ -0,0 +1,5 @@ +--- +title: Hashed Storage is enabled by default on new installations +merge_request: 29586 +author: +type: changed diff --git a/changelogs/unreleased/issue-58747.yml b/changelogs/unreleased/issue-58747.yml new file mode 100644 index 00000000000..01b610576f7 --- /dev/null +++ b/changelogs/unreleased/issue-58747.yml @@ -0,0 +1,5 @@ +--- +title: Fix GPG signature verification with recent GnuPG versions +merge_request: 29388 +author: David Palubin +type: fixed diff --git a/changelogs/unreleased/migrate_k8s_service_integration.yml b/changelogs/unreleased/migrate_k8s_service_integration.yml new file mode 100644 index 00000000000..57f03e6bdab --- /dev/null +++ b/changelogs/unreleased/migrate_k8s_service_integration.yml @@ -0,0 +1,5 @@ +--- +title: Migrate Kubernetes service integration templates to clusters +merge_request: 28534 +author: +type: added diff --git a/changelogs/unreleased/readonly_k8s_integration.yml b/changelogs/unreleased/readonly_k8s_integration.yml new file mode 100644 index 00000000000..718705e8750 --- /dev/null +++ b/changelogs/unreleased/readonly_k8s_integration.yml @@ -0,0 +1,5 @@ +--- +title: Make Kubernetes service templates readonly +merge_request: 29044 +author: +type: removed diff --git a/changelogs/unreleased/sh-fix-issue-63158.yml b/changelogs/unreleased/sh-fix-issue-63158.yml new file mode 100644 index 00000000000..1a79166b6a2 --- /dev/null +++ b/changelogs/unreleased/sh-fix-issue-63158.yml @@ -0,0 +1,5 @@ +--- +title: Fix inability to set visibility_level on project via API +merge_request: 29578 +author: +type: fixed diff --git a/changelogs/unreleased/visual-review-apps-fix-dropdown.yml b/changelogs/unreleased/visual-review-apps-fix-dropdown.yml new file mode 100644 index 00000000000..ade68c26df9 --- /dev/null +++ b/changelogs/unreleased/visual-review-apps-fix-dropdown.yml @@ -0,0 +1,5 @@ +--- +title: Move Dropdown to Stick to MR View App Button +merge_request: 29767 +author: +type: fixed diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml index 23bbc9f4035..a8234263275 100644 --- a/config/locales/doorkeeper.en.yml +++ b/config/locales/doorkeeper.en.yml @@ -73,7 +73,7 @@ en: read_user: Grants read-only access to the authenticated user's profile through the /user API endpoint, which includes username, public email, and full name. Also grants access to read-only API endpoints under /users. read_repository: - Grants read-only access to repositories on private projects using Git-over-HTTP (not using the API). + Grants read-only access to repositories on private projects using Git-over-HTTP or the Repository Files API. write_repository: Grants read-write access to repositories on private projects using Git-over-HTTP (not using the API). read_registry: diff --git a/danger/database/Dangerfile b/danger/database/Dangerfile index 38ccbd94edb..4dadf60ad24 100644 --- a/danger/database/Dangerfile +++ b/danger/database/Dangerfile @@ -13,7 +13,6 @@ DB_FILES = [ 'lib/gitlab/github_import/', 'lib/gitlab/sql/', 'rubocop/cop/migration', - 'ee/db/', 'ee/lib/gitlab/database/' ].freeze diff --git a/db/migrate/20161007073613_create_user_activities.rb b/db/migrate/20161007073613_create_user_activities.rb index be3ecb17eef..dc972bf4664 100644 --- a/db/migrate/20161007073613_create_user_activities.rb +++ b/db/migrate/20161007073613_create_user_activities.rb @@ -1,7 +1,28 @@ + class CreateUserActivities < ActiveRecord::Migration[4.2] - DOWNTIME = false + # Set this constant to true if this migration requires downtime. + DOWNTIME = true + + # When a migration requires downtime you **must** uncomment the following + # constant and define a short and easy to understand explanation as to why the + # migration requires downtime. + DOWNTIME_REASON = 'Adding foreign key'.freeze + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! - # This migration is a no-op. It just exists to match EE. def change + create_table :user_activities do |t| + t.belongs_to :user, index: { unique: true }, foreign_key: { on_delete: :cascade } + t.datetime :last_activity_at, null: false + end end end diff --git a/db/migrate/20161117114805_remove_undeleted_groups.rb b/db/migrate/20161117114805_remove_undeleted_groups.rb deleted file mode 100644 index 0a4fe1c05b7..00000000000 --- a/db/migrate/20161117114805_remove_undeleted_groups.rb +++ /dev/null @@ -1,105 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class RemoveUndeletedGroups < ActiveRecord::Migration[4.2] - DOWNTIME = false - - def up - is_ee = defined?(Gitlab::License) - - if is_ee - execute <<-EOF.strip_heredoc - DELETE FROM path_locks - WHERE project_id IN ( - SELECT project_id - FROM projects - WHERE namespace_id IN (#{namespaces_pending_removal}) - ); - EOF - - execute <<-EOF.strip_heredoc - DELETE FROM remote_mirrors - WHERE project_id IN ( - SELECT project_id - FROM projects - WHERE namespace_id IN (#{namespaces_pending_removal}) - ); - EOF - end - - execute <<-EOF.strip_heredoc - DELETE FROM lists - WHERE label_id IN ( - SELECT id - FROM labels - WHERE group_id IN (#{namespaces_pending_removal}) - ); - EOF - - execute <<-EOF.strip_heredoc - DELETE FROM lists - WHERE board_id IN ( - SELECT id - FROM boards - WHERE project_id IN ( - SELECT project_id - FROM projects - WHERE namespace_id IN (#{namespaces_pending_removal}) - ) - ); - EOF - - execute <<-EOF.strip_heredoc - DELETE FROM labels - WHERE group_id IN (#{namespaces_pending_removal}); - EOF - - execute <<-EOF.strip_heredoc - DELETE FROM boards - WHERE project_id IN ( - SELECT project_id - FROM projects - WHERE namespace_id IN (#{namespaces_pending_removal}) - ) - EOF - - execute <<-EOF.strip_heredoc - DELETE FROM projects - WHERE namespace_id IN (#{namespaces_pending_removal}); - EOF - - if is_ee - # EE adds these columns but we have to make sure this data is cleaned up - # here before we run the DELETE below. An alternative would be patching - # this migration in EE but this will only result in a mess and confusing - # migrations. - execute <<-EOF.strip_heredoc - DELETE FROM protected_branch_push_access_levels - WHERE group_id IN (#{namespaces_pending_removal}); - EOF - - execute <<-EOF.strip_heredoc - DELETE FROM protected_branch_merge_access_levels - WHERE group_id IN (#{namespaces_pending_removal}); - EOF - end - - # This removes namespaces that were supposed to be deleted but still reside - # in the database. - execute "DELETE FROM namespaces WHERE deleted_at IS NOT NULL;" - end - - def down - # This is an irreversible migration; - # If someone is trying to rollback for other reasons, we should not throw an Exception. - # raise ActiveRecord::IrreversibleMigration - end - - def namespaces_pending_removal - "SELECT id FROM ( - SELECT id - FROM namespaces - WHERE deleted_at IS NOT NULL - ) namespace_ids" - end -end diff --git a/db/migrate/20171121135738_clean_up_from_merge_request_diffs_and_commits.rb b/db/migrate/20171121135738_clean_up_from_merge_request_diffs_and_commits.rb deleted file mode 100644 index 6be7b75492d..00000000000 --- a/db/migrate/20171121135738_clean_up_from_merge_request_diffs_and_commits.rb +++ /dev/null @@ -1,36 +0,0 @@ -class CleanUpFromMergeRequestDiffsAndCommits < ActiveRecord::Migration[4.2] - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - - class MergeRequestDiff < ActiveRecord::Base - self.table_name = 'merge_request_diffs' - - include ::EachBatch - end - - disable_ddl_transaction! - - def up - Gitlab::BackgroundMigration.steal('DeserializeMergeRequestDiffsAndCommits') - - # The literal '--- []\n' value is created by the import process and treated - # as null by the application, so we can ignore those - even if we were - # migrating, it wouldn't create any rows. - literal_prefix = Gitlab::Database.postgresql? ? 'E' : '' - non_empty = " - (st_commits IS NOT NULL AND st_commits != #{literal_prefix}'--- []\n') - OR - (st_diffs IS NOT NULL AND st_diffs != #{literal_prefix}'--- []\n') - ".squish - - MergeRequestDiff.where(non_empty).each_batch(of: 500) do |relation, index| - range = relation.pluck('MIN(id)', 'MAX(id)').first - - Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits.new.perform(*range) - end - end - - def down - end -end diff --git a/db/migrate/20171216111734_clean_up_for_members.rb b/db/migrate/20171216111734_clean_up_for_members.rb deleted file mode 100644 index 2fefc6c7fd1..00000000000 --- a/db/migrate/20171216111734_clean_up_for_members.rb +++ /dev/null @@ -1,31 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class CleanUpForMembers < ActiveRecord::Migration[4.2] - include Gitlab::Database::MigrationHelpers - - # Set this constant to true if this migration requires downtime. - DOWNTIME = false - - disable_ddl_transaction! - - class Member < ActiveRecord::Base - include EachBatch - - self.table_name = 'members' - end - - def up - condition = <<~EOF.squish - invite_token IS NULL AND - NOT EXISTS (SELECT 1 FROM users WHERE users.id = members.user_id) - EOF - - Member.each_batch(of: 10_000) do |batch| - batch.where(condition).delete_all - end - end - - def down - end -end diff --git a/db/migrate/20180502122856_create_project_mirror_data.rb b/db/migrate/20180502122856_create_project_mirror_data.rb index 8bc114afc0c..9781815a97b 100644 --- a/db/migrate/20180502122856_create_project_mirror_data.rb +++ b/db/migrate/20180502122856_create_project_mirror_data.rb @@ -4,17 +4,25 @@ class CreateProjectMirrorData < ActiveRecord::Migration[4.2] DOWNTIME = false def up - return if table_exists?(:project_mirror_data) - - create_table :project_mirror_data do |t| - t.references :project, index: true, foreign_key: { on_delete: :cascade } - t.string :status - t.string :jid - t.text :last_error + if table_exists?(:project_mirror_data) + add_column :project_mirror_data, :status, :string unless column_exists?(:project_mirror_data, :status) + add_column :project_mirror_data, :jid, :string unless column_exists?(:project_mirror_data, :jid) + add_column :project_mirror_data, :last_error, :text unless column_exists?(:project_mirror_data, :last_error) + else + create_table :project_mirror_data do |t| + t.references :project, index: true, foreign_key: { on_delete: :cascade } + t.string :status + t.string :jid + t.text :last_error + end end end def down - drop_table(:project_mirror_data) if table_exists?(:project_mirror_data) + remove_column :project_mirror_data, :status + remove_column :project_mirror_data, :jid + remove_column :project_mirror_data, :last_error + + # ee/db/migrate/20170509153720_create_project_mirror_data_ee.rb will remove the table. end end diff --git a/db/migrate/20180503131624_create_remote_mirrors.rb b/db/migrate/20180503131624_create_remote_mirrors.rb index 9f4bd463e66..288ae365f0f 100644 --- a/db/migrate/20180503131624_create_remote_mirrors.rb +++ b/db/migrate/20180503131624_create_remote_mirrors.rb @@ -29,6 +29,6 @@ class CreateRemoteMirrors < ActiveRecord::Migration[4.2] end def down - drop_table(:remote_mirrors) if table_exists?(:remote_mirrors) + # ee/db/migrate/20160321161032_create_remote_mirrors_ee.rb will remove the table end end diff --git a/db/migrate/20180503141722_add_remote_mirror_available_overridden_to_projects.rb b/db/migrate/20180503141722_add_remote_mirror_available_overridden_to_projects.rb index 5e2ef5706ee..1d99d46b7d6 100644 --- a/db/migrate/20180503141722_add_remote_mirror_available_overridden_to_projects.rb +++ b/db/migrate/20180503141722_add_remote_mirror_available_overridden_to_projects.rb @@ -10,6 +10,6 @@ class AddRemoteMirrorAvailableOverriddenToProjects < ActiveRecord::Migration[4.2 end def down - remove_column(:projects, :remote_mirror_available_overridden) if column_exists?(:projects, :remote_mirror_available_overridden) + # ee/db/migrate/20171017130239_add_remote_mirror_available_overridden_to_projects_ee.rb will remove the column. end end diff --git a/db/migrate/20180503193542_add_indexes_to_remote_mirror.rb b/db/migrate/20180503193542_add_indexes_to_remote_mirror.rb index bdf05c4e69b..19bed8d0500 100644 --- a/db/migrate/20180503193542_add_indexes_to_remote_mirror.rb +++ b/db/migrate/20180503193542_add_indexes_to_remote_mirror.rb @@ -10,6 +10,7 @@ class AddIndexesToRemoteMirror < ActiveRecord::Migration[4.2] end def down + # ee/db/migrate/20170208144550_add_index_to_mirrors_last_update_at_fields.rb will remove the index. # rubocop:disable Migration/RemoveIndex remove_index :remote_mirrors, :last_successful_update_at if index_exists? :remote_mirrors, :last_successful_update_at end diff --git a/db/migrate/20180503193953_add_mirror_available_to_application_settings.rb b/db/migrate/20180503193953_add_mirror_available_to_application_settings.rb index 64f65cd23be..d6a04035d48 100644 --- a/db/migrate/20180503193953_add_mirror_available_to_application_settings.rb +++ b/db/migrate/20180503193953_add_mirror_available_to_application_settings.rb @@ -10,6 +10,6 @@ class AddMirrorAvailableToApplicationSettings < ActiveRecord::Migration[4.2] end def down - remove_column(:application_settings, :mirror_available) if column_exists?(:application_settings, :mirror_available) + # ee/db/migrate/20171017125928_add_remote_mirror_available_to_application_settings.rb will remove the column. end end diff --git a/db/migrate/20190402150158_backport_enterprise_schema.rb b/db/migrate/20190402150158_backport_enterprise_schema.rb new file mode 100644 index 00000000000..610a8808383 --- /dev/null +++ b/db/migrate/20190402150158_backport_enterprise_schema.rb @@ -0,0 +1,2144 @@ +# frozen_string_literal: true + +# rubocop: disable Metrics/AbcSize +# rubocop: disable Migration/Datetime +class BackportEnterpriseSchema < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + APPLICATION_SETTINGS_COLUMNS = [ + { type: :boolean, name: :elasticsearch_indexing, default: false, null: false }, + { type: :boolean, name: :elasticsearch_search, default: false, null: false }, + { type: :integer, name: :shared_runners_minutes, default: 0, null: false }, + { type: :bigint, name: :repository_size_limit, default: 0, null: true }, + { type: :string, name: :elasticsearch_url, default: "http://localhost:9200" }, + { type: :boolean, name: :elasticsearch_aws, default: false, null: false }, + { type: :string, name: :elasticsearch_aws_region, default: "us-east-1", null: true }, + { type: :string, name: :elasticsearch_aws_access_key, default: nil, null: true }, + { type: :string, name: :elasticsearch_aws_secret_access_key, default: nil, null: true }, + { type: :integer, name: :geo_status_timeout, default: 10, null: true }, + { type: :boolean, name: :elasticsearch_experimental_indexer, default: nil, null: true }, + { type: :boolean, name: :check_namespace_plan, default: false, null: false }, + { type: :integer, name: :mirror_max_delay, default: 300, null: false }, + { type: :integer, name: :mirror_max_capacity, default: 100, null: false }, + { type: :integer, name: :mirror_capacity_threshold, default: 50, null: false }, + { type: :boolean, name: :slack_app_enabled, default: false }, + { type: :string, name: :slack_app_id }, + { type: :string, name: :slack_app_secret }, + { type: :string, name: :slack_app_verification_token }, + { type: :boolean, name: :allow_group_owners_to_manage_ldap, default: true, null: false }, + { type: :integer, name: :default_project_creation, default: 2, null: false }, + { type: :string, name: :email_additional_text }, + { type: :integer, name: :file_template_project_id }, + { type: :boolean, name: :pseudonymizer_enabled, default: false, null: false }, + { type: :boolean, name: :snowplow_enabled, default: false, null: false }, + { type: :string, name: :snowplow_collector_uri }, + { type: :string, name: :snowplow_site_id }, + { type: :string, name: :snowplow_cookie_domain }, + { type: :integer, name: :custom_project_templates_group_id }, + { type: :boolean, name: :elasticsearch_limit_indexing, default: false, null: false }, + { type: :string, name: :geo_node_allowed_ips, default: '0.0.0.0/0, ::/0' } + ].freeze + + NAMESPACE_COLUMNS = [ + { type: :integer, name: :custom_project_templates_group_id }, + { type: :integer, name: :file_template_project_id }, + { type: :string, name: :ldap_sync_error }, + { type: :datetime, name: :ldap_sync_last_successful_update_at }, + { type: :datetime, name: :ldap_sync_last_sync_at }, + { type: :datetime, name: :ldap_sync_last_update_at }, + { type: :integer, name: :plan_id }, + { type: :integer, name: :project_creation_level }, + { type: :bigint, name: :repository_size_limit }, + { type: :string, name: :saml_discovery_token }, + { type: :integer, name: :shared_runners_minutes_limit }, + { type: :datetime_with_timezone, name: :trial_ends_on }, + { type: :integer, name: :extra_shared_runners_minutes_limit } + ].freeze + + PROJECT_MIRROR_DATA_COLUMNS = [ + { type: :datetime_with_timezone, name: :last_successful_update_at }, + { type: :datetime_with_timezone, name: :last_update_at }, + { type: :datetime, name: :last_update_scheduled_at }, + { type: :datetime, name: :last_update_started_at }, + { type: :datetime, name: :next_execution_timestamp } + ].freeze + + PROJECTS_COLUMNS = [ + { type: :boolean, name: :disable_overriding_approvers_per_merge_request }, + { type: :string, name: :external_webhook_token }, + { type: :text, name: :issues_template }, + { type: :boolean, name: :merge_requests_author_approval }, + { type: :boolean, name: :merge_requests_disable_committers_approval }, + { type: :boolean, name: :merge_requests_require_code_owner_approval }, + { type: :text, name: :merge_requests_template }, + { type: :datetime, name: :mirror_last_successful_update_at }, + { type: :datetime, name: :mirror_last_update_at }, + { type: :boolean, name: :mirror_overwrites_diverged_branches }, + { type: :integer, name: :mirror_user_id }, + { type: :boolean, name: :only_mirror_protected_branches }, + { type: :boolean, name: :packages_enabled }, + { type: :boolean, name: :pull_mirror_available_overridden }, + { type: :bigint, name: :repository_size_limit }, + { type: :boolean, name: :require_password_to_approve } + ].freeze + + USERS_COLUMNS = [ + { type: :datetime, name: :admin_email_unsubscribed_at }, + { type: :boolean, name: :email_opted_in }, + { type: :datetime, name: :email_opted_in_at }, + { type: :string, name: :email_opted_in_ip }, + { type: :integer, name: :email_opted_in_source_id }, + { type: :integer, name: :group_view }, + { type: :integer, name: :managing_group_id }, + { type: :text, name: :note }, + { type: :integer, name: :roadmap_layout, limit: 2 }, + { type: :boolean, name: :support_bot }, + { type: :integer, name: :bot_type, limit: 2 } + ].freeze + + class ApplicationSetting < ActiveRecord::Base + self.table_name = 'application_settings' + end + + class ProtectedBranchMergeAccessLevels < ActiveRecord::Base + self.table_name = 'protected_branch_merge_access_levels' + end + + class ProtectedBranchPushAccessLevels < ActiveRecord::Base + self.table_name = 'protected_branch_push_access_levels' + end + + class Project < ActiveRecord::Base + self.table_name = 'projects' + end + + def up + create_missing_tables + + update_appearances + update_application_settings + update_boards_table + update_clusters_applications_prometheus + update_identities + update_issues + update_lists + update_members + update_merge_requests + update_notes + update_ci_builds + update_environments + update_namespaces + update_notification_settings + update_project_mirror_data + update_project_statistics + update_projects + update_protected_branch_merge_access_levels + update_protected_branch_push_access_levels + update_resource_label_events + update_user_preferences + update_users + update_web_hooks + update_geo_nodes + + add_missing_foreign_keys + end + + def down + # This migration can not be reverted in a production environment, as doing + # so would lead to data loss for existing EE installations. + return if !Rails.env.test? && !Rails.env.development? + + remove_foreign_keys + remove_tables + + revert_appearances + revert_application_settings + revert_boards_table + revert_clusters_applications_prometheus + revert_identities + revert_issues + revert_lists + revert_members + revert_merge_requests + revert_notes + revert_ci_builds + revert_environments + revert_namespaces + revert_notification_settings + revert_project_mirror_data + revert_project_statistics + revert_projects + revert_protected_branch_merge_access_levels + revert_protected_branch_push_access_levels + revert_resource_label_events + revert_user_preferences + revert_users + revert_web_hooks + end + + def add_column_if_not_exists(table, name, *args) + add_column(table, name, *args) unless column_exists?(table, name) + end + + def remove_column_if_exists(table, column) + remove_column(table, column) if column_exists?(table, column) + end + + def drop_table_if_exists(table) + drop_table(table) if table_exists?(table) + end + + def add_column_with_default_if_not_exists(table, name, *args) + unless column_exists?(table, name) + add_column_with_default(table, name, *args) + end + end + + def add_missing_columns(table, columns) + columns.each do |column| + next if table.column_exists?(column[:name]) + + # We can't use (public_)send here as this doesn't work with + # `datetime_with_timezone` for some reason. + table.column( + column[:name], + column[:type], + default: column[:default], + null: column.fetch(:null, true), + limit: column[:limit] + ) + end + end + + def remove_columns(table, columns) + columns.each do |column| + remove_column_if_exists(table, column[:name]) + end + end + + def create_table_if_not_exists(name, *args, &block) + return if table_exists?(name) + + create_table(name, *args, &block) + end + + def add_concurrent_foreign_key(source, target, column:, on_delete: nil, name: nil) + # We don't want redundant VALIDATE CONSTRAINT statements to run for existing + # foreign keys, as this can take a long time on large installations such as + # GitLab.com. + return if foreign_key_exists?(source, target, column: column) + + super + end + + def update_appearances + add_column_if_not_exists(:appearances, :updated_by, :integer) + end + + def revert_appearances + remove_column_if_exists(:namespaces, :updated_by) + end + + def update_application_settings + # In the CE schema this column allows NULL values even though there is a + # default value. In EE this column is not allowed to be NULL. This means + # that if we want to add a NOT NULL clause below, we must ensure no existing + # data would violate this clause. + ApplicationSetting + .where(password_authentication_enabled_for_git: nil) + .update_all(password_authentication_enabled_for_git: true) + + change_column_null( + :application_settings, + :password_authentication_enabled_for_git, + false + ) + + # This table will only have a single row, and all operations here will be + # very fast. As such we merge all of this into a single ALTER TABLE + # statement. + change_table(:application_settings) do |t| + t.text(:help_text) unless t.column_exists?(:help_text) + + add_missing_columns(t, APPLICATION_SETTINGS_COLUMNS) + end + + add_concurrent_index( + :application_settings, + :custom_project_templates_group_id + ) + + add_concurrent_index( + :application_settings, + :file_template_project_id + ) + end + + def revert_application_settings + change_column_null( + :application_settings, + :password_authentication_enabled_for_git, + true + ) + + remove_concurrent_index( + :application_settings, + :custom_project_templates_group_id + ) + + remove_concurrent_index( + :application_settings, + :file_template_project_id + ) + + remove_columns(:application_settings, APPLICATION_SETTINGS_COLUMNS) + end + + def update_boards_table + add_column_if_not_exists(:boards, :milestone_id, :integer) + add_column_if_not_exists(:boards, :weight, :integer) + + add_column_with_default_if_not_exists( + :boards, + :name, + :string, + default: 'Development' + ) + + add_concurrent_index(:boards, :milestone_id) + end + + def revert_boards_table + remove_concurrent_index(:boards, :milestone_id) + remove_column_if_exists(:boards, :name) + remove_column_if_exists(:boards, :weight) + remove_column_if_exists(:boards, :milestone_id) + end + + def update_clusters_applications_prometheus + add_column_if_not_exists( + :clusters_applications_prometheus, + :encrypted_alert_manager_token, + :string + ) + + add_column_if_not_exists( + :clusters_applications_prometheus, + :encrypted_alert_manager_token_iv, + :string + ) + + add_column_if_not_exists( + :clusters_applications_prometheus, + :last_update_started_at, + :datetime_with_timezone + ) + end + + def revert_clusters_applications_prometheus + remove_column_if_exists( + :clusters_applications_prometheus, + :encrypted_alert_manager_token + ) + + remove_column_if_exists( + :clusters_applications_prometheus, + :encrypted_alert_manager_token_iv + ) + + remove_column_if_exists( + :clusters_applications_prometheus, + :last_update_started_at + ) + end + + def update_identities + add_column_if_not_exists(:identities, :saml_provider_id, :integer) + add_column_if_not_exists(:identities, :secondary_extern_uid, :string) + + add_concurrent_index( + :identities, + :saml_provider_id, + where: 'saml_provider_id IS NOT NULL' + ) + end + + def revert_identities + remove_column_if_exists(:identities, :saml_provider_id) + remove_column_if_exists(:identities, :secondary_extern_uid) + end + + def update_issues + add_column_if_not_exists(:issues, :service_desk_reply_to, :string) + add_column_if_not_exists(:issues, :weight, :integer) + end + + def revert_issues + remove_column_if_exists(:issues, :service_desk_reply_to) + remove_column_if_exists(:issues, :weight) + end + + def update_lists + add_column_if_not_exists(:lists, :milestone_id, :integer) + add_column_if_not_exists(:lists, :user_id, :integer) + + add_concurrent_index(:lists, :milestone_id) + add_concurrent_index(:lists, :user_id) + end + + def revert_lists + remove_column_if_exists(:lists, :milestone_id) + remove_column_if_exists(:lists, :user_id) + end + + def update_members + add_column_with_default_if_not_exists( + :members, + :ldap, + :boolean, + default: false + ) + + add_column_with_default_if_not_exists( + :members, + :override, + :boolean, + default: false + ) + end + + def revert_members + remove_column_if_exists(:members, :ldap) + remove_column_if_exists(:members, :override) + end + + def update_merge_requests + add_column_if_not_exists(:merge_requests, :approvals_before_merge, :integer) + end + + def revert_merge_requests + remove_column_if_exists(:merge_requests, :approvals_before_merge) + end + + def update_notes + add_column_if_not_exists(:notes, :review_id, :bigint) + add_concurrent_index(:notes, :review_id) + end + + def revert_notes + remove_column_if_exists(:notes, :review_id) + end + + def update_ci_builds + add_concurrent_index( + :ci_builds, + [:name], + name: 'index_ci_builds_on_name_for_security_products_values', + where: " + ( + (name)::text = ANY ( + ARRAY[ + ('container_scanning'::character varying)::text, + ('dast'::character varying)::text, + ('dependency_scanning'::character varying)::text, + ('license_management'::character varying)::text, + ('sast'::character varying)::text + ] + ) + )" + ) + end + + def revert_ci_builds + remove_concurrent_index_by_name( + :ci_builds, + 'index_ci_builds_on_name_for_security_products_values' + ) + end + + def update_environments + return unless Gitlab::Database.postgresql? + return if index_exists?(:environments, :name, name: 'index_environments_on_name_varchar_pattern_ops') + + execute('CREATE INDEX CONCURRENTLY index_environments_on_name_varchar_pattern_ops ON environments (name varchar_pattern_ops);') + end + + def revert_environments + return unless Gitlab::Database.postgresql? + + remove_concurrent_index_by_name( + :environments, + 'index_environments_on_name_varchar_pattern_ops' + ) + end + + def update_namespaces + change_table(:namespaces) do |t| + add_missing_columns(t, NAMESPACE_COLUMNS) + end + + add_column_with_default_if_not_exists( + :namespaces, + :ldap_sync_status, + :string, + default: 'ready' + ) + + add_column_with_default_if_not_exists( + :namespaces, + :membership_lock, + :boolean, + default: false, + allow_null: true + ) + + # When `add_concurrent_index` runs, it for some reason incorrectly + # determines this index does not exist when it does. To work around this, we + # check the existence by name ourselves. + unless index_exists_by_name?(:namespaces, 'index_namespaces_on_custom_project_templates_group_id_and_type') + add_concurrent_index( + :namespaces, + %i[custom_project_templates_group_id type], + where: "(custom_project_templates_group_id IS NOT NULL)" + ) + end + + add_concurrent_index(:namespaces, :file_template_project_id) + add_concurrent_index(:namespaces, :ldap_sync_last_successful_update_at) + add_concurrent_index(:namespaces, :ldap_sync_last_update_at) + add_concurrent_index(:namespaces, :plan_id) + add_concurrent_index( + :namespaces, + :trial_ends_on, + where: "(trial_ends_on IS NOT NULL)" + ) + + unless index_exists_by_name?(:namespaces, 'index_namespaces_on_shared_and_extra_runners_minutes_limit') + add_concurrent_index( + :namespaces, + %i[shared_runners_minutes_limit extra_shared_runners_minutes_limit], + name: 'index_namespaces_on_shared_and_extra_runners_minutes_limit' + ) + end + end + + def revert_namespaces + remove_columns(:namespaces, NAMESPACE_COLUMNS) + remove_column_if_exists(:namespaces, :ldap_sync_status) + remove_column_if_exists(:namespaces, :membership_lock) + + remove_concurrent_index_by_name( + :namespaces, + 'index_namespaces_on_shared_and_extra_runners_minutes_limit' + ) + end + + def update_notification_settings + add_column_if_not_exists(:notification_settings, :new_epic, :boolean) + end + + def revert_notification_settings + remove_column_if_exists(:notification_settings, :new_epic) + end + + def update_project_mirror_data + change_table(:project_mirror_data) do |t| + add_missing_columns(t, PROJECT_MIRROR_DATA_COLUMNS) + end + + add_column_with_default_if_not_exists( + :project_mirror_data, + :retry_count, + :integer, + default: 0 + ) + + add_concurrent_index(:project_mirror_data, :last_successful_update_at) + + add_concurrent_index( + :project_mirror_data, + %i[next_execution_timestamp retry_count], + name: 'index_mirror_data_on_next_execution_and_retry_count' + ) + end + + def revert_project_mirror_data + remove_columns(:project_mirror_data, PROJECT_MIRROR_DATA_COLUMNS) + + remove_concurrent_index_by_name( + :project_mirror_data, + 'index_mirror_data_on_next_execution_and_retry_count' + ) + + remove_column_if_exists(:project_statistics, :retry_count) + end + + def update_project_statistics + add_column_with_default_if_not_exists( + :project_statistics, + :shared_runners_seconds, + :bigint, + default: 0 + ) + + add_column_if_not_exists( + :project_statistics, + :shared_runners_seconds_last_reset, + :datetime + ) + end + + def revert_project_statistics + remove_column_if_exists(:project_statistics, :shared_runners_seconds) + + remove_column_if_exists( + :project_statistics, + :shared_runners_seconds_last_reset + ) + end + + def update_projects + change_table(:projects) do |t| + add_missing_columns(t, PROJECTS_COLUMNS) + end + + change_column_null(:projects, :merge_requests_rebase_enabled, true) + + add_column_with_default_if_not_exists( + :projects, + :mirror, + :boolean, + default: false + ) + + add_column_with_default_if_not_exists( + :projects, + :mirror_trigger_builds, + :boolean, + default: false + ) + + add_column_with_default_if_not_exists( + :projects, + :reset_approvals_on_push, + :boolean, + default: true, + allow_null: true + ) + + add_column_with_default_if_not_exists( + :projects, + :service_desk_enabled, + :boolean, + default: true, + allow_null: true + ) + + add_column_with_default_if_not_exists( + :projects, + :approvals_before_merge, + :integer, + default: 0 + ) + + add_concurrent_index( + :projects, + %i[archived pending_delete merge_requests_require_code_owner_approval], + name: 'projects_requiring_code_owner_approval', + where: '((pending_delete = false) AND (archived = false) AND (merge_requests_require_code_owner_approval = true))' + ) + + add_concurrent_index( + :projects, + %i[id repository_storage last_repository_updated_at], + name: 'idx_projects_on_repository_storage_last_repository_updated_at' + ) + + add_concurrent_index( + :projects, + :id, + name: 'index_projects_on_mirror_and_mirror_trigger_builds_both_true', + where: '((mirror IS TRUE) AND (mirror_trigger_builds IS TRUE))' + ) + + add_concurrent_index(:projects, :mirror_last_successful_update_at) + end + + def revert_projects + remove_columns(:projects, PROJECTS_COLUMNS) + + Project + .where(merge_requests_rebase_enabled: nil) + .update_all(merge_requests_rebase_enabled: false) + + change_column_null(:projects, :merge_requests_rebase_enabled, false) + + remove_column_if_exists(:projects, :mirror) + remove_column_if_exists(:projects, :mirror_trigger_builds) + remove_column_if_exists(:projects, :reset_approvals_on_push) + remove_column_if_exists(:projects, :service_desk_enabled) + remove_column_if_exists(:projects, :approvals_before_merge) + + remove_concurrent_index_by_name( + :projects, + 'projects_requiring_code_owner_approval' + ) + + remove_concurrent_index_by_name( + :projects, + 'idx_projects_on_repository_storage_last_repository_updated_at' + ) + + remove_concurrent_index_by_name( + :projects, + 'index_projects_on_mirror_and_mirror_trigger_builds_both_true' + ) + end + + def update_protected_branch_merge_access_levels + change_column_null(:protected_branch_merge_access_levels, :access_level, true) + + add_column_if_not_exists( + :protected_branch_merge_access_levels, + :group_id, + :integer + ) + + add_column_if_not_exists( + :protected_branch_merge_access_levels, + :user_id, + :integer + ) + + add_concurrent_index(:protected_branch_merge_access_levels, :group_id) + add_concurrent_index(:protected_branch_merge_access_levels, :user_id) + end + + def revert_protected_branch_merge_access_levels + ProtectedBranchMergeAccessLevels + .where(access_level: nil) + .update_all(access_level: false) + + change_column_null( + :protected_branch_merge_access_levels, + :access_level, + false + ) + + remove_column_if_exists(:protected_branch_merge_access_levels, :group_id) + remove_column_if_exists(:protected_branch_merge_access_levels, :user_id) + end + + def update_protected_branch_push_access_levels + change_column_null( + :protected_branch_push_access_levels, + :access_level, + true + ) + + add_column_if_not_exists( + :protected_branch_push_access_levels, + :group_id, + :integer + ) + + add_column_if_not_exists( + :protected_branch_push_access_levels, + :user_id, + :integer + ) + + add_concurrent_index(:protected_branch_push_access_levels, :group_id) + add_concurrent_index(:protected_branch_push_access_levels, :user_id) + end + + def revert_protected_branch_push_access_levels + ProtectedBranchPushAccessLevels + .where(access_level: nil) + .update_all(access_level: false) + + change_column_null( + :protected_branch_push_access_levels, + :access_level, + false + ) + + remove_column_if_exists(:protected_branch_push_access_levels, :group_id) + remove_column_if_exists(:protected_branch_push_access_levels, :user_id) + end + + def update_resource_label_events + add_column_if_not_exists(:resource_label_events, :epic_id, :integer) + add_concurrent_index(:resource_label_events, :epic_id) + end + + def revert_resource_label_events + remove_column_if_exists(:resource_label_events, :epic_id) + end + + def update_user_preferences + add_column_with_default_if_not_exists( + :user_preferences, + :epic_notes_filter, + :integer, + default: 0, + limit: 2 + ) + + add_column_if_not_exists(:user_preferences, :epics_sort, :string) + add_column_if_not_exists(:user_preferences, :roadmap_epics_state, :integer) + add_column_if_not_exists(:user_preferences, :roadmaps_sort, :string) + end + + def revert_user_preferences + remove_column_if_exists(:user_preferences, :epic_notes_filter) + remove_column_if_exists(:user_preferences, :epics_sort) + remove_column_if_exists(:user_preferences, :roadmap_epics_state) + remove_column_if_exists(:user_preferences, :roadmaps_sort) + end + + def update_users + add_column_with_default_if_not_exists( + :users, + :auditor, + :boolean, + default: false + ) + + change_table(:users) do |t| + add_missing_columns(t, USERS_COLUMNS) + end + + add_concurrent_index(:users, :group_view) + add_concurrent_index(:users, :managing_group_id) + add_concurrent_index(:users, :support_bot) + add_concurrent_index(:users, :bot_type) + + add_concurrent_index( + :users, + :state, + name: 'index_users_on_state_and_internal_attrs', + where: '((ghost <> true) AND (support_bot <> true))' + ) + + internal_index = 'index_users_on_state_and_internal' + + remove_concurrent_index(:users, :state, name: internal_index) + + add_concurrent_index( + :users, + :state, + name: internal_index, + where: '((ghost <> true) AND (bot_type IS NULL))' + ) + end + + def revert_users + remove_column_if_exists(:users, :auditor) + remove_columns(:users, USERS_COLUMNS) + + remove_concurrent_index_by_name( + :users, + 'index_users_on_state_and_internal_attrs' + ) + + internal_index = 'index_users_on_state_and_internal' + + remove_concurrent_index(:users, :state, name: internal_index) + add_concurrent_index(:users, :state, name: internal_index) + end + + def update_web_hooks + add_column_if_not_exists(:web_hooks, :group_id, :integer) + end + + def revert_web_hooks + remove_column_if_exists(:web_hooks, :group_id) + end + + def update_geo_nodes + add_column_if_not_exists(:geo_nodes, :internal_url, :string) + end + + def revert_geo_nodes + remove_column_if_exists(:geo_nodes, :internal_url) + end + + def create_missing_tables + create_table_if_not_exists "approval_merge_request_rule_sources", id: :bigserial do |t| + t.bigint "approval_merge_request_rule_id", null: false + t.bigint "approval_project_rule_id", null: false + t.index %w[approval_merge_request_rule_id], name: "index_approval_merge_request_rule_sources_1", unique: true, using: :btree + t.index %w[approval_project_rule_id], name: "index_approval_merge_request_rule_sources_2", using: :btree + end + + create_table_if_not_exists "approval_merge_request_rules", id: :bigserial do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "merge_request_id", null: false + t.integer "approvals_required", limit: 2, default: 0, null: false + t.boolean "code_owner", default: false, null: false + t.string "name", null: false + t.index %w[merge_request_id code_owner name], name: "approval_rule_name_index_for_code_owners", unique: true, where: "(code_owner = true)", using: :btree + t.index %w[merge_request_id code_owner], name: "index_approval_merge_request_rules_1", using: :btree + end + + create_table_if_not_exists "approval_merge_request_rules_approved_approvers", id: :bigserial do |t| + t.bigint "approval_merge_request_rule_id", null: false + t.integer "user_id", null: false + t.index %w[approval_merge_request_rule_id user_id], name: "index_approval_merge_request_rules_approved_approvers_1", unique: true, using: :btree + t.index %w[user_id], name: "index_approval_merge_request_rules_approved_approvers_2", using: :btree + end + + create_table_if_not_exists "approval_merge_request_rules_groups", id: :bigserial do |t| + t.bigint "approval_merge_request_rule_id", null: false + t.integer "group_id", null: false + t.index %w[approval_merge_request_rule_id group_id], name: "index_approval_merge_request_rules_groups_1", unique: true, using: :btree + t.index %w[group_id], name: "index_approval_merge_request_rules_groups_2", using: :btree + end + + create_table_if_not_exists "approval_merge_request_rules_users", id: :bigserial do |t| + t.bigint "approval_merge_request_rule_id", null: false + t.integer "user_id", null: false + t.index %w[approval_merge_request_rule_id user_id], name: "index_approval_merge_request_rules_users_1", unique: true, using: :btree + t.index %w[user_id], name: "index_approval_merge_request_rules_users_2", using: :btree + end + + create_table_if_not_exists "approval_project_rules", id: :bigserial do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "project_id", null: false + t.integer "approvals_required", limit: 2, default: 0, null: false + t.string "name", null: false + t.index %w[project_id], name: "index_approval_project_rules_on_project_id", using: :btree + end + + create_table_if_not_exists "approval_project_rules_groups", id: :bigserial do |t| + t.bigint "approval_project_rule_id", null: false + t.integer "group_id", null: false + t.index %w[approval_project_rule_id group_id], name: "index_approval_project_rules_groups_1", unique: true, using: :btree + t.index %w[group_id], name: "index_approval_project_rules_groups_2", using: :btree + end + + create_table_if_not_exists "approval_project_rules_users", id: :bigserial do |t| + t.bigint "approval_project_rule_id", null: false + t.integer "user_id", null: false + t.index %w[approval_project_rule_id user_id], name: "index_approval_project_rules_users_1", unique: true, using: :btree + t.index %w[user_id], name: "index_approval_project_rules_users_2", using: :btree + end + + create_table_if_not_exists "approvals" do |t| + t.integer "merge_request_id", null: false + t.integer "user_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.index %w[merge_request_id], name: "index_approvals_on_merge_request_id", using: :btree + end + + create_table_if_not_exists "approver_groups" do |t| + t.integer "target_id", null: false + t.string "target_type", null: false + t.integer "group_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.index %w[group_id], name: "index_approver_groups_on_group_id", using: :btree + t.index %w[target_id target_type], name: "index_approver_groups_on_target_id_and_target_type", using: :btree + end + + create_table_if_not_exists "approvers" do |t| + t.integer "target_id", null: false + t.string "target_type" + t.integer "user_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.index %w[target_id target_type], name: "index_approvers_on_target_id_and_target_type", using: :btree + t.index %w[user_id], name: "index_approvers_on_user_id", using: :btree + end + + create_table_if_not_exists "board_assignees" do |t| + t.integer "board_id", null: false + t.integer "assignee_id", null: false + t.index %w[assignee_id], name: "index_board_assignees_on_assignee_id", using: :btree + t.index %w[board_id assignee_id], name: "index_board_assignees_on_board_id_and_assignee_id", unique: true, using: :btree + end + + create_table_if_not_exists "board_labels" do |t| + t.integer "board_id", null: false + t.integer "label_id", null: false + t.index %w[board_id label_id], name: "index_board_labels_on_board_id_and_label_id", unique: true, using: :btree + t.index %w[label_id], name: "index_board_labels_on_label_id", using: :btree + end + + create_table_if_not_exists "ci_sources_pipelines" do |t| + t.integer "project_id" + t.integer "pipeline_id" + t.integer "source_project_id" + t.integer "source_job_id" + t.integer "source_pipeline_id" + t.index ["pipeline_id"], name: "index_ci_sources_pipelines_on_pipeline_id", using: :btree + t.index ["project_id"], name: "index_ci_sources_pipelines_on_project_id", using: :btree + t.index ["source_job_id"], name: "index_ci_sources_pipelines_on_source_job_id", using: :btree + t.index ["source_pipeline_id"], name: "index_ci_sources_pipelines_on_source_pipeline_id", using: :btree + t.index ["source_project_id"], name: "index_ci_sources_pipelines_on_source_project_id", using: :btree + end + + create_table_if_not_exists "design_management_designs", id: :bigserial, force: :cascade do |t| + t.integer "project_id", null: false + t.integer "issue_id", null: false + t.string "filename", null: false + t.index %w[issue_id filename], name: "index_design_management_designs_on_issue_id_and_filename", unique: true, using: :btree + t.index ["project_id"], name: "index_design_management_designs_on_project_id", using: :btree + end + + create_table_if_not_exists "design_management_designs_versions", id: false, force: :cascade do |t| + t.bigint "design_id", null: false + t.bigint "version_id", null: false + t.index %w[design_id version_id], name: "design_management_designs_versions_uniqueness", unique: true, using: :btree + t.index ["design_id"], name: "index_design_management_designs_versions_on_design_id", using: :btree + t.index ["version_id"], name: "index_design_management_designs_versions_on_version_id", using: :btree + end + + create_table_if_not_exists "design_management_versions", id: :bigserial, force: :cascade do |t| + t.binary "sha", null: false + t.index ["sha"], name: "index_design_management_versions_on_sha", unique: true, using: :btree + end + + create_table_if_not_exists "draft_notes", id: :bigserial do |t| + t.integer "merge_request_id", null: false + t.integer "author_id", null: false + t.boolean "resolve_discussion", default: false, null: false + t.string "discussion_id" + t.text "note", null: false + t.text "position" + t.text "original_position" + t.text "change_position" + t.index ["author_id"], name: "index_draft_notes_on_author_id", using: :btree + t.index ["discussion_id"], name: "index_draft_notes_on_discussion_id", using: :btree + t.index ["merge_request_id"], name: "index_draft_notes_on_merge_request_id", using: :btree + end + + create_table_if_not_exists "elasticsearch_indexed_namespaces", id: false do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "namespace_id" + t.index ["namespace_id"], name: "index_elasticsearch_indexed_namespaces_on_namespace_id", unique: true, using: :btree + end + + create_table_if_not_exists "elasticsearch_indexed_projects", id: false do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "project_id" + t.index ["project_id"], name: "index_elasticsearch_indexed_projects_on_project_id", unique: true, using: :btree + end + + create_table_if_not_exists "epic_issues" do |t| + t.integer "epic_id", null: false + t.integer "issue_id", null: false + t.integer "relative_position", default: 1073741823, null: false + t.index ["epic_id"], name: "index_epic_issues_on_epic_id", using: :btree + t.index ["issue_id"], name: "index_epic_issues_on_issue_id", unique: true, using: :btree + end + + create_table_if_not_exists "epic_metrics" do |t| + t.integer "epic_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["epic_id"], name: "index_epic_metrics", using: :btree + end + + create_table_if_not_exists "epics" do |t| + t.integer "milestone_id" + t.integer "group_id", null: false + t.integer "author_id", null: false + t.integer "assignee_id" + t.integer "iid", null: false + t.integer "cached_markdown_version" + t.integer "updated_by_id" + t.integer "last_edited_by_id" + t.integer "lock_version" + t.date "start_date" + t.date "end_date" + t.datetime "last_edited_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "title", null: false + t.string "title_html", null: false + t.text "description" + t.text "description_html" + t.integer "start_date_sourcing_milestone_id" + t.integer "due_date_sourcing_milestone_id" + t.date "start_date_fixed" + t.date "due_date_fixed" + t.boolean "start_date_is_fixed" + t.boolean "due_date_is_fixed" + t.integer "state", limit: 2, default: 1, null: false + t.integer "closed_by_id" + t.datetime "closed_at" + t.integer "parent_id" + t.integer "relative_position" + t.index ["assignee_id"], name: "index_epics_on_assignee_id", using: :btree + t.index ["author_id"], name: "index_epics_on_author_id", using: :btree + t.index ["closed_by_id"], name: "index_epics_on_closed_by_id", using: :btree + t.index ["end_date"], name: "index_epics_on_end_date", using: :btree + t.index ["group_id"], name: "index_epics_on_group_id", using: :btree + t.index ["iid"], name: "index_epics_on_iid", using: :btree + t.index ["milestone_id"], name: "index_milestone", using: :btree + t.index ["parent_id"], name: "index_epics_on_parent_id", using: :btree + t.index ["start_date"], name: "index_epics_on_start_date", using: :btree + end + + create_table_if_not_exists "geo_cache_invalidation_events", id: :bigserial do |t| + t.string "key", null: false + end + + create_table_if_not_exists "geo_event_log", id: :bigserial do |t| + t.datetime "created_at", null: false + t.bigint "repository_updated_event_id" + t.bigint "repository_deleted_event_id" + t.bigint "repository_renamed_event_id" + t.bigint "repositories_changed_event_id" + t.bigint "repository_created_event_id" + t.bigint "hashed_storage_migrated_event_id" + t.bigint "lfs_object_deleted_event_id" + t.bigint "hashed_storage_attachments_event_id" + t.bigint "upload_deleted_event_id" + t.bigint "job_artifact_deleted_event_id" + t.bigint "reset_checksum_event_id" + t.bigint "cache_invalidation_event_id" + t.index ["cache_invalidation_event_id"], name: "index_geo_event_log_on_cache_invalidation_event_id", where: "(cache_invalidation_event_id IS NOT NULL)", using: :btree + t.index ["hashed_storage_attachments_event_id"], name: "index_geo_event_log_on_hashed_storage_attachments_event_id", where: "(hashed_storage_attachments_event_id IS NOT NULL)", using: :btree + t.index ["hashed_storage_migrated_event_id"], name: "index_geo_event_log_on_hashed_storage_migrated_event_id", where: "(hashed_storage_migrated_event_id IS NOT NULL)", using: :btree + t.index ["job_artifact_deleted_event_id"], name: "index_geo_event_log_on_job_artifact_deleted_event_id", where: "(job_artifact_deleted_event_id IS NOT NULL)", using: :btree + t.index ["lfs_object_deleted_event_id"], name: "index_geo_event_log_on_lfs_object_deleted_event_id", where: "(lfs_object_deleted_event_id IS NOT NULL)", using: :btree + t.index ["repositories_changed_event_id"], name: "index_geo_event_log_on_repositories_changed_event_id", where: "(repositories_changed_event_id IS NOT NULL)", using: :btree + t.index ["repository_created_event_id"], name: "index_geo_event_log_on_repository_created_event_id", where: "(repository_created_event_id IS NOT NULL)", using: :btree + t.index ["repository_deleted_event_id"], name: "index_geo_event_log_on_repository_deleted_event_id", where: "(repository_deleted_event_id IS NOT NULL)", using: :btree + t.index ["repository_renamed_event_id"], name: "index_geo_event_log_on_repository_renamed_event_id", where: "(repository_renamed_event_id IS NOT NULL)", using: :btree + t.index ["repository_updated_event_id"], name: "index_geo_event_log_on_repository_updated_event_id", where: "(repository_updated_event_id IS NOT NULL)", using: :btree + t.index ["reset_checksum_event_id"], name: "index_geo_event_log_on_reset_checksum_event_id", where: "(reset_checksum_event_id IS NOT NULL)", using: :btree + t.index ["upload_deleted_event_id"], name: "index_geo_event_log_on_upload_deleted_event_id", where: "(upload_deleted_event_id IS NOT NULL)", using: :btree + end + + create_table_if_not_exists "geo_hashed_storage_attachments_events", id: :bigserial do |t| + t.integer "project_id", null: false + t.text "old_attachments_path", null: false + t.text "new_attachments_path", null: false + t.index ["project_id"], name: "index_geo_hashed_storage_attachments_events_on_project_id", using: :btree + end + + create_table_if_not_exists "geo_hashed_storage_migrated_events", id: :bigserial do |t| + t.integer "project_id", null: false + t.text "repository_storage_name", null: false + t.text "old_disk_path", null: false + t.text "new_disk_path", null: false + t.text "old_wiki_disk_path", null: false + t.text "new_wiki_disk_path", null: false + t.integer "old_storage_version", limit: 2 + t.integer "new_storage_version", limit: 2, null: false + t.index ["project_id"], name: "index_geo_hashed_storage_migrated_events_on_project_id", using: :btree + end + + create_table_if_not_exists "geo_job_artifact_deleted_events", id: :bigserial do |t| + t.integer "job_artifact_id", null: false + t.string "file_path", null: false + t.index ["job_artifact_id"], name: "index_geo_job_artifact_deleted_events_on_job_artifact_id", using: :btree + end + + create_table_if_not_exists "geo_lfs_object_deleted_events", id: :bigserial do |t| + t.integer "lfs_object_id", null: false + t.string "oid", null: false + t.string "file_path", null: false + t.index ["lfs_object_id"], name: "index_geo_lfs_object_deleted_events_on_lfs_object_id", using: :btree + end + + create_table_if_not_exists "geo_node_namespace_links" do |t| + t.integer "geo_node_id", null: false + t.integer "namespace_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index %w[geo_node_id namespace_id], name: "index_geo_node_namespace_links_on_geo_node_id_and_namespace_id", unique: true, using: :btree + t.index ["geo_node_id"], name: "index_geo_node_namespace_links_on_geo_node_id", using: :btree + t.index ["namespace_id"], name: "index_geo_node_namespace_links_on_namespace_id", using: :btree + end + + create_table_if_not_exists "geo_node_statuses" do |t| + t.integer "geo_node_id", null: false + t.integer "db_replication_lag_seconds" + t.integer "repositories_synced_count" + t.integer "repositories_failed_count" + t.integer "lfs_objects_count" + t.integer "lfs_objects_synced_count" + t.integer "lfs_objects_failed_count" + t.integer "attachments_count" + t.integer "attachments_synced_count" + t.integer "attachments_failed_count" + t.integer "last_event_id" + t.datetime "last_event_date" + t.integer "cursor_last_event_id" + t.datetime "cursor_last_event_date" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.datetime "last_successful_status_check_at" + t.string "status_message" + t.integer "replication_slots_count" + t.integer "replication_slots_used_count" + t.bigint "replication_slots_max_retained_wal_bytes" + t.integer "wikis_synced_count" + t.integer "wikis_failed_count" + t.integer "job_artifacts_count" + t.integer "job_artifacts_synced_count" + t.integer "job_artifacts_failed_count" + t.string "version" + t.string "revision" + t.integer "repositories_verified_count" + t.integer "repositories_verification_failed_count" + t.integer "wikis_verified_count" + t.integer "wikis_verification_failed_count" + t.integer "lfs_objects_synced_missing_on_primary_count" + t.integer "job_artifacts_synced_missing_on_primary_count" + t.integer "attachments_synced_missing_on_primary_count" + t.integer "repositories_checksummed_count" + t.integer "repositories_checksum_failed_count" + t.integer "repositories_checksum_mismatch_count" + t.integer "wikis_checksummed_count" + t.integer "wikis_checksum_failed_count" + t.integer "wikis_checksum_mismatch_count" + t.binary "storage_configuration_digest" + t.integer "repositories_retrying_verification_count" + t.integer "wikis_retrying_verification_count" + t.integer "projects_count" + t.index ["geo_node_id"], name: "index_geo_node_statuses_on_geo_node_id", unique: true, using: :btree + end + + create_table_if_not_exists "geo_nodes" do |t| + t.boolean "primary" + t.integer "oauth_application_id" + t.boolean "enabled", default: true, null: false + t.string "access_key" + t.string "encrypted_secret_access_key" + t.string "encrypted_secret_access_key_iv" + t.string "clone_url_prefix" + t.integer "files_max_capacity", default: 10, null: false + t.integer "repos_max_capacity", default: 25, null: false + t.string "url", null: false + t.string "selective_sync_type" + t.text "selective_sync_shards" + t.integer "verification_max_capacity", default: 100, null: false + t.integer "minimum_reverification_interval", default: 7, null: false + t.string "alternate_url" + t.index ["access_key"], name: "index_geo_nodes_on_access_key", using: :btree + t.index ["primary"], name: "index_geo_nodes_on_primary", using: :btree + t.index ["url"], name: "index_geo_nodes_on_url", unique: true, using: :btree + end + + create_table_if_not_exists "geo_repositories_changed_events", id: :bigserial do |t| + t.integer "geo_node_id", null: false + t.index ["geo_node_id"], name: "index_geo_repositories_changed_events_on_geo_node_id", using: :btree + end + + create_table_if_not_exists "geo_repository_created_events", id: :bigserial do |t| + t.integer "project_id", null: false + t.text "repository_storage_name", null: false + t.text "repo_path", null: false + t.text "wiki_path" + t.text "project_name", null: false + t.index ["project_id"], name: "index_geo_repository_created_events_on_project_id", using: :btree + end + + create_table_if_not_exists "geo_repository_deleted_events", id: :bigserial do |t| + t.integer "project_id", null: false + t.text "repository_storage_name", null: false + t.text "deleted_path", null: false + t.text "deleted_wiki_path" + t.text "deleted_project_name", null: false + t.index ["project_id"], name: "index_geo_repository_deleted_events_on_project_id", using: :btree + end + + create_table_if_not_exists "geo_repository_renamed_events", id: :bigserial do |t| + t.integer "project_id", null: false + t.text "repository_storage_name", null: false + t.text "old_path_with_namespace", null: false + t.text "new_path_with_namespace", null: false + t.text "old_wiki_path_with_namespace", null: false + t.text "new_wiki_path_with_namespace", null: false + t.text "old_path", null: false + t.text "new_path", null: false + t.index ["project_id"], name: "index_geo_repository_renamed_events_on_project_id", using: :btree + end + + create_table_if_not_exists "geo_repository_updated_events", id: :bigserial do |t| + t.integer "branches_affected", null: false + t.integer "tags_affected", null: false + t.integer "project_id", null: false + t.integer "source", limit: 2, null: false + t.boolean "new_branch", default: false, null: false + t.boolean "remove_branch", default: false, null: false + t.text "ref" + t.index ["project_id"], name: "index_geo_repository_updated_events_on_project_id", using: :btree + t.index ["source"], name: "index_geo_repository_updated_events_on_source", using: :btree + end + + create_table_if_not_exists "geo_reset_checksum_events", id: :bigserial do |t| + t.integer "project_id", null: false + t.index ["project_id"], name: "index_geo_reset_checksum_events_on_project_id", using: :btree + end + + create_table_if_not_exists "geo_upload_deleted_events", id: :bigserial do |t| + t.integer "upload_id", null: false + t.string "file_path", null: false + t.integer "model_id", null: false + t.string "model_type", null: false + t.string "uploader", null: false + t.index ["upload_id"], name: "index_geo_upload_deleted_events_on_upload_id", using: :btree + end + + create_table_if_not_exists "gitlab_subscriptions", id: :bigserial do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.date "start_date" + t.date "end_date" + t.date "trial_ends_on" + t.integer "namespace_id" + t.integer "hosted_plan_id" + t.integer "max_seats_used", default: 0 + t.integer "seats", default: 0 + t.boolean "trial", default: false + t.index ["hosted_plan_id"], name: "index_gitlab_subscriptions_on_hosted_plan_id", using: :btree + t.index ["namespace_id"], name: "index_gitlab_subscriptions_on_namespace_id", unique: true, using: :btree + end + + create_table_if_not_exists "historical_data" do |t| + t.date "date", null: false + t.integer "active_user_count" + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table_if_not_exists "index_statuses" do |t| + t.integer "project_id", null: false + t.datetime "indexed_at" + t.text "note" + t.string "last_commit" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["project_id"], name: "index_index_statuses_on_project_id", unique: true, using: :btree + end + + create_table_if_not_exists "insights" do |t| + t.integer "namespace_id", null: false + t.integer "project_id", null: false + t.index ["namespace_id"], name: "index_insights_on_namespace_id", using: :btree + t.index ["project_id"], name: "index_insights_on_project_id", using: :btree + end + + create_table_if_not_exists "issue_links" do |t| + t.integer "source_id", null: false + t.integer "target_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.index %w[source_id target_id], name: "index_issue_links_on_source_id_and_target_id", unique: true, using: :btree + t.index ["source_id"], name: "index_issue_links_on_source_id", using: :btree + t.index ["target_id"], name: "index_issue_links_on_target_id", using: :btree + end + + create_table_if_not_exists "jira_connect_installations", id: :bigserial do |t| + t.string "client_key" + t.string "encrypted_shared_secret" + t.string "encrypted_shared_secret_iv" + t.string "base_url" + t.index ["client_key"], name: "index_jira_connect_installations_on_client_key", unique: true, using: :btree + end + + create_table_if_not_exists "jira_connect_subscriptions", id: :bigserial do |t| + t.datetime_with_timezone "created_at", null: false + t.bigint "jira_connect_installation_id", null: false + t.integer "namespace_id", null: false + t.datetime_with_timezone "updated_at", null: false + t.index %w[jira_connect_installation_id namespace_id], name: "idx_jira_connect_subscriptions_on_installation_id_namespace_id", unique: true, using: :btree + t.index ["jira_connect_installation_id"], name: "idx_jira_connect_subscriptions_on_installation_id", using: :btree + t.index ["namespace_id"], name: "index_jira_connect_subscriptions_on_namespace_id", using: :btree + end + + create_table_if_not_exists "ldap_group_links" do |t| + t.string "cn" + t.integer "group_access", null: false + t.integer "group_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.string "provider" + t.string "filter" + end + + create_table_if_not_exists "licenses" do |t| + t.text "data", null: false + t.datetime "created_at" + t.datetime "updated_at" + end + + create_table_if_not_exists "namespace_statistics" do |t| + t.integer "namespace_id", null: false + t.integer "shared_runners_seconds", default: 0, null: false + t.datetime "shared_runners_seconds_last_reset" + t.index ["namespace_id"], name: "index_namespace_statistics_on_namespace_id", unique: true, using: :btree + end + + create_table_if_not_exists "operations_feature_flag_scopes", id: :bigserial do |t| + t.bigint "feature_flag_id", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.boolean "active", null: false + t.string "environment_scope", default: "*", null: false + t.index %w[feature_flag_id environment_scope], name: "index_feature_flag_scopes_on_flag_id_and_environment_scope", unique: true, using: :btree + end + + create_table_if_not_exists "operations_feature_flags", id: :bigserial do |t| + t.integer "project_id", null: false + t.boolean "active", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.string "name", null: false + t.text "description" + t.index %w[project_id name], name: "index_operations_feature_flags_on_project_id_and_name", unique: true, using: :btree + end + + create_table_if_not_exists "operations_feature_flags_clients", id: :bigserial do |t| + t.integer "project_id", null: false + t.string "token", null: false + t.index %w[project_id token], name: "index_operations_feature_flags_clients_on_project_id_and_token", unique: true, using: :btree + end + + create_table_if_not_exists "packages_maven_metadata", id: :bigserial do |t| + t.bigint "package_id", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.string "app_group", null: false + t.string "app_name", null: false + t.string "app_version" + t.string "path", limit: 512, null: false + t.index %w[package_id path], name: "index_packages_maven_metadata_on_package_id_and_path", using: :btree + end + + create_table_if_not_exists "packages_package_files", id: :bigserial do |t| + t.bigint "package_id", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.bigint "size" + t.integer "file_type" + t.integer "file_store" + t.binary "file_md5" + t.binary "file_sha1" + t.string "file_name", null: false + t.text "file", null: false + t.index %w[package_id file_name], name: "index_packages_package_files_on_package_id_and_file_name", using: :btree + end + + create_table_if_not_exists "packages_packages", id: :bigserial do |t| + t.integer "project_id", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.string "name", null: false + t.string "version" + t.integer "package_type", limit: 2, null: false + t.index ["project_id"], name: "index_packages_packages_on_project_id", using: :btree + end + + create_table_if_not_exists "path_locks" do |t| + t.string "path", null: false + t.integer "project_id" + t.integer "user_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["path"], name: "index_path_locks_on_path", using: :btree + t.index ["project_id"], name: "index_path_locks_on_project_id", using: :btree + t.index ["user_id"], name: "index_path_locks_on_user_id", using: :btree + end + + create_table_if_not_exists "plans" do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "name" + t.string "title" + t.integer "active_pipelines_limit" + t.integer "pipeline_size_limit" + t.index ["name"], name: "index_plans_on_name", using: :btree + end + + create_table_if_not_exists "project_alerting_settings", primary_key: "project_id", id: :integer do |t| + t.string "encrypted_token", null: false + t.string "encrypted_token_iv", null: false + end + + create_table_if_not_exists "project_feature_usages", primary_key: "project_id", id: :integer do |t| + t.datetime "jira_dvcs_cloud_last_sync_at" + t.datetime "jira_dvcs_server_last_sync_at" + t.index %w[jira_dvcs_cloud_last_sync_at project_id], name: "idx_proj_feat_usg_on_jira_dvcs_cloud_last_sync_at_and_proj_id", where: "(jira_dvcs_cloud_last_sync_at IS NOT NULL)", using: :btree + t.index %w[jira_dvcs_server_last_sync_at project_id], name: "idx_proj_feat_usg_on_jira_dvcs_server_last_sync_at_and_proj_id", where: "(jira_dvcs_server_last_sync_at IS NOT NULL)", using: :btree + t.index ["project_id"], name: "index_project_feature_usages_on_project_id", using: :btree + end + + create_table_if_not_exists "project_incident_management_settings", primary_key: "project_id", id: :integer do |t| + t.boolean "create_issue", default: false, null: false + t.boolean "send_email", default: true, null: false + t.text "issue_template_key" + end + + create_table_if_not_exists "project_repository_states" do |t| + t.integer "project_id", null: false + t.binary "repository_verification_checksum" + t.binary "wiki_verification_checksum" + t.string "last_repository_verification_failure" + t.string "last_wiki_verification_failure" + t.datetime_with_timezone "repository_retry_at" + t.datetime_with_timezone "wiki_retry_at" + t.integer "repository_retry_count" + t.integer "wiki_retry_count" + t.datetime_with_timezone "last_repository_verification_ran_at" + t.datetime_with_timezone "last_wiki_verification_ran_at" + t.index ["last_repository_verification_failure"], name: "idx_repository_states_on_repository_failure_partial", where: "(last_repository_verification_failure IS NOT NULL)", using: :btree + t.index ["last_wiki_verification_failure"], name: "idx_repository_states_on_wiki_failure_partial", where: "(last_wiki_verification_failure IS NOT NULL)", using: :btree + t.index %w[project_id last_repository_verification_ran_at], name: "idx_repository_states_on_last_repository_verification_ran_at", where: "((repository_verification_checksum IS NOT NULL) AND (last_repository_verification_failure IS NULL))", using: :btree + t.index %w[project_id last_wiki_verification_ran_at], name: "idx_repository_states_on_last_wiki_verification_ran_at", where: "((wiki_verification_checksum IS NOT NULL) AND (last_wiki_verification_failure IS NULL))", using: :btree + t.index ["project_id"], name: "idx_repository_states_outdated_checksums", where: "(((repository_verification_checksum IS NULL) AND (last_repository_verification_failure IS NULL)) OR ((wiki_verification_checksum IS NULL) AND (last_wiki_verification_failure IS NULL)))", using: :btree + t.index ["project_id"], name: "index_project_repository_states_on_project_id", unique: true, using: :btree + end + + create_table_if_not_exists "project_tracing_settings", id: :bigserial do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "project_id", null: false + t.string "external_url", null: false + t.index ["project_id"], name: "index_project_tracing_settings_on_project_id", unique: true, using: :btree + end + + create_table_if_not_exists "prometheus_alert_events", id: :bigserial do |t| + t.integer "project_id", null: false + t.integer "prometheus_alert_id", null: false + t.datetime_with_timezone "started_at", null: false + t.datetime_with_timezone "ended_at" + t.integer "status", limit: 2 + t.string "payload_key" + t.index %w[project_id status], name: "index_prometheus_alert_events_on_project_id_and_status", using: :btree + t.index %w[prometheus_alert_id payload_key], name: "index_prometheus_alert_event_scoped_payload_key", unique: true, using: :btree + end + + create_table_if_not_exists "prometheus_alerts" do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.float "threshold", null: false + t.integer "operator", null: false + t.integer "environment_id", null: false + t.integer "project_id", null: false + t.integer "prometheus_metric_id", null: false + t.index ["environment_id"], name: "index_prometheus_alerts_on_environment_id", using: :btree + t.index %w[project_id prometheus_metric_id environment_id], name: "index_prometheus_alerts_metric_environment", unique: true, using: :btree + t.index ["prometheus_metric_id"], name: "index_prometheus_alerts_on_prometheus_metric_id", using: :btree + end + + create_table_if_not_exists "protected_branch_unprotect_access_levels" do |t| + t.integer "protected_branch_id", null: false + t.integer "access_level", default: 40 + t.integer "user_id" + t.integer "group_id" + t.index ["group_id"], name: "index_protected_branch_unprotect_access_levels_on_group_id", using: :btree + t.index ["protected_branch_id"], name: "index_protected_branch_unprotect_access", using: :btree + t.index ["user_id"], name: "index_protected_branch_unprotect_access_levels_on_user_id", using: :btree + end + + create_table_if_not_exists "protected_environment_deploy_access_levels" do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "access_level", default: 40 + t.integer "protected_environment_id", null: false + t.integer "user_id" + t.integer "group_id" + t.index ["group_id"], name: "index_protected_environment_deploy_access_levels_on_group_id", using: :btree + t.index ["protected_environment_id"], name: "index_protected_environment_deploy_access", using: :btree + t.index ["user_id"], name: "index_protected_environment_deploy_access_levels_on_user_id", using: :btree + end + + create_table_if_not_exists "protected_environments" do |t| + t.integer "project_id", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.string "name", null: false + t.index %w[project_id name], name: "index_protected_environments_on_project_id_and_name", unique: true, using: :btree + t.index ["project_id"], name: "index_protected_environments_on_project_id", using: :btree + end + + create_table_if_not_exists "push_rules" do |t| + t.string "force_push_regex" + t.string "delete_branch_regex" + t.string "commit_message_regex" + t.boolean "deny_delete_tag" + t.integer "project_id" + t.datetime "created_at" + t.datetime "updated_at" + t.string "author_email_regex" + t.boolean "member_check", default: false, null: false + t.string "file_name_regex" + t.boolean "is_sample", default: false + t.integer "max_file_size", default: 0, null: false + t.boolean "prevent_secrets", default: false, null: false + t.string "branch_name_regex" + t.boolean "reject_unsigned_commits" + t.boolean "commit_committer_check" + t.boolean "regexp_uses_re2", default: true + t.string "commit_message_negative_regex" + t.index ["is_sample"], name: "index_push_rules_on_is_sample", where: "is_sample", using: :btree + t.index ["project_id"], name: "index_push_rules_on_project_id", using: :btree + end + + create_table_if_not_exists "reviews", id: :bigserial do |t| + t.integer "author_id" + t.integer "merge_request_id", null: false + t.integer "project_id", null: false + t.datetime_with_timezone "created_at", null: false + t.index ["author_id"], name: "index_reviews_on_author_id", using: :btree + t.index ["merge_request_id"], name: "index_reviews_on_merge_request_id", using: :btree + t.index ["project_id"], name: "index_reviews_on_project_id", using: :btree + end + + create_table_if_not_exists "saml_providers" do |t| + t.integer "group_id", null: false + t.boolean "enabled", null: false + t.string "certificate_fingerprint", null: false + t.string "sso_url", null: false + t.boolean "enforced_sso", default: false, null: false + t.boolean "enforced_group_managed_accounts", default: false, null: false + t.index ["group_id"], name: "index_saml_providers_on_group_id", using: :btree + end + + create_table_if_not_exists "scim_oauth_access_tokens" do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "group_id", null: false + t.string "token_encrypted", null: false + t.index %w[group_id token_encrypted], name: "index_scim_oauth_access_tokens_on_group_id_and_token_encrypted", unique: true, using: :btree + end + + create_table_if_not_exists "slack_integrations" do |t| + t.integer "service_id", null: false + t.string "team_id", null: false + t.string "team_name", null: false + t.string "alias", null: false + t.string "user_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["service_id"], name: "index_slack_integrations_on_service_id", using: :btree + t.index %w[team_id alias], name: "index_slack_integrations_on_team_id_and_alias", unique: true, using: :btree + end + + create_table_if_not_exists "smartcard_identities", id: :bigserial do |t| + t.integer "user_id", null: false + t.string "subject", null: false + t.string "issuer", null: false + t.index %w[subject issuer], name: "index_smartcard_identities_on_subject_and_issuer", unique: true, using: :btree + t.index ["user_id"], name: "index_smartcard_identities_on_user_id", using: :btree + end + + create_table_if_not_exists "software_license_policies" do |t| + t.integer "project_id", null: false + t.integer "software_license_id", null: false + t.integer "approval_status", default: 0, null: false + t.index %w[project_id software_license_id], name: "index_software_license_policies_unique_per_project", unique: true, using: :btree + t.index ["software_license_id"], name: "index_software_license_policies_on_software_license_id", using: :btree + end + + create_table_if_not_exists "software_licenses" do |t| + t.string "name", null: false + t.index ["name"], name: "index_software_licenses_on_name", using: :btree + end + + create_table_if_not_exists "users_ops_dashboard_projects", id: :bigserial do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "user_id", null: false + t.integer "project_id", null: false + t.index ["project_id"], name: "index_users_ops_dashboard_projects_on_project_id", using: :btree + t.index %w[user_id project_id], name: "index_users_ops_dashboard_projects_on_user_id_and_project_id", unique: true, using: :btree + end + + create_table_if_not_exists "vulnerability_feedback" do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "feedback_type", limit: 2, null: false + t.integer "category", limit: 2, null: false + t.integer "project_id", null: false + t.integer "author_id", null: false + t.integer "pipeline_id" + t.integer "issue_id" + t.string "project_fingerprint", limit: 40, null: false + t.integer "merge_request_id" + t.index ["author_id"], name: "index_vulnerability_feedback_on_author_id", using: :btree + t.index ["issue_id"], name: "index_vulnerability_feedback_on_issue_id", using: :btree + t.index ["merge_request_id"], name: "index_vulnerability_feedback_on_merge_request_id", using: :btree + t.index ["pipeline_id"], name: "index_vulnerability_feedback_on_pipeline_id", using: :btree + t.index %w[project_id category feedback_type project_fingerprint], name: "vulnerability_feedback_unique_idx", unique: true, using: :btree + end + + create_table_if_not_exists "vulnerability_identifiers", id: :bigserial do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "project_id", null: false + t.binary "fingerprint", null: false + t.string "external_type", null: false + t.string "external_id", null: false + t.string "name", null: false + t.text "url" + t.index %w[project_id fingerprint], name: "index_vulnerability_identifiers_on_project_id_and_fingerprint", unique: true, using: :btree + end + + create_table_if_not_exists "vulnerability_occurrence_identifiers", id: :bigserial do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.bigint "occurrence_id", null: false + t.bigint "identifier_id", null: false + t.index ["identifier_id"], name: "index_vulnerability_occurrence_identifiers_on_identifier_id", using: :btree + t.index %w[occurrence_id identifier_id], name: "index_vulnerability_occurrence_identifiers_on_unique_keys", unique: true, using: :btree + end + + create_table_if_not_exists "vulnerability_occurrence_pipelines", id: :bigserial do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.bigint "occurrence_id", null: false + t.integer "pipeline_id", null: false + t.index %w[occurrence_id pipeline_id], name: "vulnerability_occurrence_pipelines_on_unique_keys", unique: true, using: :btree + t.index ["pipeline_id"], name: "index_vulnerability_occurrence_pipelines_on_pipeline_id", using: :btree + end + + create_table_if_not_exists "vulnerability_occurrences", id: :bigserial do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "severity", limit: 2, null: false + t.integer "confidence", limit: 2, null: false + t.integer "report_type", limit: 2, null: false + t.integer "project_id", null: false + t.bigint "scanner_id", null: false + t.bigint "primary_identifier_id", null: false + t.binary "project_fingerprint", null: false + t.binary "location_fingerprint", null: false + t.string "uuid", limit: 36, null: false + t.string "name", null: false + t.string "metadata_version", null: false + t.text "raw_metadata", null: false + t.index ["primary_identifier_id"], name: "index_vulnerability_occurrences_on_primary_identifier_id", using: :btree + t.index %w[project_id primary_identifier_id location_fingerprint scanner_id], name: "index_vulnerability_occurrences_on_unique_keys", unique: true, using: :btree + t.index ["scanner_id"], name: "index_vulnerability_occurrences_on_scanner_id", using: :btree + t.index ["uuid"], name: "index_vulnerability_occurrences_on_uuid", unique: true, using: :btree + end + + create_table_if_not_exists "vulnerability_scanners", id: :bigserial do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "project_id", null: false + t.string "external_id", null: false + t.string "name", null: false + t.index %w[project_id external_id], name: "index_vulnerability_scanners_on_project_id_and_external_id", unique: true, using: :btree + end + + create_table_if_not_exists "dependency_proxy_blobs", id: :serial do |t| + t.datetime_with_timezone "created_at", null: false + t.text "file", null: false + t.string "file_name", null: false + t.integer "file_store" + t.integer "group_id", null: false + t.bigint "size" + t.datetime_with_timezone "updated_at", null: false + t.index %w[group_id file_name], name: "index_dependency_proxy_blobs_on_group_id_and_file_name", using: :btree + end + + create_table_if_not_exists "dependency_proxy_group_settings", id: :serial do |t| + t.datetime_with_timezone "created_at", null: false + t.boolean "enabled", default: false, null: false + t.integer "group_id", null: false + t.datetime_with_timezone "updated_at", null: false + t.index ["group_id"], name: "index_dependency_proxy_group_settings_on_group_id", using: :btree + end + end + + def remove_tables + drop_table_if_exists "approval_merge_request_rule_sources" + drop_table_if_exists "approval_merge_request_rules" + drop_table_if_exists "approval_merge_request_rules_approved_approvers" + drop_table_if_exists "approval_merge_request_rules_groups" + drop_table_if_exists "approval_merge_request_rules_users" + drop_table_if_exists "approval_project_rules" + drop_table_if_exists "approval_project_rules_groups" + drop_table_if_exists "approval_project_rules_users" + drop_table_if_exists "approvals" + drop_table_if_exists "approver_groups" + drop_table_if_exists "approvers" + drop_table_if_exists "board_assignees" + drop_table_if_exists "board_labels" + drop_table_if_exists "ci_sources_pipelines" + drop_table_if_exists "design_management_designs_versions" + drop_table_if_exists "design_management_versions" + drop_table_if_exists "design_management_designs" + drop_table_if_exists "draft_notes" + drop_table_if_exists "elasticsearch_indexed_namespaces" + drop_table_if_exists "elasticsearch_indexed_projects" + drop_table_if_exists "epic_issues" + drop_table_if_exists "epic_metrics" + drop_table_if_exists "epics" + drop_table_if_exists "geo_cache_invalidation_events" + drop_table_if_exists "geo_event_log" + drop_table_if_exists "geo_hashed_storage_attachments_events" + drop_table_if_exists "geo_hashed_storage_migrated_events" + drop_table_if_exists "geo_job_artifact_deleted_events" + drop_table_if_exists "geo_lfs_object_deleted_events" + drop_table_if_exists "geo_node_namespace_links" + drop_table_if_exists "geo_node_statuses" + drop_table_if_exists "geo_nodes" + drop_table_if_exists "geo_repositories_changed_events" + drop_table_if_exists "geo_repository_created_events" + drop_table_if_exists "geo_repository_deleted_events" + drop_table_if_exists "geo_repository_renamed_events" + drop_table_if_exists "geo_repository_updated_events" + drop_table_if_exists "geo_reset_checksum_events" + drop_table_if_exists "geo_upload_deleted_events" + drop_table_if_exists "gitlab_subscriptions" + drop_table_if_exists "historical_data" + drop_table_if_exists "index_statuses" + drop_table_if_exists "insights" + drop_table_if_exists "issue_links" + drop_table_if_exists "jira_connect_subscriptions" + drop_table_if_exists "jira_connect_installations" + drop_table_if_exists "ldap_group_links" + drop_table_if_exists "licenses" + drop_table_if_exists "namespace_statistics" + drop_table_if_exists "operations_feature_flag_scopes" + drop_table_if_exists "operations_feature_flags" + drop_table_if_exists "operations_feature_flags_clients" + drop_table_if_exists "packages_maven_metadata" + drop_table_if_exists "packages_package_files" + drop_table_if_exists "packages_packages" + drop_table_if_exists "path_locks" + drop_table_if_exists "plans" + drop_table_if_exists "project_alerting_settings" + drop_table_if_exists "project_feature_usages" + drop_table_if_exists "project_incident_management_settings" + drop_table_if_exists "project_repository_states" + drop_table_if_exists "project_tracing_settings" + drop_table_if_exists "prometheus_alert_events" + drop_table_if_exists "prometheus_alerts" + drop_table_if_exists "protected_branch_unprotect_access_levels" + drop_table_if_exists "protected_environment_deploy_access_levels" + drop_table_if_exists "protected_environments" + drop_table_if_exists "push_rules" + drop_table_if_exists "reviews" + drop_table_if_exists "saml_providers" + drop_table_if_exists "scim_oauth_access_tokens" + drop_table_if_exists "slack_integrations" + drop_table_if_exists "smartcard_identities" + drop_table_if_exists "software_license_policies" + drop_table_if_exists "software_licenses" + drop_table_if_exists "users_ops_dashboard_projects" + drop_table_if_exists "vulnerability_feedback" + drop_table_if_exists "vulnerability_identifiers" + drop_table_if_exists "vulnerability_occurrence_identifiers" + drop_table_if_exists "vulnerability_occurrence_pipelines" + drop_table_if_exists "vulnerability_occurrences" + drop_table_if_exists "vulnerability_scanners" + drop_table_if_exists "dependency_proxy_blobs" + drop_table_if_exists "dependency_proxy_group_settings" + end + + def add_missing_foreign_keys + add_concurrent_foreign_key("application_settings", "namespaces", column: "custom_project_templates_group_id", name: "fk_rails_b53e481273", on_delete: :nullify) + add_concurrent_foreign_key("application_settings", "projects", column: "file_template_project_id", name: "fk_ec757bd087", on_delete: :nullify) + add_concurrent_foreign_key("approval_merge_request_rule_sources", "approval_merge_request_rules", column: "approval_merge_request_rule_id", name: "fk_rails_e605a04f76", on_delete: :cascade) + add_concurrent_foreign_key("approval_merge_request_rule_sources", "approval_project_rules", column: "approval_project_rule_id", name: "fk_rails_64e8ed3c7e", on_delete: :cascade) + add_concurrent_foreign_key("approval_merge_request_rules", "merge_requests", column: "merge_request_id", name: "fk_rails_004ce82224", on_delete: :cascade) + add_concurrent_foreign_key("approval_merge_request_rules_approved_approvers", "approval_merge_request_rules", column: "approval_merge_request_rule_id", name: "fk_rails_6577725edb", on_delete: :cascade) + add_concurrent_foreign_key("approval_merge_request_rules_approved_approvers", "users", column: "user_id", name: "fk_rails_8dc94cff4d", on_delete: :cascade) + add_concurrent_foreign_key("approval_merge_request_rules_groups", "approval_merge_request_rules", column: "approval_merge_request_rule_id", name: "fk_rails_5b2ecf6139", on_delete: :cascade) + add_concurrent_foreign_key("approval_merge_request_rules_groups", "namespaces", column: "group_id", name: "fk_rails_2020a7124a", on_delete: :cascade) + add_concurrent_foreign_key("approval_merge_request_rules_users", "approval_merge_request_rules", column: "approval_merge_request_rule_id", name: "fk_rails_80e6801803", on_delete: :cascade) + add_concurrent_foreign_key("approval_merge_request_rules_users", "users", column: "user_id", name: "fk_rails_bc8972fa55", on_delete: :cascade) + add_concurrent_foreign_key("approval_project_rules", "projects", column: "project_id", name: "fk_rails_5fb4dd100b", on_delete: :cascade) + add_concurrent_foreign_key("approval_project_rules_groups", "approval_project_rules", column: "approval_project_rule_id", name: "fk_rails_9071e863d1", on_delete: :cascade) + add_concurrent_foreign_key("approval_project_rules_groups", "namespaces", column: "group_id", name: "fk_rails_396841e79e", on_delete: :cascade) + add_concurrent_foreign_key("approval_project_rules_users", "approval_project_rules", column: "approval_project_rule_id", name: "fk_rails_b9e9394efb", on_delete: :cascade) + add_concurrent_foreign_key("approval_project_rules_users", "users", column: "user_id", name: "fk_rails_f365da8250", on_delete: :cascade) + add_concurrent_foreign_key("approvals", "merge_requests", column: "merge_request_id", name: "fk_310d714958", on_delete: :cascade) + add_concurrent_foreign_key("approver_groups", "namespaces", column: "group_id", name: "fk_rails_1cdcbd7723", on_delete: :cascade) + add_concurrent_foreign_key("board_assignees", "boards", column: "board_id", name: "fk_rails_3f6f926bd5", on_delete: :cascade) + add_concurrent_foreign_key("board_assignees", "users", column: "assignee_id", name: "fk_rails_1c0ff59e82", on_delete: :cascade) + add_concurrent_foreign_key("board_labels", "boards", column: "board_id", name: "fk_rails_9374a16edd", on_delete: :cascade) + add_concurrent_foreign_key("board_labels", "labels", column: "label_id", name: "fk_rails_362b0600a3", on_delete: :cascade) + add_concurrent_foreign_key("ci_sources_pipelines", "ci_builds", column: "source_job_id", name: "fk_be5624bf37", on_delete: :cascade) + add_concurrent_foreign_key("ci_sources_pipelines", "ci_pipelines", column: "pipeline_id", name: "fk_e1bad85861", on_delete: :cascade) + add_concurrent_foreign_key("ci_sources_pipelines", "ci_pipelines", column: "source_pipeline_id", name: "fk_d4e29af7d7", on_delete: :cascade) + add_concurrent_foreign_key("ci_sources_pipelines", "projects", column: "source_project_id", name: "fk_acd9737679", on_delete: :cascade) + add_concurrent_foreign_key("ci_sources_pipelines", "projects", column: "project_id", name: "fk_1e53c97c0a", on_delete: :cascade) + add_concurrent_foreign_key("design_management_designs", "issues", column: "issue_id", name: "fk_rails_bfe283ec3c", on_delete: :cascade) + add_concurrent_foreign_key("design_management_designs", "projects", column: "project_id", name: "fk_rails_4bb1073360", on_delete: :cascade) + add_concurrent_foreign_key("design_management_designs_versions", "design_management_designs", column: "design_id", on_delete: :cascade) + add_concurrent_foreign_key("design_management_designs_versions", "design_management_versions", column: "version_id", on_delete: :cascade) + add_concurrent_foreign_key("draft_notes", "merge_requests", column: "merge_request_id", name: "fk_rails_e753681674", on_delete: :cascade) + add_concurrent_foreign_key("draft_notes", "users", column: "author_id", name: "fk_rails_2a8dac9901", on_delete: :cascade) + add_concurrent_foreign_key("elasticsearch_indexed_namespaces", "namespaces", column: "namespace_id", name: "fk_rails_bdcf044f37", on_delete: :cascade) + add_concurrent_foreign_key("elasticsearch_indexed_projects", "projects", column: "project_id", name: "fk_rails_bd13bbdc3d", on_delete: :cascade) + add_concurrent_foreign_key("epic_issues", "epics", column: "epic_id", name: "fk_rails_5d942936b4", on_delete: :cascade) + add_concurrent_foreign_key("epic_issues", "issues", column: "issue_id", name: "fk_rails_4209981af6", on_delete: :cascade) + add_concurrent_foreign_key("epic_metrics", "epics", column: "epic_id", name: "fk_rails_d071904753", on_delete: :cascade) + add_concurrent_foreign_key("epics", "epics", column: "parent_id", name: "fk_25b99c1be3", on_delete: :cascade) + add_concurrent_foreign_key("epics", "milestones", column: "milestone_id", name: "fk_rails_1bf671ebb7", on_delete: :nullify) + add_concurrent_foreign_key("epics", "namespaces", column: "group_id", name: "fk_f081aa4489", on_delete: :cascade) + add_concurrent_foreign_key("epics", "users", column: "assignee_id", name: "fk_dccd3f98fc", on_delete: :nullify) + add_concurrent_foreign_key("epics", "users", column: "author_id", name: "fk_3654b61b03", on_delete: :cascade) + add_concurrent_foreign_key("epics", "users", column: "closed_by_id", name: "fk_aa5798e761", on_delete: :nullify) + add_concurrent_foreign_key("geo_event_log", "geo_cache_invalidation_events", column: "cache_invalidation_event_id", name: "fk_42c3b54bed", on_delete: :cascade) + add_concurrent_foreign_key("geo_event_log", "geo_hashed_storage_migrated_events", column: "hashed_storage_migrated_event_id", name: "fk_27548c6db3", on_delete: :cascade) + add_concurrent_foreign_key("geo_event_log", "geo_job_artifact_deleted_events", column: "job_artifact_deleted_event_id", name: "fk_176d3fbb5d", on_delete: :cascade) + add_concurrent_foreign_key("geo_event_log", "geo_lfs_object_deleted_events", column: "lfs_object_deleted_event_id", name: "fk_d5af95fcd9", on_delete: :cascade) + add_concurrent_foreign_key("geo_event_log", "geo_repositories_changed_events", column: "repositories_changed_event_id", name: "fk_4a99ebfd60", on_delete: :cascade) + add_concurrent_foreign_key("geo_event_log", "geo_repository_created_events", column: "repository_created_event_id", name: "fk_9b9afb1916", on_delete: :cascade) + add_concurrent_foreign_key("geo_event_log", "geo_repository_deleted_events", column: "repository_deleted_event_id", name: "fk_c4b1c1f66e", on_delete: :cascade) + add_concurrent_foreign_key("geo_event_log", "geo_repository_renamed_events", column: "repository_renamed_event_id", name: "fk_86c84214ec", on_delete: :cascade) + add_concurrent_foreign_key("geo_event_log", "geo_repository_updated_events", column: "repository_updated_event_id", name: "fk_78a6492f68", on_delete: :cascade) + add_concurrent_foreign_key("geo_event_log", "geo_reset_checksum_events", column: "reset_checksum_event_id", name: "fk_cff7185ad2", on_delete: :cascade) + add_concurrent_foreign_key("geo_event_log", "geo_upload_deleted_events", column: "upload_deleted_event_id", name: "fk_c1f241c70d", on_delete: :cascade) + add_concurrent_foreign_key("geo_hashed_storage_attachments_events", "projects", column: "project_id", name: "fk_rails_d496b088e9", on_delete: :cascade) + add_concurrent_foreign_key("geo_hashed_storage_migrated_events", "projects", column: "project_id", name: "fk_rails_687ed7d7c5", on_delete: :cascade) + add_concurrent_foreign_key("geo_node_namespace_links", "geo_nodes", column: "geo_node_id", name: "fk_rails_546bf08d3e", on_delete: :cascade) + add_concurrent_foreign_key("geo_node_namespace_links", "namespaces", column: "namespace_id", name: "fk_rails_41ff5fb854", on_delete: :cascade) + add_concurrent_foreign_key("geo_node_statuses", "geo_nodes", column: "geo_node_id", name: "fk_rails_0ecc699c2a", on_delete: :cascade) + add_concurrent_foreign_key("geo_repositories_changed_events", "geo_nodes", column: "geo_node_id", name: "fk_rails_75ec0fefcc", on_delete: :cascade) + add_concurrent_foreign_key("geo_repository_created_events", "projects", column: "project_id", name: "fk_rails_1f49e46a61", on_delete: :cascade) + add_concurrent_foreign_key("geo_repository_renamed_events", "projects", column: "project_id", name: "fk_rails_4e6524febb", on_delete: :cascade) + add_concurrent_foreign_key("geo_repository_updated_events", "projects", column: "project_id", name: "fk_rails_2b70854c08", on_delete: :cascade) + add_concurrent_foreign_key("geo_reset_checksum_events", "projects", column: "project_id", name: "fk_rails_910a06f12b", on_delete: :cascade) + add_concurrent_foreign_key("gitlab_subscriptions", "namespaces", column: "namespace_id", name: "fk_e2595d00a1", on_delete: :cascade) + add_concurrent_foreign_key("gitlab_subscriptions", "plans", column: "hosted_plan_id", name: "fk_bd0c4019c3", on_delete: :cascade) + add_concurrent_foreign_key("identities", "saml_providers", column: "saml_provider_id", name: "fk_aade90f0fc", on_delete: :cascade) + add_concurrent_foreign_key("index_statuses", "projects", column: "project_id", name: "fk_74b2492545", on_delete: :cascade) + add_concurrent_foreign_key("insights", "namespaces", column: "namespace_id", name: "fk_rails_5c4391f60a", on_delete: nil) + add_concurrent_foreign_key("insights", "projects", column: "project_id", name: "fk_rails_f36fda3932", on_delete: nil) + add_concurrent_foreign_key("issue_links", "issues", column: "source_id", name: "fk_c900194ff2", on_delete: :cascade) + add_concurrent_foreign_key("issue_links", "issues", column: "target_id", name: "fk_e71bb44f1f", on_delete: :cascade) + add_concurrent_foreign_key("lists", "milestones", column: "milestone_id", name: "fk_rails_baed5f39b7", on_delete: :cascade) + add_concurrent_foreign_key("lists", "users", column: "user_id", name: "fk_d6cf4279f7", on_delete: :cascade) + add_concurrent_foreign_key("namespace_statistics", "namespaces", column: "namespace_id", name: "fk_rails_0062050394", on_delete: :cascade) + add_concurrent_foreign_key("namespaces", "namespaces", column: "custom_project_templates_group_id", name: "fk_e7a0b20a6b", on_delete: :nullify) + add_concurrent_foreign_key("namespaces", "plans", column: "plan_id", name: "fk_fdd12e5b80", on_delete: :nullify) + add_concurrent_foreign_key("namespaces", "projects", column: "file_template_project_id", name: "fk_319256d87a", on_delete: :nullify) + add_concurrent_foreign_key("notes", "reviews", column: "review_id", name: "fk_2e82291620", on_delete: :nullify) + add_concurrent_foreign_key("operations_feature_flag_scopes", "operations_feature_flags", column: "feature_flag_id", name: "fk_rails_a50a04d0a4", on_delete: :cascade) + add_concurrent_foreign_key("operations_feature_flags", "projects", column: "project_id", name: "fk_rails_648e241be7", on_delete: :cascade) + add_concurrent_foreign_key("operations_feature_flags_clients", "projects", column: "project_id", name: "fk_rails_6650ed902c", on_delete: :cascade) + add_concurrent_foreign_key("packages_maven_metadata", "packages_packages", column: "package_id", name: "fk_be88aed360", on_delete: :cascade) + add_concurrent_foreign_key("packages_package_files", "packages_packages", column: "package_id", name: "fk_86f0f182f8", on_delete: :cascade) + add_concurrent_foreign_key("packages_packages", "projects", column: "project_id", name: "fk_rails_e1ac527425", on_delete: :cascade) + add_concurrent_foreign_key("path_locks", "projects", column: "project_id", name: "fk_5265c98f24", on_delete: :cascade) + add_concurrent_foreign_key("path_locks", "users", column: "user_id", name: "fk_rails_762cdcf942", on_delete: nil) + add_concurrent_foreign_key("project_alerting_settings", "projects", column: "project_id", name: "fk_rails_27a84b407d", on_delete: :cascade) + add_concurrent_foreign_key("project_feature_usages", "projects", column: "project_id", name: "fk_rails_c22a50024b", on_delete: :cascade) + add_concurrent_foreign_key("project_incident_management_settings", "projects", column: "project_id", name: "fk_rails_9c2ea1b7dd", on_delete: :cascade) + add_concurrent_foreign_key("project_repository_states", "projects", column: "project_id", name: "fk_rails_0f2298ca8a", on_delete: :cascade) + add_concurrent_foreign_key("project_tracing_settings", "projects", column: "project_id", name: "fk_rails_fe56f57fc6", on_delete: :cascade) + add_concurrent_foreign_key("prometheus_alert_events", "projects", column: "project_id", name: "fk_rails_4675865839", on_delete: :cascade) + add_concurrent_foreign_key("prometheus_alert_events", "prometheus_alerts", column: "prometheus_alert_id", name: "fk_rails_106f901176", on_delete: :cascade) + add_concurrent_foreign_key("prometheus_alerts", "environments", column: "environment_id", name: "fk_rails_6d9b283465", on_delete: :cascade) + add_concurrent_foreign_key("prometheus_alerts", "projects", column: "project_id", name: "fk_rails_f0e8db86aa", on_delete: :cascade) + add_concurrent_foreign_key("prometheus_alerts", "prometheus_metrics", column: "prometheus_metric_id", name: "fk_rails_e6351447ec", on_delete: :cascade) + add_concurrent_foreign_key("protected_branch_merge_access_levels", "namespaces", column: "group_id", name: "fk_98f3d044fe", on_delete: :cascade) + add_concurrent_foreign_key("protected_branch_merge_access_levels", "users", column: "user_id", name: "fk_rails_5ffb4f3590", on_delete: nil) + add_concurrent_foreign_key("protected_branch_push_access_levels", "namespaces", column: "group_id", name: "fk_7111b68cdb", on_delete: :cascade) + add_concurrent_foreign_key("protected_branch_push_access_levels", "users", column: "user_id", name: "fk_rails_8dcb712d65", on_delete: nil) + add_concurrent_foreign_key("protected_branch_unprotect_access_levels", "namespaces", column: "group_id", name: "fk_rails_5be1abfc25", on_delete: :cascade) + add_concurrent_foreign_key("protected_branch_unprotect_access_levels", "protected_branches", column: "protected_branch_id", name: "fk_rails_e9eb8dc025", on_delete: :cascade) + add_concurrent_foreign_key("protected_branch_unprotect_access_levels", "users", column: "user_id", name: "fk_rails_2d2aba21ef", on_delete: :cascade) + add_concurrent_foreign_key("protected_environment_deploy_access_levels", "namespaces", column: "group_id", name: "fk_rails_45cc02a931", on_delete: :cascade) + add_concurrent_foreign_key("protected_environment_deploy_access_levels", "protected_environments", column: "protected_environment_id", name: "fk_rails_898a13b650", on_delete: :cascade) + add_concurrent_foreign_key("protected_environment_deploy_access_levels", "users", column: "user_id", name: "fk_rails_5b9f6970fe", on_delete: :cascade) + add_concurrent_foreign_key("protected_environments", "projects", column: "project_id", name: "fk_rails_a354313d11", on_delete: :cascade) + add_concurrent_foreign_key("push_rules", "projects", column: "project_id", name: "fk_83b29894de", on_delete: :cascade) + add_concurrent_foreign_key("resource_label_events", "epics", column: "epic_id", name: "fk_rails_75efb0a653", on_delete: :cascade) + add_concurrent_foreign_key("reviews", "merge_requests", column: "merge_request_id", name: "fk_rails_5ca11d8c31", on_delete: :cascade) + add_concurrent_foreign_key("reviews", "projects", column: "project_id", name: "fk_rails_64798be025", on_delete: :cascade) + add_concurrent_foreign_key("reviews", "users", column: "author_id", name: "fk_rails_29e6f859c4", on_delete: :nullify) + add_concurrent_foreign_key("saml_providers", "namespaces", column: "group_id", name: "fk_rails_306d459be7", on_delete: :cascade) + add_concurrent_foreign_key("scim_oauth_access_tokens", "namespaces", column: "group_id", name: "fk_rails_c84404fb6c", on_delete: :cascade) + add_concurrent_foreign_key("slack_integrations", "services", column: "service_id", name: "fk_rails_73db19721a", on_delete: :cascade) + add_concurrent_foreign_key("smartcard_identities", "users", column: "user_id", name: "fk_rails_4689f889a9", on_delete: :cascade) + add_concurrent_foreign_key("software_license_policies", "projects", column: "project_id", name: "fk_rails_87b2247ce5", on_delete: :cascade) + add_concurrent_foreign_key("software_license_policies", "software_licenses", column: "software_license_id", name: "fk_rails_7a7a2a92de", on_delete: :cascade) + add_concurrent_foreign_key("users", "namespaces", column: "managing_group_id", name: "fk_a4b8fefe3e", on_delete: :nullify) + add_concurrent_foreign_key("users_ops_dashboard_projects", "projects", column: "project_id", name: "fk_rails_9b4ebf005b", on_delete: :cascade) + add_concurrent_foreign_key("users_ops_dashboard_projects", "users", column: "user_id", name: "fk_rails_220a0562db", on_delete: :cascade) + add_concurrent_foreign_key("vulnerability_feedback", "ci_pipelines", column: "pipeline_id", name: "fk_rails_20976e6fd9", on_delete: :nullify) + add_concurrent_foreign_key("vulnerability_feedback", "issues", column: "issue_id", name: "fk_rails_8c77e5891a", on_delete: :nullify) + add_concurrent_foreign_key("vulnerability_feedback", "merge_requests", column: "merge_request_id", name: "fk_563ff1912e", on_delete: :nullify) + add_concurrent_foreign_key("vulnerability_feedback", "projects", column: "project_id", name: "fk_rails_debd54e456", on_delete: :cascade) + add_concurrent_foreign_key("vulnerability_feedback", "users", column: "author_id", name: "fk_rails_472f69b043", on_delete: :cascade) + add_concurrent_foreign_key("vulnerability_identifiers", "projects", column: "project_id", name: "fk_rails_a67a16c885", on_delete: :cascade) + add_concurrent_foreign_key("vulnerability_occurrence_identifiers", "vulnerability_identifiers", column: "identifier_id", name: "fk_rails_be2e49e1d0", on_delete: :cascade) + add_concurrent_foreign_key("vulnerability_occurrence_identifiers", "vulnerability_occurrences", column: "occurrence_id", name: "fk_rails_e4ef6d027c", on_delete: :cascade) + add_concurrent_foreign_key("vulnerability_occurrence_pipelines", "ci_pipelines", column: "pipeline_id", name: "fk_rails_6421e35d7d", on_delete: :cascade) + add_concurrent_foreign_key("vulnerability_occurrence_pipelines", "vulnerability_occurrences", column: "occurrence_id", name: "fk_rails_dc3ae04693", on_delete: :cascade) + add_concurrent_foreign_key("vulnerability_occurrences", "projects", column: "project_id", name: "fk_rails_90fed4faba", on_delete: :cascade) + add_concurrent_foreign_key("vulnerability_occurrences", "vulnerability_identifiers", column: "primary_identifier_id", name: "fk_rails_c8661a61eb", on_delete: :cascade) + add_concurrent_foreign_key("vulnerability_occurrences", "vulnerability_scanners", column: "scanner_id", name: "fk_rails_bf5b788ca7", on_delete: :cascade) + add_concurrent_foreign_key("vulnerability_scanners", "projects", column: "project_id", name: "fk_rails_5c9d42a221", on_delete: :cascade) + add_concurrent_foreign_key("dependency_proxy_blobs", "namespaces", column: "group_id", on_delete: :cascade) + add_concurrent_foreign_key("dependency_proxy_group_settings", "namespaces", column: "group_id", on_delete: :cascade) + add_concurrent_foreign_key("jira_connect_subscriptions", "jira_connect_installations", column: "jira_connect_installation_id", on_delete: :cascade) + add_concurrent_foreign_key("jira_connect_subscriptions", "namespaces", column: "namespace_id", on_delete: :cascade) + + remove_foreign_key_without_error("protected_tag_create_access_levels", column: :group_id) + add_concurrent_foreign_key("protected_tag_create_access_levels", "namespaces", column: :group_id, name: "fk_b4eb82fe3c", on_delete: :cascade) + end + + def remove_foreign_keys + remove_foreign_key_without_error("application_settings", column: "custom_project_templates_group_id") + remove_foreign_key_without_error("application_settings", column: "file_template_project_id") + remove_foreign_key_without_error("approval_merge_request_rule_sources", column: "approval_merge_request_rule_id") + remove_foreign_key_without_error("approval_merge_request_rule_sources", column: "approval_project_rule_id") + remove_foreign_key_without_error("approval_merge_request_rules", column: "merge_request_id") + remove_foreign_key_without_error("approval_merge_request_rules_approved_approvers", column: "approval_merge_request_rule_id") + remove_foreign_key_without_error("approval_merge_request_rules_approved_approvers", column: "user_id") + remove_foreign_key_without_error("approval_merge_request_rules_groups", column: "approval_merge_request_rule_id") + remove_foreign_key_without_error("approval_merge_request_rules_groups", column: "group_id") + remove_foreign_key_without_error("approval_merge_request_rules_users", column: "approval_merge_request_rule_id") + remove_foreign_key_without_error("approval_merge_request_rules_users", column: "user_id") + remove_foreign_key_without_error("approval_project_rules", column: "project_id") + remove_foreign_key_without_error("approval_project_rules_groups", column: "approval_project_rule_id") + remove_foreign_key_without_error("approval_project_rules_groups", column: "group_id") + remove_foreign_key_without_error("approval_project_rules_users", column: "approval_project_rule_id") + remove_foreign_key_without_error("approval_project_rules_users", column: "user_id") + remove_foreign_key_without_error("approvals", column: "merge_request_id") + remove_foreign_key_without_error("approver_groups", column: "group_id") + remove_foreign_key_without_error("board_assignees", column: "board_id") + remove_foreign_key_without_error("board_assignees", column: "assignee_id") + remove_foreign_key_without_error("board_labels", column: "board_id") + remove_foreign_key_without_error("board_labels", column: "label_id") + remove_foreign_key_without_error("ci_sources_pipelines", column: "source_job_id") + remove_foreign_key_without_error("ci_sources_pipelines", column: "pipeline_id") + remove_foreign_key_without_error("ci_sources_pipelines", column: "source_pipeline_id") + remove_foreign_key_without_error("ci_sources_pipelines", column: "source_project_id") + remove_foreign_key_without_error("ci_sources_pipelines", column: "project_id") + remove_foreign_key_without_error("design_management_designs", column: "issue_id") + remove_foreign_key_without_error("design_management_designs", column: "project_id") + remove_foreign_key_without_error("design_management_versions", column: "design_management_design_id") + remove_foreign_key_without_error("draft_notes", column: "merge_request_id") + remove_foreign_key_without_error("draft_notes", column: "author_id") + remove_foreign_key_without_error("elasticsearch_indexed_namespaces", column: "namespace_id") + remove_foreign_key_without_error("elasticsearch_indexed_projects", column: "project_id") + remove_foreign_key_without_error("epic_issues", column: "epic_id") + remove_foreign_key_without_error("epic_issues", column: "issue_id") + remove_foreign_key_without_error("epic_metrics", column: "epic_id") + remove_foreign_key_without_error("epics", column: "parent_id") + remove_foreign_key_without_error("epics", column: "milestone_id") + remove_foreign_key_without_error("epics", column: "group_id") + remove_foreign_key_without_error("epics", column: "assignee_id") + remove_foreign_key_without_error("epics", column: "author_id") + remove_foreign_key_without_error("epics", column: "closed_by_id") + remove_foreign_key_without_error("geo_event_log", column: "cache_invalidation_event_id") + remove_foreign_key_without_error("geo_event_log", column: "hashed_storage_migrated_event_id") + remove_foreign_key_without_error("geo_event_log", column: "job_artifact_deleted_event_id") + remove_foreign_key_without_error("geo_event_log", column: "lfs_object_deleted_event_id") + remove_foreign_key_without_error("geo_event_log", column: "repositories_changed_event_id") + remove_foreign_key_without_error("geo_event_log", column: "repository_created_event_id") + remove_foreign_key_without_error("geo_event_log", column: "repository_deleted_event_id") + remove_foreign_key_without_error("geo_event_log", column: "repository_renamed_event_id") + remove_foreign_key_without_error("geo_event_log", column: "repository_updated_event_id") + remove_foreign_key_without_error("geo_event_log", column: "reset_checksum_event_id") + remove_foreign_key_without_error("geo_event_log", column: "upload_deleted_event_id") + remove_foreign_key_without_error("geo_hashed_storage_attachments_events", column: "project_id") + remove_foreign_key_without_error("geo_hashed_storage_migrated_events", column: "project_id") + remove_foreign_key_without_error("geo_node_namespace_links", column: "geo_node_id") + remove_foreign_key_without_error("geo_node_namespace_links", column: "namespace_id") + remove_foreign_key_without_error("geo_node_statuses", column: "geo_node_id") + remove_foreign_key_without_error("geo_repositories_changed_events", column: "geo_node_id") + remove_foreign_key_without_error("geo_repository_created_events", column: "project_id") + remove_foreign_key_without_error("geo_repository_renamed_events", column: "project_id") + remove_foreign_key_without_error("geo_repository_updated_events", column: "project_id") + remove_foreign_key_without_error("geo_reset_checksum_events", column: "project_id") + remove_foreign_key_without_error("gitlab_subscriptions", column: "namespace_id") + remove_foreign_key_without_error("gitlab_subscriptions", column: "hosted_plan_id") + remove_foreign_key_without_error("identities", column: "saml_provider_id") + remove_foreign_key_without_error("index_statuses", column: "project_id") + remove_foreign_key_without_error("insights", column: "namespace_id", on_delete: nil) + remove_foreign_key_without_error("insights", column: "project_id", on_delete: nil) + remove_foreign_key_without_error("issue_links", column: "source_id") + remove_foreign_key_without_error("issue_links", column: "target_id") + remove_foreign_key_without_error("lists", column: "milestone_id") + remove_foreign_key_without_error("lists", column: "user_id") + remove_foreign_key_without_error("namespace_statistics", column: "namespace_id") + remove_foreign_key_without_error("namespaces", column: "custom_project_templates_group_id") + remove_foreign_key_without_error("namespaces", column: "plan_id") + remove_foreign_key_without_error("namespaces", column: "file_template_project_id") + remove_foreign_key_without_error("notes", column: "review_id") + remove_foreign_key_without_error("operations_feature_flag_scopes", column: "feature_flag_id") + remove_foreign_key_without_error("operations_feature_flags", column: "project_id") + remove_foreign_key_without_error("operations_feature_flags_clients", column: "project_id") + remove_foreign_key_without_error("packages_maven_metadata", column: "package_id") + remove_foreign_key_without_error("packages_package_files", column: "package_id") + remove_foreign_key_without_error("packages_packages", column: "project_id") + remove_foreign_key_without_error("path_locks", column: "project_id") + remove_foreign_key_without_error("path_locks", column: "user_id", on_delete: nil) + remove_foreign_key_without_error("project_alerting_settings", column: "project_id") + remove_foreign_key_without_error("project_feature_usages", column: "project_id") + remove_foreign_key_without_error("project_incident_management_settings", column: "project_id") + remove_foreign_key_without_error("project_repository_states", column: "project_id") + remove_foreign_key_without_error("project_tracing_settings", column: "project_id") + remove_foreign_key_without_error("prometheus_alert_events", column: "project_id") + remove_foreign_key_without_error("prometheus_alert_events", column: "prometheus_alert_id") + remove_foreign_key_without_error("prometheus_alerts", column: "environment_id") + remove_foreign_key_without_error("prometheus_alerts", column: "project_id") + remove_foreign_key_without_error("prometheus_alerts", column: "prometheus_metric_id") + remove_foreign_key_without_error("protected_branch_merge_access_levels", column: "group_id") + remove_foreign_key_without_error("protected_branch_merge_access_levels", column: "user_id", on_delete: nil) + remove_foreign_key_without_error("protected_branch_push_access_levels", column: "group_id") + remove_foreign_key_without_error("protected_branch_push_access_levels", column: "user_id", on_delete: nil) + remove_foreign_key_without_error("protected_branch_unprotect_access_levels", column: "group_id") + remove_foreign_key_without_error("protected_branch_unprotect_access_levels", column: "protected_branch_id") + remove_foreign_key_without_error("protected_branch_unprotect_access_levels", column: "user_id") + remove_foreign_key_without_error("protected_environment_deploy_access_levels", column: "group_id") + remove_foreign_key_without_error("protected_environment_deploy_access_levels", column: "protected_environment_id") + remove_foreign_key_without_error("protected_environment_deploy_access_levels", column: "user_id") + remove_foreign_key_without_error("protected_environments", column: "project_id") + remove_foreign_key_without_error("push_rules", column: "project_id") + remove_foreign_key_without_error("resource_label_events", column: "epic_id") + remove_foreign_key_without_error("reviews", column: "merge_request_id") + remove_foreign_key_without_error("reviews", column: "project_id") + remove_foreign_key_without_error("reviews", column: "author_id") + remove_foreign_key_without_error("saml_providers", column: "group_id") + remove_foreign_key_without_error("scim_oauth_access_tokens", column: "group_id") + remove_foreign_key_without_error("slack_integrations", column: "service_id") + remove_foreign_key_without_error("smartcard_identities", column: "user_id") + remove_foreign_key_without_error("software_license_policies", column: "project_id") + remove_foreign_key_without_error("software_license_policies", column: "software_license_id") + remove_foreign_key_without_error("users", column: "managing_group_id") + remove_foreign_key_without_error("users_ops_dashboard_projects", column: "project_id") + remove_foreign_key_without_error("users_ops_dashboard_projects", column: "user_id") + remove_foreign_key_without_error("vulnerability_feedback", column: "pipeline_id") + remove_foreign_key_without_error("vulnerability_feedback", column: "issue_id") + remove_foreign_key_without_error("vulnerability_feedback", column: "merge_request_id") + remove_foreign_key_without_error("vulnerability_feedback", column: "project_id") + remove_foreign_key_without_error("vulnerability_feedback", column: "author_id") + remove_foreign_key_without_error("vulnerability_identifiers", column: "project_id") + remove_foreign_key_without_error("vulnerability_occurrence_identifiers", column: "identifier_id") + remove_foreign_key_without_error("vulnerability_occurrence_identifiers", column: "occurrence_id") + remove_foreign_key_without_error("vulnerability_occurrence_pipelines", column: "pipeline_id") + remove_foreign_key_without_error("vulnerability_occurrence_pipelines", column: "occurrence_id") + remove_foreign_key_without_error("vulnerability_occurrences", column: "project_id") + remove_foreign_key_without_error("vulnerability_occurrences", column: "primary_identifier_id") + remove_foreign_key_without_error("vulnerability_occurrences", column: "scanner_id") + remove_foreign_key_without_error("vulnerability_scanners", column: "project_id") + remove_foreign_key_without_error("dependency_proxy_blobs", column: "group_id") + remove_foreign_key_without_error("dependency_proxy_group_settings", column: "group_id") + remove_foreign_key_without_error("jira_connect_subscriptions", "jira_connect_installations", column: "jira_connect_installation_id") + remove_foreign_key_without_error("jira_connect_subscriptions", "namespaces", column: "namespace_id") + + remove_foreign_key_without_error("protected_tag_create_access_levels", column: :group_id) + add_concurrent_foreign_key("protected_tag_create_access_levels", "namespaces", column: :group_id, on_delete: nil) + end +end +# rubocop: enable Metrics/AbcSize +# rubocop: enable Migration/Datetime diff --git a/db/migrate/20190403161806_update_designs_index.rb b/db/migrate/20190403161806_update_designs_index.rb new file mode 100644 index 00000000000..78517e372d5 --- /dev/null +++ b/db/migrate/20190403161806_update_designs_index.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class UpdateDesignsIndex < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def up + remove_concurrent_index :design_management_designs, :issue_id, unique: true + end + + def down + add_concurrent_index :design_management_designs, :issue_id, unique: true + end +end diff --git a/db/migrate/20190409224933_add_name_to_geo_nodes.rb b/db/migrate/20190409224933_add_name_to_geo_nodes.rb new file mode 100644 index 00000000000..2dff81b429c --- /dev/null +++ b/db/migrate/20190409224933_add_name_to_geo_nodes.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddNameToGeoNodes < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def up + add_column :geo_nodes, :name, :string + + # url is also unique, and its type and size is identical to the name column, + # so this is safe. + execute "UPDATE geo_nodes SET name = url;" + + # url is also `null: false`, so this is safe. + change_column :geo_nodes, :name, :string, null: false + end + + def down + remove_column :geo_nodes, :name + end +end diff --git a/db/migrate/20190410173409_add_name_index_to_geo_nodes.rb b/db/migrate/20190410173409_add_name_index_to_geo_nodes.rb new file mode 100644 index 00000000000..efbdaf1d025 --- /dev/null +++ b/db/migrate/20190410173409_add_name_index_to_geo_nodes.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddNameIndexToGeoNodes < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index :geo_nodes, :name, unique: true + end + + def down + remove_concurrent_index :geo_nodes, :name + end +end diff --git a/db/migrate/20190412183653_remove_url_index_from_geo_nodes.rb b/db/migrate/20190412183653_remove_url_index_from_geo_nodes.rb new file mode 100644 index 00000000000..86a46260553 --- /dev/null +++ b/db/migrate/20190412183653_remove_url_index_from_geo_nodes.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RemoveUrlIndexFromGeoNodes < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def up + remove_concurrent_index :geo_nodes, :url + end + + def down + add_concurrent_index :geo_nodes, :url, unique: true + end +end diff --git a/db/migrate/20190414185432_add_comment_to_vulnerability_feedback.rb b/db/migrate/20190414185432_add_comment_to_vulnerability_feedback.rb new file mode 100644 index 00000000000..63644a2f8fd --- /dev/null +++ b/db/migrate/20190414185432_add_comment_to_vulnerability_feedback.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddCommentToVulnerabilityFeedback < ActiveRecord::Migration[5.1] + DOWNTIME = false + + def up + add_column :vulnerability_feedback, :comment_author_id, :integer + add_column :vulnerability_feedback, :comment, :text + add_column :vulnerability_feedback, :comment_timestamp, :datetime_with_timezone + end + + def down + remove_column :vulnerability_feedback, :comment_author_id + remove_column :vulnerability_feedback, :comment + remove_column :vulnerability_feedback, :comment_timestamp + end +end diff --git a/db/migrate/20190415172035_update_insights_foreign_keys.rb b/db/migrate/20190415172035_update_insights_foreign_keys.rb new file mode 100644 index 00000000000..5d3aa4c05e9 --- /dev/null +++ b/db/migrate/20190415172035_update_insights_foreign_keys.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +# rubocop: disable Migration/AddConcurrentForeignKey + +class UpdateInsightsForeignKeys < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + remove_foreign_key_if_exists(:insights, column: :project_id) + add_foreign_key(:insights, :projects, column: :project_id, on_delete: :cascade) + + remove_foreign_key_if_exists(:insights, column: :namespace_id) + add_foreign_key(:insights, :namespaces, column: :namespace_id, on_delete: :cascade) + end + + def down + remove_foreign_key_if_exists(:insights, column: :namespace_id) + add_foreign_key(:insights, :namespaces, column: :namespace_id) + + remove_foreign_key_if_exists(:insights, column: :project_id) + add_foreign_key(:insights, :projects, column: :project_id) + end +end diff --git a/db/migrate/20190418132750_add_foreign_key_from_vulnerability_feedback_to_users.rb b/db/migrate/20190418132750_add_foreign_key_from_vulnerability_feedback_to_users.rb new file mode 100644 index 00000000000..0bd9012aee8 --- /dev/null +++ b/db/migrate/20190418132750_add_foreign_key_from_vulnerability_feedback_to_users.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +class AddForeignKeyFromVulnerabilityFeedbackToUsers < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_foreign_key :vulnerability_feedback, :users, column: :comment_author_id, on_delete: :nullify + add_concurrent_index :vulnerability_feedback, :comment_author_id + end + + def down + remove_foreign_key :vulnerability_feedback, column: :comment_author_id + remove_concurrent_index :vulnerability_feedback, :comment_author_id + end +end diff --git a/db/migrate/20190419121952_add_bridged_pipeline_id_to_bridges.rb b/db/migrate/20190419121952_add_bridged_pipeline_id_to_bridges.rb new file mode 100644 index 00000000000..fac556c1897 --- /dev/null +++ b/db/migrate/20190419121952_add_bridged_pipeline_id_to_bridges.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddBridgedPipelineIdToBridges < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def change + add_column :ci_builds, :upstream_pipeline_id, :integer + end +end diff --git a/db/migrate/20190419123057_add_bridged_pipeline_id_foreign_key.rb b/db/migrate/20190419123057_add_bridged_pipeline_id_foreign_key.rb new file mode 100644 index 00000000000..c31ec7bc107 --- /dev/null +++ b/db/migrate/20190419123057_add_bridged_pipeline_id_foreign_key.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddBridgedPipelineIdForeignKey < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index :ci_builds, :upstream_pipeline_id, where: 'upstream_pipeline_id IS NOT NULL' + add_concurrent_foreign_key :ci_builds, :ci_pipelines, column: :upstream_pipeline_id + end + + def down + remove_foreign_key :ci_builds, column: :upstream_pipeline_id + remove_concurrent_index :ci_builds, :upstream_pipeline_id + end +end diff --git a/db/migrate/20190423124640_add_index_to_projects_mirror_user_id.rb b/db/migrate/20190423124640_add_index_to_projects_mirror_user_id.rb new file mode 100644 index 00000000000..b008d8ce0cc --- /dev/null +++ b/db/migrate/20190423124640_add_index_to_projects_mirror_user_id.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddIndexToProjectsMirrorUserId < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index :projects, :mirror_user_id + end + + def down + remove_concurrent_index :projects, :mirror_user_id + end +end diff --git a/db/migrate/20190430131225_create_issue_tracker_data.rb b/db/migrate/20190430131225_create_issue_tracker_data.rb new file mode 100644 index 00000000000..7859bea9c22 --- /dev/null +++ b/db/migrate/20190430131225_create_issue_tracker_data.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class CreateIssueTrackerData < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + create_table :issue_tracker_data do |t| + t.references :service, foreign_key: { on_delete: :cascade }, type: :integer, index: true, null: false + t.timestamps_with_timezone + t.string :encrypted_project_url + t.string :encrypted_project_url_iv + t.string :encrypted_issues_url + t.string :encrypted_issues_url_iv + t.string :encrypted_new_issue_url + t.string :encrypted_new_issue_url_iv + end + end +end diff --git a/db/migrate/20190430142025_create_jira_tracker_data.rb b/db/migrate/20190430142025_create_jira_tracker_data.rb new file mode 100644 index 00000000000..d328ad63854 --- /dev/null +++ b/db/migrate/20190430142025_create_jira_tracker_data.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class CreateJiraTrackerData < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + create_table :jira_tracker_data do |t| + t.references :service, foreign_key: { on_delete: :cascade }, type: :integer, index: true, null: false + t.timestamps_with_timezone + t.string :encrypted_url + t.string :encrypted_url_iv + t.string :encrypted_api_url + t.string :encrypted_api_url_iv + t.string :encrypted_username + t.string :encrypted_username_iv + t.string :encrypted_password + t.string :encrypted_password_iv + t.string :jira_issue_transition_id + end + end +end diff --git a/db/migrate/20190514105711_create_ip_restriction.rb b/db/migrate/20190514105711_create_ip_restriction.rb new file mode 100644 index 00000000000..dfafbe32ad1 --- /dev/null +++ b/db/migrate/20190514105711_create_ip_restriction.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class CreateIpRestriction < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + create_table :ip_restrictions do |t| + t.references :group, references: :namespace, + column: :group_id, + type: :integer, + null: false, + index: true + t.string :range, null: false + end + + add_foreign_key(:ip_restrictions, :namespaces, column: :group_id, on_delete: :cascade) # rubocop: disable Migration/AddConcurrentForeignKey + end +end diff --git a/db/migrate/20190520200123_add_rule_type_to_approval_merge_request_approval_rules.rb b/db/migrate/20190520200123_add_rule_type_to_approval_merge_request_approval_rules.rb new file mode 100644 index 00000000000..7339a4fccba --- /dev/null +++ b/db/migrate/20190520200123_add_rule_type_to_approval_merge_request_approval_rules.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddRuleTypeToApprovalMergeRequestApprovalRules < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column_with_default(:approval_merge_request_rules, :rule_type, :integer, limit: 2, default: 1) + end + + def down + remove_column(:approval_merge_request_rules, :rule_type) + end +end diff --git a/db/migrate/20190528173628_add_index_for_code_owner_rule_type_on_approval_merge_request_rules.rb b/db/migrate/20190528173628_add_index_for_code_owner_rule_type_on_approval_merge_request_rules.rb new file mode 100644 index 00000000000..96d878a98f2 --- /dev/null +++ b/db/migrate/20190528173628_add_index_for_code_owner_rule_type_on_approval_merge_request_rules.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddIndexForCodeOwnerRuleTypeOnApprovalMergeRequestRules < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + INDEX_CODE_OWNERS_RULES_UNIQUENESS_NAME = 'index_approval_rule_name_for_code_owners_rule_type' + INDEX_CODE_OWNERS_RULES_QUERY_NAME = 'index_approval_rules_code_owners_rule_type' + + class ApprovalMergeRequestRule < ActiveRecord::Base + include EachBatch + + enum rule_types: { + regular: 1, + code_owner: 2 + } + end + + def up + # Ensure only 1 code_owner rule per merge_request + add_concurrent_index( + :approval_merge_request_rules, + [:merge_request_id, :rule_type, :name], + unique: true, + where: "rule_type = #{ApprovalMergeRequestRule.rule_types[:code_owner]}", + name: INDEX_CODE_OWNERS_RULES_UNIQUENESS_NAME + ) + + # Support lookups for all code_owner rules per merge_request + add_concurrent_index( + :approval_merge_request_rules, + [:merge_request_id, :rule_type], + where: "rule_type = #{ApprovalMergeRequestRule.rule_types[:code_owner]}", + name: INDEX_CODE_OWNERS_RULES_QUERY_NAME + ) + end + + def down + remove_concurrent_index_by_name( + :approval_merge_request_rules, + INDEX_CODE_OWNERS_RULES_UNIQUENESS_NAME + ) + + remove_concurrent_index_by_name( + :approval_merge_request_rules, + INDEX_CODE_OWNERS_RULES_QUERY_NAME + ) + end +end diff --git a/db/migrate/20190602014139_add_repository_type_to_lfs_objects_project.rb b/db/migrate/20190602014139_add_repository_type_to_lfs_objects_project.rb new file mode 100644 index 00000000000..6ff64ba12a9 --- /dev/null +++ b/db/migrate/20190602014139_add_repository_type_to_lfs_objects_project.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AddRepositoryTypeToLfsObjectsProject < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :lfs_objects_projects, :repository_type, :integer, limit: 2, null: true + end +end diff --git a/db/migrate/20190603124955_add_index_to_count_pending_mirror_updates.rb b/db/migrate/20190603124955_add_index_to_count_pending_mirror_updates.rb new file mode 100644 index 00000000000..6aa94f7b20b --- /dev/null +++ b/db/migrate/20190603124955_add_index_to_count_pending_mirror_updates.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddIndexToCountPendingMirrorUpdates < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index :project_mirror_data, [:last_update_at, :retry_count] + end + + def down + remove_concurrent_index :project_mirror_data, [:last_update_at, :retry_count] + end +end diff --git a/db/migrate/20190606034427_add_lfs_object_id_index_to_lfs_objects_projects.rb b/db/migrate/20190606034427_add_lfs_object_id_index_to_lfs_objects_projects.rb new file mode 100644 index 00000000000..fc09fcfae0f --- /dev/null +++ b/db/migrate/20190606034427_add_lfs_object_id_index_to_lfs_objects_projects.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AddLfsObjectIdIndexToLfsObjectsProjects < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index :lfs_objects_projects, :lfs_object_id + end + + def down + remove_concurrent_index :lfs_objects_projects, :lfs_object_id + end +end diff --git a/db/migrate/20190610142825_add_index_to_members_invite_email.rb b/db/migrate/20190610142825_add_index_to_members_invite_email.rb new file mode 100644 index 00000000000..58157cc5313 --- /dev/null +++ b/db/migrate/20190610142825_add_index_to_members_invite_email.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddIndexToMembersInviteEmail < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_index :members, [:invite_email] + end + + def down + remove_concurrent_index :members, [:invite_email] + end +end diff --git a/db/migrate/20190613030606_enable_hashed_storage_by_default.rb b/db/migrate/20190613030606_enable_hashed_storage_by_default.rb new file mode 100644 index 00000000000..8edefd1273e --- /dev/null +++ b/db/migrate/20190613030606_enable_hashed_storage_by_default.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +class EnableHashedStorageByDefault < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + change_column_default :application_settings, :hashed_storage_enabled, true + end + + def down + change_column_default :application_settings, :hashed_storage_enabled, false + end +end diff --git a/db/post_migrate/20161128170531_drop_user_activities_table.rb b/db/post_migrate/20161128170531_drop_user_activities_table.rb index 64d13a08953..d8b1e0731f3 100644 --- a/db/post_migrate/20161128170531_drop_user_activities_table.rb +++ b/db/post_migrate/20161128170531_drop_user_activities_table.rb @@ -1,9 +1,33 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. class DropUserActivitiesTable < ActiveRecord::Migration[4.2] include Gitlab::Database::MigrationHelpers DOWNTIME = false - # This migration is a no-op. It just exists to match EE. - def change + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def up + drop_table :user_activities if table_exists?(:user_activities) + end + + def down + unless table_exists?(:user_activities) + create_table "user_activities", force: :cascade do |t| + t.integer "user_id" + t.datetime "last_activity_at", null: false + end + + add_index "user_activities", ["user_id"], name: "index_user_activities_on_user_id", unique: true, using: :btree + end end end diff --git a/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb b/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb deleted file mode 100644 index c018d30c175..00000000000 --- a/db/post_migrate/20170502101023_cleanup_namespaceless_pending_delete_projects.rb +++ /dev/null @@ -1,47 +0,0 @@ -# This is the counterpart of RequeuePendingDeleteProjects and cleans all -# projects with `pending_delete = true` and that do not have a namespace. -class CleanupNamespacelessPendingDeleteProjects < ActiveRecord::Migration[4.2] - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - - disable_ddl_transaction! - - def up - @offset = 0 - - loop do - ids = pending_delete_batch - - break if ids.empty? - - args = ids.map { |id| Array(id) } - - NamespacelessProjectDestroyWorker.bulk_perform_async(args) - - @offset += 1 - end - end - - def down - # noop - end - - private - - def pending_delete_batch - connection.exec_query(find_batch).map { |row| row['id'].to_i } - end - - BATCH_SIZE = 5000 - - def find_batch - projects = Arel::Table.new(:projects) - projects.project(projects[:id]) - .where(projects[:pending_delete].eq(true)) - .where(projects[:namespace_id].eq(nil)) - .skip(@offset * BATCH_SIZE) - .take(BATCH_SIZE) - .to_sql - end -end diff --git a/db/post_migrate/20170703130158_schedule_merge_request_diff_migrations.rb b/db/post_migrate/20170703130158_schedule_merge_request_diff_migrations.rb deleted file mode 100644 index fd4b2859f7f..00000000000 --- a/db/post_migrate/20170703130158_schedule_merge_request_diff_migrations.rb +++ /dev/null @@ -1,33 +0,0 @@ -class ScheduleMergeRequestDiffMigrations < ActiveRecord::Migration[4.2] - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - BATCH_SIZE = 2500 - MIGRATION = 'DeserializeMergeRequestDiffsAndCommits' - - disable_ddl_transaction! - - class MergeRequestDiff < ActiveRecord::Base - self.table_name = 'merge_request_diffs' - - include ::EachBatch - end - - # Assuming that there are 5 million rows affected (which is more than on - # GitLab.com), and that each batch of 2,500 rows takes up to 5 minutes, then - # we can migrate all the rows in 7 days. - # - # On staging, plucking the IDs themselves takes 5 seconds. - def up - non_empty = 'st_commits IS NOT NULL OR st_diffs IS NOT NULL' - - MergeRequestDiff.where(non_empty).each_batch(of: BATCH_SIZE) do |relation, index| - range = relation.pluck('MIN(id)', 'MAX(id)').first - - BackgroundMigrationWorker.perform_in(index * 5.minutes, MIGRATION, range) - end - end - - def down - end -end diff --git a/db/post_migrate/20170926150348_schedule_merge_request_diff_migrations_take_two.rb b/db/post_migrate/20170926150348_schedule_merge_request_diff_migrations_take_two.rb deleted file mode 100644 index 9b675a51725..00000000000 --- a/db/post_migrate/20170926150348_schedule_merge_request_diff_migrations_take_two.rb +++ /dev/null @@ -1,32 +0,0 @@ -class ScheduleMergeRequestDiffMigrationsTakeTwo < ActiveRecord::Migration[4.2] - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - BATCH_SIZE = 500 - MIGRATION = 'DeserializeMergeRequestDiffsAndCommits' - DELAY_INTERVAL = 10.minutes - - disable_ddl_transaction! - - class MergeRequestDiff < ActiveRecord::Base - self.table_name = 'merge_request_diffs' - - include ::EachBatch - - default_scope { where('st_commits IS NOT NULL OR st_diffs IS NOT NULL') } - end - - # By this point, we assume ScheduleMergeRequestDiffMigrations - the first - # version of this - has already run. On GitLab.com, we have ~220k un-migrated - # rows, but these rows will, in general, take a long time. - # - # With a gap of 10 minutes per batch, and 500 rows per batch, these migrations - # are scheduled over 220_000 / 500 / 6 ~= 74 hours, which is a little over - # three days. - def up - queue_background_migration_jobs_by_range_at_intervals(MergeRequestDiff, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE) - end - - def down - end -end diff --git a/db/post_migrate/20171005130944_schedule_create_gpg_key_subkeys_from_gpg_keys.rb b/db/post_migrate/20171005130944_schedule_create_gpg_key_subkeys_from_gpg_keys.rb deleted file mode 100644 index e49a70f902c..00000000000 --- a/db/post_migrate/20171005130944_schedule_create_gpg_key_subkeys_from_gpg_keys.rb +++ /dev/null @@ -1,28 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class ScheduleCreateGpgKeySubkeysFromGpgKeys < ActiveRecord::Migration[4.2] - disable_ddl_transaction! - - DOWNTIME = false - MIGRATION = 'CreateGpgKeySubkeysFromGpgKeys' - - class GpgKey < ActiveRecord::Base - self.table_name = 'gpg_keys' - - include EachBatch - end - - def up - GpgKey.select(:id).each_batch do |gpg_keys| - jobs = gpg_keys.pluck(:id).map do |id| - [MIGRATION, [id]] - end - - BackgroundMigrationWorker.bulk_perform_async(jobs) - end - end - - def down - end -end diff --git a/db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb b/db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb deleted file mode 100644 index 51441a36e4b..00000000000 --- a/db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb +++ /dev/null @@ -1,37 +0,0 @@ -# frozen_string_literal: true - -class SchedulePopulateMergeRequestMetricsWithEventsData < ActiveRecord::Migration[4.2] - DOWNTIME = false - BATCH_SIZE = 10_000 - MIGRATION = 'PopulateMergeRequestMetricsWithEventsData' - - disable_ddl_transaction! - - class MergeRequest < ActiveRecord::Base - self.table_name = 'merge_requests' - - include ::EachBatch - end - - def up - say 'Scheduling `PopulateMergeRequestMetricsWithEventsData` jobs' - # It will update around 4_000_000 records in batches of 10_000 merge - # requests (running between 10 minutes) and should take around 66 hours to complete. - # Apparently, production PostgreSQL is able to vacuum 10k-20k dead_tuples by - # minute, and at maximum, each of these jobs should UPDATE 20k records. - # - # More information about the updates in `PopulateMergeRequestMetricsWithEventsData` class. - # - MergeRequest.all.each_batch(of: BATCH_SIZE) do |relation, index| - range = relation.pluck('MIN(id)', 'MAX(id)').first - - BackgroundMigrationWorker.perform_in(index * 10.minutes, MIGRATION, range) - end - end - - def down - execute "update merge_request_metrics set latest_closed_at = null" - execute "update merge_request_metrics set latest_closed_by_id = null" - execute "update merge_request_metrics set merged_by_id = null" - end -end diff --git a/db/post_migrate/20171207150343_remove_soft_removed_objects.rb b/db/post_migrate/20171207150343_remove_soft_removed_objects.rb deleted file mode 100644 index 53707c67d36..00000000000 --- a/db/post_migrate/20171207150343_remove_soft_removed_objects.rb +++ /dev/null @@ -1,208 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class RemoveSoftRemovedObjects < ActiveRecord::Migration[4.2] - include Gitlab::Database::MigrationHelpers - - # Set this constant to true if this migration requires downtime. - DOWNTIME = false - - disable_ddl_transaction! - - module SoftRemoved - extend ActiveSupport::Concern - - included do - scope :soft_removed, -> { where('deleted_at IS NOT NULL') } - end - end - - class User < ActiveRecord::Base - self.table_name = 'users' - - include EachBatch - end - - class Issue < ActiveRecord::Base - self.table_name = 'issues' - - include EachBatch - include SoftRemoved - end - - class MergeRequest < ActiveRecord::Base - self.table_name = 'merge_requests' - - include EachBatch - include SoftRemoved - end - - class Namespace < ActiveRecord::Base - self.table_name = 'namespaces' - - include EachBatch - include SoftRemoved - - scope :soft_removed_personal, -> { soft_removed.where(type: nil) } - scope :soft_removed_group, -> { soft_removed.where(type: 'Group') } - end - - class Route < ActiveRecord::Base - self.table_name = 'routes' - - include EachBatch - include SoftRemoved - end - - class Project < ActiveRecord::Base - self.table_name = 'projects' - - include EachBatch - include SoftRemoved - end - - class CiPipelineSchedule < ActiveRecord::Base - self.table_name = 'ci_pipeline_schedules' - - include EachBatch - include SoftRemoved - end - - class CiTrigger < ActiveRecord::Base - self.table_name = 'ci_triggers' - - include EachBatch - include SoftRemoved - end - - MODELS = [Issue, MergeRequest, CiPipelineSchedule, CiTrigger].freeze - - def up - disable_statement_timeout do - remove_personal_routes - remove_personal_namespaces - remove_group_namespaces - remove_simple_soft_removed_rows - end - end - - def down - # The data removed by this migration can't be restored in an automated way. - end - - def remove_simple_soft_removed_rows - create_temporary_indexes - - MODELS.each do |model| - say_with_time("Removing soft removed rows from #{model.table_name}") do - model.soft_removed.each_batch do |batch, index| - batch.delete_all - end - end - end - ensure - remove_temporary_indexes - end - - def create_temporary_indexes - MODELS.each do |model| - index_name = temporary_index_name_for(model) - - # Without this index the removal process can take a very long time. For - # example, getting the next ID of a batch for the `issues` table in - # staging would take between 15 and 20 seconds. - next if temporary_index_exists?(model) - - say_with_time("Creating temporary index #{index_name}") do - add_concurrent_index( - model.table_name, - [:deleted_at, :id], - name: index_name, - where: 'deleted_at IS NOT NULL' - ) - end - end - end - - def remove_temporary_indexes - MODELS.each do |model| - index_name = temporary_index_name_for(model) - - next unless temporary_index_exists?(model) - - say_with_time("Removing temporary index #{index_name}") do - remove_concurrent_index_by_name(model.table_name, index_name) - end - end - end - - def temporary_index_name_for(model) - "index_on_#{model.table_name}_tmp" - end - - def temporary_index_exists?(model) - index_name = temporary_index_name_for(model) - - index_exists?(model.table_name, [:deleted_at, :id], name: index_name) - end - - def remove_personal_namespaces - # Some personal namespaces are left behind in case of GitLab.com. In these - # cases the associated data such as the projects and users has already been - # removed. - Namespace.soft_removed_personal.each_batch do |batch| - batch.delete_all - end - end - - def remove_group_namespaces - admin_id = id_for_admin_user - - unless admin_id - say 'Not scheduling soft removed groups for removal as no admin user ' \ - 'could be found. You will need to remove any such groups manually.' - - return - end - - # Left over groups can't be easily removed because we may also need to - # remove memberships, repositories, and other associated data. As a result - # we'll just schedule a Sidekiq job to remove these. - # - # As of January 5th, 2018 there are 36 groups that will be removed using - # this code. - Namespace.select(:id).soft_removed_group.each_batch(of: 10) do |batch, index| - batch.each do |ns| - schedule_group_removal(index * 5.minutes, ns.id, admin_id) - end - end - end - - def schedule_group_removal(delay, group_id, user_id) - if migrate_inline? - GroupDestroyWorker.new.perform(group_id, user_id) - else - GroupDestroyWorker.perform_in(delay, group_id, user_id) - end - end - - def remove_personal_routes - namespaces = Namespace.select(1) - .soft_removed - .where('namespaces.type IS NULL') - .where('routes.source_type = ?', 'Namespace') - .where('routes.source_id = namespaces.id') - - Route.where('EXISTS (?)', namespaces).each_batch do |batch| - batch.delete_all - end - end - - def id_for_admin_user - User.where(admin: true).limit(1).pluck(:id).first - end - - def migrate_inline? - Rails.env.test? || Rails.env.development? - end -end diff --git a/db/post_migrate/20180502134117_migrate_import_attributes_data_from_projects_to_project_mirror_data.rb b/db/post_migrate/20180502134117_migrate_import_attributes_data_from_projects_to_project_mirror_data.rb deleted file mode 100644 index b82ee3569c9..00000000000 --- a/db/post_migrate/20180502134117_migrate_import_attributes_data_from_projects_to_project_mirror_data.rb +++ /dev/null @@ -1,37 +0,0 @@ -class MigrateImportAttributesDataFromProjectsToProjectMirrorData < ActiveRecord::Migration[4.2] - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - - UP_MIGRATION = 'PopulateImportState'.freeze - DOWN_MIGRATION = 'RollbackImportStateData'.freeze - - BATCH_SIZE = 1000 - DELAY_INTERVAL = 5.minutes - - disable_ddl_transaction! - - class Project < ActiveRecord::Base - include EachBatch - - self.table_name = 'projects' - end - - class ProjectImportState < ActiveRecord::Base - include EachBatch - - self.table_name = 'project_mirror_data' - end - - def up - projects = Project.where.not(import_status: :none) - - queue_background_migration_jobs_by_range_at_intervals(projects, UP_MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE) - end - - def down - import_state = ProjectImportState.where.not(status: :none) - - queue_background_migration_jobs_by_range_at_intervals(import_state, DOWN_MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE) - end -end diff --git a/db/post_migrate/20180521162137_migrate_remaining_mr_metrics_populating_background_migration.rb b/db/post_migrate/20180521162137_migrate_remaining_mr_metrics_populating_background_migration.rb deleted file mode 100644 index 39666a0cd2a..00000000000 --- a/db/post_migrate/20180521162137_migrate_remaining_mr_metrics_populating_background_migration.rb +++ /dev/null @@ -1,44 +0,0 @@ -class MigrateRemainingMrMetricsPopulatingBackgroundMigration < ActiveRecord::Migration[4.2] - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - BATCH_SIZE = 5_000 - MIGRATION = 'PopulateMergeRequestMetricsWithEventsData' - DELAY_INTERVAL = 10.minutes - - disable_ddl_transaction! - - class MergeRequest < ActiveRecord::Base - self.table_name = 'merge_requests' - - include ::EachBatch - end - - def up - # Perform any ongoing background migration that might still be running. This - # avoids scheduling way too many of the same jobs on self-hosted instances - # if they're updating GitLab across multiple versions. The "Take one" - # migration was executed on 10.4 on - # SchedulePopulateMergeRequestMetricsWithEventsData. - Gitlab::BackgroundMigration.steal(MIGRATION) - - metrics_not_exists_clause = <<~SQL - NOT EXISTS (SELECT 1 FROM merge_request_metrics - WHERE merge_request_metrics.merge_request_id = merge_requests.id) - SQL - - relation = MergeRequest.where(metrics_not_exists_clause) - - # We currently have ~400_000 MR records without metrics on GitLab.com. - # This means it'll schedule ~80 jobs (5000 MRs each) with a 10 minutes gap, - # so this should take ~14 hours for all background migrations to complete. - # - queue_background_migration_jobs_by_range_at_intervals(relation, - MIGRATION, - DELAY_INTERVAL, - batch_size: BATCH_SIZE) - end - - def down - end -end diff --git a/db/post_migrate/20180619121030_enqueue_delete_diff_files_workers.rb b/db/post_migrate/20180619121030_enqueue_delete_diff_files_workers.rb deleted file mode 100644 index 73f6a3a2a43..00000000000 --- a/db/post_migrate/20180619121030_enqueue_delete_diff_files_workers.rb +++ /dev/null @@ -1,26 +0,0 @@ -class EnqueueDeleteDiffFilesWorkers < ActiveRecord::Migration[4.2] - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - SCHEDULER = 'ScheduleDiffFilesDeletion'.freeze - TMP_INDEX = 'tmp_partial_diff_id_with_files_index'.freeze - - disable_ddl_transaction! - - def up - unless index_exists_by_name?(:merge_request_diffs, TMP_INDEX) - add_concurrent_index(:merge_request_diffs, :id, where: "(state NOT IN ('without_files', 'empty'))", name: TMP_INDEX) - end - - BackgroundMigrationWorker.perform_async(SCHEDULER) - - # We don't remove the index since it's going to be used on DeleteDiffFiles - # worker. We should remove it in an upcoming release. - end - - def down - if index_exists_by_name?(:merge_request_diffs, TMP_INDEX) - remove_concurrent_index_by_name(:merge_request_diffs, TMP_INDEX) - end - end -end diff --git a/db/post_migrate/20180723130817_delete_inconsistent_internal_id_records.rb b/db/post_migrate/20180723130817_delete_inconsistent_internal_id_records.rb deleted file mode 100644 index 440868005bb..00000000000 --- a/db/post_migrate/20180723130817_delete_inconsistent_internal_id_records.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true -class DeleteInconsistentInternalIdRecords < ActiveRecord::Migration[4.2] - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - - disable_ddl_transaction! - - # This migration cleans up any inconsistent records in internal_ids. - # - # That is, it deletes records that track a `last_value` that is - # smaller than the maximum internal id (usually `iid`) found in - # the corresponding model records. - - def up - disable_statement_timeout do - delete_internal_id_records('issues', 'project_id') - delete_internal_id_records('merge_requests', 'project_id', 'target_project_id') - delete_internal_id_records('deployments', 'project_id') - delete_internal_id_records('milestones', 'project_id') - delete_internal_id_records('milestones', 'namespace_id', 'group_id') - delete_internal_id_records('ci_pipelines', 'project_id') - end - end - - class InternalId < ActiveRecord::Base - self.table_name = 'internal_ids' - enum usage: { issues: 0, merge_requests: 1, deployments: 2, milestones: 3, epics: 4, ci_pipelines: 5 } - end - - private - - def delete_internal_id_records(base_table, scope_column_name, base_scope_column_name = scope_column_name) - sql = <<~SQL - SELECT id FROM ( -- workaround for MySQL - SELECT internal_ids.id FROM ( - SELECT #{base_scope_column_name} AS #{scope_column_name}, max(iid) as maximum_iid from #{base_table} GROUP BY #{scope_column_name} - ) maxima JOIN internal_ids USING (#{scope_column_name}) - WHERE internal_ids.usage=#{InternalId.usages.fetch(base_table)} AND maxima.maximum_iid > internal_ids.last_value - ) internal_ids - SQL - - InternalId.where("id IN (#{sql})").tap do |ids| # rubocop:disable GitlabSecurity/SqlInjection - say "Deleting internal_id records for #{base_table}: #{ids.pluck(:project_id, :last_value)}" unless ids.empty? - end.delete_all - end -end diff --git a/db/post_migrate/20180906051323_remove_orphaned_label_links.rb b/db/post_migrate/20180906051323_remove_orphaned_label_links.rb deleted file mode 100644 index a474aaf534c..00000000000 --- a/db/post_migrate/20180906051323_remove_orphaned_label_links.rb +++ /dev/null @@ -1,43 +0,0 @@ -# frozen_string_literal: true - -class RemoveOrphanedLabelLinks < ActiveRecord::Migration[4.2] - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - - disable_ddl_transaction! - - class LabelLinks < ActiveRecord::Base - self.table_name = 'label_links' - include EachBatch - - def self.orphaned - where('NOT EXISTS ( SELECT 1 FROM labels WHERE labels.id = label_links.label_id )') - end - end - - def up - # Some of these queries can take up to 10 seconds to run on GitLab.com, - # which is pretty close to our 15 second statement timeout. To ensure a - # smooth deployment procedure we disable the statement timeouts for this - # migration, just in case. - disable_statement_timeout do - # On GitLab.com there are over 2,000,000 orphaned label links. On - # staging, removing 100,000 rows generated a max replication lag of 6.7 - # MB. In total, removing all these rows will only generate about 136 MB - # of data, so it should be safe to do this. - LabelLinks.orphaned.each_batch(of: 100_000) do |batch| - batch.delete_all - end - end - - add_concurrent_foreign_key(:label_links, :labels, column: :label_id, on_delete: :cascade) - end - - def down - # There is no way to restore orphaned label links. - if foreign_key_exists?(:label_links, column: :label_id) - remove_foreign_key(:label_links, column: :label_id) - end - end -end diff --git a/db/post_migrate/20180913051323_consume_remaining_diff_files_deletion_jobs.rb b/db/post_migrate/20180913051323_consume_remaining_diff_files_deletion_jobs.rb deleted file mode 100644 index 2c266a4695b..00000000000 --- a/db/post_migrate/20180913051323_consume_remaining_diff_files_deletion_jobs.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -class ConsumeRemainingDiffFilesDeletionJobs < ActiveRecord::Migration[4.2] - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - - disable_ddl_transaction! - - MIGRATION = 'ScheduleDiffFilesDeletion'.freeze - TMP_INDEX = 'tmp_partial_diff_id_with_files_index'.freeze - - def up - # Perform any ongoing background migration that might still be scheduled. - Gitlab::BackgroundMigration.steal(MIGRATION) - - remove_concurrent_index_by_name(:merge_request_diffs, TMP_INDEX) - end - - def down - add_concurrent_index(:merge_request_diffs, :id, where: "(state NOT IN ('without_files', 'empty'))", name: TMP_INDEX) - end -end diff --git a/db/post_migrate/20180916014356_populate_external_pipeline_source.rb b/db/post_migrate/20180916014356_populate_external_pipeline_source.rb deleted file mode 100644 index a3d2df1f2bd..00000000000 --- a/db/post_migrate/20180916014356_populate_external_pipeline_source.rb +++ /dev/null @@ -1,33 +0,0 @@ -# frozen_string_literal: true - -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class PopulateExternalPipelineSource < ActiveRecord::Migration[4.2] - include Gitlab::Database::MigrationHelpers - - # Set this constant to true if this migration requires downtime. - DOWNTIME = false - MIGRATION = 'PopulateExternalPipelineSource'.freeze - BATCH_SIZE = 500 - - disable_ddl_transaction! - - class Pipeline < ActiveRecord::Base - include EachBatch - self.table_name = 'ci_pipelines' - end - - def up - Pipeline.where(source: nil).tap do |relation| - queue_background_migration_jobs_by_range_at_intervals(relation, - MIGRATION, - 5.minutes, - batch_size: BATCH_SIZE) - end - end - - def down - # noop - end -end diff --git a/db/post_migrate/20181014121030_enqueue_redact_links.rb b/db/post_migrate/20181014121030_enqueue_redact_links.rb deleted file mode 100644 index 8d1a840d594..00000000000 --- a/db/post_migrate/20181014121030_enqueue_redact_links.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -class EnqueueRedactLinks < ActiveRecord::Migration[4.2] - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - BATCH_SIZE = 1000 - DELAY_INTERVAL = 5.minutes.to_i - MIGRATION = 'RedactLinks' - - disable_ddl_transaction! - - class Note < ActiveRecord::Base - include EachBatch - - self.table_name = 'notes' - self.inheritance_column = :_type_disabled - end - - class Issue < ActiveRecord::Base - include EachBatch - - self.table_name = 'issues' - self.inheritance_column = :_type_disabled - end - - class MergeRequest < ActiveRecord::Base - include EachBatch - - self.table_name = 'merge_requests' - self.inheritance_column = :_type_disabled - end - - class Snippet < ActiveRecord::Base - include EachBatch - - self.table_name = 'snippets' - self.inheritance_column = :_type_disabled - end - - def up - disable_statement_timeout do - schedule_migration(Note, 'note') - schedule_migration(Issue, 'description') - schedule_migration(MergeRequest, 'description') - schedule_migration(Snippet, 'description') - end - end - - def down - # nothing to do - end - - private - - def schedule_migration(model, field) - link_pattern = "%/sent_notifications/" + ("_" * 32) + "/unsubscribe%" - - model.where("#{field} like ?", link_pattern).each_batch(of: BATCH_SIZE) do |batch, index| - start_id, stop_id = batch.pluck('MIN(id)', 'MAX(id)').first - - BackgroundMigrationWorker.perform_in(index * DELAY_INTERVAL, MIGRATION, [model.name.demodulize, field, start_id, stop_id]) - end - end -end diff --git a/db/post_migrate/20181204154019_populate_mr_metrics_with_events_data.rb b/db/post_migrate/20181204154019_populate_mr_metrics_with_events_data.rb deleted file mode 100644 index 1e43e3dd790..00000000000 --- a/db/post_migrate/20181204154019_populate_mr_metrics_with_events_data.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class PopulateMrMetricsWithEventsData < ActiveRecord::Migration[4.2] - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - BATCH_SIZE = 10_000 - MIGRATION = 'PopulateMergeRequestMetricsWithEventsDataImproved' - PREVIOUS_MIGRATION = 'PopulateMergeRequestMetricsWithEventsData' - - disable_ddl_transaction! - - def up - # Perform any ongoing background migration that might still be running from - # previous try (see https://gitlab.com/gitlab-org/gitlab-ce/issues/47676). - Gitlab::BackgroundMigration.steal(PREVIOUS_MIGRATION) - - say 'Scheduling `PopulateMergeRequestMetricsWithEventsData` jobs' - # It will update around 4_000_000 records in batches of 10_000 merge - # requests (running between 5 minutes) and should take around 53 hours to complete. - # Apparently, production PostgreSQL is able to vacuum 10k-20k dead_tuples - # per minute. So this should give us enough space. - # - # More information about the updates in `PopulateMergeRequestMetricsWithEventsDataImproved` class. - # - MergeRequest.all.each_batch(of: BATCH_SIZE) do |relation, index| - range = relation.pluck('MIN(id)', 'MAX(id)').first - - BackgroundMigrationWorker.perform_in(index * 8.minutes, MIGRATION, range) - end - end - - def down - end -end diff --git a/db/post_migrate/20190402224749_schedule_merge_request_assignees_migration_progress_check.rb b/db/post_migrate/20190402224749_schedule_merge_request_assignees_migration_progress_check.rb new file mode 100644 index 00000000000..6fb67deb834 --- /dev/null +++ b/db/post_migrate/20190402224749_schedule_merge_request_assignees_migration_progress_check.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class ScheduleMergeRequestAssigneesMigrationProgressCheck < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + MIGRATION = 'MergeRequestAssigneesMigrationProgressCheck'.freeze + + DOWNTIME = false + + disable_ddl_transaction! + + def up + BackgroundMigrationWorker.perform_async(MIGRATION) + end + + def down + end +end diff --git a/db/post_migrate/20190404143330_add_unique_constraint_to_approvals_user_id_and_merge_request_id.rb b/db/post_migrate/20190404143330_add_unique_constraint_to_approvals_user_id_and_merge_request_id.rb new file mode 100644 index 00000000000..447f91ebc7e --- /dev/null +++ b/db/post_migrate/20190404143330_add_unique_constraint_to_approvals_user_id_and_merge_request_id.rb @@ -0,0 +1,55 @@ +# frozen_string_literal: true + +class AddUniqueConstraintToApprovalsUserIdAndMergeRequestId < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + remove_duplicates + add_concurrent_index :approvals, [:user_id, :merge_request_id], unique: true + end + + def down + remove_concurrent_index :approvals, [:user_id, :merge_request_id] + end + + private + + def remove_duplicates + add_concurrent_index :approvals, [:user_id, :merge_request_id, :id] + + if Gitlab::Database.mysql? + execute <<-SQL + DELETE FROM a + USING approvals AS a + INNER JOIN ( + SELECT user_id, merge_request_id, MIN(id) as min_id + FROM approvals + GROUP BY user_id, merge_request_id + HAVING COUNT(id) > 1 + ) as approvals_with_duplicates + ON approvals_with_duplicates.user_id = a.user_id + AND approvals_with_duplicates.merge_request_id = a.merge_request_id + WHERE approvals_with_duplicates.min_id <> a.id; + SQL + else + execute <<-SQL + DELETE FROM approvals + USING ( + SELECT user_id, merge_request_id, MIN(id) as min_id + FROM approvals + GROUP BY user_id, merge_request_id + HAVING COUNT(id) > 1 + ) as approvals_with_duplicates + WHERE approvals_with_duplicates.user_id = approvals.user_id + AND approvals_with_duplicates.merge_request_id = approvals.merge_request_id + AND approvals_with_duplicates.min_id <> approvals.id; + SQL + end + + remove_concurrent_index :approvals, [:user_id, :merge_request_id, :id] + end +end diff --git a/db/post_migrate/20190404231137_remove_alternate_url_from_geo_nodes.rb b/db/post_migrate/20190404231137_remove_alternate_url_from_geo_nodes.rb new file mode 100644 index 00000000000..785ceb2fb28 --- /dev/null +++ b/db/post_migrate/20190404231137_remove_alternate_url_from_geo_nodes.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +# We are reverting the feature that created this column. This is for anyone who +# migrated while the feature still existed in master. +class RemoveAlternateUrlFromGeoNodes < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def up + remove_column(:geo_nodes, :alternate_url) if column_exists?(:geo_nodes, :alternate_url) + end + + def down + add_column :geo_nodes, :alternate_url, :string + end +end diff --git a/db/post_migrate/20190418132125_populate_project_statistics_packages_size.rb b/db/post_migrate/20190418132125_populate_project_statistics_packages_size.rb new file mode 100644 index 00000000000..a6bee3453c1 --- /dev/null +++ b/db/post_migrate/20190418132125_populate_project_statistics_packages_size.rb @@ -0,0 +1,46 @@ +# frozen_string_literal: true + +class PopulateProjectStatisticsPackagesSize < ActiveRecord::Migration[5.0] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + class ProjectStatistics < ActiveRecord::Base + self.table_name = 'project_statistics' + end + + def up + stats_ids = ProjectStatistics.joins( + <<~SQL.strip_heredoc + INNER JOIN projects ON projects.id = project_statistics.project_id + INNER JOIN packages_packages ON packages_packages.project_id = projects.id + INNER JOIN packages_package_files ON packages_package_files.package_id = packages_packages.id + SQL + ).distinct.select(:id) + + packages_size = Arel.sql( + '(SELECT SUM(size) FROM packages_package_files ' \ + 'JOIN packages_packages ON packages_packages.id = packages_package_files.package_id ' \ + 'WHERE packages_packages.project_id = project_statistics.project_id)' + ) + update_column_in_batches(:project_statistics, :packages_size, packages_size) do |table, query| + query.where(table[:id].in(stats_ids)) + end + + storage_size = Arel.sql('(repository_size + lfs_objects_size + build_artifacts_size + COALESCE(packages_size, 0))') + update_column_in_batches(:project_statistics, :storage_size, storage_size) do |table, query| + query.where(table[:id].in(stats_ids)) + end + end + + def down + storage_size = Arel.sql('(repository_size + lfs_objects_size + build_artifacts_size)') + update_column_in_batches(:project_statistics, :storage_size, storage_size) do |table, query| + query.where(table[:packages_size].gt(0)) + end + + update_column_in_batches(:project_statistics, :packages_size, nil) + end +end diff --git a/db/post_migrate/20190511144331_remove_users_support_type.rb b/db/post_migrate/20190511144331_remove_users_support_type.rb new file mode 100644 index 00000000000..32df33432b9 --- /dev/null +++ b/db/post_migrate/20190511144331_remove_users_support_type.rb @@ -0,0 +1,27 @@ +# frozen_string_literal: true + +class RemoveUsersSupportType < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + INDEX_STATE_INTERNAL_ATTRS = 'index_users_on_state_and_internal_attrs' + + disable_ddl_transaction! + + def up + remove_concurrent_index :users, :state, name: INDEX_STATE_INTERNAL_ATTRS + remove_concurrent_index :users, :support_bot + + remove_column :users, :support_bot + end + + def down + add_column :users, :support_bot, :boolean + + add_concurrent_index :users, :support_bot + add_concurrent_index :users, :state, + name: INDEX_STATE_INTERNAL_ATTRS, + where: 'ghost <> true AND support_bot <> true' + end +end diff --git a/db/post_migrate/20190517153211_migrate_k8s_service_integration.rb b/db/post_migrate/20190517153211_migrate_k8s_service_integration.rb new file mode 100644 index 00000000000..4bd04edb239 --- /dev/null +++ b/db/post_migrate/20190517153211_migrate_k8s_service_integration.rb @@ -0,0 +1,104 @@ +# frozen_string_literal: true + +class MigrateK8sServiceIntegration < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + class Cluster < ActiveRecord::Base + self.table_name = 'clusters' + + has_one :platform_kubernetes, class_name: 'MigrateK8sServiceIntegration::PlatformsKubernetes' + + accepts_nested_attributes_for :platform_kubernetes + + enum cluster_type: { + instance_type: 1, + group_type: 2, + project_type: 3 + } + + enum platform_type: { + kubernetes: 1 + } + + enum provider_type: { + user: 0, + gcp: 1 + } + end + + class PlatformsKubernetes < ActiveRecord::Base + self.table_name = 'cluster_platforms_kubernetes' + + belongs_to :cluster, class_name: 'MigrateK8sServiceIntegration::Cluster' + + attr_encrypted :token, + mode: :per_attribute_iv, + key: Settings.attr_encrypted_db_key_base_truncated, + algorithm: 'aes-256-cbc' + end + + class Service < ActiveRecord::Base + include EachBatch + + self.table_name = 'services' + self.inheritance_column = :_type_disabled # Disable STI, otherwise KubernetesModel will be looked up + + belongs_to :project, class_name: 'MigrateK8sServiceIntegration::Project', foreign_key: :project_id + + scope :kubernetes_service_templates, -> do + where(category: 'deployment', type: 'KubernetesService', template: true) + end + + def api_url + parsed_properties['api_url'].presence + end + + def ca_pem + parsed_properties['ca_pem'] + end + + def namespace + parsed_properties['namespace'].presence + end + + def token + parsed_properties['token'].presence + end + + private + + def parsed_properties + @parsed_properties ||= JSON.parse(self.properties) + end + end + + def up + has_instance_cluster = Cluster.instance_type.where(enabled: true).exists? + + MigrateK8sServiceIntegration::Service.kubernetes_service_templates.find_each do |service| + next unless service.api_url && service.token + + MigrateK8sServiceIntegration::Cluster.create!( + enabled: !has_instance_cluster && service.active, + managed: false, + name: 'KubernetesService', + cluster_type: 'instance_type', + provider_type: 'user', + platform_type: 'kubernetes', + platform_kubernetes_attributes: { + api_url: service.api_url, + ca_cert: service.ca_pem, + namespace: service.namespace, + token: service.token + } + ) + end + end + + def down + # It is not possible to tell which instance-level clusters were created by + # this migration. The original data is intentionally left intact. + end +end diff --git a/db/post_migrate/20190520201748_populate_rule_type_on_approval_merge_request_rules.rb b/db/post_migrate/20190520201748_populate_rule_type_on_approval_merge_request_rules.rb new file mode 100644 index 00000000000..0f0df456134 --- /dev/null +++ b/db/post_migrate/20190520201748_populate_rule_type_on_approval_merge_request_rules.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class PopulateRuleTypeOnApprovalMergeRequestRules < ActiveRecord::Migration[5.1] + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + class ApprovalMergeRequestRule < ActiveRecord::Base + include EachBatch + + enum rule_types: { + regular: 1, + code_owner: 2 + } + end + + def up + # On Gitlab.com, this should update about 17k rows. Since our updates are + # small and we are populating prior to indexing, the overhead should be small + ApprovalMergeRequestRule.where(code_owner: true).each_batch do |batch| + batch.update_all(rule_type: ApprovalMergeRequestRule.rule_types[:code_owner]) + end + end + + def down + # code_owner is already kept in sync with `rule_type`, so no changes are needed + end +end diff --git a/db/schema.rb b/db/schema.rb index 4ed7c0cb248..6098ebc2a78 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20190611161641) do +ActiveRecord::Schema.define(version: 20190613030606) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -45,6 +45,7 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.text "message_font_color" t.string "favicon" t.boolean "email_header_and_footer_enabled", default: false, null: false + t.integer "updated_by" end create_table "application_setting_terms", id: :serial, force: :cascade do |t| @@ -140,7 +141,7 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.boolean "help_page_hide_commercial_content", default: false t.string "help_page_support_url" t.integer "performance_bar_allowed_group_id" - t.boolean "hashed_storage_enabled", default: false, null: false + t.boolean "hashed_storage_enabled", default: true, null: false t.boolean "project_export_enabled", default: true, null: false t.boolean "auto_devops_enabled", default: true, null: false t.boolean "throttle_unauthenticated_enabled", default: false, null: false @@ -153,7 +154,7 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.integer "throttle_authenticated_web_requests_per_period", default: 7200, null: false t.integer "throttle_authenticated_web_period_in_seconds", default: 3600, null: false t.boolean "password_authentication_enabled_for_web" - t.boolean "password_authentication_enabled_for_git", default: true + t.boolean "password_authentication_enabled_for_git", default: true, null: false t.integer "gitaly_timeout_default", default: 55, null: false t.integer "gitaly_timeout_medium", default: 30, null: false t.integer "gitaly_timeout_fast", default: 10, null: false @@ -196,9 +197,136 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.boolean "dns_rebinding_protection_enabled", default: true, null: false t.boolean "default_project_deletion_protection", default: false, null: false t.boolean "lock_memberships_to_ldap", default: false, null: false + t.text "help_text" + t.boolean "elasticsearch_indexing", default: false, null: false + t.boolean "elasticsearch_search", default: false, null: false + t.integer "shared_runners_minutes", default: 0, null: false + t.bigint "repository_size_limit", default: 0 + t.string "elasticsearch_url", default: "http://localhost:9200" + t.boolean "elasticsearch_aws", default: false, null: false + t.string "elasticsearch_aws_region", default: "us-east-1" + t.string "elasticsearch_aws_access_key" + t.string "elasticsearch_aws_secret_access_key" + t.integer "geo_status_timeout", default: 10 + t.boolean "elasticsearch_experimental_indexer" + t.boolean "check_namespace_plan", default: false, null: false + t.integer "mirror_max_delay", default: 300, null: false + t.integer "mirror_max_capacity", default: 100, null: false + t.integer "mirror_capacity_threshold", default: 50, null: false + t.boolean "slack_app_enabled", default: false + t.string "slack_app_id" + t.string "slack_app_secret" + t.string "slack_app_verification_token" + t.boolean "allow_group_owners_to_manage_ldap", default: true, null: false + t.string "email_additional_text" + t.integer "file_template_project_id" + t.boolean "pseudonymizer_enabled", default: false, null: false + t.boolean "snowplow_enabled", default: false, null: false + t.string "snowplow_collector_uri" + t.string "snowplow_site_id" + t.string "snowplow_cookie_domain" + t.integer "custom_project_templates_group_id" + t.boolean "elasticsearch_limit_indexing", default: false, null: false + t.string "geo_node_allowed_ips", default: "0.0.0.0/0, ::/0" + t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id", using: :btree + t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id", using: :btree t.index ["usage_stats_set_by_user_id"], name: "index_application_settings_on_usage_stats_set_by_user_id", using: :btree end + create_table "approval_merge_request_rule_sources", force: :cascade do |t| + t.bigint "approval_merge_request_rule_id", null: false + t.bigint "approval_project_rule_id", null: false + t.index ["approval_merge_request_rule_id"], name: "index_approval_merge_request_rule_sources_1", unique: true, using: :btree + t.index ["approval_project_rule_id"], name: "index_approval_merge_request_rule_sources_2", using: :btree + end + + create_table "approval_merge_request_rules", force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "merge_request_id", null: false + t.integer "approvals_required", limit: 2, default: 0, null: false + t.boolean "code_owner", default: false, null: false + t.string "name", null: false + t.integer "rule_type", limit: 2, default: 1, null: false + t.index ["merge_request_id", "code_owner", "name"], name: "approval_rule_name_index_for_code_owners", unique: true, where: "(code_owner = true)", using: :btree + t.index ["merge_request_id", "code_owner"], name: "index_approval_merge_request_rules_1", using: :btree + t.index ["merge_request_id", "rule_type", "name"], name: "index_approval_rule_name_for_code_owners_rule_type", unique: true, where: "(rule_type = 2)", using: :btree + t.index ["merge_request_id", "rule_type"], name: "index_approval_rules_code_owners_rule_type", where: "(rule_type = 2)", using: :btree + end + + create_table "approval_merge_request_rules_approved_approvers", force: :cascade do |t| + t.bigint "approval_merge_request_rule_id", null: false + t.integer "user_id", null: false + t.index ["approval_merge_request_rule_id", "user_id"], name: "index_approval_merge_request_rules_approved_approvers_1", unique: true, using: :btree + t.index ["user_id"], name: "index_approval_merge_request_rules_approved_approvers_2", using: :btree + end + + create_table "approval_merge_request_rules_groups", force: :cascade do |t| + t.bigint "approval_merge_request_rule_id", null: false + t.integer "group_id", null: false + t.index ["approval_merge_request_rule_id", "group_id"], name: "index_approval_merge_request_rules_groups_1", unique: true, using: :btree + t.index ["group_id"], name: "index_approval_merge_request_rules_groups_2", using: :btree + end + + create_table "approval_merge_request_rules_users", force: :cascade do |t| + t.bigint "approval_merge_request_rule_id", null: false + t.integer "user_id", null: false + t.index ["approval_merge_request_rule_id", "user_id"], name: "index_approval_merge_request_rules_users_1", unique: true, using: :btree + t.index ["user_id"], name: "index_approval_merge_request_rules_users_2", using: :btree + end + + create_table "approval_project_rules", force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "project_id", null: false + t.integer "approvals_required", limit: 2, default: 0, null: false + t.string "name", null: false + t.index ["project_id"], name: "index_approval_project_rules_on_project_id", using: :btree + end + + create_table "approval_project_rules_groups", force: :cascade do |t| + t.bigint "approval_project_rule_id", null: false + t.integer "group_id", null: false + t.index ["approval_project_rule_id", "group_id"], name: "index_approval_project_rules_groups_1", unique: true, using: :btree + t.index ["group_id"], name: "index_approval_project_rules_groups_2", using: :btree + end + + create_table "approval_project_rules_users", force: :cascade do |t| + t.bigint "approval_project_rule_id", null: false + t.integer "user_id", null: false + t.index ["approval_project_rule_id", "user_id"], name: "index_approval_project_rules_users_1", unique: true, using: :btree + t.index ["user_id"], name: "index_approval_project_rules_users_2", using: :btree + end + + create_table "approvals", id: :serial, force: :cascade do |t| + t.integer "merge_request_id", null: false + t.integer "user_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.index ["merge_request_id"], name: "index_approvals_on_merge_request_id", using: :btree + t.index ["user_id", "merge_request_id"], name: "index_approvals_on_user_id_and_merge_request_id", unique: true, using: :btree + end + + create_table "approver_groups", id: :serial, force: :cascade do |t| + t.integer "target_id", null: false + t.string "target_type", null: false + t.integer "group_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.index ["group_id"], name: "index_approver_groups_on_group_id", using: :btree + t.index ["target_id", "target_type"], name: "index_approver_groups_on_target_id_and_target_type", using: :btree + end + + create_table "approvers", id: :serial, force: :cascade do |t| + t.integer "target_id", null: false + t.string "target_type" + t.integer "user_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.index ["target_id", "target_type"], name: "index_approvers_on_target_id_and_target_type", using: :btree + t.index ["user_id"], name: "index_approvers_on_user_id", using: :btree + end + create_table "audit_events", id: :serial, force: :cascade do |t| t.integer "author_id", null: false t.string "type", null: false @@ -233,6 +361,13 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["project_id"], name: "index_badges_on_project_id", using: :btree end + create_table "board_assignees", id: :serial, force: :cascade do |t| + t.integer "board_id", null: false + t.integer "assignee_id", null: false + t.index ["assignee_id"], name: "index_board_assignees_on_assignee_id", using: :btree + t.index ["board_id", "assignee_id"], name: "index_board_assignees_on_board_id_and_assignee_id", unique: true, using: :btree + end + create_table "board_group_recent_visits", force: :cascade do |t| t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "updated_at", null: false @@ -245,6 +380,13 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["user_id"], name: "index_board_group_recent_visits_on_user_id", using: :btree end + create_table "board_labels", id: :serial, force: :cascade do |t| + t.integer "board_id", null: false + t.integer "label_id", null: false + t.index ["board_id", "label_id"], name: "index_board_labels_on_board_id_and_label_id", unique: true, using: :btree + t.index ["label_id"], name: "index_board_labels_on_label_id", using: :btree + end + create_table "board_project_recent_visits", force: :cascade do |t| t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "updated_at", null: false @@ -262,7 +404,11 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.datetime "created_at", null: false t.datetime "updated_at", null: false t.integer "group_id" + t.integer "milestone_id" + t.integer "weight" + t.string "name", default: "Development", null: false t.index ["group_id"], name: "index_boards_on_group_id", using: :btree + t.index ["milestone_id"], name: "index_boards_on_milestone_id", using: :btree t.index ["project_id"], name: "index_boards_on_project_id", using: :btree end @@ -375,6 +521,7 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.integer "failure_reason" t.datetime_with_timezone "scheduled_at" t.string "token_encrypted" + t.integer "upstream_pipeline_id" t.index ["artifacts_expire_at"], name: "index_ci_builds_on_artifacts_expire_at", where: "(artifacts_file <> ''::text)", using: :btree t.index ["auto_canceled_by_id"], name: "index_ci_builds_on_auto_canceled_by_id", using: :btree t.index ["commit_id", "artifacts_expire_at", "id"], name: "index_ci_builds_on_commit_id_and_artifacts_expireatandidpartial", where: "(((type)::text = 'Ci::Build'::text) AND ((retried = false) OR (retried IS NULL)) AND ((name)::text = ANY (ARRAY[('sast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('sast:container'::character varying)::text, ('container_scanning'::character varying)::text, ('dast'::character varying)::text])))", using: :btree @@ -382,6 +529,7 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["commit_id", "status", "type"], name: "index_ci_builds_on_commit_id_and_status_and_type", using: :btree t.index ["commit_id", "type", "name", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_name_and_ref", using: :btree t.index ["commit_id", "type", "ref"], name: "index_ci_builds_on_commit_id_and_type_and_ref", using: :btree + t.index ["name"], name: "index_ci_builds_on_name_for_security_products_values", where: "((name)::text = ANY (ARRAY[('container_scanning'::character varying)::text, ('dast'::character varying)::text, ('dependency_scanning'::character varying)::text, ('license_management'::character varying)::text, ('sast'::character varying)::text]))", using: :btree t.index ["project_id", "id"], name: "index_ci_builds_on_project_id_and_id", using: :btree t.index ["project_id", "status"], name: "index_ci_builds_project_id_and_status_for_live_jobs_partial2", where: "(((type)::text = 'Ci::Build'::text) AND ((status)::text = ANY (ARRAY[('running'::character varying)::text, ('pending'::character varying)::text, ('created'::character varying)::text])))", using: :btree t.index ["protected"], name: "index_ci_builds_on_protected", using: :btree @@ -394,6 +542,7 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["token"], name: "index_ci_builds_on_token", unique: true, using: :btree t.index ["token_encrypted"], name: "index_ci_builds_on_token_encrypted", unique: true, where: "(token_encrypted IS NOT NULL)", using: :btree t.index ["updated_at"], name: "index_ci_builds_on_updated_at", using: :btree + t.index ["upstream_pipeline_id"], name: "index_ci_builds_on_upstream_pipeline_id", where: "(upstream_pipeline_id IS NOT NULL)", using: :btree t.index ["user_id"], name: "index_ci_builds_on_user_id", using: :btree end @@ -582,6 +731,19 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["token_encrypted"], name: "index_ci_runners_on_token_encrypted", using: :btree end + create_table "ci_sources_pipelines", id: :serial, force: :cascade do |t| + t.integer "project_id" + t.integer "pipeline_id" + t.integer "source_project_id" + t.integer "source_job_id" + t.integer "source_pipeline_id" + t.index ["pipeline_id"], name: "index_ci_sources_pipelines_on_pipeline_id", using: :btree + t.index ["project_id"], name: "index_ci_sources_pipelines_on_project_id", using: :btree + t.index ["source_job_id"], name: "index_ci_sources_pipelines_on_source_job_id", using: :btree + t.index ["source_pipeline_id"], name: "index_ci_sources_pipelines_on_source_pipeline_id", using: :btree + t.index ["source_project_id"], name: "index_ci_sources_pipelines_on_source_project_id", using: :btree + end + create_table "ci_stages", id: :serial, force: :cascade do |t| t.integer "project_id" t.integer "pipeline_id" @@ -770,6 +932,9 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.text "status_reason" t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "updated_at", null: false + t.string "encrypted_alert_manager_token" + t.string "encrypted_alert_manager_token_iv" + t.datetime_with_timezone "last_update_started_at" t.index ["cluster_id"], name: "index_clusters_applications_prometheus_on_cluster_id", unique: true, using: :btree end @@ -846,6 +1011,25 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.float "percentage_service_desk_issues", default: 0.0, null: false end + create_table "dependency_proxy_blobs", id: :serial, force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.text "file", null: false + t.string "file_name", null: false + t.integer "file_store" + t.integer "group_id", null: false + t.bigint "size" + t.datetime_with_timezone "updated_at", null: false + t.index ["group_id", "file_name"], name: "index_dependency_proxy_blobs_on_group_id_and_file_name", using: :btree + end + + create_table "dependency_proxy_group_settings", id: :serial, force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.boolean "enabled", default: false, null: false + t.integer "group_id", null: false + t.datetime_with_timezone "updated_at", null: false + t.index ["group_id"], name: "index_dependency_proxy_group_settings_on_group_id", using: :btree + end + create_table "deploy_keys_projects", id: :serial, force: :cascade do |t| t.integer "deploy_key_id", null: false t.integer "project_id", null: false @@ -893,6 +1077,55 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["project_id", "status"], name: "index_deployments_on_project_id_and_status", using: :btree end + create_table "design_management_designs", force: :cascade do |t| + t.integer "project_id", null: false + t.integer "issue_id", null: false + t.string "filename", null: false + t.index ["issue_id", "filename"], name: "index_design_management_designs_on_issue_id_and_filename", unique: true, using: :btree + t.index ["project_id"], name: "index_design_management_designs_on_project_id", using: :btree + end + + create_table "design_management_designs_versions", id: false, force: :cascade do |t| + t.bigint "design_id", null: false + t.bigint "version_id", null: false + t.index ["design_id", "version_id"], name: "design_management_designs_versions_uniqueness", unique: true, using: :btree + t.index ["design_id"], name: "index_design_management_designs_versions_on_design_id", using: :btree + t.index ["version_id"], name: "index_design_management_designs_versions_on_version_id", using: :btree + end + + create_table "design_management_versions", force: :cascade do |t| + t.binary "sha", null: false + t.index ["sha"], name: "index_design_management_versions_on_sha", unique: true, using: :btree + end + + create_table "draft_notes", force: :cascade do |t| + t.integer "merge_request_id", null: false + t.integer "author_id", null: false + t.boolean "resolve_discussion", default: false, null: false + t.string "discussion_id" + t.text "note", null: false + t.text "position" + t.text "original_position" + t.text "change_position" + t.index ["author_id"], name: "index_draft_notes_on_author_id", using: :btree + t.index ["discussion_id"], name: "index_draft_notes_on_discussion_id", using: :btree + t.index ["merge_request_id"], name: "index_draft_notes_on_merge_request_id", using: :btree + end + + create_table "elasticsearch_indexed_namespaces", id: false, force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "namespace_id" + t.index ["namespace_id"], name: "index_elasticsearch_indexed_namespaces_on_namespace_id", unique: true, using: :btree + end + + create_table "elasticsearch_indexed_projects", id: false, force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "project_id" + t.index ["project_id"], name: "index_elasticsearch_indexed_projects_on_project_id", unique: true, using: :btree + end + create_table "emails", id: :serial, force: :cascade do |t| t.integer "user_id", null: false t.string "email", null: false @@ -915,10 +1148,67 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.string "environment_type" t.string "state", default: "available", null: false t.string "slug", null: false + t.index ["name"], name: "index_environments_on_name_varchar_pattern_ops", using: :btree, opclasses: {"name"=>"varchar_pattern_ops"} t.index ["project_id", "name"], name: "index_environments_on_project_id_and_name", unique: true, using: :btree t.index ["project_id", "slug"], name: "index_environments_on_project_id_and_slug", unique: true, using: :btree end + create_table "epic_issues", id: :serial, force: :cascade do |t| + t.integer "epic_id", null: false + t.integer "issue_id", null: false + t.integer "relative_position", default: 1073741823, null: false + t.index ["epic_id"], name: "index_epic_issues_on_epic_id", using: :btree + t.index ["issue_id"], name: "index_epic_issues_on_issue_id", unique: true, using: :btree + end + + create_table "epic_metrics", id: :serial, force: :cascade do |t| + t.integer "epic_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["epic_id"], name: "index_epic_metrics", using: :btree + end + + create_table "epics", id: :serial, force: :cascade do |t| + t.integer "milestone_id" + t.integer "group_id", null: false + t.integer "author_id", null: false + t.integer "assignee_id" + t.integer "iid", null: false + t.integer "cached_markdown_version" + t.integer "updated_by_id" + t.integer "last_edited_by_id" + t.integer "lock_version" + t.date "start_date" + t.date "end_date" + t.datetime "last_edited_at" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "title", null: false + t.string "title_html", null: false + t.text "description" + t.text "description_html" + t.integer "start_date_sourcing_milestone_id" + t.integer "due_date_sourcing_milestone_id" + t.date "start_date_fixed" + t.date "due_date_fixed" + t.boolean "start_date_is_fixed" + t.boolean "due_date_is_fixed" + t.integer "state", limit: 2, default: 1, null: false + t.integer "closed_by_id" + t.datetime "closed_at" + t.integer "parent_id" + t.integer "relative_position" + t.index ["assignee_id"], name: "index_epics_on_assignee_id", using: :btree + t.index ["author_id"], name: "index_epics_on_author_id", using: :btree + t.index ["closed_by_id"], name: "index_epics_on_closed_by_id", using: :btree + t.index ["end_date"], name: "index_epics_on_end_date", using: :btree + t.index ["group_id"], name: "index_epics_on_group_id", using: :btree + t.index ["iid"], name: "index_epics_on_iid", using: :btree + t.index ["milestone_id"], name: "index_milestone", using: :btree + t.index ["parent_id"], name: "index_epics_on_parent_id", using: :btree + t.index ["start_date"], name: "index_epics_on_start_date", using: :btree + end + create_table "events", id: :serial, force: :cascade do |t| t.integer "project_id" t.integer "author_id", null: false @@ -973,6 +1263,227 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree end + create_table "geo_cache_invalidation_events", force: :cascade do |t| + t.string "key", null: false + end + + create_table "geo_event_log", force: :cascade do |t| + t.datetime "created_at", null: false + t.bigint "repository_updated_event_id" + t.bigint "repository_deleted_event_id" + t.bigint "repository_renamed_event_id" + t.bigint "repositories_changed_event_id" + t.bigint "repository_created_event_id" + t.bigint "hashed_storage_migrated_event_id" + t.bigint "lfs_object_deleted_event_id" + t.bigint "hashed_storage_attachments_event_id" + t.bigint "upload_deleted_event_id" + t.bigint "job_artifact_deleted_event_id" + t.bigint "reset_checksum_event_id" + t.bigint "cache_invalidation_event_id" + t.index ["cache_invalidation_event_id"], name: "index_geo_event_log_on_cache_invalidation_event_id", where: "(cache_invalidation_event_id IS NOT NULL)", using: :btree + t.index ["hashed_storage_attachments_event_id"], name: "index_geo_event_log_on_hashed_storage_attachments_event_id", where: "(hashed_storage_attachments_event_id IS NOT NULL)", using: :btree + t.index ["hashed_storage_migrated_event_id"], name: "index_geo_event_log_on_hashed_storage_migrated_event_id", where: "(hashed_storage_migrated_event_id IS NOT NULL)", using: :btree + t.index ["job_artifact_deleted_event_id"], name: "index_geo_event_log_on_job_artifact_deleted_event_id", where: "(job_artifact_deleted_event_id IS NOT NULL)", using: :btree + t.index ["lfs_object_deleted_event_id"], name: "index_geo_event_log_on_lfs_object_deleted_event_id", where: "(lfs_object_deleted_event_id IS NOT NULL)", using: :btree + t.index ["repositories_changed_event_id"], name: "index_geo_event_log_on_repositories_changed_event_id", where: "(repositories_changed_event_id IS NOT NULL)", using: :btree + t.index ["repository_created_event_id"], name: "index_geo_event_log_on_repository_created_event_id", where: "(repository_created_event_id IS NOT NULL)", using: :btree + t.index ["repository_deleted_event_id"], name: "index_geo_event_log_on_repository_deleted_event_id", where: "(repository_deleted_event_id IS NOT NULL)", using: :btree + t.index ["repository_renamed_event_id"], name: "index_geo_event_log_on_repository_renamed_event_id", where: "(repository_renamed_event_id IS NOT NULL)", using: :btree + t.index ["repository_updated_event_id"], name: "index_geo_event_log_on_repository_updated_event_id", where: "(repository_updated_event_id IS NOT NULL)", using: :btree + t.index ["reset_checksum_event_id"], name: "index_geo_event_log_on_reset_checksum_event_id", where: "(reset_checksum_event_id IS NOT NULL)", using: :btree + t.index ["upload_deleted_event_id"], name: "index_geo_event_log_on_upload_deleted_event_id", where: "(upload_deleted_event_id IS NOT NULL)", using: :btree + end + + create_table "geo_hashed_storage_attachments_events", force: :cascade do |t| + t.integer "project_id", null: false + t.text "old_attachments_path", null: false + t.text "new_attachments_path", null: false + t.index ["project_id"], name: "index_geo_hashed_storage_attachments_events_on_project_id", using: :btree + end + + create_table "geo_hashed_storage_migrated_events", force: :cascade do |t| + t.integer "project_id", null: false + t.text "repository_storage_name", null: false + t.text "old_disk_path", null: false + t.text "new_disk_path", null: false + t.text "old_wiki_disk_path", null: false + t.text "new_wiki_disk_path", null: false + t.integer "old_storage_version", limit: 2 + t.integer "new_storage_version", limit: 2, null: false + t.index ["project_id"], name: "index_geo_hashed_storage_migrated_events_on_project_id", using: :btree + end + + create_table "geo_job_artifact_deleted_events", force: :cascade do |t| + t.integer "job_artifact_id", null: false + t.string "file_path", null: false + t.index ["job_artifact_id"], name: "index_geo_job_artifact_deleted_events_on_job_artifact_id", using: :btree + end + + create_table "geo_lfs_object_deleted_events", force: :cascade do |t| + t.integer "lfs_object_id", null: false + t.string "oid", null: false + t.string "file_path", null: false + t.index ["lfs_object_id"], name: "index_geo_lfs_object_deleted_events_on_lfs_object_id", using: :btree + end + + create_table "geo_node_namespace_links", id: :serial, force: :cascade do |t| + t.integer "geo_node_id", null: false + t.integer "namespace_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["geo_node_id", "namespace_id"], name: "index_geo_node_namespace_links_on_geo_node_id_and_namespace_id", unique: true, using: :btree + t.index ["geo_node_id"], name: "index_geo_node_namespace_links_on_geo_node_id", using: :btree + t.index ["namespace_id"], name: "index_geo_node_namespace_links_on_namespace_id", using: :btree + end + + create_table "geo_node_statuses", id: :serial, force: :cascade do |t| + t.integer "geo_node_id", null: false + t.integer "db_replication_lag_seconds" + t.integer "repositories_synced_count" + t.integer "repositories_failed_count" + t.integer "lfs_objects_count" + t.integer "lfs_objects_synced_count" + t.integer "lfs_objects_failed_count" + t.integer "attachments_count" + t.integer "attachments_synced_count" + t.integer "attachments_failed_count" + t.integer "last_event_id" + t.datetime "last_event_date" + t.integer "cursor_last_event_id" + t.datetime "cursor_last_event_date" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.datetime "last_successful_status_check_at" + t.string "status_message" + t.integer "replication_slots_count" + t.integer "replication_slots_used_count" + t.bigint "replication_slots_max_retained_wal_bytes" + t.integer "wikis_synced_count" + t.integer "wikis_failed_count" + t.integer "job_artifacts_count" + t.integer "job_artifacts_synced_count" + t.integer "job_artifacts_failed_count" + t.string "version" + t.string "revision" + t.integer "repositories_verified_count" + t.integer "repositories_verification_failed_count" + t.integer "wikis_verified_count" + t.integer "wikis_verification_failed_count" + t.integer "lfs_objects_synced_missing_on_primary_count" + t.integer "job_artifacts_synced_missing_on_primary_count" + t.integer "attachments_synced_missing_on_primary_count" + t.integer "repositories_checksummed_count" + t.integer "repositories_checksum_failed_count" + t.integer "repositories_checksum_mismatch_count" + t.integer "wikis_checksummed_count" + t.integer "wikis_checksum_failed_count" + t.integer "wikis_checksum_mismatch_count" + t.binary "storage_configuration_digest" + t.integer "repositories_retrying_verification_count" + t.integer "wikis_retrying_verification_count" + t.integer "projects_count" + t.index ["geo_node_id"], name: "index_geo_node_statuses_on_geo_node_id", unique: true, using: :btree + end + + create_table "geo_nodes", id: :serial, force: :cascade do |t| + t.boolean "primary" + t.integer "oauth_application_id" + t.boolean "enabled", default: true, null: false + t.string "access_key" + t.string "encrypted_secret_access_key" + t.string "encrypted_secret_access_key_iv" + t.string "clone_url_prefix" + t.integer "files_max_capacity", default: 10, null: false + t.integer "repos_max_capacity", default: 25, null: false + t.string "url", null: false + t.string "selective_sync_type" + t.text "selective_sync_shards" + t.integer "verification_max_capacity", default: 100, null: false + t.integer "minimum_reverification_interval", default: 7, null: false + t.string "internal_url" + t.string "name", null: false + t.index ["access_key"], name: "index_geo_nodes_on_access_key", using: :btree + t.index ["name"], name: "index_geo_nodes_on_name", unique: true, using: :btree + t.index ["primary"], name: "index_geo_nodes_on_primary", using: :btree + end + + create_table "geo_repositories_changed_events", force: :cascade do |t| + t.integer "geo_node_id", null: false + t.index ["geo_node_id"], name: "index_geo_repositories_changed_events_on_geo_node_id", using: :btree + end + + create_table "geo_repository_created_events", force: :cascade do |t| + t.integer "project_id", null: false + t.text "repository_storage_name", null: false + t.text "repo_path", null: false + t.text "wiki_path" + t.text "project_name", null: false + t.index ["project_id"], name: "index_geo_repository_created_events_on_project_id", using: :btree + end + + create_table "geo_repository_deleted_events", force: :cascade do |t| + t.integer "project_id", null: false + t.text "repository_storage_name", null: false + t.text "deleted_path", null: false + t.text "deleted_wiki_path" + t.text "deleted_project_name", null: false + t.index ["project_id"], name: "index_geo_repository_deleted_events_on_project_id", using: :btree + end + + create_table "geo_repository_renamed_events", force: :cascade do |t| + t.integer "project_id", null: false + t.text "repository_storage_name", null: false + t.text "old_path_with_namespace", null: false + t.text "new_path_with_namespace", null: false + t.text "old_wiki_path_with_namespace", null: false + t.text "new_wiki_path_with_namespace", null: false + t.text "old_path", null: false + t.text "new_path", null: false + t.index ["project_id"], name: "index_geo_repository_renamed_events_on_project_id", using: :btree + end + + create_table "geo_repository_updated_events", force: :cascade do |t| + t.integer "branches_affected", null: false + t.integer "tags_affected", null: false + t.integer "project_id", null: false + t.integer "source", limit: 2, null: false + t.boolean "new_branch", default: false, null: false + t.boolean "remove_branch", default: false, null: false + t.text "ref" + t.index ["project_id"], name: "index_geo_repository_updated_events_on_project_id", using: :btree + t.index ["source"], name: "index_geo_repository_updated_events_on_source", using: :btree + end + + create_table "geo_reset_checksum_events", force: :cascade do |t| + t.integer "project_id", null: false + t.index ["project_id"], name: "index_geo_reset_checksum_events_on_project_id", using: :btree + end + + create_table "geo_upload_deleted_events", force: :cascade do |t| + t.integer "upload_id", null: false + t.string "file_path", null: false + t.integer "model_id", null: false + t.string "model_type", null: false + t.string "uploader", null: false + t.index ["upload_id"], name: "index_geo_upload_deleted_events_on_upload_id", using: :btree + end + + create_table "gitlab_subscriptions", force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.date "start_date" + t.date "end_date" + t.date "trial_ends_on" + t.integer "namespace_id" + t.integer "hosted_plan_id" + t.integer "max_seats_used", default: 0 + t.integer "seats", default: 0 + t.boolean "trial", default: false + t.index ["hosted_plan_id"], name: "index_gitlab_subscriptions_on_hosted_plan_id", using: :btree + t.index ["namespace_id"], name: "index_gitlab_subscriptions_on_namespace_id", unique: true, using: :btree + end + create_table "gpg_key_subkeys", id: :serial, force: :cascade do |t| t.integer "gpg_key_id", null: false t.binary "keyid" @@ -1022,12 +1533,22 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["key", "value"], name: "index_group_custom_attributes_on_key_and_value", using: :btree end + create_table "historical_data", id: :serial, force: :cascade do |t| + t.date "date", null: false + t.integer "active_user_count" + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "identities", id: :serial, force: :cascade do |t| t.string "extern_uid" t.string "provider" t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" + t.integer "saml_provider_id" + t.string "secondary_extern_uid" + t.index ["saml_provider_id"], name: "index_identities_on_saml_provider_id", where: "(saml_provider_id IS NOT NULL)", using: :btree t.index ["user_id"], name: "index_identities_on_user_id", using: :btree end @@ -1040,6 +1561,23 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["updated_at"], name: "index_import_export_uploads_on_updated_at", using: :btree end + create_table "index_statuses", id: :serial, force: :cascade do |t| + t.integer "project_id", null: false + t.datetime "indexed_at" + t.text "note" + t.string "last_commit" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["project_id"], name: "index_index_statuses_on_project_id", unique: true, using: :btree + end + + create_table "insights", id: :serial, force: :cascade do |t| + t.integer "namespace_id", null: false + t.integer "project_id", null: false + t.index ["namespace_id"], name: "index_insights_on_namespace_id", using: :btree + t.index ["project_id"], name: "index_insights_on_project_id", using: :btree + end + create_table "internal_ids", force: :cascade do |t| t.integer "project_id" t.integer "usage", null: false @@ -1051,6 +1589,12 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["usage", "project_id"], name: "index_internal_ids_on_usage_and_project_id", unique: true, where: "(project_id IS NOT NULL)", using: :btree end + create_table "ip_restrictions", force: :cascade do |t| + t.integer "group_id", null: false + t.string "range", null: false + t.index ["group_id"], name: "index_ip_restrictions_on_group_id", using: :btree + end + create_table "issue_assignees", id: false, force: :cascade do |t| t.integer "user_id", null: false t.integer "issue_id", null: false @@ -1058,6 +1602,16 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["user_id"], name: "index_issue_assignees_on_user_id", using: :btree end + create_table "issue_links", id: :serial, force: :cascade do |t| + t.integer "source_id", null: false + t.integer "target_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.index ["source_id", "target_id"], name: "index_issue_links_on_source_id_and_target_id", unique: true, using: :btree + t.index ["source_id"], name: "index_issue_links_on_source_id", using: :btree + t.index ["target_id"], name: "index_issue_links_on_target_id", using: :btree + end + create_table "issue_metrics", id: :serial, force: :cascade do |t| t.integer "issue_id", null: false t.datetime "first_mentioned_in_commit_at" @@ -1068,6 +1622,19 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["issue_id"], name: "index_issue_metrics", using: :btree end + create_table "issue_tracker_data", force: :cascade do |t| + t.integer "service_id", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.string "encrypted_project_url" + t.string "encrypted_project_url_iv" + t.string "encrypted_issues_url" + t.string "encrypted_issues_url_iv" + t.string "encrypted_new_issue_url" + t.string "encrypted_new_issue_url_iv" + t.index ["service_id"], name: "index_issue_tracker_data_on_service_id", using: :btree + end + create_table "issues", id: :serial, force: :cascade do |t| t.string "title" t.integer "author_id" @@ -1094,6 +1661,8 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.datetime_with_timezone "closed_at" t.integer "closed_by_id" t.integer "state_id", limit: 2 + t.string "service_desk_reply_to" + t.integer "weight" t.index ["author_id"], name: "index_issues_on_author_id", using: :btree t.index ["closed_by_id"], name: "index_issues_on_closed_by_id", using: :btree t.index ["confidential"], name: "index_issues_on_confidential", using: :btree @@ -1111,6 +1680,40 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["updated_by_id"], name: "index_issues_on_updated_by_id", where: "(updated_by_id IS NOT NULL)", using: :btree end + create_table "jira_connect_installations", force: :cascade do |t| + t.string "client_key" + t.string "encrypted_shared_secret" + t.string "encrypted_shared_secret_iv" + t.string "base_url" + t.index ["client_key"], name: "index_jira_connect_installations_on_client_key", unique: true, using: :btree + end + + create_table "jira_connect_subscriptions", force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.bigint "jira_connect_installation_id", null: false + t.integer "namespace_id", null: false + t.datetime_with_timezone "updated_at", null: false + t.index ["jira_connect_installation_id", "namespace_id"], name: "idx_jira_connect_subscriptions_on_installation_id_namespace_id", unique: true, using: :btree + t.index ["jira_connect_installation_id"], name: "idx_jira_connect_subscriptions_on_installation_id", using: :btree + t.index ["namespace_id"], name: "index_jira_connect_subscriptions_on_namespace_id", using: :btree + end + + create_table "jira_tracker_data", force: :cascade do |t| + t.integer "service_id", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.string "encrypted_url" + t.string "encrypted_url_iv" + t.string "encrypted_api_url" + t.string "encrypted_api_url_iv" + t.string "encrypted_username" + t.string "encrypted_username_iv" + t.string "encrypted_password" + t.string "encrypted_password_iv" + t.string "jira_issue_transition_id" + t.index ["service_id"], name: "index_jira_tracker_data_on_service_id", using: :btree + end + create_table "keys", id: :serial, force: :cascade do |t| t.integer "user_id" t.datetime "created_at" @@ -1165,6 +1768,16 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["type", "project_id"], name: "index_labels_on_type_and_project_id", using: :btree end + create_table "ldap_group_links", id: :serial, force: :cascade do |t| + t.string "cn" + t.integer "group_access", null: false + t.integer "group_id", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.string "provider" + t.string "filter" + end + create_table "lfs_file_locks", id: :serial, force: :cascade do |t| t.integer "project_id", null: false t.integer "user_id", null: false @@ -1190,9 +1803,17 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.integer "project_id", null: false t.datetime "created_at" t.datetime "updated_at" + t.integer "repository_type", limit: 2 + t.index ["lfs_object_id"], name: "index_lfs_objects_projects_on_lfs_object_id", using: :btree t.index ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree end + create_table "licenses", id: :serial, force: :cascade do |t| + t.text "data", null: false + t.datetime "created_at" + t.datetime "updated_at" + end + create_table "lists", id: :serial, force: :cascade do |t| t.integer "board_id", null: false t.integer "label_id" @@ -1200,9 +1821,13 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.integer "position" t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "milestone_id" + t.integer "user_id" t.index ["board_id", "label_id"], name: "index_lists_on_board_id_and_label_id", unique: true, using: :btree t.index ["label_id"], name: "index_lists_on_label_id", using: :btree t.index ["list_type"], name: "index_lists_on_list_type", using: :btree + t.index ["milestone_id"], name: "index_lists_on_milestone_id", using: :btree + t.index ["user_id"], name: "index_lists_on_user_id", using: :btree end create_table "members", id: :serial, force: :cascade do |t| @@ -1220,7 +1845,10 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.datetime "invite_accepted_at" t.datetime "requested_at" t.date "expires_at" + t.boolean "ldap", default: false, null: false + t.boolean "override", default: false, null: false t.index ["access_level"], name: "index_members_on_access_level", using: :btree + t.index ["invite_email"], name: "index_members_on_invite_email", using: :btree t.index ["invite_token"], name: "index_members_on_invite_token", unique: true, using: :btree t.index ["requested_at"], name: "index_members_on_requested_at", using: :btree t.index ["source_id", "source_type"], name: "index_members_on_source_id_and_source_type", using: :btree @@ -1352,6 +1980,7 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.boolean "squash", default: false, null: false t.boolean "allow_maintainer_to_push" t.integer "state_id", limit: 2 + t.integer "approvals_before_merge" t.index ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree t.index ["author_id"], name: "index_merge_requests_on_author_id", using: :btree t.index ["created_at"], name: "index_merge_requests_on_created_at", using: :btree @@ -1419,6 +2048,13 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} end + create_table "namespace_statistics", id: :serial, force: :cascade do |t| + t.integer "namespace_id", null: false + t.integer "shared_runners_seconds", default: 0, null: false + t.datetime "shared_runners_seconds_last_reset" + t.index ["namespace_id"], name: "index_namespace_statistics_on_namespace_id", unique: true, using: :btree + end + create_table "namespaces", id: :serial, force: :cascade do |t| t.string "name", null: false t.string "path", null: false @@ -1442,16 +2078,37 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.integer "project_creation_level" t.boolean "auto_devops_enabled" t.datetime_with_timezone "last_ci_minutes_notification_at" + t.integer "custom_project_templates_group_id" + t.integer "file_template_project_id" + t.string "ldap_sync_error" + t.datetime "ldap_sync_last_successful_update_at" + t.datetime "ldap_sync_last_sync_at" + t.datetime "ldap_sync_last_update_at" + t.integer "plan_id" + t.bigint "repository_size_limit" + t.string "saml_discovery_token" + t.integer "shared_runners_minutes_limit" + t.datetime_with_timezone "trial_ends_on" + t.integer "extra_shared_runners_minutes_limit" + t.string "ldap_sync_status", default: "ready", null: false + t.boolean "membership_lock", default: false t.index ["created_at"], name: "index_namespaces_on_created_at", using: :btree + t.index ["custom_project_templates_group_id", "type"], name: "index_namespaces_on_custom_project_templates_group_id_and_type", where: "(custom_project_templates_group_id IS NOT NULL)", using: :btree + t.index ["file_template_project_id"], name: "index_namespaces_on_file_template_project_id", using: :btree + t.index ["ldap_sync_last_successful_update_at"], name: "index_namespaces_on_ldap_sync_last_successful_update_at", using: :btree + t.index ["ldap_sync_last_update_at"], name: "index_namespaces_on_ldap_sync_last_update_at", using: :btree t.index ["name", "parent_id"], name: "index_namespaces_on_name_and_parent_id", unique: true, using: :btree t.index ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} t.index ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree t.index ["parent_id", "id"], name: "index_namespaces_on_parent_id_and_id", unique: true, using: :btree t.index ["path"], name: "index_namespaces_on_path", using: :btree t.index ["path"], name: "index_namespaces_on_path_trigram", using: :gin, opclasses: {"path"=>"gin_trgm_ops"} + t.index ["plan_id"], name: "index_namespaces_on_plan_id", using: :btree t.index ["require_two_factor_authentication"], name: "index_namespaces_on_require_two_factor_authentication", using: :btree t.index ["runners_token"], name: "index_namespaces_on_runners_token", unique: true, using: :btree t.index ["runners_token_encrypted"], name: "index_namespaces_on_runners_token_encrypted", unique: true, using: :btree + t.index ["shared_runners_minutes_limit", "extra_shared_runners_minutes_limit"], name: "index_namespaces_on_shared_and_extra_runners_minutes_limit", using: :btree + t.index ["trial_ends_on"], name: "index_namespaces_on_trial_ends_on", where: "(trial_ends_on IS NOT NULL)", using: :btree t.index ["type"], name: "index_namespaces_on_type", using: :btree end @@ -1492,6 +2149,7 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.integer "cached_markdown_version" t.text "change_position" t.boolean "resolved_by_push" + t.bigint "review_id" t.index ["author_id"], name: "index_notes_on_author_id", using: :btree t.index ["commit_id"], name: "index_notes_on_commit_id", using: :btree t.index ["created_at"], name: "index_notes_on_created_at", using: :btree @@ -1501,6 +2159,7 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree t.index ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree t.index ["project_id", "noteable_type"], name: "index_notes_on_project_id_and_noteable_type", using: :btree + t.index ["review_id"], name: "index_notes_on_review_id", using: :btree end create_table "notification_settings", id: :serial, force: :cascade do |t| @@ -1525,6 +2184,7 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.boolean "push_to_merge_request" t.boolean "issue_due" t.string "notification_email" + t.boolean "new_epic" t.index ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree t.index ["user_id", "source_id", "source_type"], name: "index_notifications_on_user_id_and_source_id_and_source_type", unique: true, using: :btree t.index ["user_id"], name: "index_notification_settings_on_user_id", using: :btree @@ -1577,6 +2237,66 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["access_grant_id"], name: "index_oauth_openid_requests_on_access_grant_id", using: :btree end + create_table "operations_feature_flag_scopes", force: :cascade do |t| + t.bigint "feature_flag_id", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.boolean "active", null: false + t.string "environment_scope", default: "*", null: false + t.index ["feature_flag_id", "environment_scope"], name: "index_feature_flag_scopes_on_flag_id_and_environment_scope", unique: true, using: :btree + end + + create_table "operations_feature_flags", force: :cascade do |t| + t.integer "project_id", null: false + t.boolean "active", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.string "name", null: false + t.text "description" + t.index ["project_id", "name"], name: "index_operations_feature_flags_on_project_id_and_name", unique: true, using: :btree + end + + create_table "operations_feature_flags_clients", force: :cascade do |t| + t.integer "project_id", null: false + t.string "token", null: false + t.index ["project_id", "token"], name: "index_operations_feature_flags_clients_on_project_id_and_token", unique: true, using: :btree + end + + create_table "packages_maven_metadata", force: :cascade do |t| + t.bigint "package_id", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.string "app_group", null: false + t.string "app_name", null: false + t.string "app_version" + t.string "path", limit: 512, null: false + t.index ["package_id", "path"], name: "index_packages_maven_metadata_on_package_id_and_path", using: :btree + end + + create_table "packages_package_files", force: :cascade do |t| + t.bigint "package_id", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.bigint "size" + t.integer "file_type" + t.integer "file_store" + t.binary "file_md5" + t.binary "file_sha1" + t.string "file_name", null: false + t.text "file", null: false + t.index ["package_id", "file_name"], name: "index_packages_package_files_on_package_id_and_file_name", using: :btree + end + + create_table "packages_packages", force: :cascade do |t| + t.integer "project_id", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.string "name", null: false + t.string "version" + t.integer "package_type", limit: 2, null: false + t.index ["project_id"], name: "index_packages_packages_on_project_id", using: :btree + end + create_table "pages_domain_acme_orders", force: :cascade do |t| t.integer "pages_domain_id", null: false t.datetime_with_timezone "expires_at", null: false @@ -1613,6 +2333,17 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["verified_at"], name: "index_pages_domains_on_verified_at", using: :btree end + create_table "path_locks", id: :serial, force: :cascade do |t| + t.string "path", null: false + t.integer "project_id" + t.integer "user_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["path"], name: "index_path_locks_on_path", using: :btree + t.index ["project_id"], name: "index_path_locks_on_project_id", using: :btree + t.index ["user_id"], name: "index_path_locks_on_user_id", using: :btree + end + create_table "personal_access_tokens", id: :serial, force: :cascade do |t| t.integer "user_id", null: false t.string "name", null: false @@ -1627,6 +2358,16 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["user_id"], name: "index_personal_access_tokens_on_user_id", using: :btree end + create_table "plans", id: :serial, force: :cascade do |t| + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "name" + t.string "title" + t.integer "active_pipelines_limit" + t.integer "pipeline_size_limit" + t.index ["name"], name: "index_plans_on_name", using: :btree + end + create_table "pool_repositories", force: :cascade do |t| t.integer "shard_id", null: false t.string "disk_path" @@ -1644,6 +2385,11 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["name"], name: "index_programming_languages_on_name", unique: true, using: :btree end + create_table "project_alerting_settings", primary_key: "project_id", id: :integer, default: nil, force: :cascade do |t| + t.string "encrypted_token", null: false + t.string "encrypted_token_iv", null: false + end + create_table "project_authorizations", id: false, force: :cascade do |t| t.integer "user_id", null: false t.integer "project_id", null: false @@ -1704,6 +2450,14 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.string "organization_name" end + create_table "project_feature_usages", primary_key: "project_id", id: :integer, default: nil, force: :cascade do |t| + t.datetime "jira_dvcs_cloud_last_sync_at" + t.datetime "jira_dvcs_server_last_sync_at" + t.index ["jira_dvcs_cloud_last_sync_at", "project_id"], name: "idx_proj_feat_usg_on_jira_dvcs_cloud_last_sync_at_and_proj_id", where: "(jira_dvcs_cloud_last_sync_at IS NOT NULL)", using: :btree + t.index ["jira_dvcs_server_last_sync_at", "project_id"], name: "idx_proj_feat_usg_on_jira_dvcs_server_last_sync_at_and_proj_id", where: "(jira_dvcs_server_last_sync_at IS NOT NULL)", using: :btree + t.index ["project_id"], name: "index_project_feature_usages_on_project_id", using: :btree + end + create_table "project_features", id: :serial, force: :cascade do |t| t.integer "project_id", null: false t.integer "merge_requests_access_level" @@ -1738,6 +2492,12 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["project_id"], name: "index_project_import_data_on_project_id", using: :btree end + create_table "project_incident_management_settings", primary_key: "project_id", id: :integer, default: nil, force: :cascade do |t| + t.boolean "create_issue", default: false, null: false + t.boolean "send_email", default: true, null: false + t.text "issue_template_key" + end + create_table "project_metrics_settings", primary_key: "project_id", id: :integer, default: nil, force: :cascade do |t| t.string "external_dashboard_url", null: false end @@ -1747,7 +2507,16 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.string "status" t.string "jid" t.text "last_error" + t.datetime_with_timezone "last_successful_update_at" + t.datetime_with_timezone "last_update_at" + t.datetime "last_update_scheduled_at" + t.datetime "last_update_started_at" + t.datetime "next_execution_timestamp" + t.integer "retry_count", default: 0, null: false t.index ["jid"], name: "index_project_mirror_data_on_jid", using: :btree + t.index ["last_successful_update_at"], name: "index_project_mirror_data_on_last_successful_update_at", using: :btree + t.index ["last_update_at", "retry_count"], name: "index_project_mirror_data_on_last_update_at_and_retry_count", using: :btree + t.index ["next_execution_timestamp", "retry_count"], name: "index_mirror_data_on_next_execution_and_retry_count", using: :btree t.index ["project_id"], name: "index_project_mirror_data_on_project_id", unique: true, using: :btree t.index ["status"], name: "index_project_mirror_data_on_status", using: :btree end @@ -1761,6 +2530,26 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["shard_id"], name: "index_project_repositories_on_shard_id", using: :btree end + create_table "project_repository_states", id: :serial, force: :cascade do |t| + t.integer "project_id", null: false + t.binary "repository_verification_checksum" + t.binary "wiki_verification_checksum" + t.string "last_repository_verification_failure" + t.string "last_wiki_verification_failure" + t.datetime_with_timezone "repository_retry_at" + t.datetime_with_timezone "wiki_retry_at" + t.integer "repository_retry_count" + t.integer "wiki_retry_count" + t.datetime_with_timezone "last_repository_verification_ran_at" + t.datetime_with_timezone "last_wiki_verification_ran_at" + t.index ["last_repository_verification_failure"], name: "idx_repository_states_on_repository_failure_partial", where: "(last_repository_verification_failure IS NOT NULL)", using: :btree + t.index ["last_wiki_verification_failure"], name: "idx_repository_states_on_wiki_failure_partial", where: "(last_wiki_verification_failure IS NOT NULL)", using: :btree + t.index ["project_id", "last_repository_verification_ran_at"], name: "idx_repository_states_on_last_repository_verification_ran_at", where: "((repository_verification_checksum IS NOT NULL) AND (last_repository_verification_failure IS NULL))", using: :btree + t.index ["project_id", "last_wiki_verification_ran_at"], name: "idx_repository_states_on_last_wiki_verification_ran_at", where: "((wiki_verification_checksum IS NOT NULL) AND (last_wiki_verification_failure IS NULL))", using: :btree + t.index ["project_id"], name: "idx_repository_states_outdated_checksums", where: "(((repository_verification_checksum IS NULL) AND (last_repository_verification_failure IS NULL)) OR ((wiki_verification_checksum IS NULL) AND (last_wiki_verification_failure IS NULL)))", using: :btree + t.index ["project_id"], name: "index_project_repository_states_on_project_id", unique: true, using: :btree + end + create_table "project_statistics", id: :serial, force: :cascade do |t| t.integer "project_id", null: false t.integer "namespace_id", null: false @@ -1771,10 +2560,20 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.bigint "build_artifacts_size", default: 0, null: false t.bigint "packages_size", default: 0, null: false t.bigint "wiki_size" + t.bigint "shared_runners_seconds", default: 0, null: false + t.datetime "shared_runners_seconds_last_reset" t.index ["namespace_id"], name: "index_project_statistics_on_namespace_id", using: :btree t.index ["project_id"], name: "index_project_statistics_on_project_id", unique: true, using: :btree end + create_table "project_tracing_settings", force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "project_id", null: false + t.string "external_url", null: false + t.index ["project_id"], name: "index_project_tracing_settings_on_project_id", unique: true, using: :btree + end + create_table "projects", id: :serial, force: :cascade do |t| t.string "name" t.string "path" @@ -1822,7 +2621,7 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.boolean "resolve_outdated_diff_discussions" t.boolean "repository_read_only" t.boolean "merge_requests_ff_only_enabled", default: false - t.boolean "merge_requests_rebase_enabled", default: false, null: false + t.boolean "merge_requests_rebase_enabled", default: false t.integer "jobs_cache_index" t.boolean "pages_https_only", default: true t.boolean "remote_mirror_available_overridden" @@ -1831,14 +2630,40 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.string "bfg_object_map" t.boolean "detected_repository_languages" t.string "external_authorization_classification_label" + t.boolean "disable_overriding_approvers_per_merge_request" + t.string "external_webhook_token" + t.text "issues_template" + t.boolean "merge_requests_author_approval" + t.boolean "merge_requests_disable_committers_approval" + t.boolean "merge_requests_require_code_owner_approval" + t.text "merge_requests_template" + t.datetime "mirror_last_successful_update_at" + t.datetime "mirror_last_update_at" + t.boolean "mirror_overwrites_diverged_branches" + t.integer "mirror_user_id" + t.boolean "only_mirror_protected_branches" + t.boolean "packages_enabled" + t.boolean "pull_mirror_available_overridden" + t.bigint "repository_size_limit" + t.boolean "require_password_to_approve" + t.boolean "mirror", default: false, null: false + t.boolean "mirror_trigger_builds", default: false, null: false + t.boolean "reset_approvals_on_push", default: true + t.boolean "service_desk_enabled", default: true + t.integer "approvals_before_merge", default: 0, null: false + t.index ["archived", "pending_delete", "merge_requests_require_code_owner_approval"], name: "projects_requiring_code_owner_approval", where: "((pending_delete = false) AND (archived = false) AND (merge_requests_require_code_owner_approval = true))", using: :btree t.index ["created_at"], name: "index_projects_on_created_at", using: :btree t.index ["creator_id"], name: "index_projects_on_creator_id", using: :btree t.index ["description"], name: "index_projects_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} + t.index ["id", "repository_storage", "last_repository_updated_at"], name: "idx_projects_on_repository_storage_last_repository_updated_at", using: :btree t.index ["id"], name: "index_projects_on_id_partial_for_visibility", unique: true, where: "(visibility_level = ANY (ARRAY[10, 20]))", using: :btree + t.index ["id"], name: "index_projects_on_mirror_and_mirror_trigger_builds_both_true", where: "((mirror IS TRUE) AND (mirror_trigger_builds IS TRUE))", using: :btree t.index ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree t.index ["last_repository_check_at"], name: "index_projects_on_last_repository_check_at", where: "(last_repository_check_at IS NOT NULL)", using: :btree t.index ["last_repository_check_failed"], name: "index_projects_on_last_repository_check_failed", using: :btree t.index ["last_repository_updated_at"], name: "index_projects_on_last_repository_updated_at", using: :btree + t.index ["mirror_last_successful_update_at"], name: "index_projects_on_mirror_last_successful_update_at", using: :btree + t.index ["mirror_user_id"], name: "index_projects_on_mirror_user_id", using: :btree t.index ["name"], name: "index_projects_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} t.index ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree t.index ["path"], name: "index_projects_on_path", using: :btree @@ -1853,6 +2678,30 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree end + create_table "prometheus_alert_events", force: :cascade do |t| + t.integer "project_id", null: false + t.integer "prometheus_alert_id", null: false + t.datetime_with_timezone "started_at", null: false + t.datetime_with_timezone "ended_at" + t.integer "status", limit: 2 + t.string "payload_key" + t.index ["project_id", "status"], name: "index_prometheus_alert_events_on_project_id_and_status", using: :btree + t.index ["prometheus_alert_id", "payload_key"], name: "index_prometheus_alert_event_scoped_payload_key", unique: true, using: :btree + end + + create_table "prometheus_alerts", id: :serial, force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.float "threshold", null: false + t.integer "operator", null: false + t.integer "environment_id", null: false + t.integer "project_id", null: false + t.integer "prometheus_metric_id", null: false + t.index ["environment_id"], name: "index_prometheus_alerts_on_environment_id", using: :btree + t.index ["project_id", "prometheus_metric_id", "environment_id"], name: "index_prometheus_alerts_metric_environment", unique: true, using: :btree + t.index ["prometheus_metric_id"], name: "index_prometheus_alerts_on_prometheus_metric_id", using: :btree + end + create_table "prometheus_metrics", id: :serial, force: :cascade do |t| t.integer "project_id" t.string "title", null: false @@ -1873,18 +2722,36 @@ ActiveRecord::Schema.define(version: 20190611161641) do create_table "protected_branch_merge_access_levels", id: :serial, force: :cascade do |t| t.integer "protected_branch_id", null: false - t.integer "access_level", default: 40, null: false + t.integer "access_level", default: 40 t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "group_id" + t.integer "user_id" + t.index ["group_id"], name: "index_protected_branch_merge_access_levels_on_group_id", using: :btree t.index ["protected_branch_id"], name: "index_protected_branch_merge_access", using: :btree + t.index ["user_id"], name: "index_protected_branch_merge_access_levels_on_user_id", using: :btree end create_table "protected_branch_push_access_levels", id: :serial, force: :cascade do |t| t.integer "protected_branch_id", null: false - t.integer "access_level", default: 40, null: false + t.integer "access_level", default: 40 t.datetime "created_at", null: false t.datetime "updated_at", null: false + t.integer "group_id" + t.integer "user_id" + t.index ["group_id"], name: "index_protected_branch_push_access_levels_on_group_id", using: :btree t.index ["protected_branch_id"], name: "index_protected_branch_push_access", using: :btree + t.index ["user_id"], name: "index_protected_branch_push_access_levels_on_user_id", using: :btree + end + + create_table "protected_branch_unprotect_access_levels", id: :serial, force: :cascade do |t| + t.integer "protected_branch_id", null: false + t.integer "access_level", default: 40 + t.integer "user_id" + t.integer "group_id" + t.index ["group_id"], name: "index_protected_branch_unprotect_access_levels_on_group_id", using: :btree + t.index ["protected_branch_id"], name: "index_protected_branch_unprotect_access", using: :btree + t.index ["user_id"], name: "index_protected_branch_unprotect_access_levels_on_user_id", using: :btree end create_table "protected_branches", id: :serial, force: :cascade do |t| @@ -1895,6 +2762,27 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["project_id"], name: "index_protected_branches_on_project_id", using: :btree end + create_table "protected_environment_deploy_access_levels", id: :serial, force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "access_level", default: 40 + t.integer "protected_environment_id", null: false + t.integer "user_id" + t.integer "group_id" + t.index ["group_id"], name: "index_protected_environment_deploy_access_levels_on_group_id", using: :btree + t.index ["protected_environment_id"], name: "index_protected_environment_deploy_access", using: :btree + t.index ["user_id"], name: "index_protected_environment_deploy_access_levels_on_user_id", using: :btree + end + + create_table "protected_environments", id: :serial, force: :cascade do |t| + t.integer "project_id", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.string "name", null: false + t.index ["project_id", "name"], name: "index_protected_environments_on_project_id_and_name", unique: true, using: :btree + t.index ["project_id"], name: "index_protected_environments_on_project_id", using: :btree + end + create_table "protected_tag_create_access_levels", id: :serial, force: :cascade do |t| t.integer "protected_tag_id", null: false t.integer "access_level", default: 40 @@ -1928,6 +2816,29 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["event_id"], name: "index_push_event_payloads_on_event_id", unique: true, using: :btree end + create_table "push_rules", id: :serial, force: :cascade do |t| + t.string "force_push_regex" + t.string "delete_branch_regex" + t.string "commit_message_regex" + t.boolean "deny_delete_tag" + t.integer "project_id" + t.datetime "created_at" + t.datetime "updated_at" + t.string "author_email_regex" + t.boolean "member_check", default: false, null: false + t.string "file_name_regex" + t.boolean "is_sample", default: false + t.integer "max_file_size", default: 0, null: false + t.boolean "prevent_secrets", default: false, null: false + t.string "branch_name_regex" + t.boolean "reject_unsigned_commits" + t.boolean "commit_committer_check" + t.boolean "regexp_uses_re2", default: true + t.string "commit_message_negative_regex" + t.index ["is_sample"], name: "index_push_rules_on_is_sample", where: "is_sample", using: :btree + t.index ["project_id"], name: "index_push_rules_on_project_id", using: :btree + end + create_table "redirect_routes", id: :serial, force: :cascade do |t| t.integer "source_id", null: false t.string "source_type", null: false @@ -2002,12 +2913,24 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.integer "cached_markdown_version" t.text "reference" t.text "reference_html" + t.integer "epic_id" + t.index ["epic_id"], name: "index_resource_label_events_on_epic_id", using: :btree t.index ["issue_id"], name: "index_resource_label_events_on_issue_id", using: :btree t.index ["label_id"], name: "index_resource_label_events_on_label_id", using: :btree t.index ["merge_request_id"], name: "index_resource_label_events_on_merge_request_id", using: :btree t.index ["user_id"], name: "index_resource_label_events_on_user_id", using: :btree end + create_table "reviews", force: :cascade do |t| + t.integer "author_id" + t.integer "merge_request_id", null: false + t.integer "project_id", null: false + t.datetime_with_timezone "created_at", null: false + t.index ["author_id"], name: "index_reviews_on_author_id", using: :btree + t.index ["merge_request_id"], name: "index_reviews_on_merge_request_id", using: :btree + t.index ["project_id"], name: "index_reviews_on_project_id", using: :btree + end + create_table "routes", id: :serial, force: :cascade do |t| t.integer "source_id", null: false t.string "source_type", null: false @@ -2020,6 +2943,24 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["source_type", "source_id"], name: "index_routes_on_source_type_and_source_id", unique: true, using: :btree end + create_table "saml_providers", id: :serial, force: :cascade do |t| + t.integer "group_id", null: false + t.boolean "enabled", null: false + t.string "certificate_fingerprint", null: false + t.string "sso_url", null: false + t.boolean "enforced_sso", default: false, null: false + t.boolean "enforced_group_managed_accounts", default: false, null: false + t.index ["group_id"], name: "index_saml_providers_on_group_id", using: :btree + end + + create_table "scim_oauth_access_tokens", id: :serial, force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "group_id", null: false + t.string "token_encrypted", null: false + t.index ["group_id", "token_encrypted"], name: "index_scim_oauth_access_tokens_on_group_id_and_token_encrypted", unique: true, using: :btree + end + create_table "sent_notifications", id: :serial, force: :cascade do |t| t.integer "project_id" t.integer "noteable_id" @@ -2067,6 +3008,26 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["name"], name: "index_shards_on_name", unique: true, using: :btree end + create_table "slack_integrations", id: :serial, force: :cascade do |t| + t.integer "service_id", null: false + t.string "team_id", null: false + t.string "team_name", null: false + t.string "alias", null: false + t.string "user_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.index ["service_id"], name: "index_slack_integrations_on_service_id", using: :btree + t.index ["team_id", "alias"], name: "index_slack_integrations_on_team_id_and_alias", unique: true, using: :btree + end + + create_table "smartcard_identities", force: :cascade do |t| + t.integer "user_id", null: false + t.string "subject", null: false + t.string "issuer", null: false + t.index ["subject", "issuer"], name: "index_smartcard_identities_on_subject_and_issuer", unique: true, using: :btree + t.index ["user_id"], name: "index_smartcard_identities_on_user_id", using: :btree + end + create_table "snippets", id: :serial, force: :cascade do |t| t.string "title" t.text "content" @@ -2090,6 +3051,19 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree end + create_table "software_license_policies", id: :serial, force: :cascade do |t| + t.integer "project_id", null: false + t.integer "software_license_id", null: false + t.integer "approval_status", default: 0, null: false + t.index ["project_id", "software_license_id"], name: "index_software_license_policies_unique_per_project", unique: true, using: :btree + t.index ["software_license_id"], name: "index_software_license_policies_on_software_license_id", using: :btree + end + + create_table "software_licenses", id: :serial, force: :cascade do |t| + t.string "name", null: false + t.index ["name"], name: "index_software_licenses_on_name", using: :btree + end + create_table "spam_logs", id: :serial, force: :cascade do |t| t.integer "user_id" t.string "source_ip" @@ -2289,6 +3263,10 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.string "timezone" t.boolean "time_display_relative" t.boolean "time_format_in_24h" + t.integer "epic_notes_filter", limit: 2, default: 0, null: false + t.string "epics_sort" + t.integer "roadmap_epics_state" + t.string "roadmaps_sort" t.index ["user_id"], name: "index_user_preferences_on_user_id", unique: true, using: :btree end @@ -2378,24 +3356,48 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.boolean "private_profile" t.boolean "include_private_contributions" t.string "commit_email" + t.boolean "auditor", default: false, null: false + t.datetime "admin_email_unsubscribed_at" + t.boolean "email_opted_in" + t.datetime "email_opted_in_at" + t.string "email_opted_in_ip" + t.integer "email_opted_in_source_id" + t.integer "group_view" + t.integer "managing_group_id" + t.text "note" + t.integer "roadmap_layout", limit: 2 + t.integer "bot_type", limit: 2 t.index ["accepted_term_id"], name: "index_users_on_accepted_term_id", using: :btree t.index ["admin"], name: "index_users_on_admin", using: :btree + t.index ["bot_type"], name: "index_users_on_bot_type", using: :btree t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree t.index ["created_at"], name: "index_users_on_created_at", using: :btree t.index ["email"], name: "index_users_on_email", unique: true, using: :btree t.index ["email"], name: "index_users_on_email_trigram", using: :gin, opclasses: {"email"=>"gin_trgm_ops"} t.index ["feed_token"], name: "index_users_on_feed_token", using: :btree t.index ["ghost"], name: "index_users_on_ghost", using: :btree + t.index ["group_view"], name: "index_users_on_group_view", using: :btree t.index ["incoming_email_token"], name: "index_users_on_incoming_email_token", using: :btree + t.index ["managing_group_id"], name: "index_users_on_managing_group_id", using: :btree t.index ["name"], name: "index_users_on_name", using: :btree t.index ["name"], name: "index_users_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} t.index ["public_email"], name: "index_users_on_public_email", where: "((public_email)::text <> ''::text)", using: :btree t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree t.index ["state"], name: "index_users_on_state", using: :btree + t.index ["state"], name: "index_users_on_state_and_internal", where: "((ghost <> true) AND (bot_type IS NULL))", using: :btree t.index ["username"], name: "index_users_on_username", using: :btree t.index ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"} end + create_table "users_ops_dashboard_projects", force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "user_id", null: false + t.integer "project_id", null: false + t.index ["project_id"], name: "index_users_ops_dashboard_projects_on_project_id", using: :btree + t.index ["user_id", "project_id"], name: "index_users_ops_dashboard_projects_on_user_id_and_project_id", unique: true, using: :btree + end + create_table "users_star_projects", id: :serial, force: :cascade do |t| t.integer "project_id", null: false t.integer "user_id", null: false @@ -2405,6 +3407,88 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.index ["user_id", "project_id"], name: "index_users_star_projects_on_user_id_and_project_id", unique: true, using: :btree end + create_table "vulnerability_feedback", id: :serial, force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "feedback_type", limit: 2, null: false + t.integer "category", limit: 2, null: false + t.integer "project_id", null: false + t.integer "author_id", null: false + t.integer "pipeline_id" + t.integer "issue_id" + t.string "project_fingerprint", limit: 40, null: false + t.integer "merge_request_id" + t.integer "comment_author_id" + t.text "comment" + t.datetime_with_timezone "comment_timestamp" + t.index ["author_id"], name: "index_vulnerability_feedback_on_author_id", using: :btree + t.index ["comment_author_id"], name: "index_vulnerability_feedback_on_comment_author_id", using: :btree + t.index ["issue_id"], name: "index_vulnerability_feedback_on_issue_id", using: :btree + t.index ["merge_request_id"], name: "index_vulnerability_feedback_on_merge_request_id", using: :btree + t.index ["pipeline_id"], name: "index_vulnerability_feedback_on_pipeline_id", using: :btree + t.index ["project_id", "category", "feedback_type", "project_fingerprint"], name: "vulnerability_feedback_unique_idx", unique: true, using: :btree + end + + create_table "vulnerability_identifiers", force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "project_id", null: false + t.binary "fingerprint", null: false + t.string "external_type", null: false + t.string "external_id", null: false + t.string "name", null: false + t.text "url" + t.index ["project_id", "fingerprint"], name: "index_vulnerability_identifiers_on_project_id_and_fingerprint", unique: true, using: :btree + end + + create_table "vulnerability_occurrence_identifiers", force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.bigint "occurrence_id", null: false + t.bigint "identifier_id", null: false + t.index ["identifier_id"], name: "index_vulnerability_occurrence_identifiers_on_identifier_id", using: :btree + t.index ["occurrence_id", "identifier_id"], name: "index_vulnerability_occurrence_identifiers_on_unique_keys", unique: true, using: :btree + end + + create_table "vulnerability_occurrence_pipelines", force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.bigint "occurrence_id", null: false + t.integer "pipeline_id", null: false + t.index ["occurrence_id", "pipeline_id"], name: "vulnerability_occurrence_pipelines_on_unique_keys", unique: true, using: :btree + t.index ["pipeline_id"], name: "index_vulnerability_occurrence_pipelines_on_pipeline_id", using: :btree + end + + create_table "vulnerability_occurrences", force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "severity", limit: 2, null: false + t.integer "confidence", limit: 2, null: false + t.integer "report_type", limit: 2, null: false + t.integer "project_id", null: false + t.bigint "scanner_id", null: false + t.bigint "primary_identifier_id", null: false + t.binary "project_fingerprint", null: false + t.binary "location_fingerprint", null: false + t.string "uuid", limit: 36, null: false + t.string "name", null: false + t.string "metadata_version", null: false + t.text "raw_metadata", null: false + t.index ["primary_identifier_id"], name: "index_vulnerability_occurrences_on_primary_identifier_id", using: :btree + t.index ["project_id", "primary_identifier_id", "location_fingerprint", "scanner_id"], name: "index_vulnerability_occurrences_on_unique_keys", unique: true, using: :btree + t.index ["scanner_id"], name: "index_vulnerability_occurrences_on_scanner_id", using: :btree + t.index ["uuid"], name: "index_vulnerability_occurrences_on_uuid", unique: true, using: :btree + end + + create_table "vulnerability_scanners", force: :cascade do |t| + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false + t.integer "project_id", null: false + t.string "external_id", null: false + t.string "name", null: false + t.index ["project_id", "external_id"], name: "index_vulnerability_scanners_on_project_id_and_external_id", unique: true, using: :btree + end + create_table "web_hook_logs", id: :serial, force: :cascade do |t| t.integer "web_hook_id", null: false t.string "trigger" @@ -2445,16 +3529,39 @@ ActiveRecord::Schema.define(version: 20190611161641) do t.string "encrypted_token_iv" t.string "encrypted_url" t.string "encrypted_url_iv" + t.integer "group_id" t.index ["project_id"], name: "index_web_hooks_on_project_id", using: :btree t.index ["type"], name: "index_web_hooks_on_type", using: :btree end + add_foreign_key "application_settings", "namespaces", column: "custom_project_templates_group_id", on_delete: :nullify + add_foreign_key "application_settings", "projects", column: "file_template_project_id", name: "fk_ec757bd087", on_delete: :nullify add_foreign_key "application_settings", "users", column: "usage_stats_set_by_user_id", name: "fk_964370041d", on_delete: :nullify + add_foreign_key "approval_merge_request_rule_sources", "approval_merge_request_rules", on_delete: :cascade + add_foreign_key "approval_merge_request_rule_sources", "approval_project_rules", on_delete: :cascade + add_foreign_key "approval_merge_request_rules", "merge_requests", on_delete: :cascade + add_foreign_key "approval_merge_request_rules_approved_approvers", "approval_merge_request_rules", on_delete: :cascade + add_foreign_key "approval_merge_request_rules_approved_approvers", "users", on_delete: :cascade + add_foreign_key "approval_merge_request_rules_groups", "approval_merge_request_rules", on_delete: :cascade + add_foreign_key "approval_merge_request_rules_groups", "namespaces", column: "group_id", on_delete: :cascade + add_foreign_key "approval_merge_request_rules_users", "approval_merge_request_rules", on_delete: :cascade + add_foreign_key "approval_merge_request_rules_users", "users", on_delete: :cascade + add_foreign_key "approval_project_rules", "projects", on_delete: :cascade + add_foreign_key "approval_project_rules_groups", "approval_project_rules", on_delete: :cascade + add_foreign_key "approval_project_rules_groups", "namespaces", column: "group_id", on_delete: :cascade + add_foreign_key "approval_project_rules_users", "approval_project_rules", on_delete: :cascade + add_foreign_key "approval_project_rules_users", "users", on_delete: :cascade + add_foreign_key "approvals", "merge_requests", name: "fk_310d714958", on_delete: :cascade + add_foreign_key "approver_groups", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "badges", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "badges", "projects", on_delete: :cascade + add_foreign_key "board_assignees", "boards", on_delete: :cascade + add_foreign_key "board_assignees", "users", column: "assignee_id", on_delete: :cascade add_foreign_key "board_group_recent_visits", "boards", on_delete: :cascade add_foreign_key "board_group_recent_visits", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "board_group_recent_visits", "users", on_delete: :cascade + add_foreign_key "board_labels", "boards", on_delete: :cascade + add_foreign_key "board_labels", "labels", on_delete: :cascade add_foreign_key "board_project_recent_visits", "boards", on_delete: :cascade add_foreign_key "board_project_recent_visits", "projects", on_delete: :cascade add_foreign_key "board_project_recent_visits", "users", on_delete: :cascade @@ -2468,6 +3575,7 @@ ActiveRecord::Schema.define(version: 20190611161641) do add_foreign_key "ci_build_trace_sections", "projects", on_delete: :cascade add_foreign_key "ci_builds", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_a2141b1522", on_delete: :nullify add_foreign_key "ci_builds", "ci_pipelines", column: "commit_id", name: "fk_d3130c9a7f", on_delete: :cascade + add_foreign_key "ci_builds", "ci_pipelines", column: "upstream_pipeline_id", name: "fk_87f4cefcda", on_delete: :cascade add_foreign_key "ci_builds", "ci_stages", column: "stage_id", name: "fk_3a9eaa254d", on_delete: :cascade add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade add_foreign_key "ci_builds_metadata", "ci_builds", column: "build_id", on_delete: :cascade @@ -2489,6 +3597,11 @@ ActiveRecord::Schema.define(version: 20190611161641) do add_foreign_key "ci_runner_namespaces", "ci_runners", column: "runner_id", on_delete: :cascade add_foreign_key "ci_runner_namespaces", "namespaces", on_delete: :cascade add_foreign_key "ci_runner_projects", "projects", name: "fk_4478a6f1e4", on_delete: :cascade + add_foreign_key "ci_sources_pipelines", "ci_builds", column: "source_job_id", name: "fk_be5624bf37", on_delete: :cascade + add_foreign_key "ci_sources_pipelines", "ci_pipelines", column: "pipeline_id", name: "fk_e1bad85861", on_delete: :cascade + add_foreign_key "ci_sources_pipelines", "ci_pipelines", column: "source_pipeline_id", name: "fk_d4e29af7d7", on_delete: :cascade + add_foreign_key "ci_sources_pipelines", "projects", column: "source_project_id", name: "fk_acd9737679", on_delete: :cascade + add_foreign_key "ci_sources_pipelines", "projects", name: "fk_1e53c97c0a", on_delete: :cascade add_foreign_key "ci_stages", "ci_pipelines", column: "pipeline_id", name: "fk_fb57e6cc56", on_delete: :cascade add_foreign_key "ci_stages", "projects", name: "fk_2360681d1d", on_delete: :cascade add_foreign_key "ci_trigger_requests", "ci_triggers", column: "trigger_id", name: "fk_b8ec8b7245", on_delete: :cascade @@ -2515,9 +3628,28 @@ ActiveRecord::Schema.define(version: 20190611161641) do add_foreign_key "clusters_kubernetes_namespaces", "clusters", on_delete: :cascade add_foreign_key "clusters_kubernetes_namespaces", "projects", on_delete: :nullify add_foreign_key "container_repositories", "projects" + add_foreign_key "dependency_proxy_blobs", "namespaces", column: "group_id", name: "fk_db58bbc5d7", on_delete: :cascade + add_foreign_key "dependency_proxy_group_settings", "namespaces", column: "group_id", name: "fk_616ddd680a", on_delete: :cascade add_foreign_key "deploy_keys_projects", "projects", name: "fk_58a901ca7e", on_delete: :cascade add_foreign_key "deployments", "projects", name: "fk_b9a3851b82", on_delete: :cascade + add_foreign_key "design_management_designs", "issues", on_delete: :cascade + add_foreign_key "design_management_designs", "projects", on_delete: :cascade + add_foreign_key "design_management_designs_versions", "design_management_designs", column: "design_id", name: "fk_03c671965c", on_delete: :cascade + add_foreign_key "design_management_designs_versions", "design_management_versions", column: "version_id", name: "fk_f4d25ba00c", on_delete: :cascade + add_foreign_key "draft_notes", "merge_requests", on_delete: :cascade + add_foreign_key "draft_notes", "users", column: "author_id", on_delete: :cascade + add_foreign_key "elasticsearch_indexed_namespaces", "namespaces", on_delete: :cascade + add_foreign_key "elasticsearch_indexed_projects", "projects", on_delete: :cascade add_foreign_key "environments", "projects", name: "fk_d1c8c1da6a", on_delete: :cascade + add_foreign_key "epic_issues", "epics", on_delete: :cascade + add_foreign_key "epic_issues", "issues", on_delete: :cascade + add_foreign_key "epic_metrics", "epics", on_delete: :cascade + add_foreign_key "epics", "epics", column: "parent_id", name: "fk_25b99c1be3", on_delete: :cascade + add_foreign_key "epics", "milestones", on_delete: :nullify + add_foreign_key "epics", "namespaces", column: "group_id", name: "fk_f081aa4489", on_delete: :cascade + add_foreign_key "epics", "users", column: "assignee_id", name: "fk_dccd3f98fc", on_delete: :nullify + add_foreign_key "epics", "users", column: "author_id", name: "fk_3654b61b03", on_delete: :cascade + add_foreign_key "epics", "users", column: "closed_by_id", name: "fk_aa5798e761", on_delete: :nullify add_foreign_key "events", "projects", on_delete: :cascade add_foreign_key "events", "users", column: "author_id", name: "fk_edfd187b6f", on_delete: :cascade add_foreign_key "fork_network_members", "fork_networks", on_delete: :cascade @@ -2525,24 +3657,58 @@ ActiveRecord::Schema.define(version: 20190611161641) do add_foreign_key "fork_network_members", "projects", on_delete: :cascade add_foreign_key "fork_networks", "projects", column: "root_project_id", name: "fk_e7b436b2b5", on_delete: :nullify add_foreign_key "forked_project_links", "projects", column: "forked_to_project_id", name: "fk_434510edb0", on_delete: :cascade + add_foreign_key "geo_event_log", "geo_cache_invalidation_events", column: "cache_invalidation_event_id", name: "fk_42c3b54bed", on_delete: :cascade + add_foreign_key "geo_event_log", "geo_hashed_storage_migrated_events", column: "hashed_storage_migrated_event_id", name: "fk_27548c6db3", on_delete: :cascade + add_foreign_key "geo_event_log", "geo_job_artifact_deleted_events", column: "job_artifact_deleted_event_id", name: "fk_176d3fbb5d", on_delete: :cascade + add_foreign_key "geo_event_log", "geo_lfs_object_deleted_events", column: "lfs_object_deleted_event_id", name: "fk_d5af95fcd9", on_delete: :cascade + add_foreign_key "geo_event_log", "geo_repositories_changed_events", column: "repositories_changed_event_id", name: "fk_4a99ebfd60", on_delete: :cascade + add_foreign_key "geo_event_log", "geo_repository_created_events", column: "repository_created_event_id", name: "fk_9b9afb1916", on_delete: :cascade + add_foreign_key "geo_event_log", "geo_repository_deleted_events", column: "repository_deleted_event_id", name: "fk_c4b1c1f66e", on_delete: :cascade + add_foreign_key "geo_event_log", "geo_repository_renamed_events", column: "repository_renamed_event_id", name: "fk_86c84214ec", on_delete: :cascade + add_foreign_key "geo_event_log", "geo_repository_updated_events", column: "repository_updated_event_id", name: "fk_78a6492f68", on_delete: :cascade + add_foreign_key "geo_event_log", "geo_reset_checksum_events", column: "reset_checksum_event_id", name: "fk_cff7185ad2", on_delete: :cascade + add_foreign_key "geo_event_log", "geo_upload_deleted_events", column: "upload_deleted_event_id", name: "fk_c1f241c70d", on_delete: :cascade + add_foreign_key "geo_hashed_storage_attachments_events", "projects", on_delete: :cascade + add_foreign_key "geo_hashed_storage_migrated_events", "projects", on_delete: :cascade + add_foreign_key "geo_node_namespace_links", "geo_nodes", on_delete: :cascade + add_foreign_key "geo_node_namespace_links", "namespaces", on_delete: :cascade + add_foreign_key "geo_node_statuses", "geo_nodes", on_delete: :cascade + add_foreign_key "geo_repositories_changed_events", "geo_nodes", on_delete: :cascade + add_foreign_key "geo_repository_created_events", "projects", on_delete: :cascade + add_foreign_key "geo_repository_renamed_events", "projects", on_delete: :cascade + add_foreign_key "geo_repository_updated_events", "projects", on_delete: :cascade + add_foreign_key "geo_reset_checksum_events", "projects", on_delete: :cascade + add_foreign_key "gitlab_subscriptions", "namespaces", name: "fk_e2595d00a1", on_delete: :cascade + add_foreign_key "gitlab_subscriptions", "plans", column: "hosted_plan_id", name: "fk_bd0c4019c3", on_delete: :cascade add_foreign_key "gpg_key_subkeys", "gpg_keys", on_delete: :cascade add_foreign_key "gpg_keys", "users", on_delete: :cascade add_foreign_key "gpg_signatures", "gpg_key_subkeys", on_delete: :nullify add_foreign_key "gpg_signatures", "gpg_keys", on_delete: :nullify add_foreign_key "gpg_signatures", "projects", on_delete: :cascade add_foreign_key "group_custom_attributes", "namespaces", column: "group_id", on_delete: :cascade + add_foreign_key "identities", "saml_providers", name: "fk_aade90f0fc", on_delete: :cascade add_foreign_key "import_export_uploads", "projects", on_delete: :cascade + add_foreign_key "index_statuses", "projects", name: "fk_74b2492545", on_delete: :cascade + add_foreign_key "insights", "namespaces", on_delete: :cascade + add_foreign_key "insights", "projects", on_delete: :cascade add_foreign_key "internal_ids", "namespaces", name: "fk_162941d509", on_delete: :cascade add_foreign_key "internal_ids", "projects", on_delete: :cascade + add_foreign_key "ip_restrictions", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "issue_assignees", "issues", name: "fk_b7d881734a", on_delete: :cascade add_foreign_key "issue_assignees", "users", name: "fk_5e0c8d9154", on_delete: :cascade + add_foreign_key "issue_links", "issues", column: "source_id", name: "fk_c900194ff2", on_delete: :cascade + add_foreign_key "issue_links", "issues", column: "target_id", name: "fk_e71bb44f1f", on_delete: :cascade add_foreign_key "issue_metrics", "issues", on_delete: :cascade + add_foreign_key "issue_tracker_data", "services", on_delete: :cascade add_foreign_key "issues", "issues", column: "moved_to_id", name: "fk_a194299be1", on_delete: :nullify add_foreign_key "issues", "milestones", name: "fk_96b1dd429c", on_delete: :nullify add_foreign_key "issues", "projects", name: "fk_899c8f3231", on_delete: :cascade add_foreign_key "issues", "users", column: "author_id", name: "fk_05f1e72feb", on_delete: :nullify add_foreign_key "issues", "users", column: "closed_by_id", name: "fk_c63cbf6c25", on_delete: :nullify add_foreign_key "issues", "users", column: "updated_by_id", name: "fk_ffed080f01", on_delete: :nullify + add_foreign_key "jira_connect_subscriptions", "jira_connect_installations", name: "fk_f1d617343f", on_delete: :cascade + add_foreign_key "jira_connect_subscriptions", "namespaces", name: "fk_a3c10bcf7d", on_delete: :cascade + add_foreign_key "jira_tracker_data", "services", on_delete: :cascade add_foreign_key "label_links", "labels", name: "fk_d97dd08678", on_delete: :cascade add_foreign_key "label_priorities", "labels", on_delete: :cascade add_foreign_key "label_priorities", "projects", on_delete: :cascade @@ -2552,6 +3718,8 @@ ActiveRecord::Schema.define(version: 20190611161641) do add_foreign_key "lfs_file_locks", "users", on_delete: :cascade add_foreign_key "lists", "boards", name: "fk_0d3f677137", on_delete: :cascade add_foreign_key "lists", "labels", name: "fk_7a5553d60f", on_delete: :cascade + add_foreign_key "lists", "milestones", on_delete: :cascade + add_foreign_key "lists", "users", name: "fk_d6cf4279f7", on_delete: :cascade add_foreign_key "members", "users", name: "fk_2e88fb7ce9", on_delete: :cascade add_foreign_key "merge_request_assignees", "merge_requests", on_delete: :cascade add_foreign_key "merge_request_assignees", "users", on_delete: :cascade @@ -2581,15 +3749,29 @@ ActiveRecord::Schema.define(version: 20190611161641) do add_foreign_key "merge_trains", "users", on_delete: :cascade add_foreign_key "milestones", "namespaces", column: "group_id", name: "fk_95650a40d4", on_delete: :cascade add_foreign_key "milestones", "projects", name: "fk_9bd0a0c791", on_delete: :cascade + add_foreign_key "namespace_statistics", "namespaces", on_delete: :cascade + add_foreign_key "namespaces", "namespaces", column: "custom_project_templates_group_id", name: "fk_e7a0b20a6b", on_delete: :nullify + add_foreign_key "namespaces", "plans", name: "fk_fdd12e5b80", on_delete: :nullify + add_foreign_key "namespaces", "projects", column: "file_template_project_id", name: "fk_319256d87a", on_delete: :nullify add_foreign_key "note_diff_files", "notes", column: "diff_note_id", on_delete: :cascade add_foreign_key "notes", "projects", name: "fk_99e097b079", on_delete: :cascade + add_foreign_key "notes", "reviews", name: "fk_2e82291620", on_delete: :nullify add_foreign_key "notification_settings", "users", name: "fk_0c95e91db7", on_delete: :cascade add_foreign_key "oauth_openid_requests", "oauth_access_grants", column: "access_grant_id", name: "fk_oauth_openid_requests_oauth_access_grants_access_grant_id" + add_foreign_key "operations_feature_flag_scopes", "operations_feature_flags", column: "feature_flag_id", on_delete: :cascade + add_foreign_key "operations_feature_flags", "projects", on_delete: :cascade + add_foreign_key "operations_feature_flags_clients", "projects", on_delete: :cascade + add_foreign_key "packages_maven_metadata", "packages_packages", column: "package_id", name: "fk_be88aed360", on_delete: :cascade + add_foreign_key "packages_package_files", "packages_packages", column: "package_id", name: "fk_86f0f182f8", on_delete: :cascade + add_foreign_key "packages_packages", "projects", on_delete: :cascade add_foreign_key "pages_domain_acme_orders", "pages_domains", on_delete: :cascade add_foreign_key "pages_domains", "projects", name: "fk_ea2f6dfc6f", on_delete: :cascade + add_foreign_key "path_locks", "projects", name: "fk_5265c98f24", on_delete: :cascade + add_foreign_key "path_locks", "users" add_foreign_key "personal_access_tokens", "users" add_foreign_key "pool_repositories", "projects", column: "source_project_id", on_delete: :nullify add_foreign_key "pool_repositories", "shards", on_delete: :restrict + add_foreign_key "project_alerting_settings", "projects", on_delete: :cascade add_foreign_key "project_authorizations", "projects", on_delete: :cascade add_foreign_key "project_authorizations", "users", on_delete: :cascade add_foreign_key "project_auto_devops", "projects", on_delete: :cascade @@ -2599,35 +3781,66 @@ ActiveRecord::Schema.define(version: 20190611161641) do add_foreign_key "project_deploy_tokens", "deploy_tokens", on_delete: :cascade add_foreign_key "project_deploy_tokens", "projects", on_delete: :cascade add_foreign_key "project_error_tracking_settings", "projects", on_delete: :cascade + add_foreign_key "project_feature_usages", "projects", on_delete: :cascade add_foreign_key "project_features", "projects", name: "fk_18513d9b92", on_delete: :cascade add_foreign_key "project_group_links", "projects", name: "fk_daa8cee94c", on_delete: :cascade add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade + add_foreign_key "project_incident_management_settings", "projects", on_delete: :cascade add_foreign_key "project_metrics_settings", "projects", on_delete: :cascade add_foreign_key "project_mirror_data", "projects", on_delete: :cascade add_foreign_key "project_repositories", "projects", on_delete: :cascade add_foreign_key "project_repositories", "shards", on_delete: :restrict + add_foreign_key "project_repository_states", "projects", on_delete: :cascade add_foreign_key "project_statistics", "projects", on_delete: :cascade + add_foreign_key "project_tracing_settings", "projects", on_delete: :cascade add_foreign_key "projects", "pool_repositories", name: "fk_6e5c14658a", on_delete: :nullify + add_foreign_key "prometheus_alert_events", "projects", on_delete: :cascade + add_foreign_key "prometheus_alert_events", "prometheus_alerts", on_delete: :cascade + add_foreign_key "prometheus_alerts", "environments", on_delete: :cascade + add_foreign_key "prometheus_alerts", "projects", on_delete: :cascade + add_foreign_key "prometheus_alerts", "prometheus_metrics", on_delete: :cascade add_foreign_key "prometheus_metrics", "projects", on_delete: :cascade + add_foreign_key "protected_branch_merge_access_levels", "namespaces", column: "group_id", name: "fk_98f3d044fe", on_delete: :cascade add_foreign_key "protected_branch_merge_access_levels", "protected_branches", name: "fk_8a3072ccb3", on_delete: :cascade + add_foreign_key "protected_branch_merge_access_levels", "users" + add_foreign_key "protected_branch_push_access_levels", "namespaces", column: "group_id", name: "fk_7111b68cdb", on_delete: :cascade add_foreign_key "protected_branch_push_access_levels", "protected_branches", name: "fk_9ffc86a3d9", on_delete: :cascade + add_foreign_key "protected_branch_push_access_levels", "users" + add_foreign_key "protected_branch_unprotect_access_levels", "namespaces", column: "group_id", on_delete: :cascade + add_foreign_key "protected_branch_unprotect_access_levels", "protected_branches", on_delete: :cascade + add_foreign_key "protected_branch_unprotect_access_levels", "users", on_delete: :cascade add_foreign_key "protected_branches", "projects", name: "fk_7a9c6d93e7", on_delete: :cascade - add_foreign_key "protected_tag_create_access_levels", "namespaces", column: "group_id" + add_foreign_key "protected_environment_deploy_access_levels", "namespaces", column: "group_id", on_delete: :cascade + add_foreign_key "protected_environment_deploy_access_levels", "protected_environments", on_delete: :cascade + add_foreign_key "protected_environment_deploy_access_levels", "users", on_delete: :cascade + add_foreign_key "protected_environments", "projects", on_delete: :cascade + add_foreign_key "protected_tag_create_access_levels", "namespaces", column: "group_id", name: "fk_b4eb82fe3c", on_delete: :cascade add_foreign_key "protected_tag_create_access_levels", "protected_tags", name: "fk_f7dfda8c51", on_delete: :cascade add_foreign_key "protected_tag_create_access_levels", "users" add_foreign_key "protected_tags", "projects", name: "fk_8e4af87648", on_delete: :cascade add_foreign_key "push_event_payloads", "events", name: "fk_36c74129da", on_delete: :cascade + add_foreign_key "push_rules", "projects", name: "fk_83b29894de", on_delete: :cascade add_foreign_key "release_links", "releases", on_delete: :cascade add_foreign_key "releases", "projects", name: "fk_47fe2a0596", on_delete: :cascade add_foreign_key "releases", "users", column: "author_id", name: "fk_8e4456f90f", on_delete: :nullify add_foreign_key "remote_mirrors", "projects", on_delete: :cascade add_foreign_key "repository_languages", "projects", on_delete: :cascade + add_foreign_key "resource_label_events", "epics", on_delete: :cascade add_foreign_key "resource_label_events", "issues", on_delete: :cascade add_foreign_key "resource_label_events", "labels", on_delete: :nullify add_foreign_key "resource_label_events", "merge_requests", on_delete: :cascade add_foreign_key "resource_label_events", "users", on_delete: :nullify + add_foreign_key "reviews", "merge_requests", on_delete: :cascade + add_foreign_key "reviews", "projects", on_delete: :cascade + add_foreign_key "reviews", "users", column: "author_id", on_delete: :nullify + add_foreign_key "saml_providers", "namespaces", column: "group_id", on_delete: :cascade + add_foreign_key "scim_oauth_access_tokens", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "services", "projects", name: "fk_71cce407f9", on_delete: :cascade + add_foreign_key "slack_integrations", "services", on_delete: :cascade + add_foreign_key "smartcard_identities", "users", on_delete: :cascade add_foreign_key "snippets", "projects", name: "fk_be41fd4bb7", on_delete: :cascade + add_foreign_key "software_license_policies", "projects", on_delete: :cascade + add_foreign_key "software_license_policies", "software_licenses", on_delete: :cascade add_foreign_key "subscriptions", "projects", on_delete: :cascade add_foreign_key "suggestions", "notes", on_delete: :cascade add_foreign_key "system_note_metadata", "notes", name: "fk_d83a918cb1", on_delete: :cascade @@ -2650,7 +3863,25 @@ ActiveRecord::Schema.define(version: 20190611161641) do add_foreign_key "user_statuses", "users", on_delete: :cascade add_foreign_key "user_synced_attributes_metadata", "users", on_delete: :cascade add_foreign_key "users", "application_setting_terms", column: "accepted_term_id", name: "fk_789cd90b35", on_delete: :cascade + add_foreign_key "users", "namespaces", column: "managing_group_id", name: "fk_a4b8fefe3e", on_delete: :nullify + add_foreign_key "users_ops_dashboard_projects", "projects", on_delete: :cascade + add_foreign_key "users_ops_dashboard_projects", "users", on_delete: :cascade add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade + add_foreign_key "vulnerability_feedback", "ci_pipelines", column: "pipeline_id", on_delete: :nullify + add_foreign_key "vulnerability_feedback", "issues", on_delete: :nullify + add_foreign_key "vulnerability_feedback", "merge_requests", name: "fk_563ff1912e", on_delete: :nullify + add_foreign_key "vulnerability_feedback", "projects", on_delete: :cascade + add_foreign_key "vulnerability_feedback", "users", column: "author_id", on_delete: :cascade + add_foreign_key "vulnerability_feedback", "users", column: "comment_author_id", name: "fk_94f7c8a81e", on_delete: :nullify + add_foreign_key "vulnerability_identifiers", "projects", on_delete: :cascade + add_foreign_key "vulnerability_occurrence_identifiers", "vulnerability_identifiers", column: "identifier_id", on_delete: :cascade + add_foreign_key "vulnerability_occurrence_identifiers", "vulnerability_occurrences", column: "occurrence_id", on_delete: :cascade + add_foreign_key "vulnerability_occurrence_pipelines", "ci_pipelines", column: "pipeline_id", on_delete: :cascade + add_foreign_key "vulnerability_occurrence_pipelines", "vulnerability_occurrences", column: "occurrence_id", on_delete: :cascade + add_foreign_key "vulnerability_occurrences", "projects", on_delete: :cascade + add_foreign_key "vulnerability_occurrences", "vulnerability_identifiers", column: "primary_identifier_id", on_delete: :cascade + add_foreign_key "vulnerability_occurrences", "vulnerability_scanners", column: "scanner_id", on_delete: :cascade + add_foreign_key "vulnerability_scanners", "projects", on_delete: :cascade add_foreign_key "web_hook_logs", "web_hooks", on_delete: :cascade add_foreign_key "web_hooks", "projects", name: "fk_0c8ca6d9d1", on_delete: :cascade end diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md index 38842693d73..834b41b3a2c 100644 --- a/doc/administration/repository_storage_types.md +++ b/doc/administration/repository_storage_types.md @@ -47,18 +47,12 @@ Any change in the URL will need to be reflected on disk (when groups / users or projects are renamed). This can add a lot of load in big installations, especially if using any type of network based filesystem. -CAUTION: **Caution:** -For Geo in particular: Geo does work with legacy storage, but in some -edge cases due to race conditions it can lead to errors when a project is -renamed multiple times in short succession, or a project is deleted and -recreated under the same name very quickly. We expect these race events to be -rare, and we have not observed a race condition side-effect happening yet. -This pattern also exists in other objects stored in GitLab, like issue -Attachments, GitLab Pages artifacts, Docker Containers for the integrated -Registry, etc. Hashed storage is a requirement for Geo. - ## Hashed Storage +CAUTION: **Important:** +Geo requires Hashed Storage since 12.0. If you haven't migrated yet, +check the [migration instructions](#how-to-migrate-to-hashed-storage) ASAP. + Hashed Storage is the new storage behavior we rolled out with 10.0. Instead of coupling project URL and the folder structure where the repository will be stored on disk, we are coupling a hash, based on the project's ID. This makes diff --git a/doc/api/services.md b/doc/api/services.md index 898cfad7254..f38f96f64ad 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -22,6 +22,7 @@ Parameters: | --------- | ---- | -------- | ----------- | | `api_key` | string | true | User API token. User must have access to task, all comments will be attributed to this user. | | `restrict_to_branch` | string | false | Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches. | +| `push_events` | boolean | false | Enable notifications for push events | ### Delete Asana service @@ -57,6 +58,7 @@ Parameters: | --------- | ---- | -------- | ----------- | | `token` | string | true | The authentication token | `subdomain` | string | false | The subdomain setting | +| `push_events` | boolean | false | Enable notifications for push events | ### Delete Assembla service @@ -96,6 +98,7 @@ Parameters: | `build_key` | string | true | Bamboo build plan key like KEY | | `username` | string | true | A user with API access, if applicable | | `password` | string | true | Password of the user | +| `push_events` | boolean | false | Enable notifications for push events | ### Delete Atlassian Bamboo CI service @@ -134,6 +137,7 @@ Parameters: | `project_url` | string | true | Project url | | `description` | string | false | Description | | `title` | string | false | Title | +| `push_events` | boolean | false | Enable notifications for push events | ### Delete Bugzilla Service @@ -170,6 +174,7 @@ Parameters: | `token` | string | true | Buildkite project GitLab token | | `project_url` | string | true | `https://buildkite.com/example/project` | | `enable_ssl_verification` | boolean | false | Enable SSL verification | +| `push_events` | boolean | false | Enable notifications for push events | ### Delete Buildkite service @@ -206,6 +211,7 @@ Parameters: | `token` | string | true | Campfire token | | `subdomain` | string | false | Campfire subdomain | | `room` | string | false | Campfire room | +| `push_events` | boolean | false | Enable notifications for push events | ### Delete Campfire service @@ -244,6 +250,7 @@ Parameters: | `project_url` | string | true | Project url | `description` | string | false | Description | `title` | string | false | Title +| `push_events` | boolean | false | Enable notifications for push events | ### Delete Custom Issue Tracker service @@ -280,6 +287,10 @@ Parameters: | `token` | string | true | Drone CI project specific token | | `drone_url` | string | true | `http://drone.example.com` | | `enable_ssl_verification` | boolean | false | Enable SSL verification | +| `push_events` | boolean | false | Enable notifications for push events | +| `merge_requests_events` | boolean | false | Enable notifications for merge request events | +| `tag_push_events` | boolean | false | Enable notifications for tag push events | + ### Delete Drone CI service @@ -316,6 +327,8 @@ Parameters: | `recipients` | string | true | Emails separated by whitespace | | `disable_diffs` | boolean | false | Disable code diffs | | `send_from_committer_email` | boolean | false | Send from committer | +| `push_events` | boolean | false | Enable notifications for push events | +| `tag_push_events` | boolean | false | Enable notifications for tag push events | ### Delete Emails on push service @@ -384,6 +397,7 @@ Parameters: | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `token` | string | true | Flowdock Git source token | +| `push_events` | boolean | false | Enable notifications for push events | ### Delete Flowdock service @@ -471,6 +485,14 @@ Parameters: | `room` | string | false |Room name or ID | | `api_version` | string | false | Leave blank for default (v2) | | `server` | string | false | Leave blank for default. For example, `https://hipchat.example.com`. | +| `push_events` | boolean | false | Enable notifications for push events | +| `issues_events` | boolean | false | Enable notifications for issue events | +| `confidential_issues_events` | boolean | false | Enable notifications for confidential issue events | +| `merge_requests_events` | boolean | false | Enable notifications for merge request events | +| `tag_push_events` | boolean | false | Enable notifications for tag push events | +| `note_events` | boolean | false | Enable notifications for note events | +| `confidental_note_events` | boolean | false | Enable notifications for confidential note events | +| `pipeline_events` | boolean | false | Enable notifications for pipeline events | ### Delete HipChat service @@ -511,6 +533,7 @@ Parameters: | `server_host` | string | false | localhost | | `server_port` | integer | false | 6659 | | `colorize_messages` | boolean | false | Colorize messages | +| `push_events` | boolean | false | Enable notifications for push events | ### Delete Irker (IRC gateway) service @@ -562,6 +585,8 @@ Parameters: | `password` | string | yes | The password of the user created to be used with GitLab/JIRA. | | `active` | boolean | no | Activates or deactivates the service. Defaults to false (deactivated). | | `jira_issue_transition_id` | string | no | The ID of a transition that moves issues to a closed state. You can find this number under the JIRA workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the **Transitions (id)** column ([see screenshot][trans]). By default, this ID is set to `2`. | +| `commit_events` | boolean | false | Enable notifications for commit events | +| `merge_requests_events` | boolean | false | Enable notifications for merge request events | ### Delete JIRA service @@ -715,9 +740,14 @@ PUT /projects/:id/services/packagist Parameters: -- `username` (**required**) -- `token` (**required**) -- `server` (optional) +| Parameter | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `username` | string | yes | The username of a Packagist account | +| `token` | string | yes | API token to the Packagist server | +| `server` | boolean | no | URL of the Packagist server. Leave blank for default: https://packagist.org | +| `push_events` | boolean | false | Enable notifications for push events | +| `merge_requests_events` | boolean | false | Enable notifications for merge request events | +| `tag_push_events` | boolean | false | Enable notifications for tag push events | ### Delete Packagist service @@ -755,6 +785,7 @@ Parameters: | `add_pusher` | boolean | no | Add pusher to recipients list | | `notify_only_broken_pipelines` | boolean | no | Notify only broken pipelines | | `notify_only_default_branch` | boolean | no | Send notifications only for the default branch ([introduced in GitLab 12.0](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/28271)) | +| `pipeline_events` | boolean | false | Enable notifications for pipeline events | ### Delete Pipeline-Emails service @@ -790,6 +821,7 @@ Parameters: | --------- | ---- | -------- | ----------- | | `token` | string | true | The PivotalTracker token | | `restrict_to_branch` | boolean | false | Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches. | +| `push_events` | boolean | false | Enable notifications for push events | ### Delete PivotalTracker service @@ -862,6 +894,7 @@ Parameters: | `priority` | string | true | The priority | | `device` | string | false | Leave blank for all active devices | | `sound` | string | false | The sound of the notification | +| `push_events` | boolean | false | Enable notifications for push events | ### Delete Pushover service @@ -899,6 +932,7 @@ Parameters: | `project_url` | string | true | Project url | | `issues_url` | string | true | Issue url | | `description` | string | false | Description | +| `push_events` | boolean | false | Enable notifications for push events | ### Delete Redmine service @@ -989,6 +1023,15 @@ Parameters: | Parameter | Type | Required | Description | | --------- | ---- | -------- | ----------- | | `webhook` | string | true | The Microsoft Teams webhook. For example, `https://outlook.office.com/webhook/...` | +| `push_events` | boolean | false | Enable notifications for push events | +| `issues_events` | boolean | false | Enable notifications for issue events | +| `confidential_issues_events` | boolean | false | Enable notifications for confidential issue events | +| `merge_requests_events` | boolean | false | Enable notifications for merge request events | +| `tag_push_events` | boolean | false | Enable notifications for tag push events | +| `note_events` | boolean | false | Enable notifications for note events | +| `confidental_note_events` | boolean | false | Enable notifications for confidential note events | +| `pipeline_events` | boolean | false | Enable notifications for pipeline events | +| `wiki_page_events` | boolean | false | Enable notifications for wiki page events | ### Delete Microsoft Teams service @@ -1084,6 +1127,7 @@ Parameters: | `build_type` | string | true | Build configuration ID | | `username` | string | true | A user with permissions to trigger a manual build | | `password` | string | true | The password of the user | +| `push_events` | boolean | false | Enable notifications for push events | ### Delete JetBrains TeamCity CI service @@ -1230,6 +1274,7 @@ Parameters: | `issues_url` | string | true | Issue url | | `project_url` | string | true | Project url | | `description` | string | false | Description | +| `push_events` | boolean | false | Enable notifications for push events | ### Delete YouTrack Service diff --git a/doc/ci/merge_request_pipelines/img/merge_train_cancel.png b/doc/ci/merge_request_pipelines/img/merge_train_cancel.png Binary files differnew file mode 100644 index 00000000000..1561fdcc7a5 --- /dev/null +++ b/doc/ci/merge_request_pipelines/img/merge_train_cancel.png diff --git a/doc/ci/merge_request_pipelines/img/merge_train_config.png b/doc/ci/merge_request_pipelines/img/merge_train_config.png Binary files differnew file mode 100644 index 00000000000..fb0af43556e --- /dev/null +++ b/doc/ci/merge_request_pipelines/img/merge_train_config.png diff --git a/doc/ci/merge_request_pipelines/img/merge_train_start.png b/doc/ci/merge_request_pipelines/img/merge_train_start.png Binary files differnew file mode 100644 index 00000000000..f20108157d2 --- /dev/null +++ b/doc/ci/merge_request_pipelines/img/merge_train_start.png diff --git a/doc/ci/merge_request_pipelines/img/merge_train_start_when_pipeline_succeeds.png b/doc/ci/merge_request_pipelines/img/merge_train_start_when_pipeline_succeeds.png Binary files differnew file mode 100644 index 00000000000..62c2f2f5ff5 --- /dev/null +++ b/doc/ci/merge_request_pipelines/img/merge_train_start_when_pipeline_succeeds.png diff --git a/doc/ci/merge_request_pipelines/index.md b/doc/ci/merge_request_pipelines/index.md index fe2fc790505..5adb7ebd30d 100644 --- a/doc/ci/merge_request_pipelines/index.md +++ b/doc/ci/merge_request_pipelines/index.md @@ -74,7 +74,7 @@ when a merge request was created or updated. For example: ## Pipelines for Merged Results **[PREMIUM]** > [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7380) in [GitLab Premium](https://about.gitlab.com/pricing/) 11.10. -> This feature is disabled by default until we resolve issues with [contention handling](https://gitlab.com/gitlab-org/gitlab-ee/issues/9186), but [can be enabled manually](#enabling-pipelines-for-merged-results). +> This feature is disabled by default until we resolve issues with [contention handling](https://gitlab.com/gitlab-org/gitlab-ee/issues/11222), but [can be enabled manually](#enabling-pipelines-for-merged-results). It's possible for your source and target branches to diverge, which can result in the scenario that source branch's pipeline was green, the target's pipeline was green, @@ -118,10 +118,73 @@ otherwise pipelines for merged results won't run and your merge requests will be - This feature requires [GitLab Runner](https://gitlab.com/gitlab-org/gitlab-runner) 11.9 or newer. - This feature requires [Gitaly](https://gitlab.com/gitlab-org/gitaly) 1.21.0 or newer. -- After the merge request pipeline succeeds, if the target branch has moved forward, the result of the pipeline is stale and must be retried. In busy repos, this can become a problem as it is highly probable that the target branch will have moved ahead. Improvements are [planned](https://gitlab.com/gitlab-org/gitlab-ee/issues/9186) for future versions of GitLab. - Forking/cross-repo workflows are not currently supported. To follow progress, see [#9713](https://gitlab.com/gitlab-org/gitlab-ee/issues/9713). - This feature is not available for [fast forward merges](../../user/project/merge_requests/fast_forward_merge.md) yet. To follow progress, see [#58226](https://gitlab.com/gitlab-org/gitlab-ce/issues/58226). +## Merge Trains **[PREMIUM]** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/9186) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.0. +> This feature is disabled by default, but [can be enabled manually](#enabling-merge-trains). + +[Pipelines for merged results](#pipelines-for-merged-results-premium) introduces +running a build on the result of the merged code prior to merging, as a way to keep master green. +There's a scenario, however, for teams with a high number of changes in the target branch (typically master) where in many or even all cases, +by the time the merged code is validated another commit has made it to master, invalidating the merged result. +You'd need some kind of queuing, cancellation or retry mechanism for these scenarios +in order to ensure an orderly flow of changes into the target branch. + +Each MR that joins a merge train joins as the last item in the train, +just as it works in the current state. However, instead of queuing and waiting, +each item takes the completed state of the previous (pending) merge ref, adds its own changes, +and starts the pipeline immediately in parallel under the assumption that everything is going to pass. +In this way, if all the pipelines in the train merge successfully, no pipeline time is wasted either queuing or retrying. +If the button is subsequently pressed in a different MR, instead of creating a new pipeline for the target branch, +it creates a new pipeline targeting the merge result of the previous MR plus the target branch. +Pipelines invalidated through failures are immediately canceled and requeued. + +CAUTION: **Caution:** +At the moment, each merge train can generate a merge ref and run a pipeline **one at a time**. We plan to make the pipelines for merged results [run in parallel](https://gitlab.com/gitlab-org/gitlab-ee/issues/11222) in a future release. + +### Enabling Merge Trains + +To enable merge trains at the project level: + +1. Visit your project's **Settings > General** and expand **Merge requests**. +1. Check **Allow merge trains**. +1. Click **Save changes** button. + +![Merge request pipeline config](img/merge_train_config.png) + +CAUTION: **Warning:** +This feature requires [Pipelines for merged results](#pipelines-for-merged-results-premium) to be **configured properly**. + +### How to add a merge request to a merge train + +To add a merge request to a merge train: + +1. Click "Start/Add merge train" button in a merge request + +![Start merge train](img/merge_train_start.png) + +### How to remove a merge request from a merge train + +1. Click "Remove from merge train" button in the merge request widget. + +![Cancel merge train](img/merge_train_cancel.png) + +### Tips: Start/Add to merge train when pipeline succeeds + +You can add a merge request to a merge train only when the latest pipeline in the +merge request finished. While the pipeline is running or pending, you cannot add +the merge request to a train because the current change of the merge request may +be broken thus it could affect the following merge requests. + +In this case, you can schedule to add the merge request to a merge train **when the latest +pipeline succeeds**. You can see the following button instead of the regular "Start/Add merge train" +button while the latest pipeline is running. + +![Add to merge train when pipeline succeeds](img/merge_train_start_when_pipeline_succeeds.png) + ## Excluding certain jobs The behavior of the `only: merge_requests` parameter is such that _only_ jobs with diff --git a/doc/ci/review_apps/img/review_button.png b/doc/ci/review_apps/img/review_button.png Binary files differnew file mode 100644 index 00000000000..0b231c50858 --- /dev/null +++ b/doc/ci/review_apps/img/review_button.png diff --git a/doc/ci/review_apps/img/toolbar_feeback_form.png b/doc/ci/review_apps/img/toolbar_feeback_form.png Binary files differnew file mode 100644 index 00000000000..d147981a387 --- /dev/null +++ b/doc/ci/review_apps/img/toolbar_feeback_form.png diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md index 7b039fe6654..70934e074a0 100644 --- a/doc/ci/review_apps/index.md +++ b/doc/ci/review_apps/index.md @@ -154,6 +154,44 @@ After adding Review Apps to your workflow, you follow the branched Git flow. Tha 1. Wait for the Runner to build and deploy your web application. 1. Click on the link that provided in the merge request related to the branch to see the changes live. +### Visual Reviews **[STARTER]** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/10761) in GitLab Starter 12.0. + +The Visual Reviews feedback form can be added to a Review App to enable reviewers to post comments +directly from the app back to the merge request that spawned the Review App. + +For example, a form like the following can be configured to post the contents of the +text field into the discussion thread of a merge request: + +![feedback form](img/toolbar_feeback_form.png) + +#### Using Visual Reviews + +If Visual Reviews has been [enabled](#configuring-visual-reviews) for the Review App, the Visual Reviews feedback form is overlaid on the app's pages at the bottom-right corner. + +To use the feedback form, you will need to create a [personal access token](../../user/profile/personal_access_tokens.md) with the API scope selected. + +Paste the token into the feedback box, when prompted. If you select **Remember me**, your browser stores the token so that future visits to Review Apps at the same URL will not require you to re-enter the token. To clear the token, click **Log out**. + +Because tokens must be entered on a per-domain basis and they can only be accessed once, you can save the token to your password manager specifically for the purpose of Visual Reviews. This way, you will not need to create additional tokens for each merge request. + +Comments can make use of all the [Markdown annotations](../../user/markdown.md) +available in merge request comment boxes. + +#### Configuring Visual Reviews + +The feedback form is served through a script you add to pages in your Review App. +To access the code to include the script, click the **Review** button in the **Pipeline** section of the merge request. + +![review button](img/review_button.png) + +The provided script hardcodes the project and merge request IDs. You may want to consider +using features of your programming language to use environment variables or other +means to inject these at runtime. + +Future enhancements [are planned](https://gitlab.com/gitlab-org/gitlab-ee/issues/11322) to make this process even easier. + ## Limitations Review App limitations are the same as [environments limitations](../environments.md#limitations). diff --git a/doc/development/import_export.md b/doc/development/import_export.md index fd067b80c16..64c91f151c5 100644 --- a/doc/development/import_export.md +++ b/doc/development/import_export.md @@ -147,7 +147,6 @@ The `ModelConfigurationSpec` checks and confirms the addition of new models: If you think this model should be included in the export, please add it to `#{Gitlab::ImportExport.config_file}`. Definitely add it to `#{File.expand_path(ce_models_yml)}` - #{"or `#{File.expand_path(ee_models_yml)}` if the model/associations are EE-specific\n" if ee_models_hash.any?} to signal that you've handled this error and to prevent it from showing up in the future. MSG ``` @@ -253,7 +252,7 @@ Model relationships to be included in the project import/export: ```yaml project_tree: - labels: - :priorities + - :priorities - milestones: - events: - :push_event_payload diff --git a/doc/development/testing_guide/end_to_end/quick_start_guide.md b/doc/development/testing_guide/end_to_end/quick_start_guide.md index 1802f4792e0..d33ef0fc229 100644 --- a/doc/development/testing_guide/end_to_end/quick_start_guide.md +++ b/doc/development/testing_guide/end_to_end/quick_start_guide.md @@ -532,6 +532,7 @@ end ##### Details of `select_labels_and_refresh` Notice that we have not only moved the `select_labels_and_refresh` method, but we have also changed its implementation to: + 1. Click the `:edit_link_labels` element previously defined, instead of using `find('.block.labels .edit-link').click` 2. Use `within_element(:dropdown_menu_labels, text: label)`, and inside of it, we call `send_keys_to_element(:dropdown_input_field, [label, :enter])`, which is a method that we will implement in the `QA::Page::Base` class to replace `find('.dropdown-menu-labels .dropdown-input-field').send_keys [label, :enter]` 3. Use `click_body` after iterating on each label, instead of using `find('#content-body').click` @@ -547,15 +548,27 @@ Now let's change the view and the `dropdowns_helper` files to add the selectors In the [app/views/shared/issuable/_sidebar.html.haml](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/app/views/shared/issuable/_sidebar.html.haml) file, on [line 105 ](https://gitlab.com/gitlab-org/gitlab-ee/blob/84043fa72ca7f83ae9cde48ad670e6d5d16501a3/app/views/shared/issuable/_sidebar.html.haml#L105), add an extra class `qa-edit-link-labels`. -The code should look like this: `= link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right qa-edit-link-labels'`. +The code should look like this: + +```haml += link_to _('Edit'), '#', class: 'js-sidebar-dropdown-toggle edit-link float-right qa-edit-link-labels' +``` In the same file, on [line 121](https://gitlab.com/gitlab-org/gitlab-ee/blob/84043fa72ca7f83ae9cde48ad670e6d5d16501a3/app/views/shared/issuable/_sidebar.html.haml#L121), add an extra class `.qa-dropdown-menu-labels`. -The code should look like this: `.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable.qa-dropdown-menu-labels`. +The code should look like this: + +```haml +.dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable.qa-dropdown-menu-labels +``` In the [`dropdowns_helper.rb`](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/app/helpers/dropdowns_helper.rb) file, on [line 94](https://gitlab.com/gitlab-org/gitlab-ee/blob/99e51a374f2c20bee0989cac802e4b5621f72714/app/helpers/dropdowns_helper.rb#L94), add an extra class `qa-dropdown-input-field`. -The code should look like this: `filter_output = search_field_tag search_id, nil, class: "dropdown-input-field qa-dropdown-input-field", placeholder: placeholder, autocomplete: 'off'`. +The code should look like this: + +```ruby +filter_output = search_field_tag search_id, nil, class: "dropdown-input-field qa-dropdown-input-field", placeholder: placeholder, autocomplete: 'off' +``` > Classes starting with `qa-` are used for testing purposes only, and by defining such classes in the elements we add **testability** in the application. diff --git a/doc/integration/elasticsearch.md b/doc/integration/elasticsearch.md index 6064c417900..a2f38a2fcdf 100644 --- a/doc/integration/elasticsearch.md +++ b/doc/integration/elasticsearch.md @@ -275,19 +275,18 @@ You can also use the `gitlab:elastic:clear_index_status` Rake task to force the indexer to "forget" all progress, so retrying the indexing process from the start. -To index all wikis: +The `index_projects` command enqueues jobs to index all project and wiki +repositories, and most database content. However, snippets still need to be +indexed separately. To do so, run one of these commands: ```sh # Omnibus installations -sudo gitlab-rake gitlab:elastic:index_wikis +sudo gitlab-rake gitlab:elastic:index_snippets # Installations from source -bundle exec rake gitlab:elastic:index_wikis RAILS_ENV=production +bundle exec rake gitlab:elastic:index_snippets RAILS_ENV=production ``` -The wiki indexer also supports the `ID_FROM` and `ID_TO` parameters if you want -to limit a project set. - Enable replication and refreshing again after indexing (only if you previously disabled it): ```bash @@ -335,14 +334,11 @@ There are several rake tasks available to you via the command line: - `sudo gitlab-rake gitlab:elastic:create_empty_index` - `sudo gitlab-rake gitlab:elastic:clear_index_status` - `sudo gitlab-rake gitlab:elastic:index_projects` - - `sudo gitlab-rake gitlab:elastic:index_wikis` - `sudo gitlab-rake gitlab:elastic:index_snippets` - [sudo gitlab-rake gitlab:elastic:index_projects](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake) - This iterates over all projects and queues sidekiq jobs to index them in the background. - [sudo gitlab-rake gitlab:elastic:index_projects_status](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake) - This determines the overall status of the indexing. It is done by counting the total number of indexed projects, dividing by a count of the total number of projects, then multiplying by 100. -- [sudo gitlab-rake gitlab:elastic:index_wikis](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake) - - Iterates over every project, determines if said project contains wiki data, and then indexes the blobs (content) of said wiki data. - [sudo gitlab-rake gitlab:elastic:create_empty_index](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake) - This generates an empty index on the Elasticsearch side. - [sudo gitlab-rake gitlab:elastic:clear_index_status](https://gitlab.com/gitlab-org/gitlab-ee/blob/master/ee/lib/tasks/gitlab/elastic.rake) diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md index 3c5e820c1ca..26d764fa2cf 100644 --- a/doc/user/group/clusters/index.md +++ b/doc/user/group/clusters/index.md @@ -57,10 +57,6 @@ differentiate the new cluster from the rest. > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22011) in GitLab 11.5. > Became [optional](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26565) in GitLab 11.11. -NOTE: **Note:** -Only available when creating clusters. Existing clusters not managed by GitLab -cannot become GitLab-managed later. - You can choose to allow GitLab to manage your cluster for you. If your cluster is managed by GitLab, resources for your projects will be automatically created. See the [Access controls](../../project/clusters/index.md#access-controls) section for details on which resources will diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index dc21db603d6..181b20dc710 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -225,10 +225,6 @@ applications running on the cluster. > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22011) in GitLab 11.5. > Became [optional](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/26565) in GitLab 11.11. -NOTE: **Note:** -Only available when creating clusters. Existing clusters not managed by GitLab -cannot become GitLab-managed later. - You can choose to allow GitLab to manage your cluster for you. If your cluster is managed by GitLab, resources for your projects will be automatically created. See the [Access controls](#access-controls) section for details on which resources will diff --git a/doc/user/project/web_ide/img/terminal_status.png b/doc/user/project/web_ide/img/terminal_status.png Binary files differnew file mode 100644 index 00000000000..c37aa02b07a --- /dev/null +++ b/doc/user/project/web_ide/img/terminal_status.png diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md index a634a8b2f54..ef4eb45de66 100644 --- a/doc/user/project/web_ide/index.md +++ b/doc/user/project/web_ide/index.md @@ -231,6 +231,65 @@ While the terminal is running, it can be stopped by clicking **Stop Terminal**. This will disconnect the terminal and stop the runner's terminal job. From here, click **Restart Terminal** to start a new terminal session. +### File Syncing to Web Terminal + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/5276) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0. + +File changes in the Web IDE can be synced to a running Web Terminal. +This enables users to test their code changes in a preconfigured terminal +environment. + +NOTE: **Note:** +Only file changes in the Web IDE are synced to the terminal. +Changes made in the terminal are **not** synced to the Web IDE. + +Once you have [configured the Web Terminal for File Syncing](#configuring-file-syncing), +then when the Web terminal is started, a **Terminal** status will be visible +in the status bar. + +![Web IDE Client Side Evaluation](img/terminal_status.png) + +Changes made to your files via the Web IDE will sync to the running terminal +when: + +- <kbd>Ctrl</kbd> + <kbd>S</kbd> (or <kbd>Cmd</kbd> + <kbd>S</kbd> on Mac) + is pressed while editing a file. +- Anything outside the file editor is clicked after editing a file. +- A file or folder is created, deleted, or renamed. + +### Configuring File Syncing + +NOTE: **Note:** +This feature is only available for Kubernetes runners. + +To enable file syncing to the Web Terminal, the `.gitlab/.gitlab-webide.yml` +file needs to have a `webide-file-sync` service configured. Here is an example +configuration for a Node JS project which uses this service: + +```yaml +terminal: + # This can be any image that has the necessary runtime environment for your project. + image: + name: node:10-alpine + services: + - name: registry.gitlab.com/gitlab-org/webide-file-sync:latest + alias: webide-file-sync + entrypoint: ["/bin/sh"] + command: ["-c", "sleep 5 && ./webide-file-sync -project-dir $CI_PROJECT_DIR"] + ports: + # The `webide-file-sync` executable defaults to port 3000. + - number: 3000 +``` + +> **Notes:** +> - For now, the `webide-file-sync` executable must start **after** the project +> directory is available. This is why we need to add `sleep 5` to the `command`. +> See [this issue](https://gitlab.com/gitlab-org/webide-file-sync/issues/7) for +> more info. +> - `$CI_PROJECT_DIR` is a +> [predefined environment variable](../../../ci/variables/predefined_variables.md) +> for GitLab Runners. This is where your project's repository will be. + ### Limitations Interactive Terminals is in a beta phase and will continue to be improved upon in upcoming diff --git a/doc/workflow/todos.md b/doc/workflow/todos.md index 32907db4f46..3eac79427cf 100644 --- a/doc/workflow/todos.md +++ b/doc/workflow/todos.md @@ -34,6 +34,8 @@ A Todo appears in your Todos dashboard when: - the author, or - have set it to automatically merge once pipeline succeeds. +Todo triggers are not affected by [GitLab Notification Email settings](notifications.md). + NOTE: **Note:** When an user no longer has access to a resource related to a Todo like an issue, merge request, project or group the related Todos, for security reasons, gets deleted within the next hour. The delete is delayed to prevent data loss in case user got their access revoked by mistake. diff --git a/lib/api/container_registry.rb b/lib/api/container_registry.rb index e4493910196..7d9b5e1a598 100644 --- a/lib/api/container_registry.rb +++ b/lib/api/container_registry.rb @@ -115,12 +115,8 @@ module API authorize! :read_container_image, repository end - def authorize_update_container_image! - authorize! :update_container_image, repository - end - def authorize_destroy_container_image! - authorize! :admin_container_image, repository + authorize! :destroy_container_image, repository end def authorize_admin_container_image! diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 8840accf675..25e9fdd5fce 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -997,7 +997,7 @@ module API class ProjectService < Grape::Entity expose :id, :title, :created_at, :updated_at, :active - expose :push_events, :issues_events, :confidential_issues_events + expose :commit_events, :push_events, :issues_events, :confidential_issues_events expose :merge_requests_events, :tag_push_events, :note_events expose :confidential_note_events, :pipeline_events, :wiki_page_events expose :job_events diff --git a/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys.rb b/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys.rb deleted file mode 100644 index da8265a3a5f..00000000000 --- a/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys.rb +++ /dev/null @@ -1,56 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -class Gitlab::BackgroundMigration::CreateGpgKeySubkeysFromGpgKeys - class GpgKey < ActiveRecord::Base - self.table_name = 'gpg_keys' - - include EachBatch - include ShaAttribute - - sha_attribute :primary_keyid - sha_attribute :fingerprint - - has_many :subkeys, class_name: 'GpgKeySubkey' - end - - class GpgKeySubkey < ActiveRecord::Base - self.table_name = 'gpg_key_subkeys' - - include ShaAttribute - - sha_attribute :keyid - sha_attribute :fingerprint - end - - def perform(gpg_key_id) - gpg_key = GpgKey.find_by(id: gpg_key_id) - - return if gpg_key.nil? - return if gpg_key.subkeys.any? - - create_subkeys(gpg_key) - update_signatures(gpg_key) - end - - private - - def create_subkeys(gpg_key) - gpg_subkeys = Gitlab::Gpg.subkeys_from_key(gpg_key.key) - - gpg_subkeys[gpg_key.primary_keyid.upcase]&.each do |subkey_data| - gpg_key.subkeys.build(keyid: subkey_data[:keyid], fingerprint: subkey_data[:fingerprint]) - end - - # Improve latency by doing all INSERTs in a single call - GpgKey.transaction do - gpg_key.save! - end - end - - def update_signatures(gpg_key) - return unless gpg_key.subkeys.exists? - - InvalidGpgSignatureUpdateWorker.perform_async(gpg_key.id) - end -end diff --git a/lib/gitlab/background_migration/delete_diff_files.rb b/lib/gitlab/background_migration/delete_diff_files.rb deleted file mode 100644 index 664ead1af44..00000000000 --- a/lib/gitlab/background_migration/delete_diff_files.rb +++ /dev/null @@ -1,81 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class DeleteDiffFiles - class MergeRequestDiff < ActiveRecord::Base - self.table_name = 'merge_request_diffs' - - belongs_to :merge_request - has_many :merge_request_diff_files - end - - class MergeRequestDiffFile < ActiveRecord::Base - self.table_name = 'merge_request_diff_files' - end - - DEAD_TUPLES_THRESHOLD = 50_000 - VACUUM_WAIT_TIME = 5.minutes - - def perform(ids) - @ids = ids - - # We should reschedule until deadtuples get in a desirable - # state (e.g. < 50_000). That may take more than one reschedule. - # - if should_wait_deadtuple_vacuum? - reschedule - return - end - - prune_diff_files - end - - def should_wait_deadtuple_vacuum? - return false unless Gitlab::Database.postgresql? - - diff_files_dead_tuples_count >= DEAD_TUPLES_THRESHOLD - end - - private - - def reschedule - BackgroundMigrationWorker.perform_in(VACUUM_WAIT_TIME, self.class.name.demodulize, [@ids]) - end - - def diffs_collection - MergeRequestDiff.where(id: @ids) - end - - def diff_files_dead_tuples_count - dead_tuple = - execute_statement("SELECT n_dead_tup FROM pg_stat_all_tables "\ - "WHERE relname = 'merge_request_diff_files'")[0] - - dead_tuple&.fetch('n_dead_tup', 0).to_i - end - - def prune_diff_files - removed = 0 - updated = 0 - - MergeRequestDiff.transaction do - updated = diffs_collection.update_all(state: 'without_files') - removed = MergeRequestDiffFile.where(merge_request_diff_id: @ids).delete_all - end - - log_info("Removed #{removed} merge_request_diff_files rows, "\ - "updated #{updated} merge_request_diffs rows") - end - - def execute_statement(sql) - ActiveRecord::Base.connection.execute(sql) - end - - def log_info(message) - Rails.logger.info("BackgroundMigration::DeleteDiffFiles - #{message}") - end - end - end -end diff --git a/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb b/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb deleted file mode 100644 index 58df74cfa9b..00000000000 --- a/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits.rb +++ /dev/null @@ -1,149 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Metrics/MethodLength -# rubocop:disable Metrics/AbcSize -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class DeserializeMergeRequestDiffsAndCommits - attr_reader :diff_ids, :commit_rows, :file_rows - - class Error < StandardError - def backtrace - cause.backtrace - end - end - - class MergeRequestDiff < ActiveRecord::Base - self.table_name = 'merge_request_diffs' - end - - BUFFER_ROWS = 1000 - DIFF_FILE_BUFFER_ROWS = 100 - - def perform(start_id, stop_id) - merge_request_diffs = MergeRequestDiff - .select(:id, :st_commits, :st_diffs) - .where('st_commits IS NOT NULL OR st_diffs IS NOT NULL') - .where(id: start_id..stop_id) - - reset_buffers! - - merge_request_diffs.each do |merge_request_diff| - commits, files = single_diff_rows(merge_request_diff) - - diff_ids << merge_request_diff.id - commit_rows.concat(commits) - file_rows.concat(files) - - if diff_ids.length > BUFFER_ROWS || - commit_rows.length > BUFFER_ROWS || - file_rows.length > DIFF_FILE_BUFFER_ROWS - - flush_buffers! - end - end - - flush_buffers! - rescue => e - Rails.logger.info("#{self.class.name}: failed for IDs #{merge_request_diffs.map(&:id)} with #{e.class.name}") - - raise Error.new(e.inspect) - end - - private - - def reset_buffers! - @diff_ids = [] - @commit_rows = [] - @file_rows = [] - end - - def flush_buffers! - if diff_ids.any? - commit_rows.each_slice(BUFFER_ROWS).each do |commit_rows_slice| - bulk_insert('merge_request_diff_commits', commit_rows_slice) - end - - file_rows.each_slice(DIFF_FILE_BUFFER_ROWS).each do |file_rows_slice| - bulk_insert('merge_request_diff_files', file_rows_slice) - end - - MergeRequestDiff.where(id: diff_ids).update_all(st_commits: nil, st_diffs: nil) - end - - reset_buffers! - end - - def bulk_insert(table, rows) - Gitlab::Database.bulk_insert(table, rows) - rescue ActiveRecord::RecordNotUnique - ids = rows.map { |row| row[:merge_request_diff_id] }.uniq.sort - - Rails.logger.info("#{self.class.name}: rows inserted twice for IDs #{ids}") - end - - def single_diff_rows(merge_request_diff) - sha_attribute = Gitlab::Database::ShaAttribute.new - commits = YAML.load(merge_request_diff.st_commits) rescue [] - commits ||= [] - - commit_rows = commits.map.with_index do |commit, index| - commit_hash = commit.to_hash.with_indifferent_access.except(:parent_ids) - sha = commit_hash.delete(:id) - - commit_hash.merge( - merge_request_diff_id: merge_request_diff.id, - relative_order: index, - sha: sha_attribute.serialize(sha) - ) - end - - diffs = YAML.load(merge_request_diff.st_diffs) rescue [] - diffs = [] unless valid_raw_diffs?(diffs) - - file_rows = diffs.map.with_index do |diff, index| - diff_hash = diff.to_hash.with_indifferent_access.merge( - binary: false, - merge_request_diff_id: merge_request_diff.id, - relative_order: index - ) - - diff_hash.tap do |hash| - diff_text = hash[:diff] - - hash[:too_large] = !!hash[:too_large] - - hash[:a_mode] ||= guess_mode(hash[:new_file], hash[:diff]) - hash[:b_mode] ||= guess_mode(hash[:deleted_file], hash[:diff]) - - # Compatibility with old diffs created with Psych. - if diff_text.encoding == Encoding::BINARY && !diff_text.ascii_only? - hash[:binary] = true - hash[:diff] = [diff_text].pack('m0') - end - end - end - - [commit_rows, file_rows] - end - - # This doesn't have to be 100% accurate, because it's only used for - # display - it won't change file modes in the repository. Submodules are - # created as 600, regular files as 644. - def guess_mode(file_missing, diff) - return '0' if file_missing - - diff.include?('Subproject commit') ? '160000' : '100644' - end - - # Unlike MergeRequestDiff#valid_raw_diff?, don't count Rugged objects as - # valid, because we don't render them usefully anyway. - def valid_raw_diffs?(diffs) - return false unless diffs.respond_to?(:each) - - diffs.all? { |diff| diff.is_a?(Hash) } - end - end - end -end diff --git a/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check.rb b/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check.rb new file mode 100644 index 00000000000..e948cedaad5 --- /dev/null +++ b/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # rubocop: disable Style/Documentation + class MergeRequestAssigneesMigrationProgressCheck + include Gitlab::Utils::StrongMemoize + + RESCHEDULE_DELAY = 3.hours + WORKER = 'PopulateMergeRequestAssigneesTable'.freeze + DeadJobsError = Class.new(StandardError) + + def perform + raise DeadJobsError, "Only dead background jobs in the queue for #{WORKER}" if !ongoing? && dead_jobs? + + if ongoing? + BackgroundMigrationWorker.perform_in(RESCHEDULE_DELAY, self.class.name) + else + Feature.enable(:multiple_merge_request_assignees) + end + end + + private + + def dead_jobs? + strong_memoize(:dead_jobs) do + migration_klass.dead_jobs?(WORKER) + end + end + + def ongoing? + strong_memoize(:ongoing) do + migration_klass.exists?(WORKER) || migration_klass.retrying_jobs?(WORKER) + end + end + + def migration_klass + Gitlab::BackgroundMigration + end + end + # rubocop: enable Style/Documentation + end +end diff --git a/lib/gitlab/background_migration/populate_external_pipeline_source.rb b/lib/gitlab/background_migration/populate_external_pipeline_source.rb deleted file mode 100644 index 036fe641757..00000000000 --- a/lib/gitlab/background_migration/populate_external_pipeline_source.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class PopulateExternalPipelineSource - module Migratable - class Pipeline < ActiveRecord::Base - self.table_name = 'ci_pipelines' - - def self.sources - { - unknown: nil, - push: 1, - web: 2, - trigger: 3, - schedule: 4, - api: 5, - external: 6 - } - end - end - - class CommitStatus < ActiveRecord::Base - self.table_name = 'ci_builds' - self.inheritance_column = :_type_disabled - - scope :has_pipeline, -> { where('ci_builds.commit_id=ci_pipelines.id') } - scope :of_type, -> (type) { where('type=?', type) } - end - end - - def perform(start_id, stop_id) - external_pipelines(start_id, stop_id) - .update_all(source: Migratable::Pipeline.sources[:external]) - end - - private - - def external_pipelines(start_id, stop_id) - Migratable::Pipeline.where(id: (start_id..stop_id)) - .where( - 'EXISTS (?) AND NOT EXISTS (?)', - Migratable::CommitStatus.of_type('GenericCommitStatus').has_pipeline.select(1), - Migratable::CommitStatus.of_type('Ci::Build').has_pipeline.select(1) - ) - end - end - end -end diff --git a/lib/gitlab/background_migration/populate_import_state.rb b/lib/gitlab/background_migration/populate_import_state.rb deleted file mode 100644 index 695a2a713c5..00000000000 --- a/lib/gitlab/background_migration/populate_import_state.rb +++ /dev/null @@ -1,39 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This background migration creates all the records on the - # import state table for projects that are considered imports or forks - class PopulateImportState - def perform(start_id, end_id) - move_attributes_data_to_import_state(start_id, end_id) - rescue ActiveRecord::RecordNotUnique - retry - end - - def move_attributes_data_to_import_state(start_id, end_id) - Rails.logger.info("#{self.class.name} - Moving import attributes data to project mirror data table: #{start_id} - #{end_id}") - - ActiveRecord::Base.connection.execute <<~SQL - INSERT INTO project_mirror_data (project_id, status, jid, last_error) - SELECT id, import_status, import_jid, import_error - FROM projects - WHERE projects.import_status != 'none' - AND projects.id BETWEEN #{start_id} AND #{end_id} - AND NOT EXISTS ( - SELECT id - FROM project_mirror_data - WHERE project_id = projects.id - ) - SQL - - ActiveRecord::Base.connection.execute <<~SQL - UPDATE projects - SET import_status = 'none' - WHERE import_status != 'none' - AND id BETWEEN #{start_id} AND #{end_id} - SQL - end - end - end -end diff --git a/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data.rb b/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data.rb deleted file mode 100644 index d89ce358bb9..00000000000 --- a/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data.rb +++ /dev/null @@ -1,132 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class PopulateMergeRequestMetricsWithEventsData - def perform(min_merge_request_id, max_merge_request_id) - insert_metrics_for_range(min_merge_request_id, max_merge_request_id) - update_metrics_with_events_data(min_merge_request_id, max_merge_request_id) - end - - # Inserts merge_request_metrics records for merge_requests without it for - # a given merge request batch. - def insert_metrics_for_range(min, max) - metrics_not_exists_clause = - <<-SQL.strip_heredoc - NOT EXISTS (SELECT 1 FROM merge_request_metrics - WHERE merge_request_metrics.merge_request_id = merge_requests.id) - SQL - - MergeRequest.where(metrics_not_exists_clause).where(id: min..max).each_batch do |batch| - select_sql = batch.select(:id, :created_at, :updated_at).to_sql - - execute("INSERT INTO merge_request_metrics (merge_request_id, created_at, updated_at) #{select_sql}") - end - end - - def update_metrics_with_events_data(min, max) - if Gitlab::Database.postgresql? - # Uses WITH syntax in order to update merged and closed events with a single UPDATE. - # WITH is not supported by MySQL. - update_events_for_range(min, max) - else - update_merged_events_for_range(min, max) - update_closed_events_for_range(min, max) - end - end - - private - - # Updates merge_request_metrics latest_closed_at, latest_closed_by_id and merged_by_id - # based on the latest event records on events table for a given merge request batch. - def update_events_for_range(min, max) - sql = <<-SQL.strip_heredoc - WITH events_for_update AS ( - SELECT DISTINCT ON (target_id, action) target_id, action, author_id, updated_at - FROM events - WHERE target_id BETWEEN #{min} AND #{max} - AND target_type = 'MergeRequest' - AND action IN (#{Event::CLOSED},#{Event::MERGED}) - ORDER BY target_id, action, id DESC - ) - UPDATE merge_request_metrics met - SET latest_closed_at = latest_closed.updated_at, - latest_closed_by_id = latest_closed.author_id, - merged_by_id = latest_merged.author_id - FROM (SELECT * FROM events_for_update WHERE action = #{Event::CLOSED}) AS latest_closed - FULL OUTER JOIN - (SELECT * FROM events_for_update WHERE action = #{Event::MERGED}) AS latest_merged - USING (target_id) - WHERE target_id = merge_request_id; - SQL - - execute(sql) - end - - # Updates merge_request_metrics latest_closed_at, latest_closed_by_id based on the latest closed - # records on events table for a given merge request batch. - def update_closed_events_for_range(min, max) - sql = - <<-SQL.strip_heredoc - UPDATE merge_request_metrics metrics, - (#{select_events(min, max, Event::CLOSED)}) closed_events - SET metrics.latest_closed_by_id = closed_events.author_id, - metrics.latest_closed_at = closed_events.updated_at #{where_matches_closed_events}; - SQL - - execute(sql) - end - - # Updates merge_request_metrics merged_by_id based on the latest merged - # records on events table for a given merge request batch. - def update_merged_events_for_range(min, max) - sql = - <<-SQL.strip_heredoc - UPDATE merge_request_metrics metrics, - (#{select_events(min, max, Event::MERGED)}) merged_events - SET metrics.merged_by_id = merged_events.author_id #{where_matches_merged_events}; - SQL - - execute(sql) - end - - def execute(sql) - @connection ||= ActiveRecord::Base.connection - @connection.execute(sql) - end - - def select_events(min, max, action) - select_max_event_id = <<-SQL.strip_heredoc - SELECT max(id) - FROM events - WHERE action = #{action} - AND target_type = 'MergeRequest' - AND target_id BETWEEN #{min} AND #{max} - GROUP BY target_id - SQL - - <<-SQL.strip_heredoc - SELECT author_id, updated_at, target_id - FROM events - WHERE id IN(#{select_max_event_id}) - SQL - end - - def where_matches_closed_events - <<-SQL.strip_heredoc - WHERE metrics.merge_request_id = closed_events.target_id - AND metrics.latest_closed_at IS NULL - AND metrics.latest_closed_by_id IS NULL - SQL - end - - def where_matches_merged_events - <<-SQL.strip_heredoc - WHERE metrics.merge_request_id = merged_events.target_id - AND metrics.merged_by_id IS NULL - SQL - end - end - end -end diff --git a/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_improved.rb b/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_improved.rb deleted file mode 100644 index 37592d67dd9..00000000000 --- a/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_improved.rb +++ /dev/null @@ -1,99 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class PopulateMergeRequestMetricsWithEventsDataImproved - CLOSED_EVENT_ACTION = 3 - MERGED_EVENT_ACTION = 7 - - def perform(min_merge_request_id, max_merge_request_id) - insert_metrics_for_range(min_merge_request_id, max_merge_request_id) - update_metrics_with_events_data(min_merge_request_id, max_merge_request_id) - end - - # Inserts merge_request_metrics records for merge_requests without it for - # a given merge request batch. - def insert_metrics_for_range(min, max) - metrics_not_exists_clause = - <<-SQL.strip_heredoc - NOT EXISTS (SELECT 1 FROM merge_request_metrics - WHERE merge_request_metrics.merge_request_id = merge_requests.id) - SQL - - MergeRequest.where(metrics_not_exists_clause).where(id: min..max).each_batch do |batch| - select_sql = batch.select(:id, :created_at, :updated_at).to_sql - - execute("INSERT INTO merge_request_metrics (merge_request_id, created_at, updated_at) #{select_sql}") - end - end - - def update_metrics_with_events_data(min, max) - if Gitlab::Database.postgresql? - psql_update_metrics_with_events_data(min, max) - else - mysql_update_metrics_with_events_data(min, max) - end - end - - def psql_update_metrics_with_events_data(min, max) - update_sql = <<-SQL.strip_heredoc - UPDATE merge_request_metrics - SET (latest_closed_at, - latest_closed_by_id) = - ( SELECT updated_at, - author_id - FROM events - WHERE target_id = merge_request_id - AND target_type = 'MergeRequest' - AND action = #{CLOSED_EVENT_ACTION} - ORDER BY id DESC - LIMIT 1 ), - merged_by_id = - ( SELECT author_id - FROM events - WHERE target_id = merge_request_id - AND target_type = 'MergeRequest' - AND action = #{MERGED_EVENT_ACTION} - ORDER BY id DESC - LIMIT 1 ) - WHERE merge_request_id BETWEEN #{min} AND #{max} - SQL - - execute(update_sql) - end - - def mysql_update_metrics_with_events_data(min, max) - closed_updated_at_subquery = mysql_events_select(:updated_at, CLOSED_EVENT_ACTION) - closed_author_id_subquery = mysql_events_select(:author_id, CLOSED_EVENT_ACTION) - merged_author_id_subquery = mysql_events_select(:author_id, MERGED_EVENT_ACTION) - - update_sql = <<-SQL.strip_heredoc - UPDATE merge_request_metrics - SET latest_closed_at = (#{closed_updated_at_subquery}), - latest_closed_by_id = (#{closed_author_id_subquery}), - merged_by_id = (#{merged_author_id_subquery}) - WHERE merge_request_id BETWEEN #{min} AND #{max} - SQL - - execute(update_sql) - end - - def mysql_events_select(column, action) - <<-SQL.strip_heredoc - SELECT #{column} FROM events - WHERE target_id = merge_request_id - AND target_type = 'MergeRequest' - AND action = #{action} - ORDER BY id DESC - LIMIT 1 - SQL - end - - def execute(sql) - @connection ||= ActiveRecord::Base.connection - @connection.execute(sql) - end - end - end -end diff --git a/lib/gitlab/background_migration/redact_links.rb b/lib/gitlab/background_migration/redact_links.rb deleted file mode 100644 index 92256e59a6c..00000000000 --- a/lib/gitlab/background_migration/redact_links.rb +++ /dev/null @@ -1,51 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -require_relative 'redact_links/redactable' - -module Gitlab - module BackgroundMigration - class RedactLinks - class Note < ActiveRecord::Base - include EachBatch - include ::Gitlab::BackgroundMigration::RedactLinks::Redactable - - self.table_name = 'notes' - self.inheritance_column = :_type_disabled - end - - class Issue < ActiveRecord::Base - include EachBatch - include ::Gitlab::BackgroundMigration::RedactLinks::Redactable - - self.table_name = 'issues' - self.inheritance_column = :_type_disabled - end - - class MergeRequest < ActiveRecord::Base - include EachBatch - include ::Gitlab::BackgroundMigration::RedactLinks::Redactable - - self.table_name = 'merge_requests' - self.inheritance_column = :_type_disabled - end - - class Snippet < ActiveRecord::Base - include EachBatch - include ::Gitlab::BackgroundMigration::RedactLinks::Redactable - - self.table_name = 'snippets' - self.inheritance_column = :_type_disabled - end - - def perform(model_name, field, start_id, stop_id) - link_pattern = "%/sent_notifications/" + ("_" * 32) + "/unsubscribe%" - model = "Gitlab::BackgroundMigration::RedactLinks::#{model_name}".constantize - - model.where("#{field} like ?", link_pattern).where(id: start_id..stop_id).each do |resource| - resource.redact_field!(field) - end - end - end - end -end diff --git a/lib/gitlab/background_migration/redact_links/redactable.rb b/lib/gitlab/background_migration/redact_links/redactable.rb deleted file mode 100644 index baab34221f1..00000000000 --- a/lib/gitlab/background_migration/redact_links/redactable.rb +++ /dev/null @@ -1,21 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class RedactLinks - module Redactable - extend ActiveSupport::Concern - - def redact_field!(field) - self[field].gsub!(%r{/sent_notifications/\h{32}/unsubscribe}, '/sent_notifications/REDACTED/unsubscribe') - - if self.changed? - self.update_columns(field => self[field], - "#{field}_html" => nil) - end - end - end - end - end -end diff --git a/lib/gitlab/background_migration/rollback_import_state_data.rb b/lib/gitlab/background_migration/rollback_import_state_data.rb deleted file mode 100644 index a7c986747d8..00000000000 --- a/lib/gitlab/background_migration/rollback_import_state_data.rb +++ /dev/null @@ -1,40 +0,0 @@ -# frozen_string_literal: true - -module Gitlab - module BackgroundMigration - # This background migration migrates all the data of import_state - # back to the projects table for projects that are considered imports or forks - class RollbackImportStateData - def perform(start_id, end_id) - move_attributes_data_to_project(start_id, end_id) - end - - def move_attributes_data_to_project(start_id, end_id) - Rails.logger.info("#{self.class.name} - Moving import attributes data to projects table: #{start_id} - #{end_id}") - - if Gitlab::Database.mysql? - ActiveRecord::Base.connection.execute <<~SQL - UPDATE projects, project_mirror_data - SET - projects.import_status = project_mirror_data.status, - projects.import_jid = project_mirror_data.jid, - projects.import_error = project_mirror_data.last_error - WHERE project_mirror_data.project_id = projects.id - AND project_mirror_data.id BETWEEN #{start_id} AND #{end_id} - SQL - else - ActiveRecord::Base.connection.execute <<~SQL - UPDATE projects - SET - import_status = project_mirror_data.status, - import_jid = project_mirror_data.jid, - import_error = project_mirror_data.last_error - FROM project_mirror_data - WHERE project_mirror_data.project_id = projects.id - AND project_mirror_data.id BETWEEN #{start_id} AND #{end_id} - SQL - end - end - end - end -end diff --git a/lib/gitlab/background_migration/schedule_diff_files_deletion.rb b/lib/gitlab/background_migration/schedule_diff_files_deletion.rb deleted file mode 100644 index 609cf19187c..00000000000 --- a/lib/gitlab/background_migration/schedule_diff_files_deletion.rb +++ /dev/null @@ -1,44 +0,0 @@ -# frozen_string_literal: true -# rubocop:disable Style/Documentation - -module Gitlab - module BackgroundMigration - class ScheduleDiffFilesDeletion - class MergeRequestDiff < ActiveRecord::Base - self.table_name = 'merge_request_diffs' - - belongs_to :merge_request - - include EachBatch - end - - DIFF_BATCH_SIZE = 5_000 - INTERVAL = 5.minutes - MIGRATION = 'DeleteDiffFiles' - - def perform - diffs = MergeRequestDiff - .from("(#{diffs_collection.to_sql}) merge_request_diffs") - .where('merge_request_diffs.id != merge_request_diffs.latest_merge_request_diff_id') - .select(:id) - - diffs.each_batch(of: DIFF_BATCH_SIZE) do |relation, index| - ids = relation.pluck(:id) - - BackgroundMigrationWorker.perform_in(index * INTERVAL, MIGRATION, [ids]) - end - end - - private - - def diffs_collection - MergeRequestDiff - .joins(:merge_request) - .where("merge_requests.state = 'merged'") - .where('merge_requests.latest_merge_request_diff_id IS NOT NULL') - .where("merge_request_diffs.state NOT IN ('without_files', 'empty')") - .select('merge_requests.latest_merge_request_diff_id, merge_request_diffs.id') - end - end - end -end diff --git a/lib/gitlab/ci/ansi2html.rb b/lib/gitlab/ci/ansi2html.rb index fba0de20ced..6109b45ffd2 100644 --- a/lib/gitlab/ci/ansi2html.rb +++ b/lib/gitlab/ci/ansi2html.rb @@ -131,9 +131,9 @@ module Gitlab def on_109(_) set_bg_color(9, 'l') end - attr_accessor :offset, :n_open_tags, :fg_color, :bg_color, :style_mask + attr_accessor :offset, :n_open_tags, :fg_color, :bg_color, :style_mask, :sections, :lineno_in_section - STATE_PARAMS = [:offset, :n_open_tags, :fg_color, :bg_color, :style_mask].freeze + STATE_PARAMS = [:offset, :n_open_tags, :fg_color, :bg_color, :style_mask, :sections, :lineno_in_section].freeze def convert(stream, new_state) reset_state @@ -153,10 +153,9 @@ module Gitlab start_offset = @offset - open_new_tag - stream.each_line do |line| s = StringScanner.new(line) + until s.eos? if s.scan(Gitlab::Regex.build_trace_section_regex) @@ -166,11 +165,11 @@ module Gitlab elsif s.scan(/\e(([@-_])(.*?)?)?$/) break elsif s.scan(/</) - @out << '<' + write_in_tag '<' elsif s.scan(/\r?\n/) - @out << '<br>' + handle_new_line else - @out << s.scan(/./m) + write_in_tag s.scan(/./m) end @offset += s.matched_size @@ -190,13 +189,59 @@ module Gitlab ) end + def section_to_class_name(section) + section.to_s.downcase.gsub(/[^a-z0-9]/, '-') + end + + def handle_new_line + css_classes = [] + + if @sections.any? + css_classes = %w[section line] + sections.map { |section| "s_#{section}" } + end + + write_in_tag %{<br/>} + write_raw %{<span class="#{css_classes.join(' ')}"></span>} if css_classes.any? + @lineno_in_section += 1 + open_new_tag + end + def handle_section(scanner) action = scanner[1] timestamp = scanner[2] section = scanner[3] - line = scanner.matched[0...-5] # strips \r\033[0K - @out << %{<div class="hidden" data-action="#{action}" data-timestamp="#{timestamp}" data-section="#{section}">#{line}</div>} + normalized_section = section_to_class_name(section) + + if action == "start" + handle_section_start(normalized_section, timestamp) + elsif action == "end" + handle_section_end(normalized_section, timestamp) + end + end + + def handle_section_start(section, timestamp) + return if @sections.include?(section) + + @sections << section + write_raw %{<div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer" data-timestamp="#{timestamp}" data-section="#{data_section_names}" role="button"></div>} + @lineno_in_section = 0 + end + + def handle_section_end(section, timestamp) + return unless @sections.include?(section) + + # close all sections up to section + until @sections.empty? + write_raw %{<div class="section-end" data-section="#{data_section_names}"></div>} + + last_section = @sections.pop + break if section == last_section + end + end + + def data_section_names + @sections.join(" ") end def handle_sequence(scanner) @@ -217,8 +262,6 @@ module Gitlab end evaluate_command_stack(commands) - - open_new_tag end def evaluate_command_stack(stack) @@ -231,6 +274,20 @@ module Gitlab evaluate_command_stack(stack) end + def write_in_tag(data) + ensure_open_new_tag + @out << data + end + + def write_raw(data) + close_open_tags + @out << data + end + + def ensure_open_new_tag + open_new_tag if @n_open_tags == 0 + end + def open_new_tag css_classes = [] @@ -251,7 +308,11 @@ module Gitlab css_classes << "term-#{css_class}" if @style_mask & flag != 0 end - return if css_classes.empty? + if @sections.any? + css_classes << "section" + css_classes << "js-section-header" if @lineno_in_section == 0 + css_classes += sections.map { |section| "js-s-#{section}" } + end @out << %{<span class="#{css_classes.join(' ')}">} @n_open_tags += 1 @@ -268,6 +329,8 @@ module Gitlab @offset = 0 @n_open_tags = 0 @out = +'' + @sections = [] + @lineno_in_section = 0 reset end diff --git a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb index dbdc59505ac..531c9ce4256 100644 --- a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb +++ b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb @@ -9,7 +9,7 @@ module Gitlab deployment_cluster.present? && deployment_cluster.managed? && !deployment_cluster.project_type? && - kubernetes_namespace.new_record? + (kubernetes_namespace.new_record? || kubernetes_namespace.service_account_token.blank?) end def complete! diff --git a/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb b/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb index d7f47c0e7e6..942e4e55323 100644 --- a/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb +++ b/lib/gitlab/ci/pipeline/expression/lexeme/matches.rb @@ -13,16 +13,6 @@ module Gitlab regexp = @right.evaluate(variables) regexp.scan(text.to_s).any? - - if ci_variables_complex_expressions? - # return offset of first match, or nil if no matches - if match = regexp.scan(text.to_s).first - text.to_s.index(match) - end - else - # return true or false - regexp.scan(text.to_s).any? - end end def self.build(_value, behind, ahead) @@ -32,12 +22,6 @@ module Gitlab def self.precedence 10 # See: https://ruby-doc.org/core-2.5.0/doc/syntax/precedence_rdoc.html end - - private - - def ci_variables_complex_expressions? - Feature.enabled?(:ci_variables_complex_expressions) - end end end end diff --git a/lib/gitlab/cycle_analytics/builds_event_helper.rb b/lib/gitlab/cycle_analytics/builds_event_helper.rb new file mode 100644 index 00000000000..0d6f32fdc6f --- /dev/null +++ b/lib/gitlab/cycle_analytics/builds_event_helper.rb @@ -0,0 +1,36 @@ +# frozen_string_literal: true + +module Gitlab + module CycleAnalytics + module BuildsEventHelper + def initialize(*args) + @projections = [build_table[:id]] + @order = build_table[:created_at] + + super(*args) + end + + def fetch + Updater.update!(event_result, from: 'id', to: 'build', klass: ::Ci::Build) + + super + end + + def events_query + base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id])) + + super + end + + private + + def allowed_ids + nil + end + + def serialize(event) + AnalyticsBuildSerializer.new.represent(event['build']) + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/code_event_fetcher.rb b/lib/gitlab/cycle_analytics/code_event_fetcher.rb index 591db3c35e6..6c348f1862d 100644 --- a/lib/gitlab/cycle_analytics/code_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/code_event_fetcher.rb @@ -3,6 +3,8 @@ module Gitlab module CycleAnalytics class CodeEventFetcher < BaseEventFetcher + include CodeHelper + def initialize(*args) @projections = [mr_table[:title], mr_table[:iid], diff --git a/lib/gitlab/cycle_analytics/code_helper.rb b/lib/gitlab/cycle_analytics/code_helper.rb new file mode 100644 index 00000000000..8f28bdd2502 --- /dev/null +++ b/lib/gitlab/cycle_analytics/code_helper.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Gitlab + module CycleAnalytics + module CodeHelper + def stage_query(project_ids) + super(project_ids).where(mr_table[:created_at].gteq(issue_metrics_table[:first_mentioned_in_commit_at])) + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/code_stage.rb b/lib/gitlab/cycle_analytics/code_stage.rb index 2e5f9ef5a40..89a6430221c 100644 --- a/lib/gitlab/cycle_analytics/code_stage.rb +++ b/lib/gitlab/cycle_analytics/code_stage.rb @@ -3,6 +3,8 @@ module Gitlab module CycleAnalytics class CodeStage < BaseStage + include CodeHelper + def start_time_attrs @start_time_attrs ||= issue_metrics_table[:first_mentioned_in_commit_at] end diff --git a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb index 30c6ead8968..8a870f2e2a3 100644 --- a/lib/gitlab/cycle_analytics/issue_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/issue_event_fetcher.rb @@ -3,6 +3,8 @@ module Gitlab module CycleAnalytics class IssueEventFetcher < BaseEventFetcher + include IssueHelper + def initialize(*args) @projections = [issue_table[:title], issue_table[:iid], diff --git a/lib/gitlab/cycle_analytics/issue_helper.rb b/lib/gitlab/cycle_analytics/issue_helper.rb new file mode 100644 index 00000000000..c9266341378 --- /dev/null +++ b/lib/gitlab/cycle_analytics/issue_helper.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +module Gitlab + module CycleAnalytics + module IssueHelper + def stage_query(project_ids) + query = issue_table.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id])) + .project(issue_table[:project_id].as("project_id")) + .where(issue_table[:project_id].in(project_ids)) + .where(issue_table[:created_at].gteq(@options[:from])) # rubocop:disable Gitlab/ModuleWithInstanceVariables + .where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil))) + + query + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/issue_stage.rb b/lib/gitlab/cycle_analytics/issue_stage.rb index 4eae2da512c..738cb3eba03 100644 --- a/lib/gitlab/cycle_analytics/issue_stage.rb +++ b/lib/gitlab/cycle_analytics/issue_stage.rb @@ -3,6 +3,8 @@ module Gitlab module CycleAnalytics class IssueStage < BaseStage + include IssueHelper + def start_time_attrs @start_time_attrs ||= issue_table[:created_at] end diff --git a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb index aeca9d00156..d924f956dcd 100644 --- a/lib/gitlab/cycle_analytics/plan_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/plan_event_fetcher.rb @@ -3,60 +3,26 @@ module Gitlab module CycleAnalytics class PlanEventFetcher < BaseEventFetcher + include PlanHelper + def initialize(*args) - @projections = [mr_diff_table[:id], - issue_metrics_table[:first_mentioned_in_commit_at]] + @projections = [issue_table[:title], + issue_table[:iid], + issue_table[:id], + issue_table[:created_at], + issue_table[:author_id]] super(*args) end - def events_query - base_query - .join(mr_diff_table) - .on(mr_diff_table[:merge_request_id].eq(mr_table[:id])) - - super - end - private - def allowed_ids - nil - end - - def merge_request_diff_commits - @merge_request_diff_commits ||= - MergeRequestDiffCommit - .where(merge_request_diff_id: event_result.map { |event| event['id'] }) - .group_by(&:merge_request_diff_id) - end - def serialize(event) - commit = first_time_reference_commit(event) - - return unless commit - - serialize_commit(event, commit, query) - end - - def first_time_reference_commit(event) - return unless event && merge_request_diff_commits - - commits = merge_request_diff_commits[event['id'].to_i] - - return if commits.blank? - - commits.find do |commit| - next unless commit[:committed_date] && event['first_mentioned_in_commit_at'] - - commit[:committed_date].to_i == DateTime.parse(event['first_mentioned_in_commit_at'].to_s).to_i - end + AnalyticsIssueSerializer.new(project: @project).represent(event) end - def serialize_commit(event, commit, query) - commit = Commit.from_hash(commit.to_hash, @project) - - AnalyticsCommitSerializer.new(project: @project, total_time: event['total_time']).represent(commit) + def allowed_ids_finder_class + IssuesFinder end end end diff --git a/lib/gitlab/cycle_analytics/plan_helper.rb b/lib/gitlab/cycle_analytics/plan_helper.rb new file mode 100644 index 00000000000..30fc2ce6d40 --- /dev/null +++ b/lib/gitlab/cycle_analytics/plan_helper.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +module Gitlab + module CycleAnalytics + module PlanHelper + def stage_query(project_ids) + query = issue_table.join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id])) + .project(issue_table[:project_id].as("project_id")) + .where(issue_table[:project_id].in(project_ids)) + .where(issue_table[:created_at].gteq(@options[:from])) # rubocop:disable Gitlab/ModuleWithInstanceVariables + .where(issue_metrics_table[:first_added_to_board_at].not_eq(nil).or(issue_metrics_table[:first_associated_with_milestone_at].not_eq(nil))) + .where(issue_metrics_table[:first_mentioned_in_commit_at].not_eq(nil)) + + query + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/plan_stage.rb b/lib/gitlab/cycle_analytics/plan_stage.rb index 513e4575be0..0b27d114f52 100644 --- a/lib/gitlab/cycle_analytics/plan_stage.rb +++ b/lib/gitlab/cycle_analytics/plan_stage.rb @@ -3,6 +3,8 @@ module Gitlab module CycleAnalytics class PlanStage < BaseStage + include PlanHelper + def start_time_attrs @start_time_attrs ||= [issue_metrics_table[:first_associated_with_milestone_at], issue_metrics_table[:first_added_to_board_at]] @@ -21,7 +23,7 @@ module Gitlab end def legend - _("Related Commits") + _("Related Issues") end def description diff --git a/lib/gitlab/cycle_analytics/production_event_fetcher.rb b/lib/gitlab/cycle_analytics/production_event_fetcher.rb index 6681cb42c90..6bcbe0412a9 100644 --- a/lib/gitlab/cycle_analytics/production_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/production_event_fetcher.rb @@ -2,7 +2,28 @@ module Gitlab module CycleAnalytics - class ProductionEventFetcher < IssueEventFetcher + class ProductionEventFetcher < BaseEventFetcher + include ProductionHelper + + def initialize(*args) + @projections = [issue_table[:title], + issue_table[:iid], + issue_table[:id], + issue_table[:created_at], + issue_table[:author_id]] + + super(*args) + end + + private + + def serialize(event) + AnalyticsIssueSerializer.new(project: @project).represent(event) + end + + def allowed_ids_finder_class + IssuesFinder + end end end end diff --git a/lib/gitlab/cycle_analytics/review_event_fetcher.rb b/lib/gitlab/cycle_analytics/review_event_fetcher.rb index de100295281..b6354b5ffad 100644 --- a/lib/gitlab/cycle_analytics/review_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/review_event_fetcher.rb @@ -3,6 +3,8 @@ module Gitlab module CycleAnalytics class ReviewEventFetcher < BaseEventFetcher + include ReviewHelper + def initialize(*args) @projections = [mr_table[:title], mr_table[:iid], diff --git a/lib/gitlab/cycle_analytics/review_helper.rb b/lib/gitlab/cycle_analytics/review_helper.rb new file mode 100644 index 00000000000..c53249652b5 --- /dev/null +++ b/lib/gitlab/cycle_analytics/review_helper.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Gitlab + module CycleAnalytics + module ReviewHelper + def stage_query(project_ids) + super(project_ids).where(mr_metrics_table[:merged_at].not_eq(nil)) + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/review_stage.rb b/lib/gitlab/cycle_analytics/review_stage.rb index 294b656bc55..e9df8cd5a05 100644 --- a/lib/gitlab/cycle_analytics/review_stage.rb +++ b/lib/gitlab/cycle_analytics/review_stage.rb @@ -3,6 +3,8 @@ module Gitlab module CycleAnalytics class ReviewStage < BaseStage + include ReviewHelper + def start_time_attrs @start_time_attrs ||= mr_table[:created_at] end diff --git a/lib/gitlab/cycle_analytics/staging_event_fetcher.rb b/lib/gitlab/cycle_analytics/staging_event_fetcher.rb index 70ce82383b3..1454a1a33eb 100644 --- a/lib/gitlab/cycle_analytics/staging_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/staging_event_fetcher.rb @@ -3,34 +3,8 @@ module Gitlab module CycleAnalytics class StagingEventFetcher < BaseEventFetcher - def initialize(*args) - @projections = [build_table[:id]] - @order = build_table[:created_at] - - super(*args) - end - - def fetch - Updater.update!(event_result, from: 'id', to: 'build', klass: ::Ci::Build) - - super - end - - def events_query - base_query.join(build_table).on(mr_metrics_table[:pipeline_id].eq(build_table[:commit_id])) - - super - end - - private - - def allowed_ids - nil - end - - def serialize(event) - AnalyticsBuildSerializer.new.represent(event['build']) - end + include ProductionHelper + include BuildsEventHelper end end end diff --git a/lib/gitlab/cycle_analytics/staging_stage.rb b/lib/gitlab/cycle_analytics/staging_stage.rb index dbc2414ff66..e03627c6cd1 100644 --- a/lib/gitlab/cycle_analytics/staging_stage.rb +++ b/lib/gitlab/cycle_analytics/staging_stage.rb @@ -4,6 +4,7 @@ module Gitlab module CycleAnalytics class StagingStage < BaseStage include ProductionHelper + def start_time_attrs @start_time_attrs ||= mr_metrics_table[:merged_at] end diff --git a/lib/gitlab/cycle_analytics/test_event_fetcher.rb b/lib/gitlab/cycle_analytics/test_event_fetcher.rb index 4d5ea5b7c34..2fa44b1b364 100644 --- a/lib/gitlab/cycle_analytics/test_event_fetcher.rb +++ b/lib/gitlab/cycle_analytics/test_event_fetcher.rb @@ -2,7 +2,9 @@ module Gitlab module CycleAnalytics - class TestEventFetcher < StagingEventFetcher + class TestEventFetcher < BaseEventFetcher + include TestHelper + include BuildsEventHelper end end end diff --git a/lib/gitlab/cycle_analytics/test_helper.rb b/lib/gitlab/cycle_analytics/test_helper.rb new file mode 100644 index 00000000000..32fca7fa898 --- /dev/null +++ b/lib/gitlab/cycle_analytics/test_helper.rb @@ -0,0 +1,21 @@ +# frozen_string_literal: true + +module Gitlab + module CycleAnalytics + module TestHelper + def stage_query(project_ids) + if branch + super(project_ids).where(build_table[:ref].eq(branch)) + else + super(project_ids) + end + end + + private + + def branch + @branch ||= @options[:branch] # rubocop:disable Gitlab/ModuleWithInstanceVariables + end + end + end +end diff --git a/lib/gitlab/cycle_analytics/test_stage.rb b/lib/gitlab/cycle_analytics/test_stage.rb index c31b664148b..4787a906c07 100644 --- a/lib/gitlab/cycle_analytics/test_stage.rb +++ b/lib/gitlab/cycle_analytics/test_stage.rb @@ -3,6 +3,8 @@ module Gitlab module CycleAnalytics class TestStage < BaseStage + include TestHelper + def start_time_attrs @start_time_attrs ||= mr_metrics_table[:latest_build_started_at] end @@ -26,14 +28,6 @@ module Gitlab def description _("Total test time for all commits/merges") end - - def stage_query(project_ids) - if @options[:branch] - super(project_ids).where(build_table[:ref].eq(@options[:branch])) - else - super(project_ids) - end - end end end end diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index cc61bb7fa02..1b5cd0fbb07 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -149,7 +149,7 @@ module Gitlab # column - The name of the column to create the foreign key on. # on_delete - The action to perform when associated data is removed, # defaults to "CASCADE". - def add_concurrent_foreign_key(source, target, column:, on_delete: :cascade) + def add_concurrent_foreign_key(source, target, column:, on_delete: :cascade, name: nil) # Transactions would result in ALTER TABLE locks being held for the # duration of the transaction, defeating the purpose of this method. if transaction_open? @@ -167,14 +167,18 @@ module Gitlab return end - return add_foreign_key(source, target, - column: column, - on_delete: on_delete) + key_options = { column: column, on_delete: on_delete } + + # The MySQL adapter tries to create a foreign key without a name when + # `:name` is nil, instead of generating a name for us. + key_options[:name] = name if name + + return add_foreign_key(source, target, key_options) else on_delete = 'SET NULL' if on_delete == :nullify end - key_name = concurrent_foreign_key_name(source, column) + key_name = name || concurrent_foreign_key_name(source, column) unless foreign_key_exists?(source, target, column: column) Rails.logger.warn "Foreign key not created because it exists already " \ diff --git a/lib/gitlab/gpg/commit.rb b/lib/gitlab/gpg/commit.rb index 5ff415b6126..1d317c389d2 100644 --- a/lib/gitlab/gpg/commit.rb +++ b/lib/gitlab/gpg/commit.rb @@ -52,12 +52,13 @@ module Gitlab def using_keychain Gitlab::Gpg.using_tmp_keychain do - # first we need to get the keyid from the signature to query the gpg - # key belonging to the keyid. + # first we need to get the fingerprint from the signature to query the gpg + # key belonging to the fingerprint. # This way we can add the key to the temporary keychain and extract # the proper signature. - # NOTE: the invoked method is #fingerprint but it's only returning - # 16 characters (the format used by keyid) instead of 40. + # NOTE: the invoked method is #fingerprint but versions of GnuPG + # prior to 2.2.13 return 16 characters (the format used by keyid) + # instead of 40. fingerprint = verified_signature&.fingerprint break unless fingerprint @@ -128,11 +129,13 @@ module Gitlab gpg_key&.verified_user_infos&.first || gpg_key&.user_infos&.first || {} end - # rubocop: disable CodeReuse/ActiveRecord - def find_gpg_key(keyid) - GpgKey.find_by(primary_keyid: keyid) || GpgKeySubkey.find_by(keyid: keyid) + def find_gpg_key(fingerprint) + if fingerprint.length > 16 + GpgKey.find_by_fingerprint(fingerprint) || GpgKeySubkey.find_by_fingerprint(fingerprint) + else + GpgKey.find_by_primary_keyid(fingerprint) || GpgKeySubkey.find_by_keyid(fingerprint) + end end - # rubocop: enable CodeReuse/ActiveRecord end end end diff --git a/lib/gitlab/import_export/config.rb b/lib/gitlab/import_export/config.rb new file mode 100644 index 00000000000..f6cd4eb5e0c --- /dev/null +++ b/lib/gitlab/import_export/config.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +module Gitlab + module ImportExport + class Config + # Returns a Hash of the YAML file, including EE specific data if EE is + # used. + def to_h + hash = parse_yaml + ee_hash = hash['ee'] + + if merge? && ee_hash + ee_hash.each do |key, value| + if key == 'project_tree' + merge_project_tree(value, hash[key]) + else + merge_attributes_list(value, hash[key]) + end + end + end + + # We don't want to expose this section after this point, as it is no + # longer needed. + hash.delete('ee') + + hash + end + + # Merges a project relationships tree into the target tree. + # + # @param [Array<Hash|Symbol>] source_values + # @param [Array<Hash|Symbol>] target_values + def merge_project_tree(source_values, target_values) + source_values.each do |value| + if value.is_a?(Hash) + # Examples: + # + # { 'project_tree' => [{ 'labels' => [...] }] } + # { 'notes' => [:author, { 'events' => [:push_event_payload] }] } + value.each do |key, val| + target = target_values + .find { |h| h.is_a?(Hash) && h[key] } + + if target + merge_project_tree(val, target[key]) + else + target_values << { key => val.dup } + end + end + else + # Example: :priorities, :author, etc + target_values << value + end + end + end + + # Merges a Hash containing a flat list of attributes, such as the entries + # in a `excluded_attributes` section. + # + # @param [Hash] source_values + # @param [Hash] target_values + def merge_attributes_list(source_values, target_values) + source_values.each do |key, values| + target_values[key] ||= [] + target_values[key].concat(values) + end + end + + def merge? + Gitlab.ee? + end + + def parse_yaml + YAML.load_file(Gitlab::ImportExport.config_file) + end + end + end +end diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 71c44af9254..a0fb051e806 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -1,7 +1,11 @@ # Model relationships to be included in the project import/export +# +# This list _must_ only contain relationships that are available to both CE and +# EE. EE specific relationships must be defined in the `ee` section further +# down below. project_tree: - labels: - :priorities + - :priorities - milestones: - events: - :push_event_payload @@ -15,18 +19,18 @@ project_tree: - :push_event_payload - label_links: - label: - :priorities + - :priorities - milestone: - events: - :push_event_payload - resource_label_events: - label: - :priorities + - :priorities - :issue_assignees - snippets: - :award_emoji - notes: - :author + - :author - releases: - :links - project_members: @@ -46,13 +50,13 @@ project_tree: - :timelogs - label_links: - label: - :priorities + - :priorities - milestone: - events: - :push_event_payload - resource_label_events: - label: - :priorities + - :priorities - ci_pipelines: - notes: - :author @@ -121,12 +125,22 @@ excluded_attributes: - :bfg_object_map - :detected_repository_languages - :tag_list + - :mirror_user_id + - :mirror_trigger_builds + - :only_mirror_protected_branches + - :pull_mirror_available_overridden + - :mirror_overwrites_diverged_branches + - :packages_enabled + - :mirror_last_update_at + - :mirror_last_successful_update_at namespaces: - :runners_token - :runners_token_encrypted project_import_state: - :last_error - :jid + - :last_update_at + - :last_successful_update_at prometheus_metrics: - :common - :identifier @@ -201,3 +215,12 @@ methods: - :action project_badges: - :type + +# EE specific relationships and settings to include. All of this will be merged +# into the previous structures if EE is used. +ee: + project_tree: + - protected_branches: + - :unprotect_access_levels + - protected_environments: + - :deploy_access_levels diff --git a/lib/gitlab/import_export/reader.rb b/lib/gitlab/import_export/reader.rb index bc0d18e03fa..8bdf6ca491d 100644 --- a/lib/gitlab/import_export/reader.rb +++ b/lib/gitlab/import_export/reader.rb @@ -7,7 +7,7 @@ module Gitlab def initialize(shared:) @shared = shared - config_hash = YAML.load_file(Gitlab::ImportExport.config_file).deep_symbolize_keys + config_hash = ImportExport::Config.new.to_h.deep_symbolize_keys @tree = config_hash[:project_tree] @attributes_finder = Gitlab::ImportExport::AttributesFinder.new(included_attributes: config_hash[:included_attributes], excluded_attributes: config_hash[:excluded_attributes], diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb index 8f9d5cf1e63..e2787744f09 100644 --- a/lib/gitlab/visibility_level.rb +++ b/lib/gitlab/visibility_level.rb @@ -138,5 +138,18 @@ module Gitlab def visibility=(level) self[visibility_level_field] = Gitlab::VisibilityLevel.level_value(level) end + + def visibility_attribute_present?(attributes) + visibility_level_attributes.each do |attr| + return true if attributes[attr].present? + end + + false + end + + def visibility_level_attributes + [visibility_level_field, visibility_level_field.to_s, + :visibility, 'visibility'] + end end end diff --git a/lib/tasks/gitlab/import_export.rake b/lib/tasks/gitlab/import_export.rake index 900dbf7be24..5365bd3920f 100644 --- a/lib/tasks/gitlab/import_export.rake +++ b/lib/tasks/gitlab/import_export.rake @@ -7,7 +7,7 @@ namespace :gitlab do desc "GitLab | Display exported DB structure" task data: :environment do - puts YAML.load_file(Gitlab::ImportExport.config_file)['project_tree'].to_yaml(SortKeys: true) + puts Gitlab::ImportExport::Config.new.to_h['project_tree'].to_yaml(SortKeys: true) end desc 'GitLab | Bumps the Import/Export version in fixtures and project templates' diff --git a/locale/ar_SA/gitlab.po b/locale/ar_SA/gitlab.po index bd69bc85978..e093612f1f7 100644 --- a/locale/ar_SA/gitlab.po +++ b/locale/ar_SA/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: ar\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:21\n" +"PO-Revision-Date: 2019-06-14 19:58\n" msgid " Please sign in." msgstr "" @@ -6597,6 +6597,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -7080,6 +7083,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7680,6 +7686,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -8094,6 +8103,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8442,6 +8454,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8922,6 +8937,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9456,6 +9474,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12966,6 +12996,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index d94315d5c76..aea8f16e137 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: bg\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:22\n" +"PO-Revision-Date: 2019-06-14 19:59\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "Нов етикет" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/bn_BD/gitlab.po b/locale/bn_BD/gitlab.po index 8290a84180a..3096442dd44 100644 --- a/locale/bn_BD/gitlab.po +++ b/locale/bn_BD/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: bn\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:21\n" +"PO-Revision-Date: 2019-06-14 19:59\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/bn_IN/gitlab.po b/locale/bn_IN/gitlab.po index f4c87c8fc3b..8e220736054 100644 --- a/locale/bn_IN/gitlab.po +++ b/locale/bn_IN/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: bn-IN\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:22\n" +"PO-Revision-Date: 2019-06-14 19:59\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/ca_ES/gitlab.po b/locale/ca_ES/gitlab.po index fcf7a6bc34e..7e7d6b18d6f 100644 --- a/locale/ca_ES/gitlab.po +++ b/locale/ca_ES/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: ca\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:22\n" +"PO-Revision-Date: 2019-06-14 19:59\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/cs_CZ/gitlab.po b/locale/cs_CZ/gitlab.po index 60d3a1c3f48..0063738574c 100644 --- a/locale/cs_CZ/gitlab.po +++ b/locale/cs_CZ/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: cs\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:24\n" +"PO-Revision-Date: 2019-06-14 19:58\n" msgid " Please sign in." msgstr "" @@ -6497,6 +6497,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6978,6 +6981,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7576,6 +7582,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7986,6 +7995,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8334,6 +8346,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8812,6 +8827,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9344,6 +9362,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12844,6 +12874,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/cy_GB/gitlab.po b/locale/cy_GB/gitlab.po index dd4e6d82647..3c65f30b0d6 100644 --- a/locale/cy_GB/gitlab.po +++ b/locale/cy_GB/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: cy\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:27\n" +"PO-Revision-Date: 2019-06-14 20:03\n" msgid " Please sign in." msgstr "" @@ -6597,6 +6597,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -7080,6 +7083,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7680,6 +7686,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -8094,6 +8103,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8442,6 +8454,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8922,6 +8937,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9456,6 +9474,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12966,6 +12996,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/da_DK/gitlab.po b/locale/da_DK/gitlab.po index 87b8715e114..7cca186e1af 100644 --- a/locale/da_DK/gitlab.po +++ b/locale/da_DK/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: da\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:11\n" +"PO-Revision-Date: 2019-06-14 20:01\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/de/gitlab.po b/locale/de/gitlab.po index 5a611c60e8f..ca3bb88a10c 100644 --- a/locale/de/gitlab.po +++ b/locale/de/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: de\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:23\n" +"PO-Revision-Date: 2019-06-14 20:03\n" msgid " Please sign in." msgstr " Bitte melde dich an." @@ -6397,6 +6397,9 @@ msgstr "Google Takeout" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "Google-Authentifizierung ist nicht %{link_to_documentation}. Frage deinen GitLab Administrator, wenn du diesen Service nutzen möchtest." +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "Verstanden!" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "Wenn diese Option deaktiviert ist, wird ein abweichender lokaler Branch nicht automatisch mit Commits von seinem remote Gegenstück aktualisiert, um lokalen Datenverlust zu verhindern. Wenn der Standardbranch (%{default_branch}) abweicht und nicht aktualisiert werden kann, schlägt die Spiegelung fehl. Andere abweichende Branches werden ohne Meldung ignoriert." @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "Juli" @@ -7878,6 +7887,9 @@ msgstr "Sperren" msgid "Lock %{issuableDisplayName}" msgstr "Sperre %{issuableDisplayName}" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "Sperrung nicht gefunden" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "%{paragraphStart}hat die Beschreibung %{descriptionChangedTimes} Mal %{timeDifferenceMinutes} geändert%{paragraphEnd}" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "Neuer Tag" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "Neu..." @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "Letzte »" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/el_GR/gitlab.po b/locale/el_GR/gitlab.po index 71834d27dde..538d5406c9b 100644 --- a/locale/el_GR/gitlab.po +++ b/locale/el_GR/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: el\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:19\n" +"PO-Revision-Date: 2019-06-14 20:03\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po index 6cccd00890c..1747c758133 100644 --- a/locale/eo/gitlab.po +++ b/locale/eo/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: eo\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:25\n" +"PO-Revision-Date: 2019-06-14 20:01\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "Nova etikedo" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po index 2d61063f6a1..b6391526033 100644 --- a/locale/es/gitlab.po +++ b/locale/es/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: es-ES\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:17\n" +"PO-Revision-Date: 2019-06-14 19:56\n" msgid " Please sign in." msgstr " Por favor, inicie sesión." @@ -6397,6 +6397,9 @@ msgstr "Google Takeout" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "La autenticación de Google no se encuentra %{link_to_documentation}. Pregúntale a tu administrador de GitLab si quieres utilizar este servicio." +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "¡Lo tengo!" @@ -6876,6 +6879,9 @@ msgstr "Si algún trabajo supera este umbral de tiempo de espera, se marcará co msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "Si está marcado, los propietarios de grupos pueden administrar enlaces de grupo LDAP y anular los miembros LDAP" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "en" msgid "Job|with" msgstr "con" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "Jul" @@ -7878,6 +7887,9 @@ msgstr "Bloquear" msgid "Lock %{issuableDisplayName}" msgstr "Bloquear %{issuableDisplayName}" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "Bloqueo no encontrado" @@ -8226,6 +8238,9 @@ msgstr "comenzó una discusión en el commit %{linkStart}%{commitId}%{linkEnd}" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "%{paragraphStart}cambió la descripción %{descriptionChangedTimes} veces %{timeDifferenceMinutes}%{paragraphEnd}" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "Se ha producido un error al cargar el diff completo. Por favor, inténtalo de nuevo." @@ -8702,6 +8717,9 @@ msgstr "Nueva etiqueta" msgid "New users set to external" msgstr "Nuevos usuarios configurados como externos" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "Nuevo..." @@ -9232,6 +9250,18 @@ msgstr "Dominios de Pages" msgid "Pages getting started guide" msgstr "Guía de inicio de Pages" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "Último »" @@ -12722,6 +12752,9 @@ msgstr "Desbloqueado con éxito" msgid "Successfully unlocked" msgstr "Desbloqueado con éxito" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "Cambio sugerido" diff --git a/locale/et_EE/gitlab.po b/locale/et_EE/gitlab.po index 0a15fa7ba8a..0733ebe4adf 100644 --- a/locale/et_EE/gitlab.po +++ b/locale/et_EE/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: et\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:25\n" +"PO-Revision-Date: 2019-06-14 20:02\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/fil_PH/gitlab.po b/locale/fil_PH/gitlab.po index 40d0545c177..c705b1680fe 100644 --- a/locale/fil_PH/gitlab.po +++ b/locale/fil_PH/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: fil\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:19\n" +"PO-Revision-Date: 2019-06-14 20:02\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po index 2c558704303..b1870856c52 100644 --- a/locale/fr/gitlab.po +++ b/locale/fr/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: fr\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:26\n" +"PO-Revision-Date: 2019-06-14 20:02\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "Google Takeout" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "L’authentification Google n’est pas %{link_to_documentation}. Demandez à votre administrat·eur·rice GitLab si vous souhaitez utiliser ce service." +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "Compris !" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "Si cette option est désactivée, une branche locale divergente ne sera pas automatiquement mise à jour avec les commits de son homologue distant, afin d’éviter toute perte de données locales. Si la branche par défaut (%{default_branch}) a divergé et ne peut pas être mise à jour, la mise en miroir échouera. Les autres branches divergentes sont ignorées silencieusement." @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "juill." @@ -7878,6 +7887,9 @@ msgstr "Verrouiller" msgid "Lock %{issuableDisplayName}" msgstr "Verrouiller %{issuableDisplayName}" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "Verrou non trouvé" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "%{paragraphStart}a changé la description %{descriptionChangedTimes} fois %{timeDifferenceMinutes}%{paragraphEnd}" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "Nouvelle étiquette" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "Nouveau…" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "Dernière ⇥" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 1abc20a1736..0cabaeabb9a 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2828,9 +2828,6 @@ msgstr "" msgid "Container registry images" msgstr "" -msgid "ContainerRegistry|Created" -msgstr "" - msgid "ContainerRegistry|First log in to GitLab’s Container Registry using your GitLab username and password. If you have %{link_2fa} you need to use a %{link_token}:" msgstr "" @@ -2840,6 +2837,9 @@ msgstr "" msgid "ContainerRegistry|How to use the Container Registry" msgstr "" +msgid "ContainerRegistry|Last Updated" +msgstr "" + msgid "ContainerRegistry|Learn more about" msgstr "" @@ -4500,12 +4500,6 @@ msgstr "" msgid "First day of the week" msgstr "" -msgid "FirstPushedBy|First" -msgstr "" - -msgid "FirstPushedBy|pushed by" -msgstr "" - msgid "FlowdockService|Flowdock Git source token" msgstr "" @@ -7048,9 +7042,6 @@ msgstr "" msgid "Pipeline" msgstr "" -msgid "Pipeline ID (IID)" -msgstr "" - msgid "Pipeline Schedule" msgstr "" @@ -8316,9 +8307,6 @@ msgstr "" msgid "Registry" msgstr "" -msgid "Related Commits" -msgstr "" - msgid "Related Deployed Jobs" msgstr "" @@ -10011,6 +9999,9 @@ msgstr "" msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination." msgstr "" +msgid "The instance-level Kubernetes service integration is deprecated. Your data has been migrated to an <a href=\"%{url}\"/>instance-level cluster</a>." +msgstr "" + msgid "The invitation could not be accepted." msgstr "" @@ -11955,9 +11946,6 @@ msgstr "" msgid "Your Groups" msgstr "" -msgid "Your Kubernetes cluster information on this page is still editable, but you are advised to disable and reconfigure" -msgstr "" - msgid "Your Primary Email will be used for avatar detection." msgstr "" diff --git a/locale/gl_ES/gitlab.po b/locale/gl_ES/gitlab.po index 8dac79b6133..647ec18bd1e 100644 --- a/locale/gl_ES/gitlab.po +++ b/locale/gl_ES/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: gl\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:26\n" +"PO-Revision-Date: 2019-06-14 20:02\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/he_IL/gitlab.po b/locale/he_IL/gitlab.po index 71f4068a5d9..5ae83a3e4d3 100644 --- a/locale/he_IL/gitlab.po +++ b/locale/he_IL/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: he\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:15\n" +"PO-Revision-Date: 2019-06-14 20:01\n" msgid " Please sign in." msgstr "" @@ -6497,6 +6497,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6978,6 +6981,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7576,6 +7582,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7986,6 +7995,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8334,6 +8346,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8812,6 +8827,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9344,6 +9362,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12844,6 +12874,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/hi_IN/gitlab.po b/locale/hi_IN/gitlab.po index 3a647e7b861..f1c03c61435 100644 --- a/locale/hi_IN/gitlab.po +++ b/locale/hi_IN/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: hi\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:18\n" +"PO-Revision-Date: 2019-06-14 19:58\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/hr_HR/gitlab.po b/locale/hr_HR/gitlab.po index 316cab67e8b..6d81a3b7447 100644 --- a/locale/hr_HR/gitlab.po +++ b/locale/hr_HR/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: hr\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:24\n" +"PO-Revision-Date: 2019-06-14 20:00\n" msgid " Please sign in." msgstr "" @@ -6447,6 +6447,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6927,6 +6930,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7524,6 +7530,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7932,6 +7941,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8280,6 +8292,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8757,6 +8772,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9288,6 +9306,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12783,6 +12813,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/hu_HU/gitlab.po b/locale/hu_HU/gitlab.po index d6f224c306e..d2a8e28c75a 100644 --- a/locale/hu_HU/gitlab.po +++ b/locale/hu_HU/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: hu\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:12\n" +"PO-Revision-Date: 2019-06-14 19:54\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/id_ID/gitlab.po b/locale/id_ID/gitlab.po index 2775a4148b7..5c7497673dd 100644 --- a/locale/id_ID/gitlab.po +++ b/locale/id_ID/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: id\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:12\n" +"PO-Revision-Date: 2019-06-14 19:57\n" msgid " Please sign in." msgstr "" @@ -6347,6 +6347,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6825,6 +6828,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7420,6 +7426,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7824,6 +7833,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8172,6 +8184,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8647,6 +8662,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9176,6 +9194,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12661,6 +12691,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po index dc749104ecb..48902ba3969 100644 --- a/locale/it/gitlab.po +++ b/locale/it/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: it\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:12\n" +"PO-Revision-Date: 2019-06-14 19:52\n" msgid " Please sign in." msgstr " Per favore effettua il login." @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "L'autenticazione Google non è %{link_to_documentation}. Richiedi al tuo amministratore Gitlab se desideri utilizzare il servizio." +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "Se disabilitato, un ramo locale divergente non verrà automaticamente aggiornato con i commit dalla sua controparte remota, per prevenire la perdita locale dei dati. Se il ramo predefinito (%{default_branch}) è divergente e non può essere aggiornato, il mirroring fallirà. Altri rami divergenti vengono silenziosamente ignorati." @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "Lug" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "Nuovo tag" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "Ultima »" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po index 814f4ea85d2..a8bea017870 100644 --- a/locale/ja/gitlab.po +++ b/locale/ja/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: ja\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:20\n" +"PO-Revision-Date: 2019-06-14 19:52\n" msgid " Please sign in." msgstr " サインインしてください。" @@ -347,11 +347,11 @@ msgstr[0] "%d 日" msgid "1 closed issue" msgid_plural "%{issues} closed issues" -msgstr[0] "" +msgstr[0] "%{issues}件のクローズされた課題" msgid "1 closed merge request" msgid_plural "%{merge_requests} closed merge requests" -msgstr[0] "" +msgstr[0] "%{merge_requests}件のクローズされたマージリクエスト" msgid "1 day" msgstr "1 日" @@ -362,15 +362,15 @@ msgstr[0] "%dグループ" msgid "1 merged merge request" msgid_plural "%{merge_requests} merged merge requests" -msgstr[0] "" +msgstr[0] "%{merge_requests}件のマージされたマージリクエスト" msgid "1 open issue" msgid_plural "%{issues} open issues" -msgstr[0] "" +msgstr[0] "%{issues}件の課題" msgid "1 open merge request" msgid_plural "%{merge_requests} open merge requests" -msgstr[0] "" +msgstr[0] "%{merge_requests}件のマージリクエスト" msgid "1 pipeline" msgid_plural "%d pipelines" @@ -1010,7 +1010,7 @@ msgid "Also called \"Relying party service URL\" or \"Reply URL\"" msgstr "\"Relying party service URL\" または \"Reply URL\" とも呼ばれます" msgid "Alternate support URL for help page" -msgstr "" +msgstr "ヘルプページの別のサポートURL" msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to connect." msgstr "あるいは、 %{personal_access_token_link} を使用することもできます。パーソナルアクセストークンを作成する際に、<code>repo</code> スコープを選択する必要があります。これにより、接続可能な公開リポジトリとプライベートリポジトリの一覧を表示することができます。" @@ -1377,7 +1377,7 @@ msgid "ApprovalRule|Name" msgstr "名前" msgid "ApprovalRule|No. approvals required" -msgstr "" +msgstr "いいえ。承認が必要です。" msgid "ApprovalRule|e.g. QA, Security, etc." msgstr "" @@ -1819,7 +1819,7 @@ msgid "BambooService|Bamboo root URL like https://bamboo.example.com" msgstr "Bamboo のルートURL 例: https://bamboo.example.com" msgid "BambooService|You must set up automatic revision labeling and a repository trigger in Bamboo." -msgstr "" +msgstr "Bambooで自動リビジョンラベリングとリポジトリトリガーを設定しなければなりません。" msgid "BatchComments|Delete all pending comments" msgstr "保留中のコメントをすべて削除" @@ -6347,6 +6347,9 @@ msgstr "Google テイクアウト" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "Google 認証は %{link_to_documentation} ではありません。このサービスについては GitLab 管理者に問い合わせてください。" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "入手しましょう!" @@ -6825,6 +6828,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7420,6 +7426,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "7月" @@ -7824,6 +7833,9 @@ msgstr "ロック" msgid "Lock %{issuableDisplayName}" msgstr "ロック %{issuableDisplayName}" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "ロックが見つかりません" @@ -8172,6 +8184,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8647,6 +8662,9 @@ msgstr "新規タグ" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "新規...\t" @@ -9096,7 +9114,7 @@ msgid "OperationsDashboard|Add a project to the dashboard" msgstr "ダッシュボードにプロジェクトを追加" msgid "OperationsDashboard|Add projects" -msgstr "" +msgstr "プロジェクトを追加" msgid "OperationsDashboard|More information" msgstr "" @@ -9176,6 +9194,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "最後 »" @@ -9273,7 +9303,7 @@ msgid "Personal Access Token" msgstr "個人のアクセストークン" msgid "Personal project creation is not allowed. Please contact your administrator with questions" -msgstr "" +msgstr "個人的なプロジェクトの作成は許可されていません。質問がある場合はシステムの管理者に連絡してください" msgid "Phabricator Server Import" msgstr "" @@ -12661,6 +12691,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" @@ -15492,7 +15525,7 @@ msgid "You will lose all the unstaged changes you've made in this project. This msgstr "" msgid "You will need to update your local repositories to point to the new location." -msgstr "" +msgstr "ローカルリポジトリが新しい場所を示すように更新する必要があります。" msgid "You will not get any notifications via email" msgstr "通知メールを送信しません" diff --git a/locale/ka_GE/gitlab.po b/locale/ka_GE/gitlab.po index 25d60cdf6d3..2fe4fbc9d55 100644 --- a/locale/ka_GE/gitlab.po +++ b/locale/ka_GE/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: ka\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:26\n" +"PO-Revision-Date: 2019-06-14 20:03\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/ko/gitlab.po b/locale/ko/gitlab.po index b36de3639cf..a9b1d2f2f40 100644 --- a/locale/ko/gitlab.po +++ b/locale/ko/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: ko\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:13\n" +"PO-Revision-Date: 2019-06-14 19:52\n" msgid " Please sign in." msgstr "" @@ -6347,6 +6347,9 @@ msgstr "Google Takeout" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "Google 인증을 사용할 수 없습니다. %{link_to_documentation} GitLab 관리자에게 문의하세요." +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "확인!" @@ -6825,6 +6828,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7420,6 +7426,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "7월" @@ -7824,6 +7833,9 @@ msgstr "잠금" msgid "Lock %{issuableDisplayName}" msgstr "%{issuableDisplayName} 잠금" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "잠금을 찾을 수 없습니다." @@ -8172,6 +8184,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8647,6 +8662,9 @@ msgstr "새 태그 " msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "새로 만들기..." @@ -9176,6 +9194,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "마지막 »" @@ -12661,6 +12691,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/mn_MN/gitlab.po b/locale/mn_MN/gitlab.po index 07fea230372..ba9868f8fd2 100644 --- a/locale/mn_MN/gitlab.po +++ b/locale/mn_MN/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: mn\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:13\n" +"PO-Revision-Date: 2019-06-14 19:53\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/nb_NO/gitlab.po b/locale/nb_NO/gitlab.po index 3ee90cd308f..b818109b50b 100644 --- a/locale/nb_NO/gitlab.po +++ b/locale/nb_NO/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: nb\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:13\n" +"PO-Revision-Date: 2019-06-14 19:53\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/nl_NL/gitlab.po b/locale/nl_NL/gitlab.po index cb608bb36e6..8a3a8cf3af6 100644 --- a/locale/nl_NL/gitlab.po +++ b/locale/nl_NL/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: nl\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:24\n" +"PO-Revision-Date: 2019-06-14 20:01\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/pa_IN/gitlab.po b/locale/pa_IN/gitlab.po index 271a784632a..cf3724e3ad1 100644 --- a/locale/pa_IN/gitlab.po +++ b/locale/pa_IN/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: pa-IN\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:14\n" +"PO-Revision-Date: 2019-06-14 19:52\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/pl_PL/gitlab.po b/locale/pl_PL/gitlab.po index ac3a92314ea..eee5d9cd8c3 100644 --- a/locale/pl_PL/gitlab.po +++ b/locale/pl_PL/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: pl\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:14\n" +"PO-Revision-Date: 2019-06-14 19:53\n" msgid " Please sign in." msgstr "" @@ -6497,6 +6497,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6978,6 +6981,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7576,6 +7582,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7986,6 +7995,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8334,6 +8346,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8812,6 +8827,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9344,6 +9362,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12844,6 +12874,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po index f73affc2cbc..54a81af63d9 100644 --- a/locale/pt_BR/gitlab.po +++ b/locale/pt_BR/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: pt-BR\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:11\n" +"PO-Revision-Date: 2019-06-14 19:54\n" msgid " Please sign in." msgstr " Por favor, entre usando sua conta." @@ -6397,6 +6397,9 @@ msgstr "Google Takeout" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "Autenticação do Google não está %{link_to_documentation}. Peça ao administrador do Gitlab se você deseja usar esse serviço." +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "Entendi!" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "Se desativada, um branch local divergente não será atualizada automaticamente com commits de sua contraparte remota, para evitar a perda de dados locais. Se o branch padrão (%{default_branch}) tiver divergido e não puder ser atualizado, o espelhamento falhará. Outras branches divergentes são silenciosamente ignoradas." @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "Jul" @@ -7878,6 +7887,9 @@ msgstr "Bloquear" msgid "Lock %{issuableDisplayName}" msgstr "Bloquear %{issuableDisplayName}" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "Bloqueio não encontrado" @@ -8226,6 +8238,9 @@ msgstr "iniciou uma discussão no commit %{linkStart}%{commitId}%{linkEnd}" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "%{paragraphStart}alterou a descrição %{descriptionChangedTimes} vezes %{timeDifferenceMinutes}%{paragraphEnd}" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "Erro ao carregar o diff completo. Por favor, tente novamente." @@ -8702,6 +8717,9 @@ msgstr "Nova tag" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "Novo..." @@ -9232,6 +9250,18 @@ msgstr "Domínios de páginas" msgid "Pages getting started guide" msgstr "Guia de primeiros passos a Páginas" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "Último >>" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/pt_PT/gitlab.po b/locale/pt_PT/gitlab.po index bc1732cd69a..aa680827bec 100644 --- a/locale/pt_PT/gitlab.po +++ b/locale/pt_PT/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: pt-PT\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:14\n" +"PO-Revision-Date: 2019-06-14 19:54\n" msgid " Please sign in." msgstr " Por favor, inicia a sessão." @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/ro_RO/gitlab.po b/locale/ro_RO/gitlab.po index bd79e38ba94..4055a0fe933 100644 --- a/locale/ro_RO/gitlab.po +++ b/locale/ro_RO/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: ro\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:15\n" +"PO-Revision-Date: 2019-06-14 19:54\n" msgid " Please sign in." msgstr "" @@ -6447,6 +6447,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6927,6 +6930,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7524,6 +7530,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7932,6 +7941,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8280,6 +8292,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8757,6 +8772,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9288,6 +9306,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12783,6 +12813,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po index 08836089f1d..68ce5b4d64d 100644 --- a/locale/ru/gitlab.po +++ b/locale/ru/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: ru\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:15\n" +"PO-Revision-Date: 2019-06-14 19:55\n" msgid " Please sign in." msgstr "" @@ -6497,6 +6497,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "Аутентификация Google не %{link_to_documentation}. Попросите своего администратора GitLab, если вы хотите воспользоваться этим сервисом." +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "Понятно!" @@ -6978,6 +6981,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7576,6 +7582,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "Июл." @@ -7986,6 +7995,9 @@ msgstr "Блокировка" msgid "Lock %{issuableDisplayName}" msgstr "Заблокировать %{issuableDisplayName}" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "Блокировка не найдена" @@ -8334,6 +8346,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "%{paragraphStart}изменил описание %{descriptionChangedTimes} раз, за последние %{timeDifferenceMinutes}%{paragraphEnd}" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8812,6 +8827,9 @@ msgstr "Новый тег" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "Новый..." @@ -9344,6 +9362,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "Последняя »" @@ -12844,6 +12874,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/sk_SK/gitlab.po b/locale/sk_SK/gitlab.po index 3e1d08ed59e..7bb9f36a887 100644 --- a/locale/sk_SK/gitlab.po +++ b/locale/sk_SK/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: sk\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:17\n" +"PO-Revision-Date: 2019-06-14 19:56\n" msgid " Please sign in." msgstr "" @@ -6497,6 +6497,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6978,6 +6981,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7576,6 +7582,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7986,6 +7995,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8334,6 +8346,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8812,6 +8827,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9344,6 +9362,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12844,6 +12874,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/sq_AL/gitlab.po b/locale/sq_AL/gitlab.po index c3560018979..f9454822dc4 100644 --- a/locale/sq_AL/gitlab.po +++ b/locale/sq_AL/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: sq\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:21\n" +"PO-Revision-Date: 2019-06-14 19:51\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/sr_CS/gitlab.po b/locale/sr_CS/gitlab.po index 1118681c99d..1156a705653 100644 --- a/locale/sr_CS/gitlab.po +++ b/locale/sr_CS/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: sr-CS\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:16\n" +"PO-Revision-Date: 2019-06-14 19:55\n" msgid " Please sign in." msgstr "" @@ -6447,6 +6447,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6927,6 +6930,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7524,6 +7530,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7932,6 +7941,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8280,6 +8292,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8757,6 +8772,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9288,6 +9306,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12783,6 +12813,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/sr_SP/gitlab.po b/locale/sr_SP/gitlab.po index f3ce29f397f..cdbb13acb65 100644 --- a/locale/sr_SP/gitlab.po +++ b/locale/sr_SP/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: sr\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:16\n" +"PO-Revision-Date: 2019-06-14 19:55\n" msgid " Please sign in." msgstr "" @@ -6447,6 +6447,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6927,6 +6930,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7524,6 +7530,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7932,6 +7941,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8280,6 +8292,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8757,6 +8772,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9288,6 +9306,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12783,6 +12813,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/sv_SE/gitlab.po b/locale/sv_SE/gitlab.po index 0bed58e7c61..059ac54c5b3 100644 --- a/locale/sv_SE/gitlab.po +++ b/locale/sv_SE/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: sv-SE\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:18\n" +"PO-Revision-Date: 2019-06-14 19:56\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/sw_KE/gitlab.po b/locale/sw_KE/gitlab.po index 8c4ae06837d..8d2bfb693fc 100644 --- a/locale/sw_KE/gitlab.po +++ b/locale/sw_KE/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: sw\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:17\n" +"PO-Revision-Date: 2019-06-14 19:56\n" msgid " Please sign in." msgstr "" @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8702,6 +8717,9 @@ msgstr "" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/tr_TR/gitlab.po b/locale/tr_TR/gitlab.po index b976a7d9ed0..18def601116 100644 --- a/locale/tr_TR/gitlab.po +++ b/locale/tr_TR/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: tr\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:18\n" +"PO-Revision-Date: 2019-06-14 19:57\n" msgid " Please sign in." msgstr " Lütfen oturum açın." @@ -6397,6 +6397,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6876,6 +6879,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7472,6 +7478,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "Tem" @@ -7878,6 +7887,9 @@ msgstr "" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8226,6 +8238,9 @@ msgstr "%{linkStart}%{commitId}%{linkEnd} işlemi ile ilgili bir tartışma baş msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "%{paragraphStart}açıklama %{descriptionChangedTimes}sürelerini%{timeDifferenceMinutes} değiştirdi%{paragraphEnd}" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "Tam değişiklik yüklenirken hata oluştu. Lütfen tekrar deneyin." @@ -8702,6 +8717,9 @@ msgstr "Yeni etiket" msgid "New users set to external" msgstr "Yeni kullanıcılar harici olarak ayarlandı" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "Yeni..." @@ -9232,6 +9250,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "Son »" @@ -12722,6 +12752,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po index aac293d531e..21c49e5c6f9 100644 --- a/locale/uk/gitlab.po +++ b/locale/uk/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: uk\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:20\n" +"PO-Revision-Date: 2019-06-14 21:26\n" msgid " Please sign in." msgstr " Будь ласка, увійдіть." @@ -132,10 +132,10 @@ msgstr[3] "%d запитів на злиття" msgid "%d merge request that you don't have access to." msgid_plural "%d merge requests that you don't have access to." -msgstr[0] "" -msgstr[1] "" -msgstr[2] "" -msgstr[3] "" +msgstr[0] "%d запит на злиття, до якого ви не маєте доступу." +msgstr[1] "%d запити на злиття, до яких ви не маєте доступу." +msgstr[2] "%d запитів на злиття, до яких ви не маєте доступу." +msgstr[3] "%d запитів на злиття, до яких ви не маєте доступу." msgid "%d metric" msgid_plural "%d metrics" @@ -379,7 +379,7 @@ msgstr[2] "" msgstr[3] "" msgid "(%{mrCount} merged)" -msgstr "" +msgstr "(%{mrCount} злито)" msgid "(No changes)" msgstr "(Немає змін)" @@ -1617,7 +1617,7 @@ msgid "Are you sure? Removing this GPG key does not affect already signed commit msgstr "" msgid "Are you sure? This will invalidate your registered applications and U2F devices." -msgstr "" +msgstr "Ви впевнені? Це призведе до анулювання зареєстрованих застосунків та U2F пристроїв." msgid "Artifact ID" msgstr "ID артефакту" @@ -1626,7 +1626,7 @@ msgid "Artifacts" msgstr "Артефакти" msgid "As U2F devices are only supported by a few browsers, we require that you set up a two-factor authentication app before a U2F device. That way you'll always be able to log in - even when you're using an unsupported browser." -msgstr "" +msgstr "Оскільки пристрої U2F підтримуються лише кількома браузерами, ми вимагаємо, щоб ви налаштували застосунок для двофакторної автентифікації перед пристроєм U2F. Таким чином ви завжди зможете увійти, навіть при використанні непідтримуваного браузера." msgid "AsanaService|%{user} pushed to branch %{branch} of %{project_name} ( %{commit_url} ):" msgstr "%{user} відправив код в гілку %{branch} проекту %{project_name} (%{commit_url}):" @@ -3336,7 +3336,7 @@ msgid "Code owners" msgstr "Власники коду" msgid "CodeOwner|Pattern" -msgstr "" +msgstr "Шаблон" msgid "Cohorts" msgstr "Когорти" @@ -4850,7 +4850,7 @@ msgid "Enter in your Bitbucket Server URL and personal access token below" msgstr "Введіть URL-адресу вашого Bitbucket Server і ключ доступу" msgid "Enter in your Phabricator Server URL and personal access token below" -msgstr "" +msgstr "Введіть URL-адресу вашого сервера Phabricator і персональний ключ доступу" msgid "Enter the issue description" msgstr "Введіть опис задачі" @@ -6060,7 +6060,7 @@ msgid "GeoNodes|Out of sync" msgstr "Розсинхронізовані" msgid "GeoNodes|Pausing replication stops the sync process." -msgstr "" +msgstr "Призупинення реплікації зупиняє процес синхронізації." msgid "GeoNodes|Removing a primary node stops the sync process for all nodes. Syncing cannot be resumed without losing some data on all secondaries. In this case we would recommend setting up all nodes from scratch. Are you sure?" msgstr "Видалення основного вузла зупиняє процес синхронізації для всіх вузлів. Синхронізацію неможливо буде відновити без втрати деяких даних на всіх вторинних вузлах. У цьому випадку рекомендовано налаштувати всі вузли з нуля. Ви впевнені?" @@ -6186,7 +6186,7 @@ msgid "Geo|Could not remove tracking entry for an existing project." msgstr "Не вдалося видалити запис відстеження для існуючого проекту." msgid "Geo|Could not remove tracking entry for an existing upload." -msgstr "" +msgstr "Не вдалося видалити запис відстеження для існуючого завантаження." msgid "Geo|Failed" msgstr "Невдало" @@ -6237,7 +6237,7 @@ msgid "Geo|Pending verification" msgstr "Очікування перевірки" msgid "Geo|Please refer to Geo Troubleshooting." -msgstr "" +msgstr "Будь ласка, перегляньте документацію з усунення неполадок Geo." msgid "Geo|Project" msgstr "Проект" @@ -6291,19 +6291,19 @@ msgid "Geo|Status" msgstr "Статус" msgid "Geo|Sync" -msgstr "" +msgstr "Синхронізувати" msgid "Geo|Synced" msgstr "Синхронізовано" msgid "Geo|Synced at" -msgstr "" +msgstr "Синхронізовано" msgid "Geo|Synchronization failed - %{error}" msgstr "Синхронізація невдала: %{error}" msgid "Geo|The URL defined on the primary node that secondary nodes should use to contact it. Returns `url` if not set" -msgstr "" +msgstr "URL-адреса, визначена на основному вузлі, яку використовують вторинні вузли, щоб зв'язатися з ним. Повертає \"url\", якщо не встановлено" msgid "Geo|The database is currently %{db_lag} behind the primary node." msgstr "" @@ -6497,6 +6497,9 @@ msgstr "Google Takeout" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "Автентифікація Google не %{link_to_documentation}. Попросіть свого адміністратора GitLab, якщо ви хочете скористатися цим сервісом." +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "Зрозуміло!" @@ -6978,6 +6981,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "Якщо увімкнено, тоді власники груп зможуть керувати зв'язками груп LDAP та перевизначенням членів LDAP" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "Якщо вимкнено, локальна гілка зі змінами не буде автоматично підтягувати коміти з віддаленої гілки, щоб уникнути втрати локальних даних. Якщо гілка за замовчуванням (%{default_branch}) містить зміни і не може бути оновлена, тоді дзеркалювання буде неможливим. Інші гілки зі змінами автоматично ігноруються." @@ -7576,6 +7582,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "лип." @@ -7986,6 +7995,9 @@ msgstr "Блокувати" msgid "Lock %{issuableDisplayName}" msgstr "Заблокувати %{issuableDisplayName}" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "Блокування не знайдено" @@ -8334,6 +8346,9 @@ msgstr "розпочав (-ла) коміту %{linkStart}%{commitId}%{linkEnd}" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "%{paragraphStart} опис змінено %{descriptionChangedTimes} раз(а,ів) %{timeDifferenceMinutes}%{paragraphEnd}" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "Помилка при заванаженні повного порівняння. Будь ласка, спробуйте знову." @@ -8812,6 +8827,9 @@ msgstr "Новий тег" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "Новий..." @@ -9344,6 +9362,18 @@ msgstr "Домени Pages" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "Остання »" @@ -12844,6 +12874,9 @@ msgstr "Успішно розблоковано" msgid "Successfully unlocked" msgstr "Успішно розблоковано" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "Пропонована зміна" diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po index cd245f36bab..ae644040a5a 100644 --- a/locale/zh_CN/gitlab.po +++ b/locale/zh_CN/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: zh-CN\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:19\n" +"PO-Revision-Date: 2019-06-14 20:00\n" msgid " Please sign in." msgstr " 请登录。" @@ -6347,6 +6347,9 @@ msgstr "Google Takeout" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "Google身份验证未%{link_to_documentation}。如需使用此服务,请咨询GitLab管理员。" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "了解!" @@ -6825,6 +6828,9 @@ msgstr "如果任何作业超过这个超时阈值,它将被标记为失败。 msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "如果选中,则群组所有者可以管理 LDAP 群组链接和 LDAP 成员覆盖" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "如果禁用,则不会使用远程副本的提交自动更新分叉的本地分支,以防止本地数据丢失。如果默认分支 (%{default_branch}) 已分叉且无法更新,则镜像将失败。其他分叉的分支默默被忽略。" @@ -7420,6 +7426,9 @@ msgstr "合并入" msgid "Job|with" msgstr "由" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "7月" @@ -7824,6 +7833,9 @@ msgstr "锁定" msgid "Lock %{issuableDisplayName}" msgstr "锁定 %{issuableDisplayName}" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "未找到锁" @@ -8172,6 +8184,9 @@ msgstr "开始讨论提交%{linkStart}%{commitId}%{linkEnd}" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "%{paragraphStart}%{timeDifferenceMinutes}%{descriptionChangedTimes}次更改了描述%{paragraphEnd}" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "加载完整差异时出错。请再试一次。" @@ -8647,6 +8662,9 @@ msgstr "新建标签" msgid "New users set to external" msgstr "新用户设置为外部" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "新建..." @@ -9176,6 +9194,18 @@ msgstr "Pages域名" msgid "Pages getting started guide" msgstr "Pages 入门指南" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "尾页 »" @@ -12661,6 +12691,9 @@ msgstr "成功解除禁用" msgid "Successfully unlocked" msgstr "成功解除禁用" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "变更建议" diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index 09c414249b3..af61b64ccb9 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: zh-HK\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:23\n" +"PO-Revision-Date: 2019-06-14 20:00\n" msgid " Please sign in." msgstr "請登入您的帳戶" @@ -6347,6 +6347,9 @@ msgstr "" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "" @@ -6825,6 +6828,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "" @@ -7420,6 +7426,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "七月" @@ -7824,6 +7833,9 @@ msgstr "鎖定" msgid "Lock %{issuableDisplayName}" msgstr "" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "" @@ -8172,6 +8184,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8647,6 +8662,9 @@ msgstr "新增標籤" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "" @@ -9176,6 +9194,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "" @@ -12661,6 +12691,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po index 784d722e453..435c5979172 100644 --- a/locale/zh_TW/gitlab.po +++ b/locale/zh_TW/gitlab.po @@ -13,7 +13,7 @@ msgstr "" "X-Crowdin-Project: gitlab-ee\n" "X-Crowdin-Language: zh-TW\n" "X-Crowdin-File: /master/locale/gitlab.pot\n" -"PO-Revision-Date: 2019-06-14 10:23\n" +"PO-Revision-Date: 2019-06-14 20:00\n" msgid " Please sign in." msgstr "" @@ -6347,6 +6347,9 @@ msgstr "Google Takeout" msgid "Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service." msgstr "Google 身份驗證不是 %{link_to_documentation}。如果您想使用此服務,請諮詢 GitLab 管理員。" +msgid "Got it" +msgstr "" + msgid "Got it!" msgstr "了解!" @@ -6825,6 +6828,9 @@ msgstr "" msgid "If checked, group owners can manage LDAP group links and LDAP member overrides" msgstr "" +msgid "If checked, new group memberships and permissions can only be added via LDAP synchronization" +msgstr "" + msgid "If disabled, a diverged local branch will not be automatically updated with commits from its remote counterpart, to prevent local data loss. If the default branch (%{default_branch}) has diverged and cannot be updated, mirroring will fail. Other diverged branches are silently ignored." msgstr "如果停用,則不會使用遠端副本的提交自動更新分支的本機分支,以防止本機資料被刪除。如果預設分支 (%{default_branch}) 已被再分支且無法更新時,則鏡像將會失敗。其他再分支的分支自動被忽略。" @@ -7420,6 +7426,9 @@ msgstr "" msgid "Job|with" msgstr "" +msgid "Join Zoom meeting" +msgstr "" + msgid "Jul" msgstr "七月" @@ -7824,6 +7833,9 @@ msgstr "鎖定" msgid "Lock %{issuableDisplayName}" msgstr "鎖定 %{issuableDisplayName}" +msgid "Lock memberships to LDAP synchronization" +msgstr "" + msgid "Lock not found" msgstr "找不到鎖定的檔案" @@ -8172,6 +8184,9 @@ msgstr "" msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}" msgstr "%{paragraphStart} %{timeDifferenceMinutes} 修改了 %{descriptionChangedTimes} 次說明 %{paragraphEnd}" +msgid "MergeRequest|Error dismissing suggestion popover. Please try again." +msgstr "" + msgid "MergeRequest|Error loading full diff. Please try again." msgstr "" @@ -8647,6 +8662,9 @@ msgstr "新增標籤" msgid "New users set to external" msgstr "" +msgid "New! Suggest changes directly" +msgstr "" + msgid "New..." msgstr "新增…" @@ -9176,6 +9194,18 @@ msgstr "" msgid "Pages getting started guide" msgstr "" +msgid "Pagination|Go to first page" +msgstr "" + +msgid "Pagination|Go to last page" +msgstr "" + +msgid "Pagination|Go to next page" +msgstr "" + +msgid "Pagination|Go to previous page" +msgstr "" + msgid "Pagination|Last »" msgstr "最末頁 »" @@ -12661,6 +12691,9 @@ msgstr "" msgid "Successfully unlocked" msgstr "" +msgid "Suggest code changes which are immediately applied. Try it out!" +msgstr "" + msgid "Suggested change" msgstr "" diff --git a/qa/qa/page/dashboard/projects.rb b/qa/qa/page/dashboard/projects.rb index 7ab8ee39f72..0c23d7cffbb 100644 --- a/qa/qa/page/dashboard/projects.rb +++ b/qa/qa/page/dashboard/projects.rb @@ -5,7 +5,7 @@ module QA module Dashboard class Projects < Page::Base view 'app/views/shared/projects/_search_form.html.haml' do - element :form_filter_by_name, /form_tag.+id: 'project-filter-form'/ # rubocop:disable QA/ElementWithPattern + element :project_filter_form, required: true end def go_to_project(name) @@ -14,10 +14,14 @@ module QA find_link(text: name).click end + def self.path + '/' + end + private def filter_by_name(name) - page.within('form#project-filter-form') do + within_element(:project_filter_form) do fill_in :name, with: name end end diff --git a/qa/qa/resource/project.rb b/qa/qa/resource/project.rb index d706439a891..e8ea947581a 100644 --- a/qa/qa/resource/project.rb +++ b/qa/qa/resource/project.rb @@ -18,7 +18,11 @@ module QA end attribute :path_with_namespace do - "#{group.sandbox.path}/#{group.path}/#{name}" if group + "#{sandbox_path}#{group.path}/#{name}" if group + end + + def sandbox_path + group.respond_to?('sandbox') ? "#{group.sandbox.path}/" : '' end attribute :repository_ssh_location do diff --git a/qa/qa/resource/sandbox.rb b/qa/qa/resource/sandbox.rb index 942eea5cc40..e2b1c4c0831 100644 --- a/qa/qa/resource/sandbox.rb +++ b/qa/qa/resource/sandbox.rb @@ -44,6 +44,10 @@ module QA "/groups/#{path}" end + def api_members_path + "#{api_get_path}/members" + end + def api_post_path '/groups' end diff --git a/qa/qa/runtime/address.rb b/qa/qa/runtime/address.rb index 98d042fb43a..c622051bb6d 100644 --- a/qa/qa/runtime/address.rb +++ b/qa/qa/runtime/address.rb @@ -5,7 +5,7 @@ module QA class Address attr_reader :address - def initialize(instance, page = nil) + def initialize(instance, page) @instance = instance @address = host + (page.is_a?(String) ? page : page&.path) end diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index 3bf4b3bbbfb..ed0779b93cc 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -1,6 +1,7 @@ # frozen_string_literal: true require 'rspec/core' +require 'rspec/expectations' require 'capybara/rspec' require 'capybara-screenshot/rspec' require 'selenium-webdriver' @@ -27,13 +28,12 @@ module QA # In case of an address that is a symbol we will try to guess address # based on `Runtime::Scenario#something_address`. # - def visit(address, page = nil, &block) - Browser::Session.new(address, page).perform(&block) + def visit(address, page_class, &block) + Browser::Session.new(address, page_class).perform(&block) end - def self.visit(address, page = nil, &block) - new.visit(address, page, &block) - page.validate_elements_present! + def self.visit(address, page_class, &block) + new.visit(address, page_class, &block) end def self.configure! @@ -128,8 +128,11 @@ module QA class Session include Capybara::DSL - def initialize(instance, page = nil) - @session_address = Runtime::Address.new(instance, page) + attr_reader :page_class + + def initialize(instance, page_class) + @session_address = Runtime::Address.new(instance, page_class) + @page_class = page_class end def url @@ -139,6 +142,8 @@ module QA def perform(&block) visit(url) + page_class.validate_elements_present! + if QA::Runtime::Env.qa_cookies browser = Capybara.current_session.driver.browser QA::Runtime::Env.qa_cookies.each do |cookie| diff --git a/scripts/gitaly_test.rb b/scripts/gitaly_test.rb index b5d3facd18a..b5cc5118530 100644 --- a/scripts/gitaly_test.rb +++ b/scripts/gitaly_test.rb @@ -23,7 +23,10 @@ module GitalyTest 'BUNDLE_FLAGS' => "--jobs=4 --retry=3", 'BUNDLE_INSTALL_FLAGS' => nil, 'BUNDLE_GEMFILE' => gemfile, - 'RUBYOPT' => nil + 'RUBYOPT' => nil, + + # Git hooks can't run during tests as the internal API is not running. + 'GITALY_TESTING_NO_GIT_HOOKS' => "1" } if ENV['CI'] diff --git a/spec/controllers/admin/clusters_controller_spec.rb b/spec/controllers/admin/clusters_controller_spec.rb index 7b77cb186a4..7709f525119 100644 --- a/spec/controllers/admin/clusters_controller_spec.rb +++ b/spec/controllers/admin/clusters_controller_spec.rb @@ -396,6 +396,7 @@ describe Admin::ClustersController do cluster: { enabled: false, name: 'my-new-cluster-name', + managed: false, base_domain: domain } } @@ -409,6 +410,7 @@ describe Admin::ClustersController do expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.') expect(cluster.enabled).to be_falsey expect(cluster.name).to eq('my-new-cluster-name') + expect(cluster).not_to be_managed expect(cluster.domain).to eq('test-domain.com') end @@ -433,6 +435,7 @@ describe Admin::ClustersController do cluster: { enabled: false, name: 'my-new-cluster-name', + managed: false, domain: domain } } @@ -445,6 +448,7 @@ describe Admin::ClustersController do expect(response).to have_http_status(:no_content) expect(cluster.enabled).to be_falsey expect(cluster.name).to eq('my-new-cluster-name') + expect(cluster).not_to be_managed end end diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb index 7349cb7094c..2f64c7f3460 100644 --- a/spec/controllers/groups/clusters_controller_spec.rb +++ b/spec/controllers/groups/clusters_controller_spec.rb @@ -463,6 +463,7 @@ describe Groups::ClustersController do cluster: { enabled: false, name: 'my-new-cluster-name', + managed: false, base_domain: domain } } @@ -476,6 +477,7 @@ describe Groups::ClustersController do expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.') expect(cluster.enabled).to be_falsey expect(cluster.name).to eq('my-new-cluster-name') + expect(cluster).not_to be_managed expect(cluster.domain).to eq('test-domain.com') end @@ -500,6 +502,7 @@ describe Groups::ClustersController do cluster: { enabled: false, name: 'my-new-cluster-name', + managed: false, domain: domain } } @@ -512,6 +515,7 @@ describe Groups::ClustersController do expect(response).to have_http_status(:no_content) expect(cluster.enabled).to be_falsey expect(cluster.name).to eq('my-new-cluster-name') + expect(cluster).not_to be_managed end end diff --git a/spec/controllers/projects/branches_controller_spec.rb b/spec/controllers/projects/branches_controller_spec.rb index c778b7888dc..cf201c9f735 100644 --- a/spec/controllers/projects/branches_controller_spec.rb +++ b/spec/controllers/projects/branches_controller_spec.rb @@ -123,7 +123,11 @@ describe Projects::BranchesController do expect(response).to redirect_to project_tree_path(project, branch) end - shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do + context 'when user configured kubernetes from CI/CD > Clusters' do + before do + create(:cluster, :provided_by_gcp, projects: [project]) + end + it 'redirects to autodeploy setup page' do result = { status: :success, branch: double(name: branch) } @@ -143,22 +147,6 @@ describe Projects::BranchesController do end end - context 'when user configured kubernetes from Integration > Kubernetes' do - before do - project.services << build(:kubernetes_service) - end - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end - - context 'when user configured kubernetes from CI/CD > Clusters' do - before do - create(:cluster, :provided_by_gcp, projects: [project]) - end - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end - it 'redirects to autodeploy setup page' do result = { status: :success, branch: double(name: branch) } diff --git a/spec/controllers/projects/clusters_controller_spec.rb b/spec/controllers/projects/clusters_controller_spec.rb index 8d37bd82d21..fa49438287f 100644 --- a/spec/controllers/projects/clusters_controller_spec.rb +++ b/spec/controllers/projects/clusters_controller_spec.rb @@ -449,6 +449,7 @@ describe Projects::ClustersController do cluster: { enabled: false, name: 'my-new-cluster-name', + managed: false, platform_kubernetes_attributes: { namespace: 'my-namespace' } @@ -464,6 +465,7 @@ describe Projects::ClustersController do expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.') expect(cluster.enabled).to be_falsey expect(cluster.name).to eq('my-new-cluster-name') + expect(cluster).not_to be_managed expect(cluster.platform_kubernetes.namespace).to eq('my-namespace') end @@ -475,6 +477,7 @@ describe Projects::ClustersController do cluster: { enabled: false, name: 'my-new-cluster-name', + managed: false, platform_kubernetes_attributes: { namespace: 'my-namespace' } @@ -489,6 +492,7 @@ describe Projects::ClustersController do expect(response).to have_http_status(:no_content) expect(cluster.enabled).to be_falsey expect(cluster.name).to eq('my-new-cluster-name') + expect(cluster).not_to be_managed expect(cluster.platform_kubernetes.namespace).to eq('my-namespace') end end diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 490e9841492..0dabe27977a 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -540,7 +540,7 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do expect(response).to have_gitlab_http_status(:ok) expect(json_response['id']).to eq job.id expect(json_response['status']).to eq job.status - expect(json_response['html']).to eq('BUILD TRACE') + expect(json_response['html']).to eq('<span class="">BUILD TRACE</span>') end end diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index 3608d175d50..5c7f8d95f82 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -141,20 +141,6 @@ describe Projects::ServicesController do end end - context 'with a deprecated service' do - let(:service) { create(:kubernetes_service, project: project) } - - before do - put :update, - params: { namespace_id: project.namespace, project_id: project, id: service.to_param, service: { namespace: 'updated_namespace' } } - end - - it 'does not update the service' do - service.reload - expect(service.namespace).not_to eq('updated_namespace') - end - end - context 'when activating JIRA service from a template' do let(:template_service) { create(:jira_service, project: project, template: true) } @@ -168,20 +154,10 @@ describe Projects::ServicesController do describe "GET #edit" do before do - get :edit, params: { namespace_id: project.namespace, project_id: project, id: service_id } + get :edit, params: { namespace_id: project.namespace, project_id: project, id: 'jira' } end context 'with approved services' do - let(:service_id) { 'jira' } - - it 'renders edit page' do - expect(response).to be_success - end - end - - context 'with a deprecated service' do - let(:service_id) { 'kubernetes' } - it 'renders edit page' do expect(response).to be_success end diff --git a/spec/db/schema_spec.rb b/spec/db/schema_spec.rb index 40c3a6d90d0..6cfec5f4017 100644 --- a/spec/db/schema_spec.rb +++ b/spec/db/schema_spec.rb @@ -9,9 +9,13 @@ describe 'Database schema' do # Use if you are certain that this column should not have a foreign key IGNORED_FK_COLUMNS = { abuse_reports: %w[reporter_id user_id], - application_settings: %w[performance_bar_allowed_group_id], + application_settings: %w[performance_bar_allowed_group_id slack_app_id snowplow_site_id], + approvers: %w[target_id user_id], + approvals: %w[user_id], + approver_groups: %w[target_id], audit_events: %w[author_id entity_id], award_emoji: %w[awardable_id user_id], + boards: %w[milestone_id], chat_names: %w[chat_id service_id team_id user_id], chat_teams: %w[team_id], ci_builds: %w[erased_by_id runner_id trigger_request_id user_id], @@ -21,14 +25,25 @@ describe 'Database schema' do cluster_providers_gcp: %w[gcp_project_id operation_id], deploy_keys_projects: %w[deploy_key_id], deployments: %w[deployable_id environment_id user_id], + draft_notes: %w[discussion_id], emails: %w[user_id], events: %w[target_id], + epics: %w[updated_by_id last_edited_by_id start_date_sourcing_milestone_id due_date_sourcing_milestone_id], forked_project_links: %w[forked_from_project_id], + geo_event_log: %w[hashed_storage_attachments_event_id], + geo_job_artifact_deleted_events: %w[job_artifact_id], + geo_lfs_object_deleted_events: %w[lfs_object_id], + geo_node_statuses: %w[last_event_id cursor_last_event_id], + geo_nodes: %w[oauth_application_id], + geo_repository_deleted_events: %w[project_id], + geo_upload_deleted_events: %w[upload_id model_id], identities: %w[user_id], issues: %w[last_edited_by_id state_id], + jira_tracker_data: %w[jira_issue_transition_id], keys: %w[user_id], label_links: %w[target_id], lfs_objects_projects: %w[lfs_object_id project_id], + ldap_group_links: %w[group_id], members: %w[source_id created_by_id], merge_requests: %w[last_edited_by_id state_id], namespaces: %w[owner_id parent_id], @@ -39,7 +54,7 @@ describe 'Database schema' do oauth_applications: %w[owner_id], project_group_links: %w[group_id], project_statistics: %w[namespace_id], - projects: %w[creator_id namespace_id ci_id], + projects: %w[creator_id namespace_id ci_id mirror_user_id], redirect_routes: %w[source_id], repository_languages: %w[programming_language_id], routes: %w[source_id], @@ -47,14 +62,17 @@ describe 'Database schema' do snippets: %w[author_id], spam_logs: %w[user_id], subscriptions: %w[user_id subscribable_id], + slack_integrations: %w[team_id user_id], taggings: %w[tag_id taggable_id tagger_id], timelogs: %w[user_id], todos: %w[target_id commit_id], uploads: %w[model_id], user_agent_details: %w[subject_id], - users: %w[color_scheme_id created_by_id theme_id], + users: %w[color_scheme_id created_by_id theme_id email_opted_in_source_id], users_star_projects: %w[user_id], - web_hooks: %w[service_id], + vulnerability_identifiers: %w[external_id], + vulnerability_scanners: %w[external_id], + web_hooks: %w[service_id group_id], suggestions: %w[commit_id] }.with_indifferent_access.freeze diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index a473136b57b..5f7c75a3a92 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -228,6 +228,26 @@ FactoryBot.define do end end + trait :trace_with_duplicate_sections do + after(:create) do |build, evaluator| + trace = File.binread( + File.expand_path( + Rails.root.join('spec/fixtures/trace/trace_with_duplicate_sections'))) + + build.trace.set(trace) + end + end + + trait :trace_with_sections do + after(:create) do |build, evaluator| + trace = File.binread( + File.expand_path( + Rails.root.join('spec/fixtures/trace/trace_with_sections'))) + + build.trace.set(trace) + end + end + trait :unicode_trace_live do after(:create) do |build, evaluator| trace = File.binread( diff --git a/spec/factories/clusters/kubernetes_namespaces.rb b/spec/factories/clusters/kubernetes_namespaces.rb index 3b50a57433f..042be7b4c4a 100644 --- a/spec/factories/clusters/kubernetes_namespaces.rb +++ b/spec/factories/clusters/kubernetes_namespaces.rb @@ -16,5 +16,9 @@ FactoryBot.define do trait :with_token do service_account_token { FFaker::Lorem.characters(10) } end + + trait :without_token do + service_account_token nil + end end end diff --git a/spec/factories/lfs_objects_projects.rb b/spec/factories/lfs_objects_projects.rb index c225387a5de..4804d0bb884 100644 --- a/spec/factories/lfs_objects_projects.rb +++ b/spec/factories/lfs_objects_projects.rb @@ -2,5 +2,6 @@ FactoryBot.define do factory :lfs_objects_project do lfs_object project + repository_type :project end end diff --git a/spec/factories/services.rb b/spec/factories/services.rb index 0d8c26a2ee9..763909f30bd 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -24,6 +24,8 @@ FactoryBot.define do api_url: 'https://kubernetes.example.com', token: 'a' * 40 }) + + skip_deprecation_validation true end factory :mock_deployment_service do diff --git a/spec/factories/services_data.rb b/spec/factories/services_data.rb new file mode 100644 index 00000000000..387e130a743 --- /dev/null +++ b/spec/factories/services_data.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +FactoryBot.define do + factory :jira_tracker_data do + service + url 'http://jira.example.com' + api_url 'http://api-jira.example.com' + username 'jira_username' + password 'jira_password' + end + + factory :issue_tracker_data do + service + project_url 'http://issuetracker.example.com' + issues_url 'http://issues.example.com' + new_issue_url 'http://new-issue.example.com' + end +end diff --git a/spec/factories/users.rb b/spec/factories/users.rb index 1d2b724a5e5..4f3392cdcbf 100644 --- a/spec/factories/users.rb +++ b/spec/factories/users.rb @@ -66,6 +66,16 @@ FactoryBot.define do end end + transient do + developer_projects [] + end + + after(:create) do |user, evaluator| + evaluator.developer_projects.each do |project| + project.add_developer(user) + end + end + factory :omniauth_user do transient do extern_uid '123456' diff --git a/spec/features/commits_spec.rb b/spec/features/commits_spec.rb index e6f44aa7d20..a2dd34e7f7c 100644 --- a/spec/features/commits_spec.rb +++ b/spec/features/commits_spec.rb @@ -10,8 +10,7 @@ describe 'Commits' do stub_ci_pipeline_to_return_yaml_file end - let(:creator) { create(:user) } - + let(:creator) { create(:user, developer_projects: [project]) } let!(:pipeline) do create(:ci_pipeline, project: project, @@ -77,10 +76,11 @@ describe 'Commits' do describe 'Commit builds', :js do before do + project.add_developer(user) visit pipeline_path(pipeline) end - it 'shows pipeline`s data' do + it 'shows pipeline data' do expect(page).to have_content pipeline.sha[0..7] expect(page).to have_content pipeline.git_commit_message.gsub!(/\s+/, ' ') expect(page).to have_content pipeline.user.name diff --git a/spec/features/cycle_analytics_spec.rb b/spec/features/cycle_analytics_spec.rb index 48edc764a8e..4108a0f370d 100644 --- a/spec/features/cycle_analytics_spec.rb +++ b/spec/features/cycle_analytics_spec.rb @@ -58,7 +58,7 @@ describe 'Cycle Analytics', :js do expect_issue_to_be_present click_stage('Plan') - expect(find('.stage-events')).to have_content(mr.commits.last.title) + expect_issue_to_be_present click_stage('Code') expect_merge_request_to_be_present diff --git a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb index 586b3ba170d..85c4d778fd0 100644 --- a/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb +++ b/spec/features/merge_request/user_merges_when_pipeline_succeeds_spec.rb @@ -52,7 +52,7 @@ describe 'Merge request > User merges when pipeline succeeds', :js do # so we have to wait for asynchronous call to reload it # and have_content expectation handles that. # - expect(page).to have_content "Pipeline ##{pipeline.id} (##{pipeline.iid}) running" + expect(page).to have_content "Pipeline ##{pipeline.id} running" end it_behaves_like 'Merge when pipeline succeeds activator' diff --git a/spec/features/merge_request/user_sees_merge_widget_spec.rb b/spec/features/merge_request/user_sees_merge_widget_spec.rb index 393077a916f..733e8aa3eba 100644 --- a/spec/features/merge_request/user_sees_merge_widget_spec.rb +++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb @@ -160,7 +160,7 @@ describe 'Merge request > User sees merge widget', :js do it 'shows head pipeline information' do within '.ci-widget-content' do - expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \ + expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ "for #{pipeline.short_sha} " \ "on #{pipeline.ref}") end @@ -189,7 +189,7 @@ describe 'Merge request > User sees merge widget', :js do it 'shows head pipeline information' do within '.ci-widget-content' do - expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \ + expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ "for #{pipeline.short_sha} " \ "on #{merge_request.to_reference} " \ "with #{merge_request.source_branch}") @@ -201,7 +201,7 @@ describe 'Merge request > User sees merge widget', :js do it 'shows head pipeline information' do within '.ci-widget-content' do - expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \ + expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ "for #{pipeline.short_sha} " \ "on #{merge_request.to_reference} " \ "with #{merge_request.source_branch}") @@ -234,7 +234,7 @@ describe 'Merge request > User sees merge widget', :js do it 'shows head pipeline information' do within '.ci-widget-content' do - expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \ + expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ "for #{pipeline.short_sha} " \ "on #{merge_request.to_reference} " \ "with #{merge_request.source_branch} " \ @@ -248,7 +248,7 @@ describe 'Merge request > User sees merge widget', :js do it 'shows head pipeline information' do within '.ci-widget-content' do - expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) pending " \ + expect(page).to have_content("Pipeline ##{pipeline.id} pending " \ "for #{pipeline.short_sha} " \ "on #{merge_request.to_reference} " \ "with #{merge_request.source_branch} " \ diff --git a/spec/features/projects/clusters/interchangeability_spec.rb b/spec/features/projects/clusters/interchangeability_spec.rb deleted file mode 100644 index 0033e12b6b1..00000000000 --- a/spec/features/projects/clusters/interchangeability_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'spec_helper' - -describe 'Interchangeability between KubernetesService and Platform::Kubernetes' do - EXCEPT_METHODS = %i[test title description help fields initialize_properties namespace namespace= api_url api_url= deprecated? deprecation_message].freeze - EXCEPT_METHODS_GREP_V = %w[_touched? _changed? _was].freeze - - it 'Clusters::Platform::Kubernetes covers core interfaces in KubernetesService' do - expected_interfaces = KubernetesService.instance_methods(false) - expected_interfaces = expected_interfaces - EXCEPT_METHODS - EXCEPT_METHODS_GREP_V.each do |g| - expected_interfaces = expected_interfaces.grep_v(/#{Regexp.escape(g)}\z/) - end - - expect(expected_interfaces - Clusters::Platforms::Kubernetes.instance_methods).to be_empty - end -end diff --git a/spec/features/projects/commits/user_browses_commits_spec.rb b/spec/features/projects/commits/user_browses_commits_spec.rb index a84fee34669..fc74a370e72 100644 --- a/spec/features/projects/commits/user_browses_commits_spec.rb +++ b/spec/features/projects/commits/user_browses_commits_spec.rb @@ -61,7 +61,7 @@ describe 'User browses commits' do it 'renders commit ci info' do visit project_commit_path(project, sample_commit.id) - expect(page).to have_content "Pipeline ##{pipeline.id} (##{pipeline.iid}) pending" + expect(page).to have_content "Pipeline ##{pipeline.id} pending" end end diff --git a/spec/features/projects/environments/environment_spec.rb b/spec/features/projects/environments/environment_spec.rb index da4ef6428d4..fbaf12be64e 100644 --- a/spec/features/projects/environments/environment_spec.rb +++ b/spec/features/projects/environments/environment_spec.rb @@ -155,7 +155,10 @@ describe 'Environment' do end context 'with terminal' do - shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do + context 'when user configured kubernetes from CI/CD > Clusters' do + let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:project) { cluster.project } + context 'for project maintainer' do let(:role) { :maintainer } @@ -191,19 +194,6 @@ describe 'Environment' do end end end - - context 'when user configured kubernetes from Integration > Kubernetes' do - let(:project) { create(:kubernetes_project, :test_repo) } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end - - context 'when user configured kubernetes from CI/CD > Clusters' do - let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end end context 'when environment is available' do diff --git a/spec/features/projects/environments/environments_spec.rb b/spec/features/projects/environments/environments_spec.rb index 7b7e45312d9..1b5d9083932 100644 --- a/spec/features/projects/environments/environments_spec.rb +++ b/spec/features/projects/environments/environments_spec.rb @@ -248,7 +248,10 @@ describe 'Environments page', :js do end context 'when kubernetes terminal is available' do - shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do + context 'when user configured kubernetes from CI/CD > Clusters' do + let(:cluster) { create(:cluster, :provided_by_gcp, projects: [create(:project, :repository)]) } + let(:project) { cluster.project } + context 'for project maintainer' do let(:role) { :maintainer } @@ -265,19 +268,6 @@ describe 'Environments page', :js do end end end - - context 'when user configured kubernetes from Integration > Kubernetes' do - let(:project) { create(:kubernetes_project, :test_repo) } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end - - context 'when user configured kubernetes from CI/CD > Clusters' do - let(:cluster) { create(:cluster, :provided_by_gcp, projects: [create(:project, :repository)]) } - let(:project) { cluster.project } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end end end diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb index 9d74a96ab3d..c71a778fc84 100644 --- a/spec/features/projects/import_export/export_file_spec.rb +++ b/spec/features/projects/import_export/export_file_spec.rb @@ -10,7 +10,7 @@ describe 'Import/Export - project export integration test', :js do let(:user) { create(:admin) } let(:export_path) { "#{Dir.tmpdir}/import_file_spec" } - let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys } + let(:config_hash) { Gitlab::ImportExport::Config.new.to_h.deep_stringify_keys } let(:sensitive_words) { %w[pass secret token key encrypted html] } let(:safe_list) do diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb index 54b462da87a..fbe765d4c44 100644 --- a/spec/features/projects/jobs/user_browses_job_spec.rb +++ b/spec/features/projects/jobs/user_browses_job_spec.rb @@ -34,6 +34,52 @@ describe 'User browses a job', :js do expect(page).to have_content('Job has been erased') end + shared_examples 'has collapsible sections' do + it 'collapses the section clicked' do + wait_for_requests + text_to_hide = "Cloning into '/nolith/ci-tests'" + text_to_show = 'Waiting for pod' + + expect(page).to have_content(text_to_hide) + expect(page).to have_content(text_to_show) + + first('.js-section-start[data-section="get-sources"]').click + + expect(page).not_to have_content(text_to_hide) + expect(page).to have_content(text_to_show) + end + end + + context 'when job trace contains sections' do + let!(:build) { create(:ci_build, :success, :trace_with_sections, :coverage, pipeline: pipeline) } + + it_behaves_like 'has collapsible sections' + end + + context 'when job trace contains duplicate sections' do + let!(:build) { create(:ci_build, :success, :trace_with_duplicate_sections, :coverage, pipeline: pipeline) } + + it_behaves_like 'has collapsible sections' + end + + context 'when job trace contains sections' do + let!(:build) { create(:ci_build, :success, :trace_with_duplicate_sections, :coverage, pipeline: pipeline) } + + it 'collapses a section' do + wait_for_requests + text_to_hide = "Cloning into '/nolith/ci-tests'" + text_to_show = 'Waiting for pod' + + expect(page).to have_content(text_to_hide) + expect(page).to have_content(text_to_show) + + first('.js-section-start[data-section="get-sources"]').click + + expect(page).not_to have_content(text_to_hide) + expect(page).to have_content(text_to_show) + end + end + context 'with a failed job' do let!(:build) { create(:ci_build, :failed, :trace_artifact, pipeline: pipeline) } diff --git a/spec/features/projects/jobs/user_browses_jobs_spec.rb b/spec/features/projects/jobs/user_browses_jobs_spec.rb index bd6c73f4b85..ebc20d15d67 100644 --- a/spec/features/projects/jobs/user_browses_jobs_spec.rb +++ b/spec/features/projects/jobs/user_browses_jobs_spec.rb @@ -16,12 +16,6 @@ describe 'User browses jobs' do visit(project_jobs_path(project)) end - it 'shows pipeline id and IID' do - page.within('td.pipeline-link') do - expect(page).to have_content("##{pipeline.id} (##{pipeline.iid})") - end - end - it 'shows the coverage' do page.within('td.coverage') do expect(page).to have_content('99.9%') diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 03562bd382e..f4ed89adc0f 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -129,7 +129,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do visit project_job_path(project, job) within '.js-pipeline-info' do - expect(page).to have_content("Pipeline ##{pipeline.id} (##{pipeline.iid}) for #{pipeline.ref}") + expect(page).to have_content("Pipeline ##{pipeline.id} for #{pipeline.ref}") end end diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 77f0f237d0a..9759fd04ad2 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -5,6 +5,7 @@ require 'spec_helper' describe 'Pipeline', :js do include RoutesHelpers include ProjectForksHelper + include ::ExclusiveLeaseHelpers let(:project) { create(:project) } let(:user) { create(:user) } @@ -539,6 +540,44 @@ describe 'Pipeline', :js do expect(page).to have_selector('.pipeline-visualization') expect(page).to have_content('cross-build') end + + context 'when a scheduled pipeline is created by a blocked user' do + let(:project) { create(:project, :repository) } + + let(:schedule) do + create(:ci_pipeline_schedule, + project: project, + owner: project.owner, + description: 'blocked user schedule' + ).tap do |schedule| + schedule.update_column(:next_run_at, 1.minute.ago) + end + end + + before do + schedule.owner.block! + + begin + PipelineScheduleWorker.new.perform + rescue Ci::CreatePipelineService::CreateError + # Do nothing, assert view code after the Pipeline failed to create. + end + end + + it 'displays the PipelineSchedule in an active state' do + visit project_pipeline_schedules_path(project) + page.click_link('Active') + + expect(page).to have_selector('table.ci-table > tbody > tr > td', text: 'blocked user schedule') + end + + it 'does not create a new Pipeline' do + visit project_pipelines_path(project) + + expect(page).not_to have_selector('.ci-table') + expect(schedule.last_pipeline).to be_nil + end + end end describe 'GET /:project/pipelines/:id/builds' do diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index de780f13681..885d5f85989 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -469,7 +469,7 @@ describe 'Pipelines', :js do visit_project_pipelines end - it 'has artifats' do + it 'has artifacts' do expect(page).to have_selector('.build-artifacts') end diff --git a/spec/finders/fork_projects_finder_spec.rb b/spec/finders/fork_projects_finder_spec.rb index b3fdffc3331..98cff37205e 100644 --- a/spec/finders/fork_projects_finder_spec.rb +++ b/spec/finders/fork_projects_finder_spec.rb @@ -12,6 +12,8 @@ describe ForkProjectsFinder do let(:private_fork_member) { create(:user) } before do + stub_feature_flags(object_pools: { enabled: false, thing: source_project }) + private_fork.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) private_fork.add_developer(private_fork_member) diff --git a/spec/finders/pipelines_finder_spec.rb b/spec/finders/pipelines_finder_spec.rb index c2c304589c9..b23fd8ccdc6 100644 --- a/spec/finders/pipelines_finder_spec.rb +++ b/spec/finders/pipelines_finder_spec.rb @@ -170,8 +170,9 @@ describe PipelinesFinder do context 'when order_by and sort are specified' do context 'when order_by user_id' do - let(:params) { { order_by: 'user_id', sort: 'asc' } } - let!(:pipelines) { Array.new(2) { create(:ci_pipeline, project: project, user: create(:user)) } } + let(:params) { { order_by: 'user_id', sort: 'asc' } } + let(:users) { Array.new(2) { create(:user, developer_projects: [project]) } } + let!(:pipelines) { users.map { |user| create(:ci_pipeline, project: project, user: user) } } it 'sorts as user_id: :asc' do is_expected.to match_array(pipelines) diff --git a/spec/fixtures/trace/trace_with_duplicate_sections b/spec/fixtures/trace/trace_with_duplicate_sections new file mode 100644 index 00000000000..7a894e80e38 --- /dev/null +++ b/spec/fixtures/trace/trace_with_duplicate_sections @@ -0,0 +1,30 @@ +[0KRunning with gitlab-runner dev (HEAD) + on kitsune minikube (a21b584f) +[0;m[0;33mWARNING: Namespace is empty, therefore assuming 'default'. +[0;m[0KUsing Kubernetes namespace: default +[0;m[0KUsing Kubernetes executor with image alpine:3.4 ... +[0;msection_start:1506004954:prepare_script
[0KWaiting for pod default/runner-a21b584f-project-1208199-concurrent-0sg03f to be running, status is Pending +Running on runner-a21b584f-project-1208199-concurrent-0sg03f via kitsune.local... +section_end:1506004957:prepare_script
[0Ksection_start:1506004957:get_sources
[0K[32;1mCloning repository...[0;m +Cloning into '/nolith/ci-tests'... +[32;1mChecking out dddd7a6e as master...[0;m +[32;1mSkipping Git submodules setup[0;m +section_end:1506004958:get_sources
[0Ksection_start:1506004958:restore_cache
[0Ksection_end:1506004958:restore_cache
[0Ksection_start:1506004958:download_artifacts
[0Ksection_end:1506004958:download_artifacts
[0Ksection_start:1506004958:build_script
[0K[32;1m$ whoami[0;m +root +section_end:1506004959:build_script
[0Ksection_start:1506004959:after_script
[0Ksection_end:1506004959:after_script
[0Ksection_start:1506004959:archive_cache
[0Ksection_end:1506004959:archive_cache
[0Ksection_start:1506004959:upload_artifacts
[0Ksection_end:1506004959:upload_artifacts
[0K[32;1mJob succeeded +[0;m +[0KRunning with gitlab-runner dev (HEAD) + on kitsune minikube (a21b584f) +[0;m[0;33mWARNING: Namespace is empty, therefore assuming 'default'. +[0;m[0KUsing Kubernetes namespace: default +[0;m[0KUsing Kubernetes executor with image alpine:3.4 ... +[0;msection_start:1506004954:prepare_script
[0KWaiting for pod default/runner-a21b584f-project-1208199-concurrent-0sg03f to be running, status is Pending +Running on runner-a21b584f-project-1208199-concurrent-0sg03f via kitsune.local... +section_end:1506004957:prepare_script
[0Ksection_start:1506004957:get_sources
[0K[32;1mCloning repository...[0;m +Cloning into '/nolith/ci-tests'... +[32;1mChecking out dddd7a6e as master...[0;m +[32;1mSkipping Git submodules setup[0;m +section_end:1506004958:get_sources
[0Ksection_start:1506004958:restore_cache
[0Ksection_end:1506004958:restore_cache
[0Ksection_start:1506004958:download_artifacts
[0Ksection_end:1506004958:download_artifacts
[0Ksection_start:1506004958:build_script
[0K[32;1m$ whoami[0;m +root +section_end:1506004959:build_script
[0Ksection_start:1506004959:after_script
[0Ksection_end:1506004959:after_script
[0Ksection_start:1506004959:archive_cache
[0Ksection_end:1506004959:archive_cache
[0Ksection_start:1506004959:upload_artifacts
[0Ksection_end:1506004959:upload_artifacts
[0K[32;1mJob succeeded +[0;m diff --git a/spec/javascripts/fixtures/pipelines.rb b/spec/javascripts/fixtures/pipelines.rb index de6fcfe10f4..6b6b0eefab9 100644 --- a/spec/javascripts/fixtures/pipelines.rb +++ b/spec/javascripts/fixtures/pipelines.rb @@ -8,7 +8,7 @@ describe Projects::PipelinesController, '(JavaScript fixtures)', type: :controll let(:project) { create(:project, :repository, namespace: namespace, path: 'pipelines-project') } let(:commit) { create(:commit, project: project) } let(:commit_without_author) { RepoHelpers.another_sample_commit } - let!(:user) { create(:user, email: commit.author_email) } + let!(:user) { create(:user, developer_projects: [project], email: commit.author_email) } let!(:pipeline) { create(:ci_pipeline, project: project, sha: commit.id, user: user) } let!(:pipeline_without_author) { create(:ci_pipeline, project: project, sha: commit_without_author.id) } let!(:pipeline_without_commit) { create(:ci_pipeline, project: project, sha: '0000') } diff --git a/spec/javascripts/jobs/components/job_log_spec.js b/spec/javascripts/jobs/components/job_log_spec.js index dc0f77ceb80..7e2ec2ec3f7 100644 --- a/spec/javascripts/jobs/components/job_log_spec.js +++ b/spec/javascripts/jobs/components/job_log_spec.js @@ -3,6 +3,7 @@ import component from '~/jobs/components/job_log.vue'; import createStore from '~/jobs/store'; import { mountComponentWithStore } from 'spec/helpers/vue_mount_component_helper'; import { resetStore } from '../store/helpers'; +import { logWithCollapsibleSections } from '../mock_data'; describe('Job Log', () => { const Component = Vue.extend(component); @@ -62,4 +63,40 @@ describe('Job Log', () => { expect(vm.$el.querySelector('.js-log-animation')).toBeNull(); }); }); + + describe('Collapsible sections', () => { + beforeEach(() => { + vm = mountComponentWithStore(Component, { + props: { + trace: logWithCollapsibleSections.html, + isComplete: true, + }, + store, + }); + }); + + it('renders open arrow', () => { + expect(vm.$el.querySelector('.fa-caret-down')).not.toBeNull(); + }); + + it('toggles hidden class to the sibilings rows when arrow is clicked', done => { + vm.$nextTick() + .then(() => { + const { section } = vm.$el.querySelector('.js-section-start').dataset; + vm.$el.querySelector('.js-section-start').click(); + + vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => { + expect(el.classList.contains('hidden')).toEqual(true); + }); + + vm.$el.querySelector('.js-section-start').click(); + + vm.$el.querySelectorAll(`.js-s-${section}:not(.js-section-header)`).forEach(el => { + expect(el.classList.contains('hidden')).toEqual(false); + }); + }) + .then(done) + .catch(done.fail); + }); + }); }); diff --git a/spec/javascripts/jobs/components/stages_dropdown_spec.js b/spec/javascripts/jobs/components/stages_dropdown_spec.js index e98639bf21e..52bb5161123 100644 --- a/spec/javascripts/jobs/components/stages_dropdown_spec.js +++ b/spec/javascripts/jobs/components/stages_dropdown_spec.js @@ -9,7 +9,6 @@ describe('Stages Dropdown', () => { const mockPipelineData = { id: 28029444, - iid: 123, details: { status: { details_path: '/gitlab-org/gitlab-ce/pipelines/28029444', @@ -78,8 +77,8 @@ describe('Stages Dropdown', () => { expect(vm.$el.querySelector('.dropdown .js-selected-stage').textContent).toContain('deploy'); }); - it(`renders the pipeline info text like "Pipeline #123 (#12) for source_branch"`, () => { - const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for ${pipeline.ref.name}`; + it(`renders the pipeline info text like "Pipeline #123 for source_branch"`, () => { + const expected = `Pipeline #${pipeline.id} for ${pipeline.ref.name}`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText); expect(actual).toBe(expected); @@ -101,10 +100,10 @@ describe('Stages Dropdown', () => { }); }); - it(`renders the pipeline info text like "Pipeline #123 (#12) for !456 with source_branch into target_branch"`, () => { - const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for !${ - pipeline.merge_request.iid - } with ${pipeline.merge_request.source_branch} into ${pipeline.merge_request.target_branch}`; + it(`renders the pipeline info text like "Pipeline #123 for !456 with source_branch into target_branch"`, () => { + const expected = `Pipeline #${pipeline.id} for !${pipeline.merge_request.iid} with ${ + pipeline.merge_request.source_branch + } into ${pipeline.merge_request.target_branch}`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText); expect(actual).toBe(expected); @@ -144,10 +143,10 @@ describe('Stages Dropdown', () => { }); }); - it(`renders the pipeline info like "Pipeline #123 (#12) for !456 with source_branch"`, () => { - const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) for !${ - pipeline.merge_request.iid - } with ${pipeline.merge_request.source_branch}`; + it(`renders the pipeline info like "Pipeline #123 for !456 with source_branch"`, () => { + const expected = `Pipeline #${pipeline.id} for !${pipeline.merge_request.iid} with ${ + pipeline.merge_request.source_branch + }`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info').innerText); expect(actual).toBe(expected); diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js index 88b0bb206ee..c5022d3e93d 100644 --- a/spec/javascripts/jobs/mock_data.js +++ b/spec/javascripts/jobs/mock_data.js @@ -960,7 +960,6 @@ export default { }, pipeline: { id: 140, - iid: 13, user: { name: 'Root', username: 'root', @@ -1190,3 +1189,18 @@ export const jobsInStage = { path: '/gitlab-org/gitlab-shell/pipelines/27#build', dropdown_path: '/gitlab-org/gitlab-shell/pipelines/27/stage.json?stage=build', }; + +export const logWithCollapsibleSections = { + append: false, + complete: true, + html: + '<div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer" data-timestamp="1559571405" data-section="after-script" role="button"></div><span class="term-fg-l-green term-bold section js-section-header js-s-after-script">Running after script...</span><span class="section js-section-header js-s-after-script"><br /></span><span class="section s_after-script line"></span><span class="section js-s-after-script"></span><span class="term-fg-l-green term-bold section js-s-after-script">$ date</span><span class="section js-s-after-script"><br /></span><span class="section s_after-script line"></span><span class="section js-s-after-script">Mon Jun 3 14:16:46 UTC 2019<br /></span><span class="section s_after-script line"></span><span class="section js-s-after-script"></span><div class="section-end" data-section="after-script"></div><div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer"data-timestamp="1559571408" data-section="archive-cache" role="button" ></div><span class="term-fg-l-green term-bold section js-section-header js-s-archive-cache">Not uploading cache debian-stretch-ruby-2.6.3-node-10.x-3 due to policy</span><span class="section js-section-header js-s-archive-cache"><br /></span><span class="section s_archive-cache line"></span><span class="section js-s-archive-cache"></span><div class="section-end" data-section="archive-cache"></div><div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer" data-timestamp="1559571409" data-section="upload-artifacts-on-success" role="button"></div><span class="term-fg-l-green term-bold section js-section-header js-s-upload-artifacts-on-success">Uploading artifacts...</span><span class="section js-section-header js-s-upload-artifacts-on-success"><br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">coverage/: found 5 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">knapsack/: found 4 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">rspec_flaky/: found 4 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">rspec_profiling/: found 1 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success"></span><span class="term-fg-yellow section js-s-upload-artifacts-on-success">WARNING: tmp/capybara/: no matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">Uploading artifacts to coordinator... ok </span><span class="section js-s-upload-artifacts-on-success"> id</span><span class="section js-s-upload-artifacts-on-success">=224162288 responseStatus</span><span class="section js-s-upload-artifacts-on-success">=201 Created token</span><span class="section js-s-upload-artifacts-on-success">=bBmyXJNW<br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success"></span><span class="term-fg-l-green term-bold section js-s-upload-artifacts-on-success">Uploading artifacts...</span><span class="section js-s-upload-artifacts-on-success"><br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">junit_rspec.xml: found 1 matching files </span><span class="section js-s-upload-artifacts-on-success"> <br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success">Uploading artifacts to coordinator... ok </span><span class="section js-s-upload-artifacts-on-success"> id</span><span class="section js-s-upload-artifacts-on-success">=224162288 responseStatus</span><span class="section js-s-upload-artifacts-on-success">=201 Created token</span><span class="section js-s-upload-artifacts-on-success">=bBmyXJNW<br /></span><span class="section s_upload-artifacts-on-success line"></span><span class="section js-s-upload-artifacts-on-success"></span><div class="section-end" data-section="upload-artifacts-on-success"></div><span class="term-fg-l-green term-bold">Job succeeded<br /><span class="term-fg-l-green term-bold"></span></span>', + id: 1385, + offset: 0, + size: 78815, + state: + 'eyJvZmZzZXQiOjc4ODE1LCJuX29wZW5fdGFncyI6MCwiZmdfY29sb3IiOm51bGwsImJnX2NvbG9yIjpudWxsLCJzdHlsZV9tYXNrIjowLCJzZWN0aW9ucyI6W10sImxpbmVub19pbl9zZWN0aW9uIjoxMX0=', + status: 'success', + total: 78815, + truncated: false, +}; diff --git a/spec/javascripts/pipelines/mock_data.js b/spec/javascripts/pipelines/mock_data.js index 8eef9166b8d..03ead6cd8ba 100644 --- a/spec/javascripts/pipelines/mock_data.js +++ b/spec/javascripts/pipelines/mock_data.js @@ -1,6 +1,5 @@ export const pipelineWithStages = { id: 20333396, - iid: 304399, user: { id: 128633, name: 'Rémy Coutable', diff --git a/spec/javascripts/pipelines/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js index 88c0137dc58..aa196af2f33 100644 --- a/spec/javascripts/pipelines/pipeline_url_spec.js +++ b/spec/javascripts/pipelines/pipeline_url_spec.js @@ -13,7 +13,6 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, - iid: 1, path: 'foo', flags: {}, }, @@ -29,7 +28,6 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, - iid: 1, path: 'foo', flags: {}, }, @@ -49,7 +47,6 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, - iid: 1, path: 'foo', flags: { latest: true, @@ -81,7 +78,6 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, - iid: 1, path: 'foo', flags: { latest: true, @@ -104,7 +100,6 @@ describe('Pipeline Url Component', () => { propsData: { pipeline: { id: 1, - iid: 1, path: 'foo', flags: { failure_reason: true, diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js index a2308b0dfdb..75017d20473 100644 --- a/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js +++ b/spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js @@ -103,7 +103,7 @@ describe('MRWidgetPipeline', () => { it('should render pipeline ID', () => { expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual( - `#${mockData.pipeline.id} (#${mockData.pipeline.iid})`, + `#${mockData.pipeline.id}`, ); }); @@ -150,7 +150,7 @@ describe('MRWidgetPipeline', () => { it('should render pipeline ID', () => { expect(vm.$el.querySelector('.pipeline-id').textContent.trim()).toEqual( - `#${mockData.pipeline.id} (#${mockData.pipeline.iid})`, + `#${mockData.pipeline.id}`, ); }); @@ -222,9 +222,9 @@ describe('MRWidgetPipeline', () => { sourceBranchLink: mockCopy.source_branch_link, }); - const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${ - pipeline.details.status.label - } for ${pipeline.commit.short_id} on ${mockCopy.source_branch_link}`; + const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${ + pipeline.commit.short_id + } on ${mockCopy.source_branch_link}`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText); @@ -247,11 +247,11 @@ describe('MRWidgetPipeline', () => { sourceBranchLink: mockCopy.source_branch_link, }); - const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${ - pipeline.details.status.label - } for ${pipeline.commit.short_id} on !${pipeline.merge_request.iid} with ${ - pipeline.merge_request.source_branch - } into ${pipeline.merge_request.target_branch}`; + const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${ + pipeline.commit.short_id + } on !${pipeline.merge_request.iid} with ${pipeline.merge_request.source_branch} into ${ + pipeline.merge_request.target_branch + }`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText); @@ -274,11 +274,9 @@ describe('MRWidgetPipeline', () => { sourceBranchLink: mockCopy.source_branch_link, }); - const expected = `Pipeline #${pipeline.id} (#${pipeline.iid}) ${ - pipeline.details.status.label - } for ${pipeline.commit.short_id} on !${pipeline.merge_request.iid} with ${ - pipeline.merge_request.source_branch - }`; + const expected = `Pipeline #${pipeline.id} ${pipeline.details.status.label} for ${ + pipeline.commit.short_id + } on !${pipeline.merge_request.iid} with ${pipeline.merge_request.source_branch}`; const actual = trimText(vm.$el.querySelector('.js-pipeline-info-container').innerText); diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index 3c9a5cece90..48f812f0db4 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -61,7 +61,6 @@ export default { "Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22", pipeline: { id: 172, - iid: 32, user: { name: 'Administrator', username: 'root', @@ -243,8 +242,6 @@ export default { export const mockStore = { pipeline: { id: 0, - iid: 0, - path: '/root/acets-app/pipelines/0', details: { status: { details_path: '/root/review-app-tester/pipelines/66', @@ -262,8 +259,6 @@ export const mockStore = { }, mergePipeline: { id: 1, - iid: 1, - path: '/root/acets-app/pipelines/0', details: { status: { details_path: '/root/review-app-tester/pipelines/66', diff --git a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js index 08f7a17515e..ac2fb16bd10 100644 --- a/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/javascripts/vue_mr_widget/mr_widget_options_spec.js @@ -544,7 +544,6 @@ describe('mrWidgetOptions', () => { ]; const deploymentMockData = { id: 15, - iid: 7, name: 'review/diplo', url: '/root/acets-review-apps/environments/15', stop_url: '/root/acets-review-apps/environments/15/stop', @@ -591,7 +590,6 @@ describe('mrWidgetOptions', () => { vm.mr.state = 'merged'; vm.mr.mergePipeline = { id: 127, - iid: 35, user: { id: 1, name: 'Administrator', diff --git a/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb b/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb deleted file mode 100644 index f974dc8fda2..00000000000 --- a/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'spec_helper' - -describe Gitlab::BackgroundMigration::CreateGpgKeySubkeysFromGpgKeys, :migration, schema: 20171005130944 do - context 'when GpgKey exists' do - let!(:gpg_key) { create(:gpg_key, key: GpgHelpers::User3.public_key) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - - before do - GpgKeySubkey.destroy_all # rubocop: disable DestroyAll - end - - it 'generate the subkeys' do - expect do - described_class.new.perform(gpg_key.id) - end.to change { gpg_key.subkeys.count }.from(0).to(2) - end - - it 'schedules the signature update worker' do - expect(InvalidGpgSignatureUpdateWorker).to receive(:perform_async).with(gpg_key.id) - - described_class.new.perform(gpg_key.id) - end - end - - context 'when GpgKey does not exist' do - it 'does not do anything' do - expect(Gitlab::Gpg).not_to receive(:subkeys_from_key) - expect(InvalidGpgSignatureUpdateWorker).not_to receive(:perform_async) - - described_class.new.perform(123) - end - end -end diff --git a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb deleted file mode 100644 index 0a5b99d27e7..00000000000 --- a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb +++ /dev/null @@ -1,81 +0,0 @@ -require 'spec_helper' - -# rubocop:disable RSpec/FactoriesInMigrationSpecs -describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, schema: 20180619121030 do - describe '#perform' do - before do - # This migration was created before we introduced ProjectCiCdSetting#default_git_depth - allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0) - allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil) - end - - context 'when diff files can be deleted' do - let(:merge_request) { create(:merge_request, :merged) } - let!(:merge_request_diff) do - merge_request.create_merge_request_diff - merge_request.merge_request_diffs.first - end - - let(:perform) do - described_class.new.perform(MergeRequestDiff.pluck(:id)) - end - - it 'deletes all merge request diff files' do - expect { perform } - .to change { merge_request_diff.merge_request_diff_files.count } - .from(20).to(0) - end - - it 'updates state to without_files' do - expect { perform } - .to change { merge_request_diff.reload.state } - .from('collected').to('without_files') - end - - it 'rollsback if something goes wrong' do - expect(described_class::MergeRequestDiffFile).to receive_message_chain(:where, :delete_all) - .and_raise - - expect { perform } - .to raise_error - - merge_request_diff.reload - - expect(merge_request_diff.state).to eq('collected') - expect(merge_request_diff.merge_request_diff_files.count).to eq(20) - end - end - - it 'reschedules itself when should_wait_deadtuple_vacuum' do - merge_request = create(:merge_request, :merged) - first_diff = merge_request.merge_request_diff - second_diff = merge_request.create_merge_request_diff - - Sidekiq::Testing.fake! do - worker = described_class.new - allow(worker).to receive(:should_wait_deadtuple_vacuum?) { true } - - worker.perform([first_diff.id, second_diff.id]) - - expect(described_class.name.demodulize).to be_scheduled_delayed_migration(5.minutes, [first_diff.id, second_diff.id]) - expect(BackgroundMigrationWorker.jobs.size).to eq(1) - end - end - end - - describe '#should_wait_deadtuple_vacuum?' do - it 'returns true when hitting merge_request_diff_files hits DEAD_TUPLES_THRESHOLD', :postgresql do - worker = described_class.new - threshold_query_result = [{ "n_dead_tup" => described_class::DEAD_TUPLES_THRESHOLD.to_s }] - normal_query_result = [{ "n_dead_tup" => '3' }] - - allow(worker) - .to receive(:execute_statement) - .with(/SELECT n_dead_tup */) - .and_return(threshold_query_result, normal_query_result) - - expect(worker.should_wait_deadtuple_vacuum?).to be(true) - end - end -end -# rubocop:enable RSpec/FactoriesInMigrationSpecs diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb deleted file mode 100644 index d3f7f1ded16..00000000000 --- a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb +++ /dev/null @@ -1,326 +0,0 @@ -require 'spec_helper' - -# rubocop:disable RSpec/FactoriesInMigrationSpecs -describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :migration, schema: 20171114162227 do - include GitHelpers - - let(:merge_request_diffs) { table(:merge_request_diffs) } - let(:merge_requests) { table(:merge_requests) } - - describe '#perform' do - let(:project) { create(:project, :repository) } - let(:merge_request) { merge_requests.create!(iid: 1, target_project_id: project.id, source_project_id: project.id, target_branch: 'feature', source_branch: 'master').becomes(MergeRequest) } - let(:merge_request_diff) { MergeRequest.find(merge_request.id).create_merge_request_diff } - let(:updated_merge_request_diff) { MergeRequestDiff.find(merge_request_diff.id) } - let(:rugged) { rugged_repo(project.repository) } - - before do - allow_any_instance_of(MergeRequestDiff) - .to receive(:commits_count=).and_return(nil) - end - - def diffs_to_hashes(diffs) - diffs.as_json(only: Gitlab::Git::Diff::SERIALIZE_KEYS).map(&:with_indifferent_access) - end - - def quote_yaml(value) - MergeRequestDiff.connection.quote(YAML.dump(value)) - end - - def convert_to_yaml(merge_request_diff_id, commits, diffs) - MergeRequestDiff.where(id: merge_request_diff_id).update_all( - "st_commits = #{quote_yaml(commits)}, st_diffs = #{quote_yaml(diffs)}" - ) - end - - shared_examples 'updated MR diff' do - before do - convert_to_yaml(merge_request_diff.id, commits, diffs) - - MergeRequestDiffCommit.delete_all - MergeRequestDiffFile.delete_all - - subject.perform(merge_request_diff.id, merge_request_diff.id) - end - - it 'creates correct entries in the merge_request_diff_commits table' do - expect(updated_merge_request_diff.merge_request_diff_commits.count).to eq(expected_commits.count) - expect(updated_merge_request_diff.commits.map(&:to_hash)).to eq(expected_commits) - end - - it 'creates correct entries in the merge_request_diff_files table' do - expect(updated_merge_request_diff.merge_request_diff_files.count).to eq(expected_diffs.count) - expect(diffs_to_hashes(updated_merge_request_diff.raw_diffs)).to eq(expected_diffs) - end - - it 'sets the st_commits and st_diffs columns to nil' do - expect(updated_merge_request_diff.st_commits_before_type_cast).to be_nil - expect(updated_merge_request_diff.st_diffs_before_type_cast).to be_nil - end - end - - context 'when the diff IDs passed do not exist' do - it 'does not raise' do - expect { subject.perform(0, 0) }.not_to raise_exception - end - end - - context 'when the merge request diff has no serialised commits or diffs' do - before do - merge_request_diff.update(st_commits: nil, st_diffs: nil) - end - - it 'does not raise' do - expect { subject.perform(merge_request_diff.id, merge_request_diff.id) } - .not_to raise_exception - end - end - - context 'processing multiple merge request diffs' do - let(:start_id) { described_class::MergeRequestDiff.minimum(:id) } - let(:stop_id) { described_class::MergeRequestDiff.maximum(:id) } - - before do - merge_request.create_merge_request_diff - - convert_to_yaml(start_id, merge_request_diff.commits, diffs_to_hashes(merge_request_diff.merge_request_diff_files)) - convert_to_yaml(stop_id, updated_merge_request_diff.commits, diffs_to_hashes(updated_merge_request_diff.merge_request_diff_files)) - - MergeRequestDiffCommit.delete_all - MergeRequestDiffFile.delete_all - end - - context 'when BUFFER_ROWS is exceeded' do - before do - stub_const("#{described_class}::BUFFER_ROWS", 1) - - allow(Gitlab::Database).to receive(:bulk_insert).and_call_original - end - - it 'inserts commit rows in chunks of BUFFER_ROWS' do - # There are 29 commits in each diff, so we should have slices of 20 + 9 + 20 + 9. - stub_const("#{described_class}::BUFFER_ROWS", 20) - - expect(Gitlab::Database).to receive(:bulk_insert) - .with('merge_request_diff_commits', anything) - .exactly(4) - .times - .and_call_original - - subject.perform(start_id, stop_id) - end - - it 'inserts diff rows in chunks of DIFF_FILE_BUFFER_ROWS' do - # There are 20 files in each diff, so we should have slices of 20 + 20. - stub_const("#{described_class}::DIFF_FILE_BUFFER_ROWS", 20) - - expect(Gitlab::Database).to receive(:bulk_insert) - .with('merge_request_diff_files', anything) - .exactly(2) - .times - .and_call_original - - subject.perform(start_id, stop_id) - end - end - - context 'when BUFFER_ROWS is not exceeded' do - it 'only updates once' do - expect(Gitlab::Database).to receive(:bulk_insert) - .with('merge_request_diff_commits', anything) - .once - .and_call_original - - expect(Gitlab::Database).to receive(:bulk_insert) - .with('merge_request_diff_files', anything) - .once - .and_call_original - - subject.perform(start_id, stop_id) - end - end - - context 'when some rows were already inserted due to a previous failure' do - before do - subject.perform(start_id, stop_id) - - convert_to_yaml(start_id, merge_request_diff.commits, diffs_to_hashes(merge_request_diff.merge_request_diff_files)) - convert_to_yaml(stop_id, updated_merge_request_diff.commits, diffs_to_hashes(updated_merge_request_diff.merge_request_diff_files)) - end - - it 'does not raise' do - expect { subject.perform(start_id, stop_id) }.not_to raise_exception - end - - it 'logs a message' do - expect(Rails.logger).to receive(:info) - .with( - a_string_matching(described_class.name).and(matching([start_id, stop_id].inspect)) - ) - .twice - - subject.perform(start_id, stop_id) - end - - it 'ends up with the correct rows' do - expect(updated_merge_request_diff.commits.count).to eq(29) - expect(updated_merge_request_diff.raw_diffs.count).to eq(20) - end - end - - context 'when the merge request diff update fails' do - let(:exception) { ActiveRecord::RecordNotFound } - - let(:perform_ignoring_exceptions) do - subject.perform(start_id, stop_id) - rescue described_class::Error - end - - before do - allow_any_instance_of(ActiveRecord::Relation) - .to receive(:update_all).and_raise(exception) - end - - it 'raises an error' do - expect { subject.perform(start_id, stop_id) } - .to raise_exception(described_class::Error) - end - - it 'logs the error' do - expect(Rails.logger).to receive(:info).with( - a_string_matching(described_class.name) - .and(matching([start_id, stop_id].inspect)) - .and(matching(exception.name)) - ) - - perform_ignoring_exceptions - end - - it 'still adds diff commits' do - expect { perform_ignoring_exceptions } - .to change { MergeRequestDiffCommit.count } - end - - it 'still adds diff files' do - expect { perform_ignoring_exceptions } - .to change { MergeRequestDiffFile.count } - end - end - end - - context 'when the merge request diff has valid commits and diffs' do - let(:commits) { merge_request_diff.commits.map(&:to_hash) } - let(:expected_commits) { commits } - let(:diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) } - let(:expected_diffs) { diffs } - - include_examples 'updated MR diff' - end - - context 'when the merge request diff has diffs but no commits' do - let(:commits) { nil } - let(:expected_commits) { [] } - let(:diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) } - let(:expected_diffs) { diffs } - - include_examples 'updated MR diff' - end - - context 'when the merge request diffs do not have too_large set' do - let(:commits) { merge_request_diff.commits.map(&:to_hash) } - let(:expected_commits) { commits } - let(:expected_diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) } - - let(:diffs) do - expected_diffs.map { |diff| diff.except(:too_large) } - end - - include_examples 'updated MR diff' - end - - context 'when the merge request diffs do not have a_mode and b_mode set' do - let(:commits) { merge_request_diff.commits.map(&:to_hash) } - let(:expected_commits) { commits } - let(:expected_diffs) { diffs_to_hashes(merge_request_diff.merge_request_diff_files) } - - let(:diffs) do - expected_diffs.map { |diff| diff.except(:a_mode, :b_mode) } - end - - include_examples 'updated MR diff' - end - - context 'when the merge request diffs have binary content' do - let(:commits) { merge_request_diff.commits.map(&:to_hash) } - let(:expected_commits) { commits } - let(:expected_diffs) { diffs } - - # The start of a PDF created by Illustrator - let(:binary_string) do - "\x25\x50\x44\x46\x2d\x31\x2e\x35\x0d\x25\xe2\xe3\xcf\xd3\x0d\x0a".force_encoding(Encoding::BINARY) - end - - let(:diffs) do - [ - { - 'diff' => binary_string, - 'new_path' => 'path', - 'old_path' => 'path', - 'a_mode' => '100644', - 'b_mode' => '100644', - 'new_file' => false, - 'renamed_file' => false, - 'deleted_file' => false, - 'too_large' => false - } - ] - end - - include_examples 'updated MR diff' - end - - context 'when the merge request diff has commits, but no diffs' do - let(:commits) { merge_request_diff.commits.map(&:to_hash) } - let(:expected_commits) { commits } - let(:diffs) { [] } - let(:expected_diffs) { diffs } - - include_examples 'updated MR diff' - end - - context 'when the merge request diffs have invalid content' do - let(:commits) { merge_request_diff.commits.map(&:to_hash) } - let(:expected_commits) { commits } - let(:diffs) { ['--broken-diff'] } - let(:expected_diffs) { [] } - - include_examples 'updated MR diff' - end - - context 'when the merge request diffs are Rugged::Patch instances' do - let(:commits) { merge_request_diff.commits.map(&:to_hash) } - let(:first_commit) { project.repository.commit(merge_request_diff.head_commit_sha) } - let(:expected_commits) { commits } - let(:diffs) { rugged_diff(first_commit.sha).patches } - let(:expected_diffs) { [] } - - include_examples 'updated MR diff' - end - - context 'when the merge request diffs are Rugged::Diff::Delta instances' do - let(:commits) { merge_request_diff.commits.map(&:to_hash) } - let(:first_commit) { project.repository.commit(merge_request_diff.head_commit_sha) } - let(:expected_commits) { commits } - let(:diffs) { rugged_diff(first_commit.sha).deltas } - let(:expected_diffs) { [] } - - include_examples 'updated MR diff' - end - - def rugged_diff(commit_sha) - rugged_commit = rugged.lookup(commit_sha) - rugged_commit.parents[0].diff(rugged_commit) - end - end -end -# rubocop:enable RSpec/FactoriesInMigrationSpecs diff --git a/spec/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check_spec.rb b/spec/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check_spec.rb new file mode 100644 index 00000000000..eecd290e3ca --- /dev/null +++ b/spec/lib/gitlab/background_migration/merge_request_assignees_migration_progress_check_spec.rb @@ -0,0 +1,95 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::BackgroundMigration::MergeRequestAssigneesMigrationProgressCheck do + context 'rescheduling' do + context 'when there are ongoing and no dead jobs' do + it 'reschedules check' do + allow(Gitlab::BackgroundMigration).to receive(:exists?) + .with('PopulateMergeRequestAssigneesTable') + .and_return(true) + + allow(Gitlab::BackgroundMigration).to receive(:dead_jobs?) + .with('PopulateMergeRequestAssigneesTable') + .and_return(false) + + expect(BackgroundMigrationWorker).to receive(:perform_in).with(described_class::RESCHEDULE_DELAY, described_class.name) + + described_class.new.perform + end + end + + context 'when there are ongoing and dead jobs' do + it 'reschedules check' do + allow(Gitlab::BackgroundMigration).to receive(:exists?) + .with('PopulateMergeRequestAssigneesTable') + .and_return(true) + + allow(Gitlab::BackgroundMigration).to receive(:dead_jobs?) + .with('PopulateMergeRequestAssigneesTable') + .and_return(true) + + expect(BackgroundMigrationWorker).to receive(:perform_in).with(described_class::RESCHEDULE_DELAY, described_class.name) + + described_class.new.perform + end + end + + context 'when there retrying jobs and no scheduled' do + it 'reschedules check' do + allow(Gitlab::BackgroundMigration).to receive(:exists?) + .with('PopulateMergeRequestAssigneesTable') + .and_return(false) + + allow(Gitlab::BackgroundMigration).to receive(:retrying_jobs?) + .with('PopulateMergeRequestAssigneesTable') + .and_return(true) + + expect(BackgroundMigrationWorker).to receive(:perform_in).with(described_class::RESCHEDULE_DELAY, described_class.name) + + described_class.new.perform + end + end + end + + context 'when there are no scheduled, or retrying or dead' do + it 'enables feature' do + allow(Gitlab::BackgroundMigration).to receive(:exists?) + .with('PopulateMergeRequestAssigneesTable') + .and_return(false) + + allow(Gitlab::BackgroundMigration).to receive(:retrying_jobs?) + .with('PopulateMergeRequestAssigneesTable') + .and_return(false) + + allow(Gitlab::BackgroundMigration).to receive(:dead_jobs?) + .with('PopulateMergeRequestAssigneesTable') + .and_return(false) + + expect(Feature).to receive(:enable).with(:multiple_merge_request_assignees) + + described_class.new.perform + end + end + + context 'when there are only dead jobs' do + it 'raises DeadJobsError error' do + allow(Gitlab::BackgroundMigration).to receive(:exists?) + .with('PopulateMergeRequestAssigneesTable') + .and_return(false) + + allow(Gitlab::BackgroundMigration).to receive(:retrying_jobs?) + .with('PopulateMergeRequestAssigneesTable') + .and_return(false) + + allow(Gitlab::BackgroundMigration).to receive(:dead_jobs?) + .with('PopulateMergeRequestAssigneesTable') + .and_return(true) + + expect { described_class.new.perform } + .to raise_error(described_class::DeadJobsError, + "Only dead background jobs in the queue for #{described_class::WORKER}") + end + end +end diff --git a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb deleted file mode 100644 index c6bc3db88a3..00000000000 --- a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb +++ /dev/null @@ -1,72 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -# rubocop:disable RSpec/FactoriesInMigrationSpecs -describe Gitlab::BackgroundMigration::PopulateExternalPipelineSource, :migration, schema: 20180916011959 do - let(:migration) { described_class.new } - - before do - # This migration was created before we introduced metadata configs - stub_feature_flags(ci_build_metadata_config: false) - # This migration was created before we introduced ProjectCiCdSetting#default_git_depth - allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil) - allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0) - end - - let!(:internal_pipeline) { create(:ci_pipeline, source: :web) } - let(:pipelines) { [internal_pipeline, unknown_pipeline].map(&:id) } - - let!(:unknown_pipeline) do - build(:ci_pipeline, source: :unknown) - .tap { |pipeline| pipeline.save(validate: false) } - end - - subject { migration.perform(pipelines.min, pipelines.max) } - - shared_examples 'no changes' do - it 'does not change the pipeline source' do - expect { subject }.not_to change { unknown_pipeline.reload.source } - end - end - - context 'when unknown pipeline is external' do - before do - create(:generic_commit_status, pipeline: unknown_pipeline) - end - - it 'populates the pipeline source' do - subject - - expect(unknown_pipeline.reload.source).to eq('external') - end - - it 'can be repeated without effect' do - subject - - expect { subject }.not_to change { unknown_pipeline.reload.source } - end - end - - context 'when unknown pipeline has just a build' do - before do - create(:ci_build, pipeline: unknown_pipeline) - end - - it_behaves_like 'no changes' - end - - context 'when unknown pipeline has no statuses' do - it_behaves_like 'no changes' - end - - context 'when unknown pipeline has a build and a status' do - before do - create(:generic_commit_status, pipeline: unknown_pipeline) - create(:ci_build, pipeline: unknown_pipeline) - end - - it_behaves_like 'no changes' - end -end -# rubocop:enable RSpec/FactoriesInMigrationSpecs diff --git a/spec/lib/gitlab/background_migration/populate_import_state_spec.rb b/spec/lib/gitlab/background_migration/populate_import_state_spec.rb deleted file mode 100644 index fcb869022de..00000000000 --- a/spec/lib/gitlab/background_migration/populate_import_state_spec.rb +++ /dev/null @@ -1,38 +0,0 @@ -require 'spec_helper' - -describe Gitlab::BackgroundMigration::PopulateImportState, :migration, schema: 20180502134117 do - let(:migration) { described_class.new } - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - let(:import_state) { table(:project_mirror_data) } - - before do - namespaces.create(id: 1, name: 'gitlab-org', path: 'gitlab-org') - - projects.create!(id: 1, namespace_id: 1, name: 'gitlab1', - path: 'gitlab1', import_error: "foo", import_status: :started, - import_url: generate(:url)) - projects.create!(id: 2, namespace_id: 1, name: 'gitlab2', path: 'gitlab2', - import_status: :none, import_url: generate(:url)) - projects.create!(id: 3, namespace_id: 1, name: 'gitlab3', - path: 'gitlab3', import_error: "bar", import_status: :failed, - import_url: generate(:url)) - - allow(BackgroundMigrationWorker).to receive(:perform_in) - end - - it "creates new import_state records with project's import data" do - expect(projects.where.not(import_status: :none).count).to eq(2) - - expect do - migration.perform(1, 3) - end.to change { import_state.all.count }.from(0).to(2) - - expect(import_state.first.last_error).to eq("foo") - expect(import_state.last.last_error).to eq("bar") - expect(import_state.first.status).to eq("started") - expect(import_state.last.status).to eq("failed") - expect(projects.first.import_status).to eq("none") - expect(projects.last.import_status).to eq("none") - end -end diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_improved_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_improved_spec.rb deleted file mode 100644 index d1d64574627..00000000000 --- a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_improved_spec.rb +++ /dev/null @@ -1,57 +0,0 @@ -# frozen_string_literal: true - -require 'rails_helper' - -describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsDataImproved, :migration, schema: 20181204154019 do - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - let(:users) { table(:users) } - let(:events) { table(:events) } - - let(:user) { users.create!(email: 'test@example.com', projects_limit: 100, username: 'test') } - - let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') } - let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') } - let(:merge_requests) { table(:merge_requests) } - - def create_merge_request(id, params = {}) - params.merge!(id: id, - target_project_id: project.id, - target_branch: 'master', - source_project_id: project.id, - source_branch: 'mr name', - title: "mr name#{id}") - - merge_requests.create(params) - end - - def create_merge_request_event(id, params = {}) - params.merge!(id: id, - project_id: project.id, - author_id: user.id, - target_type: 'MergeRequest') - - events.create(params) - end - - describe '#perform' do - it 'creates and updates closed and merged events' do - timestamp = Time.new('2018-01-01 12:00:00').utc - - create_merge_request(1) - create_merge_request_event(1, target_id: 1, action: 3, updated_at: timestamp) - create_merge_request_event(2, target_id: 1, action: 3, updated_at: timestamp + 10.seconds) - - create_merge_request_event(3, target_id: 1, action: 7, updated_at: timestamp) - create_merge_request_event(4, target_id: 1, action: 7, updated_at: timestamp + 10.seconds) - - subject.perform(1, 1) - - merge_request = MergeRequest.first - - expect(merge_request.metrics).to have_attributes(latest_closed_by_id: user.id, - latest_closed_at: timestamp + 10.seconds, - merged_by_id: user.id) - end - end -end diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb deleted file mode 100644 index ff1bd9f7850..00000000000 --- a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb +++ /dev/null @@ -1,132 +0,0 @@ -require 'rails_helper' - -# rubocop:disable RSpec/FactoriesInMigrationSpecs -describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData, :migration, schema: 20171128214150 do - # commits_count attribute is added in a next migration - before do - allow_any_instance_of(MergeRequestDiff) - .to receive(:commits_count=).and_return(nil) - end - - describe '#perform' do - let(:mr_with_event) { create(:merge_request) } - let!(:merged_event) { create(:event, :merged, target: mr_with_event) } - let!(:closed_event) { create(:event, :closed, target: mr_with_event) } - - before do - # Make sure no metrics are created and kept through after_* callbacks. - mr_with_event.metrics.destroy! - end - - it 'inserts metrics and updates closed and merged events' do - subject.perform(mr_with_event.id, mr_with_event.id) - - mr_with_event.reload - - expect(mr_with_event.metrics).to have_attributes(latest_closed_by_id: closed_event.author_id, - merged_by_id: merged_event.author_id) - expect(mr_with_event.metrics.latest_closed_at.to_s).to eq(closed_event.updated_at.to_s) - end - end - - describe '#insert_metrics_for_range' do - let!(:mrs_without_metrics) { create_list(:merge_request, 3) } - let!(:mrs_with_metrics) { create_list(:merge_request, 2) } - - before do - # Make sure no metrics are created and kept through after_* callbacks. - mrs_without_metrics.each { |m| m.metrics.destroy! } - end - - it 'inserts merge_request_metrics for merge_requests without one' do - expect { subject.insert_metrics_for_range(MergeRequest.first.id, MergeRequest.last.id) } - .to change(MergeRequest::Metrics, :count).from(2).to(5) - - mrs_without_metrics.each do |mr_without_metrics| - expect(mr_without_metrics.reload.metrics).to be_present - end - end - - it 'does not inserts merge_request_metrics for MRs out of given range' do - expect { subject.insert_metrics_for_range(mrs_with_metrics.first.id, mrs_with_metrics.last.id) } - .not_to change(MergeRequest::Metrics, :count).from(2) - end - end - - describe '#update_metrics_with_events_data' do - context 'closed events data update' do - let(:users) { create_list(:user, 3) } - let(:mrs_with_event) { create_list(:merge_request, 3) } - - before do - create_list(:event, 2, :closed, author: users.first, target: mrs_with_event.first) - create_list(:event, 3, :closed, author: users.second, target: mrs_with_event.second) - create(:event, :closed, author: users.third, target: mrs_with_event.third) - end - - it 'migrates multiple MR metrics with closed event data' do - mr_without_event = create(:merge_request) - create(:event, :merged) - - subject.update_metrics_with_events_data(mrs_with_event.first.id, mrs_with_event.last.id) - - mrs_with_event.each do |mr_with_event| - latest_event = Event.where(action: 3, target: mr_with_event).last - - mr_with_event.metrics.reload - - expect(mr_with_event.metrics.latest_closed_by).to eq(latest_event.author) - expect(mr_with_event.metrics.latest_closed_at.to_s).to eq(latest_event.updated_at.to_s) - end - - expect(mr_without_event.metrics.reload).to have_attributes(latest_closed_by_id: nil, - latest_closed_at: nil) - end - - it 'does not updates metrics out of given range' do - out_of_range_mr = create(:merge_request) - create(:event, :closed, author: users.last, target: out_of_range_mr) - - expect { subject.perform(mrs_with_event.first.id, mrs_with_event.second.id) } - .not_to change { out_of_range_mr.metrics.reload.merged_by } - .from(nil) - end - end - - context 'merged events data update' do - let(:users) { create_list(:user, 3) } - let(:mrs_with_event) { create_list(:merge_request, 3) } - - before do - create_list(:event, 2, :merged, author: users.first, target: mrs_with_event.first) - create_list(:event, 3, :merged, author: users.second, target: mrs_with_event.second) - create(:event, :merged, author: users.third, target: mrs_with_event.third) - end - - it 'migrates multiple MR metrics with merged event data' do - mr_without_event = create(:merge_request) - create(:event, :merged) - - subject.update_metrics_with_events_data(mrs_with_event.first.id, mrs_with_event.last.id) - - mrs_with_event.each do |mr_with_event| - latest_event = Event.where(action: Event::MERGED, target: mr_with_event).last - - expect(mr_with_event.metrics.reload.merged_by).to eq(latest_event.author) - end - - expect(mr_without_event.metrics.reload).to have_attributes(merged_by_id: nil) - end - - it 'does not updates metrics out of given range' do - out_of_range_mr = create(:merge_request) - create(:event, :merged, author: users.last, target: out_of_range_mr) - - expect { subject.perform(mrs_with_event.first.id, mrs_with_event.second.id) } - .not_to change { out_of_range_mr.metrics.reload.merged_by } - .from(nil) - end - end - end -end -# rubocop:enable RSpec/FactoriesInMigrationSpecs diff --git a/spec/lib/gitlab/background_migration/redact_links_spec.rb b/spec/lib/gitlab/background_migration/redact_links_spec.rb deleted file mode 100644 index a40e68069cc..00000000000 --- a/spec/lib/gitlab/background_migration/redact_links_spec.rb +++ /dev/null @@ -1,96 +0,0 @@ -require 'spec_helper' - -describe Gitlab::BackgroundMigration::RedactLinks, :migration, schema: 20181014121030 do - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - let(:issues) { table(:issues) } - let(:notes) { table(:notes) } - let(:snippets) { table(:snippets) } - let(:users) { table(:users) } - let(:merge_requests) { table(:merge_requests) } - let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') } - let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') } - let(:user) { users.create!(email: 'test@example.com', projects_limit: 100, username: 'test') } - - def create_merge_request(id, params) - params.merge!(id: id, - target_project_id: project.id, - target_branch: 'master', - source_project_id: project.id, - source_branch: 'mr name', - title: "mr name#{id}") - - merge_requests.create(params) - end - - def create_issue(id, params) - params.merge!(id: id, title: "issue#{id}", project_id: project.id) - - issues.create(params) - end - - def create_note(id, params) - params[:id] = id - - notes.create(params) - end - - def create_snippet(id, params) - params.merge!(id: id, author_id: user.id) - - snippets.create(params) - end - - def create_resource(model, id, params) - send("create_#{model.name.underscore}", id, params) - end - - shared_examples_for 'redactable resource' do - it 'updates only matching texts' do - matching_text = 'some text /sent_notifications/00000000000000000000000000000000/unsubscribe more text' - redacted_text = 'some text /sent_notifications/REDACTED/unsubscribe more text' - create_resource(model, 1, { field => matching_text }) - create_resource(model, 2, { field => 'not matching text' }) - create_resource(model, 3, { field => matching_text }) - create_resource(model, 4, { field => redacted_text }) - create_resource(model, 5, { field => matching_text }) - - expected = { field => 'some text /sent_notifications/REDACTED/unsubscribe more text', - "#{field}_html" => nil } - expect_any_instance_of("Gitlab::BackgroundMigration::RedactLinks::#{model}".constantize).to receive(:update_columns).with(expected).and_call_original - - subject.perform(model, field, 2, 4) - - expect(model.where(field => matching_text).pluck(:id)).to eq [1, 5] - expect(model.find(3).reload[field]).to eq redacted_text - end - end - - context 'resource is Issue' do - it_behaves_like 'redactable resource' do - let(:model) { Issue } - let(:field) { :description } - end - end - - context 'resource is Merge Request' do - it_behaves_like 'redactable resource' do - let(:model) { MergeRequest } - let(:field) { :description } - end - end - - context 'resource is Note' do - it_behaves_like 'redactable resource' do - let(:model) { Note } - let(:field) { :note } - end - end - - context 'resource is Snippet' do - it_behaves_like 'redactable resource' do - let(:model) { Snippet } - let(:field) { :description } - end - end -end diff --git a/spec/lib/gitlab/background_migration/rollback_import_state_data_spec.rb b/spec/lib/gitlab/background_migration/rollback_import_state_data_spec.rb deleted file mode 100644 index cef3b6e4568..00000000000 --- a/spec/lib/gitlab/background_migration/rollback_import_state_data_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'spec_helper' - -describe Gitlab::BackgroundMigration::RollbackImportStateData, :migration, schema: 20180502134117 do - let(:migration) { described_class.new } - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - let(:import_state) { table(:project_mirror_data) } - - before do - namespaces.create(id: 1, name: 'gitlab-org', path: 'gitlab-org') - - projects.create!(id: 1, namespace_id: 1, name: 'gitlab1', import_url: generate(:url)) - projects.create!(id: 2, namespace_id: 1, name: 'gitlab2', path: 'gitlab2', import_url: generate(:url)) - - import_state.create!(id: 1, project_id: 1, status: :started, last_error: "foo") - import_state.create!(id: 2, project_id: 2, status: :failed) - - allow(BackgroundMigrationWorker).to receive(:perform_in) - end - - it "creates new import_state records with project's import data" do - migration.perform(1, 2) - - expect(projects.first.import_status).to eq("started") - expect(projects.second.import_status).to eq("failed") - expect(projects.first.import_error).to eq("foo") - end -end diff --git a/spec/lib/gitlab/background_migration/schedule_diff_files_deletion_spec.rb b/spec/lib/gitlab/background_migration/schedule_diff_files_deletion_spec.rb deleted file mode 100644 index ec8ba0ce127..00000000000 --- a/spec/lib/gitlab/background_migration/schedule_diff_files_deletion_spec.rb +++ /dev/null @@ -1,43 +0,0 @@ -require 'spec_helper' - -describe Gitlab::BackgroundMigration::ScheduleDiffFilesDeletion, :migration, :sidekiq, schema: 20180619121030 do - describe '#perform' do - let(:merge_request_diffs) { table(:merge_request_diffs) } - let(:merge_requests) { table(:merge_requests) } - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - - before do - stub_const("#{described_class.name}::DIFF_BATCH_SIZE", 3) - - namespaces.create!(id: 1, name: 'gitlab', path: 'gitlab') - projects.create!(id: 1, namespace_id: 1, name: 'gitlab', path: 'gitlab') - - merge_requests.create!(id: 1, target_project_id: 1, source_project_id: 1, target_branch: 'feature', source_branch: 'master', state: 'merged') - - merge_request_diffs.create!(id: 1, merge_request_id: 1, state: 'collected') - merge_request_diffs.create!(id: 2, merge_request_id: 1, state: 'empty') - merge_request_diffs.create!(id: 3, merge_request_id: 1, state: 'without_files') - merge_request_diffs.create!(id: 4, merge_request_id: 1, state: 'collected') - merge_request_diffs.create!(id: 5, merge_request_id: 1, state: 'collected') - merge_request_diffs.create!(id: 6, merge_request_id: 1, state: 'collected') - merge_request_diffs.create!(id: 7, merge_request_id: 1, state: 'collected') - - merge_requests.update(1, latest_merge_request_diff_id: 7) - end - - it 'correctly schedules diff file deletion workers' do - Sidekiq::Testing.fake! do - Timecop.freeze do - described_class.new.perform - - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, [1, 4, 5]) - - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(10.minutes, [6]) - - expect(BackgroundMigrationWorker.jobs.size).to eq(2) - end - end - end - end -end diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb index 5a5c071c639..ac4612dda92 100644 --- a/spec/lib/gitlab/ci/ansi2html_spec.rb +++ b/spec/lib/gitlab/ci/ansi2html_spec.rb @@ -4,11 +4,11 @@ describe Gitlab::Ci::Ansi2html do subject { described_class } it "prints non-ansi as-is" do - expect(convert_html("Hello")).to eq('Hello') + expect(convert_html("Hello")).to eq('<span class="">Hello</span>') end it "strips non-color-changing control sequences" do - expect(convert_html("Hello \e[2Kworld")).to eq('Hello world') + expect(convert_html("Hello \e[2Kworld")).to eq('<span class="">Hello world</span>') end it "prints simply red" do @@ -32,7 +32,7 @@ describe Gitlab::Ci::Ansi2html do end it "resets colors after red on blue" do - expect(convert_html("\e[31;44mHello\e[0m world")).to eq('<span class="term-fg-red term-bg-blue">Hello</span> world') + expect(convert_html("\e[31;44mHello\e[0m world")).to eq('<span class="term-fg-red term-bg-blue">Hello</span><span class=""> world</span>') end it "performs color change from red/blue to yellow/blue" do @@ -44,11 +44,11 @@ describe Gitlab::Ci::Ansi2html do end it "performs color change from red/blue to reset to yellow/green" do - expect(convert_html("\e[31;44mHello\e[0m \e[33;42mworld")).to eq('<span class="term-fg-red term-bg-blue">Hello</span> <span class="term-fg-yellow term-bg-green">world</span>') + expect(convert_html("\e[31;44mHello\e[0m \e[33;42mworld")).to eq('<span class="term-fg-red term-bg-blue">Hello</span><span class=""> </span><span class="term-fg-yellow term-bg-green">world</span>') end it "ignores unsupported codes" do - expect(convert_html("\e[51mHello\e[0m")).to eq('Hello') + expect(convert_html("\e[51mHello\e[0m")).to eq('<span class="">Hello</span>') end it "prints light red" do @@ -72,8 +72,8 @@ describe Gitlab::Ci::Ansi2html do end it "resets bold text" do - expect(convert_html("\e[1mHello\e[21m world")).to eq('<span class="term-bold">Hello</span> world') - expect(convert_html("\e[1mHello\e[22m world")).to eq('<span class="term-bold">Hello</span> world') + expect(convert_html("\e[1mHello\e[21m world")).to eq('<span class="term-bold">Hello</span><span class=""> world</span>') + expect(convert_html("\e[1mHello\e[22m world")).to eq('<span class="term-bold">Hello</span><span class=""> world</span>') end it "prints italic text" do @@ -81,7 +81,7 @@ describe Gitlab::Ci::Ansi2html do end it "resets italic text" do - expect(convert_html("\e[3mHello\e[23m world")).to eq('<span class="term-italic">Hello</span> world') + expect(convert_html("\e[3mHello\e[23m world")).to eq('<span class="term-italic">Hello</span><span class=""> world</span>') end it "prints underlined text" do @@ -89,7 +89,7 @@ describe Gitlab::Ci::Ansi2html do end it "resets underlined text" do - expect(convert_html("\e[4mHello\e[24m world")).to eq('<span class="term-underline">Hello</span> world') + expect(convert_html("\e[4mHello\e[24m world")).to eq('<span class="term-underline">Hello</span><span class=""> world</span>') end it "prints concealed text" do @@ -97,7 +97,7 @@ describe Gitlab::Ci::Ansi2html do end it "resets concealed text" do - expect(convert_html("\e[8mHello\e[28m world")).to eq('<span class="term-conceal">Hello</span> world') + expect(convert_html("\e[8mHello\e[28m world")).to eq('<span class="term-conceal">Hello</span><span class=""> world</span>') end it "prints crossed-out text" do @@ -105,7 +105,7 @@ describe Gitlab::Ci::Ansi2html do end it "resets crossed-out text" do - expect(convert_html("\e[9mHello\e[29m world")).to eq('<span class="term-cross">Hello</span> world') + expect(convert_html("\e[9mHello\e[29m world")).to eq('<span class="term-cross">Hello</span><span class=""> world</span>') end it "can print 256 xterm fg colors" do @@ -137,15 +137,15 @@ describe Gitlab::Ci::Ansi2html do end it "prints <" do - expect(convert_html("<")).to eq('<') + expect(convert_html("<")).to eq('<span class=""><</span>') end it "replaces newlines with line break tags" do - expect(convert_html("\n")).to eq('<br>') + expect(convert_html("\n")).to eq('<span class=""><br/><span class=""></span></span>') end it "groups carriage returns with newlines" do - expect(convert_html("\r\n")).to eq('<br>') + expect(convert_html("\r\n")).to eq('<span class=""><br/><span class=""></span></span>') end describe "incremental update" do @@ -166,14 +166,14 @@ describe Gitlab::Ci::Ansi2html do let(:pre_text) { "\e[1mHello" } let(:pre_html) { "<span class=\"term-bold\">Hello</span>" } let(:text) { "\e[1mWorld" } - let(:html) { "<span class=\"term-bold\"></span><span class=\"term-bold\">World</span>" } + let(:html) { "<span class=\"term-bold\">World</span>" } it_behaves_like 'stateable converter' end context "with split sequence" do let(:pre_text) { "\e[1m" } - let(:pre_html) { "<span class=\"term-bold\"></span>" } + let(:pre_html) { "" } let(:text) { "Hello" } let(:html) { "<span class=\"term-bold\">Hello</span>" } @@ -182,7 +182,7 @@ describe Gitlab::Ci::Ansi2html do context "with partial sequence" do let(:pre_text) { "Hello\e" } - let(:pre_html) { "Hello" } + let(:pre_html) { "<span class=\"\">Hello</span>" } let(:text) { "[1m World" } let(:html) { "<span class=\"term-bold\"> World</span>" } @@ -191,9 +191,9 @@ describe Gitlab::Ci::Ansi2html do context 'with new line' do let(:pre_text) { "Hello\r" } - let(:pre_html) { "Hello\r" } + let(:pre_html) { "<span class=\"\">Hello\r</span>" } let(:text) { "\nWorld" } - let(:html) { "<br>World" } + let(:html) { "<span class=\"\"><br/><span class=\"\">World</span></span>" } it_behaves_like 'stateable converter' end @@ -207,20 +207,20 @@ describe Gitlab::Ci::Ansi2html do let(:section_start) { "section_start:#{section_start_time.to_i}:#{section_name}\r\033[0K"} let(:section_end) { "section_end:#{section_end_time.to_i}:#{section_name}\r\033[0K"} let(:section_start_html) do - '<div class="hidden" data-action="start"'\ - " data-timestamp=\"#{section_start_time.to_i}\" data-section=\"#{section_name}\">"\ - "#{section_start[0...-5]}</div>" + '<div class="js-section-start fa fa-caret-down append-right-8 cursor-pointer"' \ + " data-timestamp=\"#{section_start_time.to_i}\" data-section=\"#{class_name(section_name)}\"" \ + ' role="button"></div>' end let(:section_end_html) do - '<div class="hidden" data-action="end"'\ - " data-timestamp=\"#{section_end_time.to_i}\" data-section=\"#{section_name}\">"\ - "#{section_end[0...-5]}</div>" + "<div class=\"section-end\" data-section=\"#{class_name(section_name)}\"></div>" end shared_examples 'forbidden char in section_name' do it 'ignores sections' do text = "#{section_start}Some text#{section_end}" - html = text.gsub("\033[0K", '').gsub('<', '<') + class_name_start = section_start.gsub("\033[0K", '').gsub('<', '<') + class_name_end = section_end.gsub("\033[0K", '').gsub('<', '<') + html = %{<span class="">#{class_name_start}Some text#{class_name_end}</span>} expect(convert_html(text)).to eq(html) end @@ -231,7 +231,11 @@ describe Gitlab::Ci::Ansi2html do it 'prints light red' do text = "#{section_start}\e[91mHello\e[0m\n#{section_end}" - html = %{#{section_start_html}<span class="term-fg-l-red">Hello</span><br>#{section_end_html}} + header = %{<span class="term-fg-l-red section js-section-header js-s-#{class_name(section_name)}">Hello</span>} + line_break = %{<span class="section js-section-header js-s-#{class_name(section_name)}"><br/></span>} + line = %{<span class="section line s_#{class_name(section_name)}"></span>} + empty_line = %{<span class="section js-s-#{class_name(section_name)}"></span>} + html = "#{section_start_html}#{header}#{line_break}#{line}#{empty_line}#{section_end_html}" expect(convert_html(text)).to eq(html) end @@ -294,4 +298,8 @@ describe Gitlab::Ci::Ansi2html do stream = StringIO.new(data) subject.convert(stream).html end + + def class_name(section) + subject::Converter.new.section_to_class_name(section) + end end diff --git a/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb b/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb index 4884d5f8ba4..4510b82ca9d 100644 --- a/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb +++ b/spec/lib/gitlab/ci/build/policy/kubernetes_spec.rb @@ -4,24 +4,14 @@ describe Gitlab::Ci::Build::Policy::Kubernetes do let(:pipeline) { create(:ci_pipeline, project: project) } context 'when kubernetes service is active' do - shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do - it 'is satisfied by a kubernetes pipeline' do - expect(described_class.new('active')) - .to be_satisfied_by(pipeline) - end - end - - context 'when user configured kubernetes from Integration > Kubernetes' do - let(:project) { create(:kubernetes_project) } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end - context 'when user configured kubernetes from CI/CD > Clusters' do let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:project) { cluster.project } - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' + it 'is satisfied by a kubernetes pipeline' do + expect(described_class.new('active')) + .to be_satisfied_by(pipeline) + end end end diff --git a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb index 5387863bd07..5ac5122e800 100644 --- a/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb +++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb @@ -35,9 +35,15 @@ describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do end context 'and a namespace is already created for this project' do - let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, project: build.project) } + let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster, project: build.project) } it { is_expected.to be_falsey } + + context 'and the service_account_token is blank' do + let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :without_token, cluster: cluster, project: build.project) } + + it { is_expected.to be_truthy } + end end context 'and cluster is project type' do diff --git a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb index 3debd42ac65..50cb45c39d1 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/build_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Gitlab::Ci::Pipeline::Chain::Build do set(:project) { create(:project, :repository) } - set(:user) { create(:user) } + set(:user) { create(:user, developer_projects: [project]) } let(:pipeline) { Ci::Pipeline.new } let(:variables_attributes) do diff --git a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb index 97da66d2bcc..a6fdec832a3 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/lexeme/matches_spec.rb @@ -50,21 +50,21 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do let(:left_value) { 'my-string' } let(:right_value) { Gitlab::UntrustedRegexp.new('something') } - it { is_expected.to eq(nil) } + it { is_expected.to eq(false) } end context 'when left and right match' do let(:left_value) { 'my-awesome-string' } let(:right_value) { Gitlab::UntrustedRegexp.new('awesome.string$') } - it { is_expected.to eq(3) } + it { is_expected.to eq(true) } end context 'when left is nil' do let(:left_value) { nil } let(:right_value) { Gitlab::UntrustedRegexp.new('pattern') } - it { is_expected.to eq(nil) } + it { is_expected.to eq(false) } end context 'when left is a multiline string and matches right' do @@ -78,7 +78,7 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') } - it { is_expected.to eq(24) } + it { is_expected.to eq(true) } end context 'when left is a multiline string and does not match right' do @@ -92,7 +92,7 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do let(:right_value) { Gitlab::UntrustedRegexp.new('text-string') } - it { is_expected.to eq(nil) } + it { is_expected.to eq(false) } end context 'when a matching pattern uses regex flags' do @@ -104,7 +104,7 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)awesome') } - it { is_expected.to eq(3) } + it { is_expected.to eq(true) } end context 'when a non-matching pattern uses regex flags' do @@ -116,7 +116,7 @@ describe Gitlab::Ci::Pipeline::Expression::Lexeme::Matches do let(:right_value) { Gitlab::UntrustedRegexp.new('(?i)terrible') } - it { is_expected.to eq(nil) } + it { is_expected.to eq(false) } end end end diff --git a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb index 057e2f3fbe8..a2c2e3653d5 100644 --- a/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/expression/statement_spec.rb @@ -41,17 +41,17 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do 'null == $UNDEFINED_VARIABLE' | true '$PRESENT_VARIABLE' | 'my variable' '$UNDEFINED_VARIABLE' | nil - "$PRESENT_VARIABLE =~ /var.*e$/" | 3 - '$PRESENT_VARIABLE =~ /va\r.*e$/' | nil - '$PRESENT_VARIABLE =~ /va\/r.*e$/' | nil - "$PRESENT_VARIABLE =~ /var.*e$/" | 3 - "$PRESENT_VARIABLE =~ /^var.*/" | nil - "$EMPTY_VARIABLE =~ /var.*/" | nil - "$UNDEFINED_VARIABLE =~ /var.*/" | nil - "$PRESENT_VARIABLE =~ /VAR.*/i" | 3 - '$PATH_VARIABLE =~ /path\/variable/' | 2 - '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/' | 0 - '$FULL_PATH_VARIABLE =~ /\\/path\\/variable\\/value$/' | 7 + "$PRESENT_VARIABLE =~ /var.*e$/" | true + '$PRESENT_VARIABLE =~ /va\r.*e$/' | false + '$PRESENT_VARIABLE =~ /va\/r.*e$/' | false + "$PRESENT_VARIABLE =~ /var.*e$/" | true + "$PRESENT_VARIABLE =~ /^var.*/" | false + "$EMPTY_VARIABLE =~ /var.*/" | false + "$UNDEFINED_VARIABLE =~ /var.*/" | false + "$PRESENT_VARIABLE =~ /VAR.*/i" | true + '$PATH_VARIABLE =~ /path\/variable/' | true + '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/' | true + '$FULL_PATH_VARIABLE =~ /\\/path\\/variable\\/value$/' | true '$PRESENT_VARIABLE != "my variable"' | false '"my variable" != $PRESENT_VARIABLE' | false '$PRESENT_VARIABLE != null' | true @@ -82,7 +82,7 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do '"string" && "string"' | 'string' 'null && null' | nil - '$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/' | nil + '$PRESENT_VARIABLE =~ /my var/ && $EMPTY_VARIABLE =~ /nope/' | false '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE' | 'my variable' '$EMPTY_VARIABLE == "" && $PRESENT_VARIABLE != "nope"' | true '$PRESENT_VARIABLE && $EMPTY_VARIABLE' | '' @@ -90,17 +90,17 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do '$UNDEFINED_VARIABLE && $EMPTY_VARIABLE' | nil '$UNDEFINED_VARIABLE && $PRESENT_VARIABLE' | nil - '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ && $PATH_VARIABLE =~ /path\/variable/' | 2 - '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ && $PATH_VARIABLE =~ /path\/variable/' | nil - '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ && $PATH_VARIABLE =~ /bad\/path\/variable/' | nil - '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ && $PATH_VARIABLE =~ /bad\/path\/variable/' | nil + '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ && $PATH_VARIABLE =~ /path\/variable/' | true + '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ && $PATH_VARIABLE =~ /path\/variable/' | false + '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ && $PATH_VARIABLE =~ /bad\/path\/variable/' | false + '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ && $PATH_VARIABLE =~ /bad\/path\/variable/' | false - '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ || $PATH_VARIABLE =~ /path\/variable/' | 0 - '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ || $PATH_VARIABLE =~ /path\/variable/' | 2 - '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ || $PATH_VARIABLE =~ /bad\/path\/variable/' | 0 - '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ || $PATH_VARIABLE =~ /bad\/path\/variable/' | nil + '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ || $PATH_VARIABLE =~ /path\/variable/' | true + '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ || $PATH_VARIABLE =~ /path\/variable/' | true + '$FULL_PATH_VARIABLE =~ /^\/a\/full\/path\/variable\/value$/ || $PATH_VARIABLE =~ /bad\/path\/variable/' | true + '$FULL_PATH_VARIABLE =~ /^\/a\/bad\/path\/variable\/value$/ || $PATH_VARIABLE =~ /bad\/path\/variable/' | false - '$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/' | 0 + '$PRESENT_VARIABLE =~ /my var/ || $EMPTY_VARIABLE =~ /nope/' | true '$EMPTY_VARIABLE == "" || $PRESENT_VARIABLE' | true '$PRESENT_VARIABLE != "nope" || $EMPTY_VARIABLE == ""' | true @@ -117,21 +117,6 @@ describe Gitlab::Ci::Pipeline::Expression::Statement do it "evaluates to `#{params[:value].inspect}`" do expect(subject.evaluate).to eq(value) end - - # This test is used to ensure that our parser - # returns exactly the same results as if we - # were evaluating using ruby's `eval` - context 'when using Ruby eval' do - let(:expression_ruby) do - expression - .gsub(/null/, 'nil') - .gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)/) { "variables['#{Regexp.last_match(1)}']" } - end - - it 'behaves exactly the same' do - expect(instance_eval(expression_ruby)).to eq(subject.evaluate) - end - end end context 'with the ci_variables_complex_expressions feature flag disabled' do diff --git a/spec/lib/gitlab/ci/trace/stream_spec.rb b/spec/lib/gitlab/ci/trace/stream_spec.rb index e45ea1c2528..35250632e86 100644 --- a/spec/lib/gitlab/ci/trace/stream_spec.rb +++ b/spec/lib/gitlab/ci/trace/stream_spec.rb @@ -64,7 +64,10 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do result = stream.html - expect(result).to eq("ヾ(´༎ຶД༎ຶ`)ノ<br><span class=\"term-fg-green\">許功蓋</span><br>") + expect(result).to eq( + "<span class=\"\">ヾ(´༎ຶД༎ຶ`)ノ<br/><span class=\"\"></span></span>"\ + "<span class=\"term-fg-green\">許功蓋</span><span class=\"\"><br/>"\ + "<span class=\"\"></span></span>") expect(result.encoding).to eq(Encoding.default_external) end end @@ -250,7 +253,7 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do it 'returns html content with state' do result = stream.html_with_state - expect(result.html).to eq("1234") + expect(result.html).to eq("<span class=\"\">1234</span>") end context 'follow-up state' do @@ -266,7 +269,7 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do result = stream.html_with_state(last_result.state) expect(result.append).to be_truthy - expect(result.html).to eq("5678") + expect(result.html).to eq("<span class=\"\">5678</span>") end end end @@ -302,11 +305,13 @@ describe Gitlab::Ci::Trace::Stream, :clean_gitlab_redis_cache do describe '#html' do shared_examples_for 'htmls' do it "returns html" do - expect(stream.html).to eq("12<br>34<br>56") + expect(stream.html).to eq( + "<span class=\"\">12<br/><span class=\"\">34<br/>"\ + "<span class=\"\">56</span></span></span>") end it "returns html for last line only" do - expect(stream.html(last_lines: 1)).to eq("56") + expect(stream.html(last_lines: 1)).to eq("<span class=\"\">56</span>") end end diff --git a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb index e8fc67acf05..c738cc49c1f 100644 --- a/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/code_stage_spec.rb @@ -4,5 +4,41 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec' describe Gitlab::CycleAnalytics::CodeStage do let(:stage_name) { :code } + let(:project) { create(:project) } + let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) } + let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) } + let!(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) } + let!(:mr_1) { create(:merge_request, source_project: project, created_at: 15.minutes.ago) } + let!(:mr_2) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'A') } + let!(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') } + let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) } + + before do + issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 45.minutes.ago) + issue_2.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago) + issue_3.metrics.update!(first_added_to_board_at: 60.minutes.ago, first_mentioned_in_commit_at: 40.minutes.ago) + create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_1) + create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2) + end + it_behaves_like 'base stage' + + describe '#median' do + around do |example| + Timecop.freeze { example.run } + end + + it 'counts median from issues with metrics' do + expect(stage.median).to eq(ISSUES_MEDIAN) + end + end + + describe '#events' do + it 'exposes merge requests that closes issues' do + result = stage.events + + expect(result.count).to eq(2) + expect(result.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title) + end + end end diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb index 397dd4e5d2c..f8b103c0fab 100644 --- a/spec/lib/gitlab/cycle_analytics/events_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb @@ -53,20 +53,28 @@ describe 'cycle analytics events' do describe '#plan_events' do let(:stage) { :plan } - it 'has a title' do - expect(events.first[:title]).not_to be_nil + before do + create_commit_referencing_issue(context) end - it 'has a sha short ID' do - expect(events.first[:short_sha]).not_to be_nil + it 'has the total time' do + expect(events.first[:total_time]).not_to be_empty + end + + it 'has a title' do + expect(events.first[:title]).to eq(context.title) end it 'has the URL' do - expect(events.first[:commit_url]).not_to be_nil + expect(events.first[:url]).not_to be_nil end - it 'has the total time' do - expect(events.first[:total_time]).not_to be_empty + it 'has an iid' do + expect(events.first[:iid]).to eq(context.iid.to_s) + end + + it 'has a created_at timestamp' do + expect(events.first[:created_at]).to end_with('ago') end it "has the author's URL" do @@ -78,12 +86,13 @@ describe 'cycle analytics events' do end it "has the author's name" do - expect(events.first[:author][:name]).not_to be_nil + expect(events.first[:author][:name]).to eq(context.author.name) end end describe '#code_events' do let(:stage) { :code } + let!(:merge_request) { MergeRequest.first } before do create_commit_referencing_issue(context) @@ -122,6 +131,7 @@ describe 'cycle analytics events' do let(:stage) { :test } let(:merge_request) { MergeRequest.first } + let!(:context) { create(:issue, project: project, created_at: 2.days.ago) } let!(:pipeline) do create(:ci_pipeline, @@ -137,6 +147,7 @@ describe 'cycle analytics events' do pipeline.run! pipeline.succeed! + merge_merge_requests_closing_issue(user, project, context) end it 'has the name' do @@ -180,6 +191,10 @@ describe 'cycle analytics events' do let(:stage) { :review } let!(:context) { create(:issue, project: project, created_at: 2.days.ago) } + before do + merge_merge_requests_closing_issue(user, project, context) + end + it 'has the total time' do expect(events.first[:total_time]).not_to be_empty end diff --git a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb index 3127f01989d..3b6af9cbaed 100644 --- a/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/issue_stage_spec.rb @@ -3,6 +3,37 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec' describe Gitlab::CycleAnalytics::IssueStage do let(:stage_name) { :issue } + let(:project) { create(:project) } + let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) } + let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) } + let!(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) } + let!(:issue_without_milestone) { create(:issue, project: project, created_at: 1.minute.ago) } + let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) } + + before do + issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago ) + issue_2.metrics.update!(first_added_to_board_at: 30.minutes.ago) + issue_3.metrics.update!(first_added_to_board_at: 15.minutes.ago) + end it_behaves_like 'base stage' + + describe '#median' do + around do |example| + Timecop.freeze { example.run } + end + + it 'counts median from issues with metrics' do + expect(stage.median).to eq(ISSUES_MEDIAN) + end + end + + describe '#events' do + it 'exposes issues with metrics' do + result = stage.events + + expect(result.count).to eq(3) + expect(result.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title, issue_3.title) + end + end end diff --git a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb index 4c715921ad6..506a8160412 100644 --- a/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/plan_stage_spec.rb @@ -3,6 +3,37 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec' describe Gitlab::CycleAnalytics::PlanStage do let(:stage_name) { :plan } + let(:project) { create(:project) } + let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) } + let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) } + let!(:issue_3) { create(:issue, project: project, created_at: 30.minutes.ago) } + let!(:issue_without_milestone) { create(:issue, project: project, created_at: 1.minute.ago) } + let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) } + + before do + issue_1.metrics.update!(first_associated_with_milestone_at: 60.minutes.ago, first_mentioned_in_commit_at: 10.minutes.ago) + issue_2.metrics.update!(first_added_to_board_at: 30.minutes.ago, first_mentioned_in_commit_at: 20.minutes.ago) + issue_3.metrics.update!(first_added_to_board_at: 15.minutes.ago) + end it_behaves_like 'base stage' + + describe '#median' do + around do |example| + Timecop.freeze { example.run } + end + + it 'counts median from issues with metrics' do + expect(stage.median).to eq(ISSUES_MEDIAN) + end + end + + describe '#events' do + it 'exposes issues with metrics' do + result = stage.events + + expect(result.count).to eq(2) + expect(result.map { |event| event[:title] }).to contain_exactly(issue_1.title, issue_2.title) + end + end end diff --git a/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb index 1412c8dfa08..f072a9644e8 100644 --- a/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/review_stage_spec.rb @@ -3,6 +3,43 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec' describe Gitlab::CycleAnalytics::ReviewStage do let(:stage_name) { :review } + let(:project) { create(:project) } + let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) } + let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) } + let!(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) } + let!(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) } + let!(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') } + let!(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') } + let!(:mr_4) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'C') } + let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) } + + before do + mr_1.metrics.update!(merged_at: 30.minutes.ago) + mr_2.metrics.update!(merged_at: 10.minutes.ago) + + create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_1) + create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2) + create(:merge_requests_closing_issues, merge_request: mr_3, issue: issue_3) + end it_behaves_like 'base stage' + + describe '#median' do + around do |example| + Timecop.freeze { example.run } + end + + it 'counts median from issues with metrics' do + expect(stage.median).to eq(ISSUES_MEDIAN) + end + end + + describe '#events' do + it 'exposes merge requests that close issues' do + result = stage.events + + expect(result.count).to eq(2) + expect(result.map { |event| event[:title] }).to contain_exactly(mr_1.title, mr_2.title) + end + end end diff --git a/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb index 08425acbfc8..1a4b572cc11 100644 --- a/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/shared_stage_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' shared_examples 'base stage' do + ISSUES_MEDIAN = 30.minutes.to_i + let(:stage) { described_class.new(project: double, options: {}) } before do diff --git a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb index 8154b3ac701..17d5fbb9733 100644 --- a/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/staging_stage_spec.rb @@ -4,5 +4,46 @@ require 'lib/gitlab/cycle_analytics/shared_stage_spec' describe Gitlab::CycleAnalytics::StagingStage do let(:stage_name) { :staging } + let(:project) { create(:project) } + let!(:issue_1) { create(:issue, project: project, created_at: 90.minutes.ago) } + let!(:issue_2) { create(:issue, project: project, created_at: 60.minutes.ago) } + let!(:issue_3) { create(:issue, project: project, created_at: 60.minutes.ago) } + let!(:mr_1) { create(:merge_request, :closed, source_project: project, created_at: 60.minutes.ago) } + let!(:mr_2) { create(:merge_request, :closed, source_project: project, created_at: 40.minutes.ago, source_branch: 'A') } + let!(:mr_3) { create(:merge_request, source_project: project, created_at: 10.minutes.ago, source_branch: 'B') } + let(:build_1) { create(:ci_build, project: project) } + let(:build_2) { create(:ci_build, project: project) } + + let(:stage) { described_class.new(project: project, options: { from: 2.days.ago, current_user: project.creator }) } + + before do + mr_1.metrics.update!(merged_at: 80.minutes.ago, first_deployed_to_production_at: 50.minutes.ago, pipeline_id: build_1.commit_id) + mr_2.metrics.update!(merged_at: 60.minutes.ago, first_deployed_to_production_at: 30.minutes.ago, pipeline_id: build_2.commit_id) + mr_3.metrics.update!(merged_at: 10.minutes.ago, first_deployed_to_production_at: 3.days.ago, pipeline_id: create(:ci_build, project: project).commit_id) + + create(:merge_requests_closing_issues, merge_request: mr_1, issue: issue_1) + create(:merge_requests_closing_issues, merge_request: mr_2, issue: issue_2) + create(:merge_requests_closing_issues, merge_request: mr_3, issue: issue_3) + end + it_behaves_like 'base stage' + + describe '#median' do + around do |example| + Timecop.freeze { example.run } + end + + it 'counts median from issues with metrics' do + expect(stage.median).to eq(ISSUES_MEDIAN) + end + end + + describe '#events' do + it 'exposes builds connected to merge request' do + result = stage.events + + expect(result.count).to eq(2) + expect(result.map { |event| event[:name] }).to contain_exactly(build_1.name, build_2.name) + end + end end diff --git a/spec/lib/gitlab/danger/helper_spec.rb b/spec/lib/gitlab/danger/helper_spec.rb index 22e52901758..c8e65a3e59d 100644 --- a/spec/lib/gitlab/danger/helper_spec.rb +++ b/spec/lib/gitlab/danger/helper_spec.rb @@ -162,7 +162,6 @@ describe Gitlab::Danger::Helper do 'db/foo' | :database 'qa/foo' | :qa - 'ee/db/foo' | :database 'ee/qa/foo' | :qa 'changelogs/foo' | :none diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 4e83b27e4a5..1e4c4c38f74 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -214,6 +214,23 @@ describe Gitlab::Database::MigrationHelpers do model.add_concurrent_foreign_key(:projects, :users, column: :user_id) end + it 'allows the use of a custom key name' do + expect(model).to receive(:add_foreign_key).with( + :projects, + :users, + column: :user_id, + on_delete: :cascade, + name: :foo + ) + + model.add_concurrent_foreign_key( + :projects, + :users, + column: :user_id, + name: :foo + ) + end + it 'does not create a foreign key if it exists already' do expect(model).to receive(:foreign_key_exists?).with(:projects, :users, column: :user_id).and_return(true) expect(model).not_to receive(:add_foreign_key) @@ -257,6 +274,16 @@ describe Gitlab::Database::MigrationHelpers do model.add_concurrent_foreign_key(:projects, :users, column: :user_id) end + + it 'allows the use of a custom key name' do + expect(model).to receive(:disable_statement_timeout).and_call_original + expect(model).to receive(:execute).with(/statement_timeout/) + expect(model).to receive(:execute).ordered.with(/NOT VALID/) + expect(model).to receive(:execute).ordered.with(/VALIDATE CONSTRAINT.+foo/) + expect(model).to receive(:execute).with(/RESET ALL/) + + model.add_concurrent_foreign_key(:projects, :users, column: :user_id, name: :foo) + end end end end diff --git a/spec/lib/gitlab/gpg/commit_spec.rb b/spec/lib/gitlab/gpg/commit_spec.rb index 8229f0eb794..47e6f5d4220 100644 --- a/spec/lib/gitlab/gpg/commit_spec.rb +++ b/spec/lib/gitlab/gpg/commit_spec.rb @@ -109,6 +109,89 @@ describe Gitlab::Gpg::Commit do end end + context 'valid key signed using recent version of Gnupg' do + let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User1.emails.first } + + let!(:user) { create(:user, email: GpgHelpers::User1.emails.first) } + + let!(:gpg_key) do + create :gpg_key, key: GpgHelpers::User1.public_key, user: user + end + + let!(:crypto) { instance_double(GPGME::Crypto) } + + before do + fake_signature = [ + GpgHelpers::User1.signed_commit_signature, + GpgHelpers::User1.signed_commit_base_data + ] + + allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily) + .with(Gitlab::Git::Repository, commit_sha) + .and_return(fake_signature) + end + + it 'returns a valid signature' do + verified_signature = double('verified-signature', fingerprint: GpgHelpers::User1.fingerprint, valid?: true) + allow(GPGME::Crypto).to receive(:new).and_return(crypto) + allow(crypto).to receive(:verify).and_return(verified_signature) + + signature = described_class.new(commit).signature + + expect(signature).to have_attributes( + commit_sha: commit_sha, + project: project, + gpg_key: gpg_key, + gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid, + gpg_key_user_name: GpgHelpers::User1.names.first, + gpg_key_user_email: GpgHelpers::User1.emails.first, + verification_status: 'verified' + ) + end + end + + context 'valid key signed using older version of Gnupg' do + let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User1.emails.first } + + let!(:user) { create(:user, email: GpgHelpers::User1.emails.first) } + + let!(:gpg_key) do + create :gpg_key, key: GpgHelpers::User1.public_key, user: user + end + + let!(:crypto) { instance_double(GPGME::Crypto) } + + before do + fake_signature = [ + GpgHelpers::User1.signed_commit_signature, + GpgHelpers::User1.signed_commit_base_data + ] + + allow(Gitlab::Git::Commit).to receive(:extract_signature_lazily) + .with(Gitlab::Git::Repository, commit_sha) + .and_return(fake_signature) + end + + it 'returns a valid signature' do + keyid = GpgHelpers::User1.fingerprint.last(16) + verified_signature = double('verified-signature', fingerprint: keyid, valid?: true) + allow(GPGME::Crypto).to receive(:new).and_return(crypto) + allow(crypto).to receive(:verify).and_return(verified_signature) + + signature = described_class.new(commit).signature + + expect(signature).to have_attributes( + commit_sha: commit_sha, + project: project, + gpg_key: gpg_key, + gpg_key_primary_keyid: GpgHelpers::User1.primary_keyid, + gpg_key_user_name: GpgHelpers::User1.names.first, + gpg_key_user_email: GpgHelpers::User1.emails.first, + verification_status: 'verified' + ) + end + end + context 'commit signed with a subkey' do let!(:commit) { create :commit, project: project, sha: commit_sha, committer_email: GpgHelpers::User3.emails.first } diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 2242543daad..7a250603b6b 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -20,6 +20,9 @@ issues: - timelogs - issue_assignees - closed_by +- epic_issue +- epic +- designs events: - author - project @@ -38,6 +41,7 @@ notes: - system_note_metadata - note_diff_file - suggestions +- review label_links: - target - label @@ -57,6 +61,7 @@ milestone: - merge_requests - participants - events +- boards snippets: - author - project @@ -103,6 +108,19 @@ merge_requests: - merge_request_assignees - suggestions - assignees +- reviews +- approval_rules +- approvals +- approvers +- approver_users +- approver_groups +- approved_by_users +- draft_notes +- merge_train +- blocks_as_blocker +- blocks_as_blockee +- blocking_merge_requests +- blocked_merge_requests merge_request_diff: - merge_request - merge_request_diff_commits @@ -135,6 +153,16 @@ ci_pipelines: - deployments - environments - chat_data +- source_pipeline +- source_bridge +- source_job +- sourced_pipelines +- triggered_by_pipeline +- triggered_pipelines +- downstream_bridges +- job_artifacts +- vulnerabilities_occurrence_pipelines +- vulnerabilities pipeline_variables: - pipeline stages: @@ -175,6 +203,8 @@ deploy_keys: services: - project - service_hook +- jira_tracker_data +- issue_tracker_data hooks: - project - web_hook_logs @@ -182,13 +212,18 @@ protected_branches: - project - merge_access_levels - push_access_levels +- unprotect_access_levels protected_tags: - project - create_access_levels merge_access_levels: - protected_branch +- user +- group push_access_levels: - protected_branch +- user +- group create_access_levels: - user - protected_tag @@ -323,6 +358,45 @@ project: - kubernetes_namespaces - error_tracking_setting - metrics_setting +- gitlab_slack_application_service +- github_service +- protected_environments +- mirror_user +- push_rule +- jenkins_service +- jenkins_deprecated_service +- index_status +- feature_usage +- approval_rules +- approvers +- approver_users +- pages_domains +- audit_events +- path_locks +- approver_groups +- repository_state +- source_pipelines +- sourced_pipelines +- prometheus_metrics +- vulnerabilities +- vulnerability_feedback +- vulnerability_identifiers +- vulnerability_scanners +- operations_feature_flags +- operations_feature_flags_client +- prometheus_alerts +- prometheus_alert_events +- software_license_policies +- project_registry +- packages +- package_files +- tracing_setting +- alerting_setting +- webide_pipelines +- reviews +- incident_management_setting +- merge_trains +- designs award_emoji: - awardable - user @@ -330,6 +404,7 @@ priorities: - label prometheus_metrics: - project +- prometheus_alerts timelogs: - issue - merge_request @@ -363,3 +438,34 @@ suggestions: - note metrics_setting: - project +protected_environments: +- project +- deploy_access_levels +deploy_access_levels: +- protected_environment +- user +- group +unprotect_access_levels: +- user +- protected_branch +- group +prometheus_alerts: +- project +- prometheus_alert_events +prometheus_alert_events: +- project +epic_issues: +- issue +- epic +tracing_setting: +- project +reviews: +- project +- merge_request +- author +- notes +incident_management_setting: +- project +merge_trains: +- project +- merge_request diff --git a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb index ddfbb020a55..fef84c87509 100644 --- a/spec/lib/gitlab/import_export/attribute_configuration_spec.rb +++ b/spec/lib/gitlab/import_export/attribute_configuration_spec.rb @@ -10,7 +10,7 @@ require 'spec_helper' describe 'Import/Export attribute configuration' do include ConfigurationHelper - let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys } + let(:config_hash) { Gitlab::ImportExport::Config.new.to_h.deep_stringify_keys } let(:relation_names) do names = names_from_tree(config_hash['project_tree']) @@ -23,9 +23,6 @@ describe 'Import/Export attribute configuration' do let(:safe_attributes_file) { 'spec/lib/gitlab/import_export/safe_model_attributes.yml' } let(:safe_model_attributes) { YAML.load_file(safe_attributes_file) } - let(:ee_safe_attributes_file) { 'ee/spec/lib/gitlab/import_export/safe_model_attributes.yml' } - let(:ee_safe_model_attributes) { File.exist?(ee_safe_attributes_file) ? YAML.load_file(ee_safe_attributes_file) : {} } - it 'has no new columns' do relation_names.each do |relation_name| relation_class = relation_class_for_name(relation_name) @@ -34,10 +31,6 @@ describe 'Import/Export attribute configuration' do current_attributes = parsed_attributes(relation_name, relation_attributes) safe_attributes = safe_model_attributes[relation_class.to_s].dup || [] - ee_safe_model_attributes[relation_class.to_s].to_a.each do |attribute| - safe_attributes << attribute - end - expect(safe_attributes).not_to be_nil, "Expected exported class #{relation_class} to exist in safe_model_attributes" new_attributes = current_attributes - safe_attributes @@ -51,8 +44,7 @@ describe 'Import/Export attribute configuration' do It looks like #{relation_class}, which is exported using the project Import/Export, has new attributes: #{new_attributes.join(',')} Please add the attribute(s) to SAFE_MODEL_ATTRIBUTES if you consider this can be exported. - #{"If the model/associations are EE-specific, use `#{File.expand_path(ee_safe_attributes_file)}`.\n" if ee_safe_model_attributes.any?} - Otherwise, please blacklist the attribute(s) in IMPORT_EXPORT_CONFIG by adding it to its correspondent + Please blacklist the attribute(s) in IMPORT_EXPORT_CONFIG by adding it to its correspondent model in the +excluded_attributes+ section. SAFE_MODEL_ATTRIBUTES: #{File.expand_path(safe_attributes_file)} diff --git a/spec/lib/gitlab/import_export/config_spec.rb b/spec/lib/gitlab/import_export/config_spec.rb new file mode 100644 index 00000000000..cf396dba382 --- /dev/null +++ b/spec/lib/gitlab/import_export/config_spec.rb @@ -0,0 +1,164 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe Gitlab::ImportExport::Config do + let(:yaml_file) { described_class.new } + + describe '#to_h' do + context 'when using CE' do + before do + allow(yaml_file) + .to receive(:merge?) + .and_return(false) + end + + it 'just returns the parsed Hash without the EE section' do + expected = YAML.load_file(Gitlab::ImportExport.config_file) + expected.delete('ee') + + expect(yaml_file.to_h).to eq(expected) + end + end + + context 'when using EE' do + before do + allow(yaml_file) + .to receive(:merge?) + .and_return(true) + end + + it 'merges the EE project tree into the CE project tree' do + allow(yaml_file) + .to receive(:parse_yaml) + .and_return({ + 'project_tree' => [ + { + 'issues' => [ + :id, + :title, + { 'notes' => [:id, :note, { 'author' => [:name] }] } + ] + } + ], + 'ee' => { + 'project_tree' => [ + { + 'issues' => [ + :description, + { 'notes' => [:date, { 'author' => [:email] }] } + ] + }, + { 'foo' => [{ 'bar' => %i[baz] }] } + ] + } + }) + + expect(yaml_file.to_h).to eq({ + 'project_tree' => [ + { + 'issues' => [ + :id, + :title, + { + 'notes' => [ + :id, + :note, + { 'author' => [:name, :email] }, + :date + ] + }, + :description + ] + }, + { 'foo' => [{ 'bar' => %i[baz] }] } + ] + }) + end + + it 'merges the excluded attributes list' do + allow(yaml_file) + .to receive(:parse_yaml) + .and_return({ + 'project_tree' => [], + 'excluded_attributes' => { + 'project' => %i[id title], + 'notes' => %i[id] + }, + 'ee' => { + 'project_tree' => [], + 'excluded_attributes' => { + 'project' => %i[date], + 'foo' => %i[bar baz] + } + } + }) + + expect(yaml_file.to_h).to eq({ + 'project_tree' => [], + 'excluded_attributes' => { + 'project' => %i[id title date], + 'notes' => %i[id], + 'foo' => %i[bar baz] + } + }) + end + + it 'merges the included attributes list' do + allow(yaml_file) + .to receive(:parse_yaml) + .and_return({ + 'project_tree' => [], + 'included_attributes' => { + 'project' => %i[id title], + 'notes' => %i[id] + }, + 'ee' => { + 'project_tree' => [], + 'included_attributes' => { + 'project' => %i[date], + 'foo' => %i[bar baz] + } + } + }) + + expect(yaml_file.to_h).to eq({ + 'project_tree' => [], + 'included_attributes' => { + 'project' => %i[id title date], + 'notes' => %i[id], + 'foo' => %i[bar baz] + } + }) + end + + it 'merges the methods list' do + allow(yaml_file) + .to receive(:parse_yaml) + .and_return({ + 'project_tree' => [], + 'methods' => { + 'project' => %i[id title], + 'notes' => %i[id] + }, + 'ee' => { + 'project_tree' => [], + 'methods' => { + 'project' => %i[date], + 'foo' => %i[bar baz] + } + } + }) + + expect(yaml_file.to_h).to eq({ + 'project_tree' => [], + 'methods' => { + 'project' => %i[id title date], + 'notes' => %i[id], + 'foo' => %i[bar baz] + } + }) + end + end + end +end diff --git a/spec/lib/gitlab/import_export/model_configuration_spec.rb b/spec/lib/gitlab/import_export/model_configuration_spec.rb index 2e28f978c3a..5ed9fef1597 100644 --- a/spec/lib/gitlab/import_export/model_configuration_spec.rb +++ b/spec/lib/gitlab/import_export/model_configuration_spec.rb @@ -6,7 +6,7 @@ require 'spec_helper' describe 'Import/Export model configuration' do include ConfigurationHelper - let(:config_hash) { YAML.load_file(Gitlab::ImportExport.config_file).deep_stringify_keys } + let(:config_hash) { Gitlab::ImportExport::Config.new.to_h.deep_stringify_keys } let(:model_names) do names = names_from_tree(config_hash['project_tree']) @@ -16,26 +16,9 @@ describe 'Import/Export model configuration' do # - User, Author... Models we do not care about for checking models names.flatten.uniq - %w(milestones labels user author) + ['project'] end - let(:ce_models_yml) { 'spec/lib/gitlab/import_export/all_models.yml' } - let(:ce_models_hash) { YAML.load_file(ce_models_yml) } - - let(:ee_models_yml) { 'ee/spec/lib/gitlab/import_export/all_models.yml' } - let(:ee_models_hash) { File.exist?(ee_models_yml) ? YAML.load_file(ee_models_yml) : {} } - + let(:all_models_yml) { 'spec/lib/gitlab/import_export/all_models.yml' } + let(:all_models_hash) { YAML.load_file(all_models_yml) } let(:current_models) { setup_models } - let(:all_models_hash) do - all_models_hash = ce_models_hash.dup - - all_models_hash.each do |model, associations| - associations.concat(ee_models_hash[model] || []) - end - - ee_models_hash.each do |model, associations| - all_models_hash[model] ||= associations - end - - all_models_hash - end it 'has no new models' do model_names.each do |model_name| @@ -59,8 +42,7 @@ describe 'Import/Export model configuration' do If you think this model should be included in the export, please add it to `#{Gitlab::ImportExport.config_file}`. - Definitely add it to `#{File.expand_path(ce_models_yml)}` - #{"or `#{File.expand_path(ee_models_yml)}` if the model/associations are EE-specific\n" if ee_models_hash.any?} + Definitely add it to `#{File.expand_path(all_models_yml)}` to signal that you've handled this error and to prevent it from showing up in the future. MSG end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 9093d21647a..a406c25b1d8 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -24,6 +24,7 @@ Issue: - weight - time_estimate - relative_position +- service_desk_reply_to - last_edited_at - last_edited_by_id - discussion_locked @@ -68,6 +69,7 @@ Note: - resolved_by_push - discussion_id - original_discussion_id +- review_id LabelLink: - id - label_id @@ -144,6 +146,8 @@ ProjectMember: - invite_accepted_at - requested_at - expires_at +- ldap +- override User: - id - username @@ -316,6 +320,7 @@ CommitStatus: - protected - failure_reason - scheduled_at +- upstream_pipeline_id Ci::Variable: - id - project_id @@ -491,6 +496,17 @@ Project: - printing_merge_request_link_enabled - resolve_outdated_diff_discussions - build_allow_git_fetch +- merge_requests_template +- merge_requests_rebase_enabled +- approvals_before_merge +- merge_requests_author_approval +- reset_approvals_on_push +- disable_overriding_approvers_per_merge_request +- merge_requests_ff_only_enabled +- issues_template +- repository_size_limit +- sync_time +- service_desk_enabled - last_repository_updated_at - ci_config_path - delete_error @@ -498,7 +514,13 @@ Project: - merge_requests_rebase_enabled - jobs_cache_index - external_authorization_classification_label +- external_webhook_token - pages_https_only +- merge_requests_disable_committers_approval +- merge_requests_require_code_owner_approval +- require_password_to_approve +ProjectTracingSetting: +- external_url Author: - name ProjectFeature: @@ -519,12 +541,24 @@ ProtectedBranch::MergeAccessLevel: - access_level - created_at - updated_at +- user_id +- group_id ProtectedBranch::PushAccessLevel: - id - protected_branch_id - access_level - created_at - updated_at +- user_id +- group_id +ProtectedBranch::UnprotectAccessLevel: +- id +- protected_branch_id +- access_level +- created_at +- updated_at +- user_id +- group_id ProtectedTag::CreateAccessLevel: - id - protected_tag_id @@ -587,6 +621,12 @@ PrometheusMetric: - group - common - identifier +PrometheusAlert: +- threshold +- operator +- environment_id +- project_id +- prometheus_metric_id Badge: - id - link_url @@ -598,6 +638,20 @@ Badge: - type ProjectCiCdSetting: - group_runners_enabled +ProtectedEnvironment: +- id +- project_id +- name +- created_at +- updated_at +ProtectedEnvironment::DeployAccessLevel: +- id +- protected_environment_id +- access_level +- created_at +- updated_at +- user_id +- group_id ResourceLabelEvent: - id - action diff --git a/spec/migrations/active_record/schema_spec.rb b/spec/migrations/active_record/schema_spec.rb index 9d35b3cd642..fbf5d387d0e 100644 --- a/spec/migrations/active_record/schema_spec.rb +++ b/spec/migrations/active_record/schema_spec.rb @@ -5,8 +5,7 @@ require 'spec_helper' describe ActiveRecord::Schema do let(:latest_migration_timestamp) do - migrations_paths = %w[db ee/db] - .product(%w[migrate post_migrate]) + migrations_paths = %w[db/migrate db/post_migrate] .map { |path| Rails.root.join(*path, '*') } migrations = Dir[*migrations_paths] diff --git a/spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb b/spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb new file mode 100644 index 00000000000..cad10ba30ef --- /dev/null +++ b/spec/migrations/add_unique_constraint_to_approvals_user_id_and_merge_request_id_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190404143330_add_unique_constraint_to_approvals_user_id_and_merge_request_id.rb') + +describe AddUniqueConstraintToApprovalsUserIdAndMergeRequestId, :migration do + let(:migration) { described_class.new } + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:merge_requests) { table(:merge_requests) } + let(:approvals) { table(:approvals) } + + describe '#up' do + before do + namespaces.create(id: 1, name: 'ns', path: 'ns') + projects.create(id: 1, namespace_id: 1) + merge_requests.create(id: 1, target_branch: 'master', source_branch: 'feature-1', target_project_id: 1) + merge_requests.create(id: 2, target_branch: 'master', source_branch: 'feature-2', target_project_id: 1) + end + + it 'deletes duplicate records and keeps the first one' do + first_approval = approvals.create(id: 1, merge_request_id: 1, user_id: 1) + approvals.create(id: 2, merge_request_id: 1, user_id: 1) + + migration.up + + expect(approvals.all.to_a).to contain_exactly(first_approval) + end + + it 'does not delete unique records' do + unique_approvals = [ + approvals.create(id: 1, merge_request_id: 1, user_id: 1), + approvals.create(id: 2, merge_request_id: 1, user_id: 2), + approvals.create(id: 3, merge_request_id: 2, user_id: 1) + ] + + migration.up + + expect(approvals.all.to_a).to contain_exactly(*unique_approvals) + end + + it 'creates unique index' do + migration.up + + expect(migration.index_exists?(:approvals, [:user_id, :merge_request_id], unique: true)).to be_truthy + end + end + + describe '#down' do + it 'removes unique index' do + migration.up + migration.down + + expect(migration.index_exists?(:approvals, [:user_id, :merge_request_id], unique: true)).to be_falsey + end + end +end diff --git a/spec/migrations/clean_up_for_members_spec.rb b/spec/migrations/clean_up_for_members_spec.rb deleted file mode 100644 index 1a79f94cf0d..00000000000 --- a/spec/migrations/clean_up_for_members_spec.rb +++ /dev/null @@ -1,83 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'migrate', '20171216111734_clean_up_for_members.rb') - -describe CleanUpForMembers, :migration do - before do - stub_feature_flags(enforced_sso: false) - end - - let(:migration) { described_class.new } - let(:groups) { table(:namespaces) } - let!(:group_member) { create_group_member } - let!(:unbinded_group_member) { create_group_member } - let!(:invited_group_member) { create_group_member(true) } - let!(:not_valid_group_member) { create_group_member } - let!(:project_member) { create_project_member } - let!(:invited_project_member) { create_project_member(true) } - let!(:unbinded_project_member) { create_project_member } - let!(:not_valid_project_member) { create_project_member } - - it 'removes members without proper user_id' do - unbinded_group_member.update_column(:user_id, nil) - not_valid_group_member.update_column(:user_id, 9999) - unbinded_project_member.update_column(:user_id, nil) - not_valid_project_member.update_column(:user_id, 9999) - - migrate! - - expect(Member.all).not_to include(unbinded_group_member, not_valid_group_member, unbinded_project_member, not_valid_project_member) - expect(Member.all).to include(group_member, invited_group_member, project_member, invited_project_member) - end - - def create_group_member(invited = false) - fill_member(GroupMember.new(source_id: create_group.id, source_type: 'Namespace'), invited) - end - - def create_project_member(invited = false) - fill_member(ProjectMember.new(project: create_project), invited) - end - - def fill_member(member_object, invited) - member_object.tap do |m| - m.access_level = 40 - m.notification_level = 3 - - if invited - m.user_id = nil - m.invite_token = 'xxx' - m.invite_email = 'email@email.com' - else - m.user_id = create_user.id - end - - m.save - end - - member_object - end - - def create_group - name = FFaker::Lorem.characters(10) - - groups.create!(type: 'Group', name: name, path: name.downcase.gsub(/\s/, '_')) - end - - def create_project - name = FFaker::Lorem.characters(10) - creator = create_user - - Project.create(name: name, - path: name.downcase.gsub(/\s/, '_'), - namespace: creator.namespace, - creator: creator) - end - - def create_user - User.create(email: FFaker::Internet.email, - password: '12345678', - name: FFaker::Name.name, - username: FFaker::Internet.user_name, - confirmed_at: Time.now, - confirmation_token: nil) - end -end diff --git a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb deleted file mode 100644 index 651341906c2..00000000000 --- a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb +++ /dev/null @@ -1,34 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170502101023_cleanup_namespaceless_pending_delete_projects.rb') - -describe CleanupNamespacelessPendingDeleteProjects, :migration, schema: 20180222043024 do - let(:projects) { table(:projects) } - - before do - # Stub after_save callbacks that will fail when Project has no namespace - allow_any_instance_of(Project).to receive(:ensure_storage_path_exists).and_return(nil) - allow_any_instance_of(Project).to receive(:update_project_statistics).and_return(nil) - end - - describe '#up' do - it 'only cleans up pending delete projects' do - projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1) - projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ee', namespace_id: 2, pending_delete: true) - project = Project.new(pending_delete: true, namespace_id: nil) - project.save(validate: false) - - expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id]]) - - described_class.new.up - end - - it 'does nothing when no pending delete projects without namespace found' do - projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1) - projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ee', namespace_id: 2, pending_delete: true) - - expect(NamespacelessProjectDestroyWorker).not_to receive(:bulk_perform_async) - - described_class.new.up - end - end -end diff --git a/spec/migrations/create_missing_namespace_for_internal_users_spec.rb b/spec/migrations/create_missing_namespace_for_internal_users_spec.rb index ac3a4b1f68f..3fd4c5bc8d6 100644 --- a/spec/migrations/create_missing_namespace_for_internal_users_spec.rb +++ b/spec/migrations/create_missing_namespace_for_internal_users_spec.rb @@ -6,37 +6,32 @@ describe CreateMissingNamespaceForInternalUsers, :migration do let(:namespaces) { table(:namespaces) } let(:routes) { table(:routes) } - internal_user_types = [:ghost] - internal_user_types << :support_bot if ActiveRecord::Base.connection.column_exists?(:users, :support_bot) - - internal_user_types.each do |attr| - context "for #{attr} user" do - let(:internal_user) do - users.create!(email: 'test@example.com', projects_limit: 100, username: 'test', attr => true) - end + context "for ghost user" do + let(:internal_user) do + users.create!(email: 'test@example.com', projects_limit: 100, username: 'test', ghost: true) + end - it 'creates the missing namespace' do - expect(namespaces.find_by(owner_id: internal_user.id)).to be_nil + it 'creates the missing namespace' do + expect(namespaces.find_by(owner_id: internal_user.id)).to be_nil - migrate! + migrate! - namespace = Namespace.find_by(type: nil, owner_id: internal_user.id) - route = namespace.route + namespace = Namespace.find_by(type: nil, owner_id: internal_user.id) + route = namespace.route - expect(namespace.path).to eq(route.path) - expect(namespace.name).to eq(route.name) - end + expect(namespace.path).to eq(route.path) + expect(namespace.name).to eq(route.name) + end - it 'sets notification email' do - users.update(internal_user.id, notification_email: nil) + it 'sets notification email' do + users.update(internal_user.id, notification_email: nil) - expect(users.find(internal_user.id).notification_email).to be_nil + expect(users.find(internal_user.id).notification_email).to be_nil - migrate! + migrate! - user = users.find(internal_user.id) - expect(user.notification_email).to eq(user.email) - end + user = users.find(internal_user.id) + expect(user.notification_email).to eq(user.email) end end end diff --git a/spec/migrations/delete_inconsistent_internal_id_records_spec.rb b/spec/migrations/delete_inconsistent_internal_id_records_spec.rb deleted file mode 100644 index 58b8b4a16f0..00000000000 --- a/spec/migrations/delete_inconsistent_internal_id_records_spec.rb +++ /dev/null @@ -1,161 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20180723130817_delete_inconsistent_internal_id_records.rb') - -describe DeleteInconsistentInternalIdRecords, :migration do - let!(:namespace) { table(:namespaces).create!(name: 'test', path: 'test') } - let!(:project1) { table(:projects).create!(namespace_id: namespace.id) } - let!(:project2) { table(:projects).create!(namespace_id: namespace.id) } - let!(:project3) { table(:projects).create!(namespace_id: namespace.id) } - - let(:internal_ids) { table(:internal_ids) } - let(:internal_id_query) { ->(project) { InternalId.where(usage: InternalId.usages[scope.to_s.tableize], project_id: project.id) } } - - let(:create_models) do - [project1, project2, project3].each do |project| - 3.times do |i| - attributes = required_attributes.merge(project_id: project.id, - iid: i.succ) - - table(scope.to_s.pluralize).create!(attributes) - end - end - end - - shared_examples_for 'deleting inconsistent internal_id records' do - before do - create_models - - [project1, project2, project3].each do |project| - internal_ids.create!(project_id: project.id, usage: InternalId.usages[scope.to_s.tableize], last_value: 3) - end - - internal_id_query.call(project1).first.tap do |iid| - iid.last_value = iid.last_value - 2 - # This is an inconsistent record - iid.save! - end - - internal_id_query.call(project3).first.tap do |iid| - iid.last_value = iid.last_value + 2 - # This is a consistent record - iid.save! - end - end - - it "deletes inconsistent records" do - expect { migrate! }.to change { internal_id_query.call(project1).size }.from(1).to(0) - end - - it "retains consistent records" do - expect { migrate! }.not_to change { internal_id_query.call(project2).size } - end - - it "retains consistent records, especially those with a greater last_value" do - expect { migrate! }.not_to change { internal_id_query.call(project3).size } - end - end - - context 'for issues' do - let(:scope) { :issue } - let(:required_attributes) { {} } - - it_behaves_like 'deleting inconsistent internal_id records' - end - - context 'for merge_requests' do - let(:scope) { :merge_request } - - let(:create_models) do - [project1, project2, project3].each do |project| - 3.times do |i| - table(:merge_requests).create!( - target_project_id: project.id, - source_project_id: project.id, - target_branch: 'master', - source_branch: j.to_s, - iid: i.succ - ) - end - end - end - - it_behaves_like 'deleting inconsistent internal_id records' - end - - context 'for deployments' do - let(:scope) { :deployment } - let(:deployments) { table(:deployments) } - - let(:create_models) do - 3.times { |i| deployments.create!(project_id: project1.id, iid: i, environment_id: 1, ref: 'master', sha: 'a', tag: false) } - 3.times { |i| deployments.create!(project_id: project2.id, iid: i, environment_id: 1, ref: 'master', sha: 'a', tag: false) } - 3.times { |i| deployments.create!(project_id: project3.id, iid: i, environment_id: 1, ref: 'master', sha: 'a', tag: false) } - end - - it_behaves_like 'deleting inconsistent internal_id records' - end - - context 'for milestones (by project)' do - let(:scope) { :milestone } - let(:required_attributes) { { title: 'test' } } - - it_behaves_like 'deleting inconsistent internal_id records' - end - - context 'for ci_pipelines' do - let(:scope) { :ci_pipeline } - let(:required_attributes) { { ref: 'test' } } - - it_behaves_like 'deleting inconsistent internal_id records' - end - - context 'for milestones (by group)' do - # milestones (by group) is a little different than most of the other models - let(:groups) { table(:namespaces) } - let(:group1) { groups.create(name: 'Group 1', type: 'Group', path: 'group_1') } - let(:group2) { groups.create(name: 'Group 2', type: 'Group', path: 'group_2') } - let(:group3) { groups.create(name: 'Group 2', type: 'Group', path: 'group_3') } - - let(:internal_id_query) { ->(group) { InternalId.where(usage: InternalId.usages['milestones'], namespace_id: group.id) } } - - before do - [group1, group2, group3].each do |group| - 3.times do |i| - table(:milestones).create!( - group_id: group.id, - title: 'test', - iid: i.succ - ) - end - - internal_ids.create!(namespace_id: group.id, usage: InternalId.usages['milestones'], last_value: 3) - end - - internal_id_query.call(group1).first.tap do |iid| - iid.last_value = iid.last_value - 2 - # This is an inconsistent record - iid.save! - end - - internal_id_query.call(group3).first.tap do |iid| - iid.last_value = iid.last_value + 2 - # This is a consistent record - iid.save! - end - end - - it "deletes inconsistent records" do - expect { migrate! }.to change { internal_id_query.call(group1).size }.from(1).to(0) - end - - it "retains consistent records" do - expect { migrate! }.not_to change { internal_id_query.call(group2).size } - end - - it "retains consistent records, especially those with a greater last_value" do - expect { migrate! }.not_to change { internal_id_query.call(group3).size } - end - end -end diff --git a/spec/migrations/enqueue_delete_diff_files_workers_spec.rb b/spec/migrations/enqueue_delete_diff_files_workers_spec.rb deleted file mode 100644 index 6bae870920c..00000000000 --- a/spec/migrations/enqueue_delete_diff_files_workers_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20180619121030_enqueue_delete_diff_files_workers.rb') - -describe EnqueueDeleteDiffFilesWorkers, :migration, :sidekiq do - it 'correctly schedules diff files deletion schedulers' do - Sidekiq::Testing.fake! do - expect(BackgroundMigrationWorker) - .to receive(:perform_async) - .with(described_class::SCHEDULER) - .and_call_original - - migrate! - - expect(BackgroundMigrationWorker.jobs.size).to eq(1) - end - end -end diff --git a/spec/migrations/enqueue_redact_links_spec.rb b/spec/migrations/enqueue_redact_links_spec.rb deleted file mode 100644 index a5da76977b7..00000000000 --- a/spec/migrations/enqueue_redact_links_spec.rb +++ /dev/null @@ -1,42 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20181014121030_enqueue_redact_links.rb') - -describe EnqueueRedactLinks, :migration, :sidekiq do - let(:merge_requests) { table(:merge_requests) } - let(:issues) { table(:issues) } - let(:notes) { table(:notes) } - let(:projects) { table(:projects) } - let(:namespaces) { table(:namespaces) } - let(:snippets) { table(:snippets) } - let(:users) { table(:users) } - let(:user) { users.create!(email: 'test@example.com', projects_limit: 100, username: 'test') } - - before do - stub_const("#{described_class.name}::BATCH_SIZE", 1) - - text = 'some text /sent_notifications/00000000000000000000000000000000/unsubscribe more text' - group = namespaces.create!(name: 'gitlab', path: 'gitlab') - project = projects.create!(namespace_id: group.id) - - merge_requests.create!(id: 1, target_project_id: project.id, source_project_id: project.id, target_branch: 'feature', source_branch: 'master', description: text) - issues.create!(id: 1, description: text) - notes.create!(id: 1, note: text) - notes.create!(id: 2, note: text) - snippets.create!(id: 1, description: text, author_id: user.id) - end - - it 'correctly schedules background migrations' do - Sidekiq::Testing.fake! do - Timecop.freeze do - migrate! - - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, "Note", "note", 1, 1) - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(10.minutes, "Note", "note", 2, 2) - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, "Issue", "description", 1, 1) - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, "MergeRequest", "description", 1, 1) - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, "Snippet", "description", 1, 1) - expect(BackgroundMigrationWorker.jobs.size).to eq 5 - end - end - end -end diff --git a/spec/migrations/migrate_import_attributes_data_from_projects_to_project_mirror_data_spec.rb b/spec/migrations/migrate_import_attributes_data_from_projects_to_project_mirror_data_spec.rb deleted file mode 100644 index 972c6dffc6f..00000000000 --- a/spec/migrations/migrate_import_attributes_data_from_projects_to_project_mirror_data_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20180502134117_migrate_import_attributes_data_from_projects_to_project_mirror_data.rb') - -describe MigrateImportAttributesDataFromProjectsToProjectMirrorData, :sidekiq, :migration do - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - let(:import_state) { table(:project_mirror_data) } - - before do - stub_const("#{described_class}::BATCH_SIZE", 1) - namespaces.create(id: 1, name: 'gitlab-org', path: 'gitlab-org') - - projects.create!(id: 1, namespace_id: 1, name: 'gitlab1', - path: 'gitlab1', import_error: "foo", import_status: :started, - import_url: generate(:url)) - projects.create!(id: 2, namespace_id: 1, name: 'gitlab2', - path: 'gitlab2', import_error: "bar", import_status: :failed, - import_url: generate(:url)) - projects.create!(id: 3, namespace_id: 1, name: 'gitlab3', path: 'gitlab3', import_status: :none, import_url: generate(:url)) - end - - it 'schedules delayed background migrations in batches in bulk' do - Sidekiq::Testing.fake! do - Timecop.freeze do - expect(projects.where.not(import_status: :none).count).to eq(2) - - subject.up - - expect(BackgroundMigrationWorker.jobs.size).to eq 2 - expect(described_class::UP_MIGRATION).to be_scheduled_delayed_migration(5.minutes, 1, 1) - expect(described_class::UP_MIGRATION).to be_scheduled_delayed_migration(10.minutes, 2, 2) - end - end - end - - describe '#down' do - before do - import_state.create!(id: 1, project_id: 1, status: :started) - import_state.create!(id: 2, project_id: 2, status: :started) - end - - it 'schedules delayed background migrations in batches in bulk for rollback' do - Sidekiq::Testing.fake! do - Timecop.freeze do - expect(import_state.where.not(status: :none).count).to eq(2) - - subject.down - - expect(BackgroundMigrationWorker.jobs.size).to eq 2 - expect(described_class::DOWN_MIGRATION).to be_scheduled_delayed_migration(5.minutes, 1, 1) - expect(described_class::DOWN_MIGRATION).to be_scheduled_delayed_migration(10.minutes, 2, 2) - end - end - end - end -end diff --git a/spec/migrations/migrate_k8s_service_integration_spec.rb b/spec/migrations/migrate_k8s_service_integration_spec.rb new file mode 100644 index 00000000000..4dd0c09632a --- /dev/null +++ b/spec/migrations/migrate_k8s_service_integration_spec.rb @@ -0,0 +1,161 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190517153211_migrate_k8s_service_integration.rb') + +describe MigrateK8sServiceIntegration, :migration do + context 'template service' do + context 'with namespace' do + let!(:service) do + MigrateK8sServiceIntegration::Service.create!( + active: true, + template: true, + category: 'deployment', + type: 'KubernetesService', + properties: "{\"namespace\":\"prod\",\"api_url\":\"https://sample.kubernetes.com\",\"ca_pem\":\"ca_pem-sample\",\"token\":\"token-sample\"}" + ) + end + + let(:cluster) { MigrateK8sServiceIntegration::Cluster.instance_type.last! } + let(:platform) { cluster.platform_kubernetes } + + it 'migrates the KubernetesService template to Platform::Kubernetes' do + expect { migrate! }.to change { MigrateK8sServiceIntegration::Cluster.count }.by(1) + + expect(cluster).to be_enabled + expect(cluster).to be_user + expect(cluster).not_to be_managed + expect(cluster.environment_scope).to eq('*') + expect(platform.api_url).to eq('https://sample.kubernetes.com') + expect(platform.ca_cert).to eq('ca_pem-sample') + expect(platform.namespace).to eq('prod') + expect(platform.token).to eq('token-sample') + end + end + + context 'without namespace' do + let!(:service) do + MigrateK8sServiceIntegration::Service.create!( + active: true, + template: true, + category: 'deployment', + type: 'KubernetesService', + properties: "{\"namespace\":\"\",\"api_url\":\"https://sample.kubernetes.com\",\"ca_pem\":\"ca_pem-sample\",\"token\":\"token-sample\"}" + ) + end + + let(:cluster) { MigrateK8sServiceIntegration::Cluster.instance_type.last! } + let(:platform) { cluster.platform_kubernetes } + + it 'migrates the KubernetesService template to Platform::Kubernetes' do + expect { migrate! }.to change { MigrateK8sServiceIntegration::Cluster.count }.by(1) + + expect(cluster).to be_enabled + expect(cluster).to be_user + expect(cluster).not_to be_managed + expect(cluster.environment_scope).to eq('*') + expect(platform.api_url).to eq('https://sample.kubernetes.com') + expect(platform.ca_cert).to eq('ca_pem-sample') + expect(platform.namespace).to be_nil + expect(platform.token).to eq('token-sample') + end + end + + context 'with nullified parameters' do + let!(:service) do + MigrateK8sServiceIntegration::Service.create!( + active: true, + template: true, + category: 'deployment', + type: 'KubernetesService', + properties: "{}" + ) + end + + it 'does not migrate the KubernetesService' do + expect { migrate! }.not_to change { MigrateK8sServiceIntegration::Cluster.count } + end + end + + context 'when disabled' do + let!(:service) do + MigrateK8sServiceIntegration::Service.create!( + active: false, + template: true, + category: 'deployment', + type: 'KubernetesService', + properties: "{\"namespace\":\"prod\",\"api_url\":\"https://sample.kubernetes.com\",\"ca_pem\":\"ca_pem-sample\",\"token\":\"token-sample\"}" + ) + end + + let(:cluster) { MigrateK8sServiceIntegration::Cluster.instance_type.last! } + let(:platform) { cluster.platform_kubernetes } + + it 'migrates the KubernetesService template to Platform::Kubernetes' do + expect { migrate! }.to change { MigrateK8sServiceIntegration::Cluster.count }.by(1) + + expect(cluster).not_to be_enabled + expect(cluster).to be_user + expect(cluster).not_to be_managed + expect(cluster.environment_scope).to eq('*') + expect(platform.api_url).to eq('https://sample.kubernetes.com') + expect(platform.ca_cert).to eq('ca_pem-sample') + expect(platform.namespace).to eq('prod') + expect(platform.token).to eq('token-sample') + end + end + + context 'when an instance cluster already exists' do + let!(:service) do + MigrateK8sServiceIntegration::Service.create!( + active: true, + template: true, + category: 'deployment', + type: 'KubernetesService', + properties: "{\"namespace\":\"prod\",\"api_url\":\"https://sample.kubernetes.com\",\"ca_pem\":\"ca_pem-sample\",\"token\":\"token-sample\"}" + ) + end + + let!(:existing_cluster) do + MigrateK8sServiceIntegration::Cluster.create!( + name: 'test-cluster', + cluster_type: :instance_type, + managed: true, + provider_type: :user, + platform_type: :kubernetes + ) + end + let(:new_cluster) { MigrateK8sServiceIntegration::Cluster.instance_type.last! } + let(:platform) { new_cluster.platform_kubernetes } + + it 'migrates the KubernetesService template to disabled Platform::Kubernetes' do + expect { migrate! }.to change { MigrateK8sServiceIntegration::Cluster.count }.by(1) + + expect(new_cluster).not_to be_enabled + expect(new_cluster).to be_user + expect(new_cluster).not_to be_managed + expect(new_cluster.environment_scope).to eq('*') + expect(platform.api_url).to eq('https://sample.kubernetes.com') + expect(platform.ca_cert).to eq('ca_pem-sample') + expect(platform.namespace).to eq('prod') + expect(platform.token).to eq('token-sample') + end + end + end + + context 'non-template service' do + let!(:service) do + MigrateK8sServiceIntegration::Service.create!( + active: true, + template: false, + category: 'deployment', + type: 'KubernetesService', + properties: "{\"namespace\":\"prod\",\"api_url\":\"https://sample.kubernetes.com\",\"ca_pem\":\"ca_pem-sample\",\"token\":\"token-sample\"}" + ) + end + + it 'does not migrate the KubernetesService' do + expect { migrate! }.not_to change { MigrateK8sServiceIntegration::Cluster.count } + end + end +end diff --git a/spec/migrations/migrate_remaining_mr_metrics_populating_background_migration_spec.rb b/spec/migrations/migrate_remaining_mr_metrics_populating_background_migration_spec.rb deleted file mode 100644 index 47dab18183c..00000000000 --- a/spec/migrations/migrate_remaining_mr_metrics_populating_background_migration_spec.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20180521162137_migrate_remaining_mr_metrics_populating_background_migration.rb') - -describe MigrateRemainingMrMetricsPopulatingBackgroundMigration, :migration, :sidekiq do - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - let(:mrs) { table(:merge_requests) } - - before do - namespaces.create!(id: 1, name: 'foo', path: 'foo') - projects.create!(id: 123, name: 'gitlab1', path: 'gitlab1', namespace_id: 1) - projects.create!(id: 456, name: 'gitlab2', path: 'gitlab2', namespace_id: 1) - projects.create!(id: 789, name: 'gitlab3', path: 'gitlab3', namespace_id: 1) - mrs.create!(title: 'foo', target_branch: 'target', source_branch: 'source', target_project_id: 123) - mrs.create!(title: 'bar', target_branch: 'target', source_branch: 'source', target_project_id: 456) - mrs.create!(title: 'kux', target_branch: 'target', source_branch: 'source', target_project_id: 789) - end - - it 'correctly schedules background migrations' do - stub_const("#{described_class.name}::BATCH_SIZE", 2) - - Sidekiq::Testing.fake! do - Timecop.freeze do - migrate! - - expect(described_class::MIGRATION) - .to be_scheduled_delayed_migration(10.minutes, mrs.first.id, mrs.second.id) - - expect(described_class::MIGRATION) - .to be_scheduled_delayed_migration(20.minutes, mrs.third.id, mrs.third.id) - - expect(BackgroundMigrationWorker.jobs.size).to eq(2) - end - end - end -end diff --git a/spec/migrations/populate_mr_metrics_with_events_data_spec.rb b/spec/migrations/populate_mr_metrics_with_events_data_spec.rb deleted file mode 100644 index 291a52b904d..00000000000 --- a/spec/migrations/populate_mr_metrics_with_events_data_spec.rb +++ /dev/null @@ -1,47 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20181204154019_populate_mr_metrics_with_events_data.rb') - -describe PopulateMrMetricsWithEventsData, :migration, :sidekiq do - let(:namespaces) { table(:namespaces) } - let(:projects) { table(:projects) } - let(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab-org') } - let(:project) { projects.create(namespace_id: namespace.id, name: 'foo') } - let(:merge_requests) { table(:merge_requests) } - - def create_merge_request(id) - params = { - id: id, - target_project_id: project.id, - target_branch: 'master', - source_project_id: project.id, - source_branch: 'mr name', - title: "mr name#{id}" - } - - merge_requests.create!(params) - end - - it 'correctly schedules background migrations' do - create_merge_request(1) - create_merge_request(2) - create_merge_request(3) - - stub_const("#{described_class.name}::BATCH_SIZE", 2) - - Sidekiq::Testing.fake! do - Timecop.freeze do - migrate! - - expect(described_class::MIGRATION) - .to be_scheduled_delayed_migration(8.minutes, 1, 2) - - expect(described_class::MIGRATION) - .to be_scheduled_delayed_migration(16.minutes, 3, 3) - - expect(BackgroundMigrationWorker.jobs.size).to eq(2) - end - end - end -end diff --git a/spec/migrations/populate_project_statistics_packages_size_spec.rb b/spec/migrations/populate_project_statistics_packages_size_spec.rb new file mode 100644 index 00000000000..4ad91342f25 --- /dev/null +++ b/spec/migrations/populate_project_statistics_packages_size_spec.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190418132125_populate_project_statistics_packages_size.rb') + +describe PopulateProjectStatisticsPackagesSize, :migration do + let(:project_statistics) { table(:project_statistics) } + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:packages) { table(:packages_packages) } + let(:package_files) { table(:packages_package_files) } + + let(:file_size) { 1.kilobyte } + let(:repo_size) { 2.megabytes } + let(:lfs_size) { 3.gigabytes } + let(:artifacts_size) { 4.terabytes } + let(:storage_size) { repo_size + lfs_size + artifacts_size } + + let(:namespace) { namespaces.create(name: 'foo', path: 'foo') } + let(:package) { packages.create!(project_id: project.id, name: 'a package', package_type: 1) } + let(:project) { projects.create!(namespace_id: namespace.id) } + + let!(:statistics) { project_statistics.create!(project_id: project.id, namespace_id: namespace.id, storage_size: storage_size, repository_size: repo_size, lfs_objects_size: lfs_size, build_artifacts_size: artifacts_size) } + let!(:package_file) { package_files.create!(package_id: package.id, file: 'a file.txt', file_name: 'a file.txt', size: file_size)} + + it 'backfills ProjectStatistics packages_size' do + expect { migrate! } + .to change { statistics.reload.packages_size } + .from(nil).to(file_size) + end + + it 'updates ProjectStatistics storage_size' do + expect { migrate! } + .to change { statistics.reload.storage_size } + .by(file_size) + end +end diff --git a/spec/migrations/populate_rule_type_on_approval_merge_request_rules_spec.rb b/spec/migrations/populate_rule_type_on_approval_merge_request_rules_spec.rb new file mode 100644 index 00000000000..99dfb165173 --- /dev/null +++ b/spec/migrations/populate_rule_type_on_approval_merge_request_rules_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190520201748_populate_rule_type_on_approval_merge_request_rules.rb') + +describe PopulateRuleTypeOnApprovalMergeRequestRules, :migration do + let(:migration) { described_class.new } + + describe '#up' do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:merge_requests) { table(:merge_requests) } + let(:approval_rules) { table(:approval_merge_request_rules) } + + # We use integers here since at the time of writing CE does not yet have the + # appropriate models and enum definitions. + let(:regular_rule_type) { 1 } + let(:code_owner_rule_type) { 2 } + + before do + namespaces.create!(id: 11, name: 'gitlab', path: 'gitlab') + projects.create!(id: 101, namespace_id: 11, name: 'gitlab', path: 'gitlab') + merge_requests.create!(id: 1, target_project_id: 101, source_project_id: 101, target_branch: 'feature', source_branch: 'master') + + approval_rules.create!(id: 1, merge_request_id: 1, name: "Default", code_owner: false, rule_type: regular_rule_type) + approval_rules.create!(id: 2, merge_request_id: 1, name: "Code Owners", code_owner: true, rule_type: regular_rule_type) + end + + it 'backfills ApprovalMergeRequestRules code_owner rule_type' do + expect(approval_rules.where(rule_type: regular_rule_type).pluck(:id)).to contain_exactly(1, 2) + expect(approval_rules.where(rule_type: code_owner_rule_type).pluck(:id)).to be_empty + + migrate! + + expect(approval_rules.where(rule_type: regular_rule_type).pluck(:id)).to contain_exactly(1) + expect(approval_rules.where(rule_type: code_owner_rule_type).pluck(:id)).to contain_exactly(2) + end + end +end diff --git a/spec/migrations/remove_orphaned_label_links_spec.rb b/spec/migrations/remove_orphaned_label_links_spec.rb deleted file mode 100644 index e8c44c141c3..00000000000 --- a/spec/migrations/remove_orphaned_label_links_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20180906051323_remove_orphaned_label_links.rb') - -describe RemoveOrphanedLabelLinks, :migration do - let(:label_links) { table(:label_links) } - let(:labels) { table(:labels) } - - let(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - let(:label) { create_label } - - before do - # This migration was created before we introduced ProjectCiCdSetting#default_git_depth - allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth).and_return(nil) - allow_any_instance_of(ProjectCiCdSetting).to receive(:default_git_depth=).and_return(0) - end - - context 'add foreign key on label_id' do - let!(:label_link_with_label) { create_label_link(label_id: label.id) } - let!(:label_link_without_label) { create_label_link(label_id: nil) } - - it 'removes orphaned labels without corresponding label' do - expect { migrate! }.to change { LabelLink.count }.from(2).to(1) - end - - it 'does not remove entries with valid label_id' do - expect { migrate! }.not_to change { label_link_with_label.reload } - end - end - - def create_label(**opts) - labels.create!( - project_id: project.id, - **opts - ) - end - - def create_label_link(**opts) - label_links.create!( - target_id: 1, - target_type: 'Issue', - **opts - ) - end -end diff --git a/spec/migrations/remove_soft_removed_objects_spec.rb b/spec/migrations/remove_soft_removed_objects_spec.rb deleted file mode 100644 index d0bde98b80e..00000000000 --- a/spec/migrations/remove_soft_removed_objects_spec.rb +++ /dev/null @@ -1,99 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20171207150343_remove_soft_removed_objects.rb') - -describe RemoveSoftRemovedObjects, :migration do - describe '#up' do - let!(:groups) do - table(:namespaces).tap do |t| - t.inheritance_column = nil - end - end - - let!(:routes) do - table(:routes).tap do |t| - t.inheritance_column = nil - end - end - - it 'removes various soft removed objects' do - 5.times do - create_with_deleted_at(:issue) - end - - regular_issue = create(:issue) # rubocop:disable RSpec/FactoriesInMigrationSpecs - - run_migration - - expect(Issue.count).to eq(1) - expect(Issue.first).to eq(regular_issue) - end - - it 'removes the temporary indexes once soft removed data has been removed' do - migration = described_class.new - - run_migration - - disable_migrations_output do - expect(migration.temporary_index_exists?(Issue)).to eq(false) - end - end - - it 'removes routes of soft removed personal namespaces' do - namespace = create_with_deleted_at(:namespace) - group = groups.create!(name: 'group', path: 'group_path', type: 'Group') - routes.create!(source_id: group.id, source_type: 'Group', name: 'group', path: 'group_path') - - expect(routes.where(source_id: namespace.id).exists?).to eq(true) - expect(routes.where(source_id: group.id).exists?).to eq(true) - - run_migration - - expect(routes.where(source_id: namespace.id).exists?).to eq(false) - expect(routes.where(source_id: group.id).exists?).to eq(true) - end - - it 'schedules the removal of soft removed groups' do - group = create_deleted_group - admin = create(:user, admin: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs - - expect_any_instance_of(GroupDestroyWorker) - .to receive(:perform) - .with(group.id, admin.id) - - run_migration - end - - it 'does not remove soft removed groups when no admin user could be found' do - create_deleted_group - - expect_any_instance_of(GroupDestroyWorker) - .not_to receive(:perform) - - run_migration - end - end - - def run_migration - disable_migrations_output do - migrate! - end - end - - def create_with_deleted_at(*args) - row = create(*args) # rubocop:disable RSpec/FactoriesInMigrationSpecs - - # We set "deleted_at" this way so we don't run into any column cache issues. - row.class.where(id: row.id).update_all(deleted_at: 1.year.ago) - - row - end - - def create_deleted_group - group = groups.create!(name: 'group', path: 'group_path', type: 'Group') - routes.create!(source_id: group.id, source_type: 'Group', name: 'group', path: 'group_path') - - groups.where(id: group.id).update_all(deleted_at: 1.year.ago) - - group - end -end diff --git a/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb b/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb deleted file mode 100644 index c4427910518..00000000000 --- a/spec/migrations/schedule_create_gpg_key_subkeys_from_gpg_keys_spec.rb +++ /dev/null @@ -1,31 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20171005130944_schedule_create_gpg_key_subkeys_from_gpg_keys') - -describe ScheduleCreateGpgKeySubkeysFromGpgKeys, :migration, :sidekiq do - before do - create(:gpg_key, id: 1, key: GpgHelpers::User1.public_key) # rubocop:disable RSpec/FactoriesInMigrationSpecs - create(:gpg_key, id: 2, key: GpgHelpers::User3.public_key) # rubocop:disable RSpec/FactoriesInMigrationSpecs - # Delete all subkeys so they can be recreated - GpgKeySubkey.destroy_all # rubocop: disable DestroyAll - end - - it 'correctly schedules background migrations' do - Sidekiq::Testing.fake! do - migrate! - - expect(described_class::MIGRATION).to be_scheduled_migration(1) - expect(described_class::MIGRATION).to be_scheduled_migration(2) - expect(BackgroundMigrationWorker.jobs.size).to eq(2) - end - end - - it 'schedules background migrations' do - perform_enqueued_jobs do - expect(GpgKeySubkey.count).to eq(0) - - migrate! - - expect(GpgKeySubkey.count).to eq(3) - end - end -end diff --git a/spec/migrations/schedule_merge_request_assignees_migration_progress_check_spec.rb b/spec/migrations/schedule_merge_request_assignees_migration_progress_check_spec.rb new file mode 100644 index 00000000000..bea985fabb1 --- /dev/null +++ b/spec/migrations/schedule_merge_request_assignees_migration_progress_check_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'spec_helper' +require Rails.root.join('db', 'post_migrate', '20190402224749_schedule_merge_request_assignees_migration_progress_check.rb') + +describe ScheduleMergeRequestAssigneesMigrationProgressCheck do + describe '#up' do + it 'schedules MergeRequestAssigneesMigrationProgressCheck background job' do + expect(BackgroundMigrationWorker).to receive(:perform_async) + .with(described_class::MIGRATION) + .and_call_original + + subject.up + end + end +end diff --git a/spec/migrations/schedule_merge_request_diff_migrations_spec.rb b/spec/migrations/schedule_merge_request_diff_migrations_spec.rb deleted file mode 100644 index 9f7e47bae0d..00000000000 --- a/spec/migrations/schedule_merge_request_diff_migrations_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170703130158_schedule_merge_request_diff_migrations') - -describe ScheduleMergeRequestDiffMigrations, :migration, :sidekiq do - let(:merge_request_diffs) { table(:merge_request_diffs) } - let(:merge_requests) { table(:merge_requests) } - let(:projects) { table(:projects) } - - before do - stub_const("#{described_class.name}::BATCH_SIZE", 1) - - projects.create!(id: 1, name: 'gitlab', path: 'gitlab') - - merge_requests.create!(id: 1, target_project_id: 1, source_project_id: 1, target_branch: 'feature', source_branch: 'master') - - merge_request_diffs.create!(id: 1, merge_request_id: 1, st_commits: YAML.dump([]), st_diffs: nil) - merge_request_diffs.create!(id: 2, merge_request_id: 1, st_commits: nil, st_diffs: YAML.dump([])) - merge_request_diffs.create!(id: 3, merge_request_id: 1, st_commits: nil, st_diffs: nil) - merge_request_diffs.create!(id: 4, merge_request_id: 1, st_commits: YAML.dump([]), st_diffs: YAML.dump([])) - end - - it 'correctly schedules background migrations' do - Sidekiq::Testing.fake! do - Timecop.freeze do - migrate! - - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(5.minutes, 1, 1) - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(10.minutes, 2, 2) - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(15.minutes, 4, 4) - expect(BackgroundMigrationWorker.jobs.size).to eq 3 - end - end - end - - it 'schedules background migrations' do - perform_enqueued_jobs do - non_empty = 'st_commits IS NOT NULL OR st_diffs IS NOT NULL' - - expect(merge_request_diffs.where(non_empty).count).to eq 3 - - migrate! - - expect(merge_request_diffs.where(non_empty).count).to eq 0 - end - end -end diff --git a/spec/migrations/schedule_merge_request_diff_migrations_take_two_spec.rb b/spec/migrations/schedule_merge_request_diff_migrations_take_two_spec.rb deleted file mode 100644 index 5bcb923af7b..00000000000 --- a/spec/migrations/schedule_merge_request_diff_migrations_take_two_spec.rb +++ /dev/null @@ -1,46 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20170926150348_schedule_merge_request_diff_migrations_take_two') - -describe ScheduleMergeRequestDiffMigrationsTakeTwo, :migration, :sidekiq do - let(:merge_request_diffs) { table(:merge_request_diffs) } - let(:merge_requests) { table(:merge_requests) } - let(:projects) { table(:projects) } - - before do - stub_const("#{described_class.name}::BATCH_SIZE", 1) - - projects.create!(id: 1, name: 'gitlab', path: 'gitlab') - - merge_requests.create!(id: 1, target_project_id: 1, source_project_id: 1, target_branch: 'feature', source_branch: 'master') - - merge_request_diffs.create!(id: 1, merge_request_id: 1, st_commits: YAML.dump([]), st_diffs: nil) - merge_request_diffs.create!(id: 2, merge_request_id: 1, st_commits: nil, st_diffs: YAML.dump([])) - merge_request_diffs.create!(id: 3, merge_request_id: 1, st_commits: nil, st_diffs: nil) - merge_request_diffs.create!(id: 4, merge_request_id: 1, st_commits: YAML.dump([]), st_diffs: YAML.dump([])) - end - - it 'correctly schedules background migrations' do - Sidekiq::Testing.fake! do - Timecop.freeze do - migrate! - - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(10.minutes, 1, 1) - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(20.minutes, 2, 2) - expect(described_class::MIGRATION).to be_scheduled_delayed_migration(30.minutes, 4, 4) - expect(BackgroundMigrationWorker.jobs.size).to eq 3 - end - end - end - - it 'migrates the data' do - perform_enqueued_jobs do - non_empty = 'st_commits IS NOT NULL OR st_diffs IS NOT NULL' - - expect(merge_request_diffs.where(non_empty).count).to eq 3 - - migrate! - - expect(merge_request_diffs.where(non_empty).count).to eq 0 - end - end -end diff --git a/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb b/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb deleted file mode 100644 index 578440cba20..00000000000 --- a/spec/migrations/schedule_populate_merge_request_metrics_with_events_data_spec.rb +++ /dev/null @@ -1,30 +0,0 @@ -require 'spec_helper' -require Rails.root.join('db', 'post_migrate', '20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb') - -describe SchedulePopulateMergeRequestMetricsWithEventsData, :migration, :sidekiq do - # commits_count attribute is added in a next migration - before do - allow_any_instance_of(MergeRequestDiff) - .to receive(:commits_count=).and_return(nil) - end - - let!(:mrs) { create_list(:merge_request, 3) } # rubocop:disable RSpec/FactoriesInMigrationSpecs - - it 'correctly schedules background migrations' do - stub_const("#{described_class.name}::BATCH_SIZE", 2) - - Sidekiq::Testing.fake! do - Timecop.freeze do - migrate! - - expect(described_class::MIGRATION) - .to be_scheduled_delayed_migration(10.minutes, mrs.first.id, mrs.second.id) - - expect(described_class::MIGRATION) - .to be_scheduled_delayed_migration(20.minutes, mrs.third.id, mrs.third.id) - - expect(BackgroundMigrationWorker.jobs.size).to eq(2) - end - end - end -end diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index a8701f0efa4..6ebc6337d50 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -962,7 +962,11 @@ describe Ci::Pipeline, :mailer do end context 'when kubernetes is active' do - shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do + context 'when user configured kubernetes from CI/CD > Clusters' do + let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:project) { cluster.project } + let(:pipeline) { build(:ci_pipeline, project: project, config: config) } + it 'returns seeds for kubernetes dependent job' do seeds = pipeline.stage_seeds @@ -971,21 +975,6 @@ describe Ci::Pipeline, :mailer do expect(seeds.dig(1, 0, :name)).to eq 'production' end end - - context 'when user configured kubernetes from Integration > Kubernetes' do - let(:project) { create(:kubernetes_project) } - let(:pipeline) { build(:ci_pipeline, project: project, config: config) } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end - - context 'when user configured kubernetes from CI/CD > Clusters' do - let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } - let(:pipeline) { build(:ci_pipeline, project: project, config: config) } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end end context 'when kubernetes is not active' do @@ -1679,23 +1668,13 @@ describe Ci::Pipeline, :mailer do describe '#has_kubernetes_active?' do context 'when kubernetes is active' do - shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do - it 'returns true' do - expect(pipeline).to have_kubernetes_active - end - end - - context 'when user configured kubernetes from Integration > Kubernetes' do - let(:project) { create(:kubernetes_project) } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end - context 'when user configured kubernetes from CI/CD > Clusters' do let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:project) { cluster.project } - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' + it 'returns true' do + expect(pipeline).to have_kubernetes_active + end end end @@ -2736,7 +2715,7 @@ describe Ci::Pipeline, :mailer do create(:ci_pipeline, project: project, sha: project.commit('master').sha, - user: create(:user)) + user: project.owner) end before do diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb index f206bb41f45..52661178d76 100644 --- a/spec/models/clusters/cluster_spec.rb +++ b/spec/models/clusters/cluster_spec.rb @@ -514,19 +514,43 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do subject { cluster.allow_user_defined_namespace? } context 'project type cluster' do - it { is_expected.to be_truthy } + context 'gitlab managed' do + it { is_expected.to be_truthy } + end + + context 'not managed' do + let(:cluster) { create(:cluster, :provided_by_gcp, managed: false) } + + it { is_expected.to be_truthy } + end end context 'group type cluster' do - let(:cluster) { create(:cluster, :provided_by_gcp, :group) } + context 'gitlab managed' do + let(:cluster) { create(:cluster, :provided_by_gcp, :group) } - it { is_expected.to be_falsey } + it { is_expected.to be_falsey } + end + + context 'not managed' do + let(:cluster) { create(:cluster, :provided_by_gcp, :group, managed: false) } + + it { is_expected.to be_truthy } + end end context 'instance type cluster' do - let(:cluster) { create(:cluster, :provided_by_gcp, :instance) } + context 'gitlab managed' do + let(:cluster) { create(:cluster, :provided_by_gcp, :instance) } - it { is_expected.to be_falsey } + it { is_expected.to be_falsey } + end + + context 'not managed' do + let(:cluster) { create(:cluster, :provided_by_gcp, :instance, managed: false) } + + it { is_expected.to be_truthy } + end end end @@ -555,6 +579,63 @@ describe Clusters::Cluster, :use_clean_rails_memory_store_caching do end end + describe '#find_or_initialize_kubernetes_namespace_for_project' do + let(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:project) { cluster.projects.first } + + subject { cluster.find_or_initialize_kubernetes_namespace_for_project(project) } + + context 'kubernetes namespace exists' do + context 'with no service account token' do + let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, project: project, cluster: cluster) } + + it { is_expected.to eq kubernetes_namespace } + end + + context 'with a service account token' do + let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, project: project, cluster: cluster) } + + it { is_expected.to eq kubernetes_namespace } + end + end + + context 'kubernetes namespace does not exist' do + it 'initializes a new namespace and sets default values' do + expect(subject).to be_new_record + expect(subject.project).to eq project + expect(subject.cluster).to eq cluster + expect(subject.namespace).to be_present + expect(subject.service_account_name).to be_present + end + end + + context 'a custom scope is provided' do + let(:scope) { cluster.kubernetes_namespaces.has_service_account_token } + + subject { cluster.find_or_initialize_kubernetes_namespace_for_project(project, scope: scope) } + + context 'kubernetes namespace exists' do + context 'with no service account token' do + let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, project: project, cluster: cluster) } + + it 'initializes a new namespace and sets default values' do + expect(subject).to be_new_record + expect(subject.project).to eq project + expect(subject.cluster).to eq cluster + expect(subject.namespace).to be_present + expect(subject.service_account_name).to be_present + end + end + + context 'with a service account token' do + let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, project: project, cluster: cluster) } + + it { is_expected.to eq kubernetes_namespace } + end + end + end + end + describe '#predefined_variables' do subject { cluster.predefined_variables } diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb index c485850c16e..1fb3a8de808 100644 --- a/spec/models/clusters/platforms/kubernetes_spec.rb +++ b/spec/models/clusters/platforms/kubernetes_spec.rb @@ -223,19 +223,33 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching let(:namespace) { 'namespace-123' } it { is_expected.to eq(namespace) } + + context 'kubernetes namespace is present but has no service account token' do + let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) } + + it { is_expected.to eq(namespace) } + end end context 'with no namespace assigned' do let(:namespace) { nil } context 'when kubernetes namespace is present' do - let(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) } + let(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster) } before do kubernetes_namespace end it { is_expected.to eq(kubernetes_namespace.namespace) } + + context 'kubernetes namespace has no service account token' do + before do + kubernetes_namespace.update!(namespace: 'old-namespace', service_account_token: nil) + end + + it { is_expected.to eq("#{project.path}-#{project.id}") } + end end context 'when kubernetes namespace is not present' do @@ -284,6 +298,46 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false, masked: true } ) end + + context 'the cluster has been set to unmanaged after the namespace was created' do + before do + cluster.update!(managed: false) + end + + it_behaves_like 'setting variables' + + it 'sets KUBE_TOKEN from the platform' do + expect(subject).to include( + { key: 'KUBE_TOKEN', value: kubernetes.token, public: false, masked: true } + ) + end + + context 'the platform has a custom namespace set' do + before do + kubernetes.update!(namespace: 'custom-namespace') + end + + it 'sets KUBE_NAMESPACE from the platform' do + expect(subject).to include( + { key: 'KUBE_NAMESPACE', value: kubernetes.namespace, public: true, masked: false } + ) + end + end + + context 'there is no namespace specified on the platform' do + let(:project) { cluster.project } + + before do + kubernetes.update!(namespace: nil) + end + + it 'sets KUBE_NAMESPACE to a default for the project' do + expect(subject).to include( + { key: 'KUBE_NAMESPACE', value: "#{project.path}-#{project.id}", public: true, masked: false } + ) + end + end + end end context 'namespace is provided' do diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 7233d2454c6..379dda1f5c4 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -515,29 +515,19 @@ describe Environment do context 'when the environment is available' do context 'with a deployment service' do - shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do - context 'and a deployment' do + context 'when user configured kubernetes from CI/CD > Clusters' do + let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:project) { cluster.project } + + context 'with deployment' do let!(:deployment) { create(:deployment, :success, environment: environment) } it { is_expected.to be_truthy } end - context 'but no deployments' do + context 'without deployments' do it { is_expected.to be_falsy } end end - - context 'when user configured kubernetes from Integration > Kubernetes' do - let(:project) { create(:kubernetes_project) } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end - - context 'when user configured kubernetes from CI/CD > Clusters' do - let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end end context 'without a deployment service' do @@ -546,8 +536,6 @@ describe Environment do end context 'when the environment is unavailable' do - let(:project) { create(:kubernetes_project) } - before do environment.stop end @@ -590,7 +578,10 @@ describe Environment do allow(environment).to receive(:has_terminals?).and_return(true) end - shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do + context 'when user configured kubernetes from CI/CD > Clusters' do + let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:project) { cluster.project } + it 'returns the terminals from the deployment service' do expect(environment.deployment_platform) .to receive(:terminals).with(environment) @@ -599,19 +590,6 @@ describe Environment do is_expected.to eq(:fake_terminals) end end - - context 'when user configured kubernetes from Integration > Kubernetes' do - let(:project) { create(:kubernetes_project) } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end - - context 'when user configured kubernetes from CI/CD > Clusters' do - let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end end context 'when the environment does not have terminals' do diff --git a/spec/models/lfs_object_spec.rb b/spec/models/lfs_object_spec.rb index 3d4d4b7d795..85bfc3f1387 100644 --- a/spec/models/lfs_object_spec.rb +++ b/spec/models/lfs_object_spec.rb @@ -3,6 +3,20 @@ require 'spec_helper' describe LfsObject do + it 'has a distinct has_many :projects relation through lfs_objects_projects' do + lfs_object = create(:lfs_object) + project = create(:project) + [:project, :design].each do |repository_type| + create(:lfs_objects_project, project: project, + lfs_object: lfs_object, + repository_type: repository_type) + end + + expect(lfs_object.lfs_objects_projects.size).to eq(2) + expect(lfs_object.projects.size).to eq(1) + expect(lfs_object.projects.to_a).to eql([project]) + end + describe '#local_store?' do it 'returns true when file_store is equal to LfsObjectUploader::Store::LOCAL' do subject.file_store = LfsObjectUploader::Store::LOCAL diff --git a/spec/models/lfs_objects_project_spec.rb b/spec/models/lfs_objects_project_spec.rb index 3e86ee38566..e320f873989 100644 --- a/spec/models/lfs_objects_project_spec.rb +++ b/spec/models/lfs_objects_project_spec.rb @@ -20,8 +20,8 @@ describe LfsObjectsProject do it 'validates object id' do is_expected.to validate_uniqueness_of(:lfs_object_id) - .scoped_to(:project_id) - .with_message("already exists in project") + .scoped_to(:project_id, :repository_type) + .with_message("already exists in repository") end end diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index a04b984c1f6..a1bd0855708 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -301,7 +301,7 @@ describe HipchatService do end context 'pipeline events' do - let(:pipeline) { create(:ci_empty_pipeline, user: create(:user)) } + let(:pipeline) { create(:ci_empty_pipeline, user: project.owner) } let(:data) { Gitlab::DataBuilder::Pipeline.build(pipeline) } context 'for failed' do diff --git a/spec/models/project_services/issue_tracker_data_spec.rb b/spec/models/project_services/issue_tracker_data_spec.rb new file mode 100644 index 00000000000..aaab654f874 --- /dev/null +++ b/spec/models/project_services/issue_tracker_data_spec.rb @@ -0,0 +1,35 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe IssueTrackerData do + let(:service) { create(:custom_issue_tracker_service, active: false, properties: {}) } + + describe 'Associations' do + it { is_expected.to belong_to :service } + end + + describe 'Validations' do + subject { described_class.new(service: service) } + + context 'url validations' do + context 'when service is inactive' do + it { is_expected.not_to validate_presence_of(:project_url) } + it { is_expected.not_to validate_presence_of(:issues_url) } + end + + context 'when service is active' do + before do + service.update(active: true) + end + + it_behaves_like 'issue tracker service URL attribute', :project_url + it_behaves_like 'issue tracker service URL attribute', :issues_url + it_behaves_like 'issue tracker service URL attribute', :new_issue_url + + it { is_expected.to validate_presence_of(:project_url) } + it { is_expected.to validate_presence_of(:issues_url) } + end + end + end +end diff --git a/spec/models/project_services/jira_tracker_data_spec.rb b/spec/models/project_services/jira_tracker_data_spec.rb new file mode 100644 index 00000000000..1b6ece8531b --- /dev/null +++ b/spec/models/project_services/jira_tracker_data_spec.rb @@ -0,0 +1,42 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe JiraTrackerData do + let(:service) { create(:jira_service, active: false, properties: {}) } + + describe 'Associations' do + it { is_expected.to belong_to(:service) } + end + + describe 'Validations' do + subject { described_class.new(service: service) } + + context 'jira_issue_transition_id' do + it { is_expected.to allow_value(nil).for(:jira_issue_transition_id) } + it { is_expected.to allow_value('1,2,3').for(:jira_issue_transition_id) } + it { is_expected.to allow_value('1;2;3').for(:jira_issue_transition_id) } + it { is_expected.not_to allow_value('a,b,cd').for(:jira_issue_transition_id) } + end + + context 'url validations' do + context 'when service is inactive' do + it { is_expected.not_to validate_presence_of(:url) } + it { is_expected.not_to validate_presence_of(:username) } + it { is_expected.not_to validate_presence_of(:password) } + end + + context 'when service is active' do + before do + service.update(active: true) + end + + it_behaves_like 'issue tracker service URL attribute', :url + + it { is_expected.to validate_presence_of(:url) } + it { is_expected.to validate_presence_of(:username) } + it { is_expected.to validate_presence_of(:password) } + end + end + end +end diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index 2fce120381b..34ee1eafd5c 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -17,6 +17,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do context 'when service is active' do before do subject.active = true + subject.skip_deprecation_validation = true end it { is_expected.not_to validate_presence_of(:namespace) } @@ -67,6 +68,7 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do before do kubernetes_service.update_attribute(:active, false) + kubernetes_service.skip_deprecation_validation = false kubernetes_service.properties['namespace'] = "foo" end @@ -80,19 +82,11 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do end end - context 'with a non-deprecated service' do - let(:kubernetes_service) { create(:kubernetes_service) } - - it 'updates attributes' do - kubernetes_service.properties['namespace'] = 'foo' - expect(kubernetes_service.save).to be_truthy - end - end - context 'with an active and deprecated service' do let(:kubernetes_service) { create(:kubernetes_service) } before do + kubernetes_service.skip_deprecation_validation = false kubernetes_service.active = false kubernetes_service.properties['namespace'] = 'foo' kubernetes_service.save @@ -110,19 +104,6 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do expect(kubernetes_service.properties['namespace']).to eq("foo") end end - - context 'with a template service' do - let(:kubernetes_service) { create(:kubernetes_service, template: true, active: false) } - - before do - kubernetes_service.properties['namespace'] = 'foo' - end - - it 'updates attributes' do - expect(kubernetes_service.save).to be_truthy - expect(kubernetes_service.properties['namespace']).to eq('foo') - end - end end describe '#initialize_properties' do @@ -393,17 +374,8 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do describe "#deprecated?" do let(:kubernetes_service) { create(:kubernetes_service) } - context 'with an active kubernetes service' do - it 'returns false' do - expect(kubernetes_service.deprecated?).to be_falsy - end - end - - context 'with a inactive kubernetes service' do - it 'returns true' do - kubernetes_service.update_attribute(:active, false) - expect(kubernetes_service.deprecated?).to be_truthy - end + it 'returns true' do + expect(kubernetes_service.deprecated?).to be_truthy end end @@ -414,12 +386,6 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do expect(kubernetes_service.deprecation_message).to match(/Kubernetes service integration has been deprecated/) end - context 'if the services is active' do - it 'returns a message' do - expect(kubernetes_service.deprecation_message).to match(/Your Kubernetes cluster information on this page is still editable/) - end - end - context 'if the service is not active' do it 'returns a message' do kubernetes_service.update_attribute(:active, false) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index aad08b9d4aa..20b98b5eb85 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -103,6 +103,20 @@ describe Project do expect(described_class.reflect_on_association(:merge_requests).has_inverse?).to eq(:target_project) end + it 'has a distinct has_many :lfs_objects relation through lfs_objects_projects' do + project = create(:project) + lfs_object = create(:lfs_object) + [:project, :design].each do |repository_type| + create(:lfs_objects_project, project: project, + lfs_object: lfs_object, + repository_type: repository_type) + end + + expect(project.lfs_objects_projects.size).to eq(2) + expect(project.lfs_objects.size).to eq(1) + expect(project.lfs_objects.to_a).to eql([lfs_object]) + end + context 'after initialized' do it "has a project_feature" do expect(described_class.new.project_feature).to be_present @@ -1479,11 +1493,28 @@ describe Project do end context 'when set to INTERNAL in application settings' do + using RSpec::Parameterized::TableSyntax + before do stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL) end it { is_expected.to eq(Gitlab::VisibilityLevel::INTERNAL) } + + where(:attribute_name, :value) do + :visibility | 'public' + :visibility_level | Gitlab::VisibilityLevel::PUBLIC + 'visibility' | 'public' + 'visibility_level' | Gitlab::VisibilityLevel::PUBLIC + end + + with_them do + it 'sets the visibility level' do + proj = described_class.new(attribute_name => value, name: 'test', path: 'test') + + expect(proj.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC) + end + end end end @@ -2621,7 +2652,10 @@ describe Project do end context 'when project has a deployment service' do - shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do + context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has not been executed' do + let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:project) { cluster.project } + it 'returns variables from this service' do expect(project.deployment_variables).to include( { key: 'KUBE_TOKEN', value: project.deployment_platform.token, public: false, masked: true } @@ -2629,19 +2663,6 @@ describe Project do end end - context 'when user configured kubernetes from Integration > Kubernetes' do - let(:project) { create(:kubernetes_project) } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end - - context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has not been executed' do - let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end - context 'when user configured kubernetes from CI/CD > Clusters and KubernetesNamespace migration has been executed' do let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token) } let!(:cluster) { kubernetes_namespace.cluster } @@ -3447,6 +3468,7 @@ describe Project do before do allow(project).to receive(:gitlab_shell).and_return(gitlab_shell) + stub_application_setting(hashed_storage_enabled: false) end describe '#base_dir' do @@ -3553,10 +3575,6 @@ describe Project do let(:hashed_prefix) { File.join('@hashed', hash[0..1], hash[2..3]) } let(:hashed_path) { File.join(hashed_prefix, hash) } - before do - stub_application_setting(hashed_storage_enabled: true) - end - describe '#legacy_storage?' do it 'returns false' do expect(project.legacy_storage?).to be_falsey @@ -4698,10 +4716,6 @@ describe Project do subject { project.object_pool_params } - before do - stub_application_setting(hashed_storage_enabled: true) - end - context 'when the objects cannot be pooled' do let(:project) { create(:project, :repository, :private) } @@ -4747,10 +4761,6 @@ describe Project do context 'when objects are poolable' do let(:project) { create(:project, :repository, :public) } - before do - stub_application_setting(hashed_storage_enabled: true) - end - it { is_expected.to be_git_objects_poolable } end end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 64db32781fe..d442c73c118 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -6,6 +6,8 @@ describe Service do describe "Associations" do it { is_expected.to belong_to :project } it { is_expected.to have_one :service_hook } + it { is_expected.to have_one :jira_tracker_data } + it { is_expected.to have_one :issue_tracker_data } end describe 'Validations' do @@ -80,7 +82,7 @@ describe Service do context 'when template is invalid' do it 'sets service template to inactive when template is invalid' do project = create(:project) - template = KubernetesService.new(template: true, active: true) + template = build(:prometheus_service, template: true, active: true, properties: {}) template.save(validate: false) service = described_class.build_from_template(project.id, template) @@ -307,10 +309,10 @@ describe Service do end describe '.find_by_template' do - let!(:kubernetes_service) { create(:kubernetes_service, template: true) } + let!(:service) { create(:service, template: true) } it 'returns service template' do - expect(KubernetesService.find_by_template).to eq(kubernetes_service) + expect(described_class.find_by_template).to eq(service) end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index ed0e82ef179..4b723a52b51 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -39,7 +39,7 @@ describe ProjectPolicy do admin_milestone admin_merge_request update_merge_request create_commit_status update_commit_status create_build update_build create_pipeline update_pipeline create_merge_request_from create_wiki push_code - resolve_note create_container_image update_container_image + resolve_note create_container_image update_container_image destroy_container_image create_environment create_deployment create_release update_release ] end diff --git a/spec/requests/api/container_registry_spec.rb b/spec/requests/api/container_registry_spec.rb index ea035a8be4a..4ad15ed6bea 100644 --- a/spec/requests/api/container_registry_spec.rb +++ b/spec/requests/api/container_registry_spec.rb @@ -201,10 +201,10 @@ describe API::ContainerRegistry do describe 'DELETE /projects/:id/registry/repositories/:repository_id/tags/:tag_name' do subject { delete api("/projects/#{project.id}/registry/repositories/#{root_repository.id}/tags/rootA", api_user) } - it_behaves_like 'being disallowed', :developer + it_behaves_like 'being disallowed', :reporter - context 'for maintainer' do - let(:api_user) { maintainer } + context 'for developer' do + let(:api_user) { developer } before do stub_container_registry_tags(repository: root_repository.path, tags: %w(rootA), with_manifest: true) diff --git a/spec/requests/api/graphql/tasks/task_completion_status_spec.rb b/spec/requests/api/graphql/tasks/task_completion_status_spec.rb new file mode 100644 index 00000000000..c457a6d7c25 --- /dev/null +++ b/spec/requests/api/graphql/tasks/task_completion_status_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'getting task completion status information' do + include GraphqlHelpers + + DESCRIPTION_0_DONE = '- [ ] task 1\n- [ ] task 2' + DESCRIPTION_1_DONE = '- [x] task 1\n- [ ] task 2' + DESCRIPTION_2_DONE = '- [x] task 1\n- [x] task 2' + + set(:user1) { create(:user) } + set(:project) { create(:project, :repository, :public) } + + let(:fields) do + <<~QUERY + taskCompletionStatus { + count, + completedCount + } + QUERY + end + + def create_task_completion_status_query_for(type, iid) + graphql_query_for( + 'project', + { 'fullPath' => project.full_path }, + query_graphql_field(type, { iid: iid }, fields) + ) + end + + shared_examples_for 'graphql task completion status provider' do |type| + it 'returns the expected task completion status' do + post_graphql(create_task_completion_status_query_for(type, item.iid), current_user: user1) + + expect(response).to have_gitlab_http_status(200) + + task_completion_status = graphql_data.dig('project', type, 'taskCompletionStatus') + expect(task_completion_status).not_to be_nil + expect(task_completion_status['count']).to eq(item.task_completion_status[:count]) + expect(task_completion_status['completedCount']).to eq(item.task_completion_status[:completed_count]) + end + end + + [DESCRIPTION_0_DONE, DESCRIPTION_1_DONE, DESCRIPTION_2_DONE].each do |desc| + context "with description #{desc}" do + context 'when type is issue' do + it_behaves_like 'graphql task completion status provider', 'issue' do + let(:item) { create(:issue, project: project, description: desc) } + end + end + + context 'when type is merge request' do + it_behaves_like 'graphql task completion status provider', 'mergeRequest' do + let(:item) { create(:merge_request, author: user1, source_project: project, description: desc) } + end + end + end + end +end diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index e260aa21e25..3f79e332b90 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -10,7 +10,10 @@ describe API::Services do end Service.available_services_names.each do |service| - describe "PUT /projects/:id/services/#{service.dasherize}" do + # TODO: Remove below `if: (service != "kubernetes")` in the next release + # KubernetesService was deprecated and it can't be updated. Right now it's + # only readable. It should be completely removed in the next iteration. + describe "PUT /projects/:id/services/#{service.dasherize}", if: (service != "kubernetes") do include_context service it "updates #{service} settings" do @@ -19,13 +22,22 @@ describe API::Services do expect(response).to have_gitlab_http_status(200) current_service = project.services.first - event = current_service.event_names.empty? ? "foo" : current_service.event_names.first - state = current_service[event] || false + events = current_service.event_names.empty? ? ["foo"].freeze : current_service.event_names + query_strings = [] + events.each do |event| + query_strings << "#{event}=#{!current_service[event]}" + end + query_strings = query_strings.join('&') - put api("/projects/#{project.id}/services/#{dashed_service}?#{event}=#{!state}", user), params: service_attrs + put api("/projects/#{project.id}/services/#{dashed_service}?#{query_strings}", user), params: service_attrs expect(response).to have_gitlab_http_status(200) - expect(project.services.first[event]).not_to eq(state) unless event == "foo" + events.each do |event| + next if event == "foo" + + expect(project.services.first[event]).not_to eq(current_service[event]), + "expected #{!current_service[event]} for event #{event} for service #{current_service.title}, got #{current_service[event]}" + end end it "returns if required fields missing" do @@ -50,7 +62,10 @@ describe API::Services do end end - describe "DELETE /projects/:id/services/#{service.dasherize}" do + # TODO: Remove below `if: (service != "kubernetes")` in the next release + # KubernetesService was deprecated and it can't be updated. Right now it's + # only readable. It should be completely removed in the next iteration. + describe "DELETE /projects/:id/services/#{service.dasherize}", if: (service != "kubernetes") do include_context service before do diff --git a/spec/requests/projects/cycle_analytics_events_spec.rb b/spec/requests/projects/cycle_analytics_events_spec.rb index 49412b628b3..25390f8a23e 100644 --- a/spec/requests/projects/cycle_analytics_events_spec.rb +++ b/spec/requests/projects/cycle_analytics_events_spec.rb @@ -32,10 +32,10 @@ describe 'cycle analytics events' do it 'lists the plan events' do get project_cycle_analytics_plan_path(project, format: :json) - first_mr_short_sha = project.merge_requests.sort_by_attribute(:created_asc).first.commits.first.short_id + first_issue_iid = project.issues.sort_by_attribute(:created_desc).pluck(:iid).first.to_s expect(json_response['events']).not_to be_empty - expect(json_response['events'].first['short_sha']).to eq(first_mr_short_sha) + expect(json_response['events'].first['iid']).to eq(first_issue_iid) end it 'lists the code events' do diff --git a/spec/serializers/environment_entity_spec.rb b/spec/serializers/environment_entity_spec.rb index c2312734042..906449f470b 100644 --- a/spec/serializers/environment_entity_spec.rb +++ b/spec/serializers/environment_entity_spec.rb @@ -59,15 +59,5 @@ describe EnvironmentEntity do expect(subject[:cluster_type]).to eq('project_type') end end - - context 'when deployment platform is a Kubernetes Service' do - before do - create(:kubernetes_service, project: project) - end - - it 'does not include cluster_type' do - expect(subject).not_to include(:cluster_type) - end - end end end diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 867692d4d64..d9b61dfe503 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -1132,5 +1132,17 @@ describe Ci::CreatePipelineService do .with_message('Insufficient permissions to create a new pipeline') end end + + context 'when a user with permissions has been blocked' do + before do + user.block! + end + + it 'raises an error' do + expect { subject } + .to raise_error(described_class::CreateError) + .with_message('Insufficient permissions to create a new pipeline') + end + end end end diff --git a/spec/services/ci/play_build_service_spec.rb b/spec/services/ci/play_build_service_spec.rb index 634f865e2d8..1e68b7956ea 100644 --- a/spec/services/ci/play_build_service_spec.rb +++ b/spec/services/ci/play_build_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Ci::PlayBuildService, '#execute' do - let(:user) { create(:user) } + let(:user) { create(:user, developer_projects: [project]) } let(:project) { create(:project) } let(:pipeline) { create(:ci_pipeline, project: project) } let(:build) { create(:ci_build, :manual, pipeline: pipeline) } @@ -16,8 +16,6 @@ describe Ci::PlayBuildService, '#execute' do let(:project) { create(:project) } it 'allows user to play build if protected branch rules are met' do - project.add_developer(user) - create(:protected_branch, :developers_can_merge, name: build.ref, project: project) @@ -27,8 +25,6 @@ describe Ci::PlayBuildService, '#execute' do end it 'does not allow user with developer role to play build' do - project.add_developer(user) - expect { service.execute(build) } .to raise_error Gitlab::Access::AccessDeniedError end @@ -38,23 +34,21 @@ describe Ci::PlayBuildService, '#execute' do let(:project) { create(:project, :repository) } it 'allows user with developer role to play a build' do - project.add_developer(user) - service.execute(build) expect(build.reload).to be_pending end + + it 'prevents a blocked developer from playing a build' do + user.block! + + expect { service.execute(build) }.to raise_error(Gitlab::Access::AccessDeniedError) + end end context 'when build is a playable manual action' do let(:build) { create(:ci_build, :manual, pipeline: pipeline) } - - before do - project.add_developer(user) - - create(:protected_branch, :developers_can_merge, - name: build.ref, project: project) - end + let!(:branch) { create(:protected_branch, :developers_can_merge, name: build.ref, project: project) } it 'enqueues the build' do expect(service.execute(build)).to eq build @@ -70,13 +64,7 @@ describe Ci::PlayBuildService, '#execute' do context 'when build is not a playable manual action' do let(:build) { create(:ci_build, when: :manual, pipeline: pipeline) } - - before do - project.add_developer(user) - - create(:protected_branch, :developers_can_merge, - name: build.ref, project: project) - end + let!(:branch) { create(:protected_branch, :developers_can_merge, name: build.ref, project: project) } it 'duplicates the build' do duplicate = service.execute(build) @@ -94,6 +82,7 @@ describe Ci::PlayBuildService, '#execute' do end context 'when build is not action' do + let(:user) { create(:user) } let(:build) { create(:ci_build, :success, pipeline: pipeline) } it 'raises an error' do @@ -103,10 +92,8 @@ describe Ci::PlayBuildService, '#execute' do end context 'when user does not have ability to trigger action' do - before do - create(:protected_branch, :no_one_can_push, - name: build.ref, project: project) - end + let(:user) { create(:user) } + let!(:branch) { create(:protected_branch, :developers_can_merge, name: build.ref, project: project) } it 'raises an error' do expect { service.execute(build) } diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index e9a26400723..11b06ef5019 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -38,7 +38,7 @@ describe Ci::RetryBuildService do runner_id tag_taggings taggings tags trigger_request_id user_id auto_canceled_by_id retried failure_reason sourced_pipelines artifacts_file_store artifacts_metadata_store - metadata runner_session trace_chunks + metadata runner_session trace_chunks upstream_pipeline_id artifacts_file artifacts_metadata artifacts_size].freeze shared_examples 'build duplication' do diff --git a/spec/services/lfs/file_transformer_spec.rb b/spec/services/lfs/file_transformer_spec.rb index 888eea6e91e..9973d64930b 100644 --- a/spec/services/lfs/file_transformer_spec.rb +++ b/spec/services/lfs/file_transformer_spec.rb @@ -3,13 +3,13 @@ require "spec_helper" describe Lfs::FileTransformer do - let(:project) { create(:project, :repository) } + let(:project) { create(:project, :repository, :wiki_repo) } let(:repository) { project.repository } let(:file_content) { 'Test file content' } let(:branch_name) { 'lfs' } let(:file_path) { 'test_file.lfs' } - subject { described_class.new(project, branch_name) } + subject { described_class.new(project, repository, branch_name) } describe '#new_file' do context 'with lfs disabled' do @@ -100,6 +100,12 @@ describe Lfs::FileTransformer do end.to change { project.lfs_objects.count }.by(1) end + it 'saves the repository_type to LfsObjectsProject' do + subject.new_file(file_path, file_content) + + expect(project.lfs_objects_projects.first.repository_type).to eq('project') + end + context 'when LfsObject already exists' do let(:lfs_pointer) { Gitlab::Git::LfsPointerFile.new(file_content) } @@ -113,6 +119,56 @@ describe Lfs::FileTransformer do end.to change { project.lfs_objects.count }.by(1) end end + + context 'when the LfsObject is already linked to project' do + before do + subject.new_file(file_path, file_content) + end + + shared_examples 'a new LfsObject is not created' do + it do + expect do + second_service.new_file(file_path, file_content) + end.not_to change { project.lfs_objects.count } + end + end + + context 'and the service is called again with the same repository type' do + let(:second_service) { described_class.new(project, repository, branch_name) } + + include_examples 'a new LfsObject is not created' + + it 'does not create a new LfsObjectsProject record' do + expect do + second_service.new_file(file_path, file_content) + end.not_to change { project.lfs_objects_projects.count } + end + end + + context 'and the service is called again with a different repository type' do + let(:second_service) { described_class.new(project, project.wiki.repository, branch_name) } + + before do + expect(second_service).to receive(:lfs_file?).and_return(true) + end + + include_examples 'a new LfsObject is not created' + + it 'creates a new LfsObjectsProject record' do + expect do + second_service.new_file(file_path, file_content) + end.to change { project.lfs_objects_projects.count }.by(1) + end + + it 'sets the correct repository_type on the new LfsObjectsProject record' do + second_service.new_file(file_path, file_content) + + repository_types = project.lfs_objects_projects.order(:id).pluck(:repository_type) + + expect(repository_types).to eq(%w(project wiki)) + end + end + end end end end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 4b40c86574f..f25e2fe5e2b 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -2217,10 +2217,12 @@ describe NotificationService, :mailer do let(:pipeline) { create(:ci_pipeline, :failed, project: project, user: pipeline_user) } it 'emails project owner and user that triggered the pipeline' do + project.add_developer(pipeline_user) + notification.autodevops_disabled(pipeline, [owner.email, pipeline_user.email]) - should_email(owner) - should_email(pipeline_user) + should_email(owner, times: 1) # Once for the disable pipeline. + should_email(pipeline_user, times: 2) # Once for the new permission, once for the disable. end end end diff --git a/spec/services/projects/after_rename_service_spec.rb b/spec/services/projects/after_rename_service_spec.rb index b8055a285f2..8585d495ffb 100644 --- a/spec/services/projects/after_rename_service_spec.rb +++ b/spec/services/projects/after_rename_service_spec.rb @@ -23,6 +23,7 @@ describe Projects::AfterRenameService do allow(project).to receive(:gitlab_shell).and_return(gitlab_shell) stub_feature_flags(skip_hashed_storage_upgrade: false) + stub_application_setting(hashed_storage_enabled: false) end it 'renames a repository' do diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index f54f9200661..b0b74407812 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -152,6 +152,33 @@ describe Projects::CreateService, '#execute' do end end + context 'default visibility level' do + let(:group) { create(:group, :private) } + + before do + stub_application_setting(default_project_visibility: Gitlab::VisibilityLevel::INTERNAL) + group.add_developer(user) + + opts.merge!( + visibility: 'private', + name: 'test', + namespace: group, + path: 'foo' + ) + end + + it 'creates a private project' do + project = create_project(user, opts) + + expect(project).to respond_to(:errors) + + expect(project.errors.any?).to be(false) + expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) + expect(project.saved?).to be(true) + expect(project.valid?).to be(true) + end + end + context 'restricted visibility level' do before do stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC]) @@ -201,6 +228,7 @@ describe Projects::CreateService, '#execute' do context 'with legacy storage' do before do + stub_application_setting(hashed_storage_enabled: false) gitlab_shell.create_repository(repository_storage, "#{user.namespace.full_path}/existing", 'group/project') end @@ -232,7 +260,6 @@ describe Projects::CreateService, '#execute' do let(:hashed_path) { '@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' } before do - stub_application_setting(hashed_storage_enabled: true) allow(Digest::SHA2).to receive(:hexdigest) { hash } end diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb index 6afc91d5e95..0c109e26a6a 100644 --- a/spec/services/projects/fork_service_spec.rb +++ b/spec/services/projects/fork_service_spec.rb @@ -116,11 +116,12 @@ describe Projects::ForkService do end end - context 'repository already exists' do + context 'repository in legacy storage already exists' do let(:repository_storage) { 'default' } let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path } before do + stub_application_setting(hashed_storage_enabled: false) gitlab_shell.create_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}", "#{@to_user.namespace.full_path}/#{@from_project.path}") end diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb index e95c7f2a6d6..bcf6669f37d 100644 --- a/spec/support/helpers/graphql_helpers.rb +++ b/spec/support/helpers/graphql_helpers.rb @@ -157,7 +157,7 @@ module GraphqlHelpers when Array # multiplexed queries json_response.map { |response| response['errors'] } else - raise "Unkown GraphQL response type #{json_response.class}" + raise "Unknown GraphQL response type #{json_response.class}" end end diff --git a/spec/support/helpers/query_recorder.rb b/spec/support/helpers/query_recorder.rb index c4ae62b25e4..d45377267f3 100644 --- a/spec/support/helpers/query_recorder.rb +++ b/spec/support/helpers/query_recorder.rb @@ -11,7 +11,7 @@ module ActiveRecord def show_backtrace(values) Rails.logger.debug("QueryRecorder SQL: #{values[:sql]}") - caller.each { |line| Rails.logger.debug(" --> #{line}") } + Gitlab::Profiler.clean_backtrace(caller).each { |line| Rails.logger.debug(" --> #{line}") } end def callback(name, start, finish, message_id, values) diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index 06b5ecdf150..77f22d9dd24 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -146,19 +146,6 @@ module TestEnv install_dir: Gitlab.config.gitlab_shell.path, version: Gitlab::Shell.version_required, task: 'gitlab:shell:install') - - # gitlab-shell hooks don't work in our test environment because they try to make internal API calls - sabotage_gitlab_shell_hooks - end - - def sabotage_gitlab_shell_hooks - create_fake_git_hooks(Gitlab::Shell.new.hooks_path) - end - - def create_fake_git_hooks(hooks_dir) - %w[pre-receive post-receive update].each do |hook| - File.open(File.join(hooks_dir, hook), 'w', 0755) { |f| f.puts '#!/bin/sh' } - end end def setup_gitaly @@ -172,7 +159,6 @@ module TestEnv task: "gitlab:gitaly:install[#{install_gitaly_args}]") do Gitlab::SetupHelper.create_gitaly_configuration(gitaly_dir, { 'default' => repos_path }, force: true) - create_fake_git_hooks(File.join(gitaly_dir, 'ruby/git-hooks')) start_gitaly(gitaly_dir) end end diff --git a/spec/support/prometheus/additional_metrics_shared_examples.rb b/spec/support/prometheus/additional_metrics_shared_examples.rb index 8044b061ca5..de21e808932 100644 --- a/spec/support/prometheus/additional_metrics_shared_examples.rb +++ b/spec/support/prometheus/additional_metrics_shared_examples.rb @@ -44,7 +44,9 @@ RSpec.shared_examples 'additional metrics query' do end describe 'project has Kubernetes service' do - shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do + context 'when user configured kubernetes from CI/CD > Clusters' do + let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } + let(:project) { cluster.project } let(:environment) { create(:environment, slug: 'environment-slug', project: project) } let(:kube_namespace) { project.deployment_platform.kubernetes_namespace_for(project) } @@ -56,19 +58,6 @@ RSpec.shared_examples 'additional metrics query' do subject.query(*query_params) end end - - context 'when user configured kubernetes from Integration > Kubernetes' do - let(:project) { create(:kubernetes_project) } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end - - context 'when user configured kubernetes from CI/CD > Clusters' do - let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } - let(:project) { cluster.project } - - it_behaves_like 'same behavior between KubernetesService and Platform::Kubernetes' - end end describe 'project without Kubernetes service' do diff --git a/spec/support/shared_contexts/services_shared_context.rb b/spec/support/shared_contexts/services_shared_context.rb index 089f1798cd2..0c3a24d206f 100644 --- a/spec/support/shared_contexts/services_shared_context.rb +++ b/spec/support/shared_contexts/services_shared_context.rb @@ -37,8 +37,7 @@ Service.available_services_names.each do |service| def initialize_service(service) service_item = project.find_or_initialize_service(service) service_item.properties = service_attrs - service_item.active = true if service == "kubernetes" - service_item.save + service_item.save! service_item end end diff --git a/spec/support/shared_examples/ci_trace_shared_examples.rb b/spec/support/shared_examples/ci_trace_shared_examples.rb index db935bcb388..f985b2dcbba 100644 --- a/spec/support/shared_examples/ci_trace_shared_examples.rb +++ b/spec/support/shared_examples/ci_trace_shared_examples.rb @@ -5,11 +5,11 @@ shared_examples_for 'common trace features' do end it "returns formatted html" do - expect(trace.html).to eq("12<br>34") + expect(trace.html).to eq("<span class=\"\">12<br/><span class=\"\">34</span></span>") end it "returns last line of formatted html" do - expect(trace.html(last_lines: 1)).to eq("34") + expect(trace.html(last_lines: 1)).to eq("<span class=\"\">34</span>") end end diff --git a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb index d04affc7df1..623237b111a 100644 --- a/spec/views/notify/pipeline_failed_email.html.haml_spec.rb +++ b/spec/views/notify/pipeline_failed_email.html.haml_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'notify/pipeline_failed_email.html.haml' do include Devise::Test::ControllerHelpers - let(:user) { create(:user) } + let(:user) { create(:user, developer_projects: [project]) } let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, :simple, source_project: project) } diff --git a/spec/views/notify/pipeline_failed_email.text.erb_spec.rb b/spec/views/notify/pipeline_failed_email.text.erb_spec.rb index 079fb865d7b..81245239eba 100644 --- a/spec/views/notify/pipeline_failed_email.text.erb_spec.rb +++ b/spec/views/notify/pipeline_failed_email.text.erb_spec.rb @@ -4,7 +4,7 @@ require 'spec_helper' describe 'notify/pipeline_failed_email.text.erb' do include Devise::Test::ControllerHelpers - let(:user) { create(:user) } + let(:user) { create(:user, developer_projects: [project]) } let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, :simple, source_project: project) } diff --git a/spec/views/notify/pipeline_success_email.html.haml_spec.rb b/spec/views/notify/pipeline_success_email.html.haml_spec.rb index 8ee7f954d70..a876bf13e11 100644 --- a/spec/views/notify/pipeline_success_email.html.haml_spec.rb +++ b/spec/views/notify/pipeline_success_email.html.haml_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe 'notify/pipeline_success_email.html.haml' do include Devise::Test::ControllerHelpers - let(:user) { create(:user) } + let(:user) { create(:user, developer_projects: [project]) } let(:project) { create(:project, :repository) } let(:merge_request) { create(:merge_request, :simple, source_project: project) } diff --git a/spec/views/projects/commit/_commit_box.html.haml_spec.rb b/spec/views/projects/commit/_commit_box.html.haml_spec.rb index 457dd2e940f..1086546c10d 100644 --- a/spec/views/projects/commit/_commit_box.html.haml_spec.rb +++ b/spec/views/projects/commit/_commit_box.html.haml_spec.rb @@ -27,7 +27,7 @@ describe 'projects/commit/_commit_box.html.haml' do render - expect(rendered).to have_text("Pipeline ##{third_pipeline.id} (##{third_pipeline.iid}) failed") + expect(rendered).to have_text("Pipeline ##{third_pipeline.id} failed") end end @@ -40,7 +40,7 @@ describe 'projects/commit/_commit_box.html.haml' do it 'shows correct pipeline description' do render - expect(rendered).to have_text "Pipeline ##{pipeline.id} (##{pipeline.iid}) " \ + expect(rendered).to have_text "Pipeline ##{pipeline.id} " \ 'waiting for manual action' end end diff --git a/spec/views/projects/jobs/_build.html.haml_spec.rb b/spec/views/projects/jobs/_build.html.haml_spec.rb index 97b25a6976f..1d58891036e 100644 --- a/spec/views/projects/jobs/_build.html.haml_spec.rb +++ b/spec/views/projects/jobs/_build.html.haml_spec.rb @@ -4,7 +4,7 @@ describe 'projects/ci/jobs/_build' do include Devise::Test::ControllerHelpers let(:project) { create(:project, :repository) } - let(:pipeline) { create(:ci_empty_pipeline, id: 1337, iid: 57, project: project, sha: project.commit.id) } + let(:pipeline) { create(:ci_empty_pipeline, id: 1337, project: project, sha: project.commit.id) } let(:build) { create(:ci_build, pipeline: pipeline, stage: 'test', stage_idx: 1, name: 'rspec 0:2', status: :pending) } before do @@ -15,14 +15,14 @@ describe 'projects/ci/jobs/_build' do it 'won\'t include a column with a link to its pipeline by default' do render partial: 'projects/ci/builds/build', locals: { build: build } - expect(rendered).not_to have_link('#1337 (#57)') - expect(rendered).not_to have_text('#1337 (#57) by API') + expect(rendered).not_to have_link('#1337') + expect(rendered).not_to have_text('#1337 by API') end it 'can include a column with a link to its pipeline' do render partial: 'projects/ci/builds/build', locals: { build: build, pipeline_link: true } - expect(rendered).to have_link('#1337 (#57)') - expect(rendered).to have_text('#1337 (#57) by API') + expect(rendered).to have_link('#1337') + expect(rendered).to have_text('#1337 by API') end end diff --git a/spec/workers/auto_devops/disable_worker_spec.rb b/spec/workers/auto_devops/disable_worker_spec.rb index 800a07e41a5..53113f286ba 100644 --- a/spec/workers/auto_devops/disable_worker_spec.rb +++ b/spec/workers/auto_devops/disable_worker_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe AutoDevops::DisableWorker, '#perform' do - let(:user) { create(:user) } + let(:user) { create(:user, developer_projects: [project]) } let(:project) { create(:project, :repository, :auto_devops) } let(:auto_devops) { project.auto_devops } let(:pipeline) { create(:ci_pipeline, :failed, :auto_devops_source, project: project, user: user) } @@ -10,6 +10,7 @@ describe AutoDevops::DisableWorker, '#perform' do subject { described_class.new } before do + project.add_developer(user) stub_application_setting(auto_devops_enabled: true) auto_devops.update_attribute(:enabled, nil) end diff --git a/spec/workers/reactive_caching_worker_spec.rb b/spec/workers/reactive_caching_worker_spec.rb index 2395e6ec947..b8ca6063ccd 100644 --- a/spec/workers/reactive_caching_worker_spec.rb +++ b/spec/workers/reactive_caching_worker_spec.rb @@ -6,16 +6,6 @@ describe ReactiveCachingWorker do let(:service) { project.deployment_platform } describe '#perform' do - context 'when user configured kubernetes from Integration > Kubernetes' do - let(:project) { create(:kubernetes_project) } - - it 'calls #exclusively_update_reactive_cache!' do - expect_any_instance_of(KubernetesService).to receive(:exclusively_update_reactive_cache!) - - described_class.new.perform("KubernetesService", service.id) - end - end - context 'when user configured kubernetes from CI/CD > Clusters' do let!(:cluster) { create(:cluster, :project, :provided_by_gcp) } let(:project) { cluster.project } |