summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml51
-rw-r--r--.gitlab/issue_templates/Database Reviewer.md32
-rw-r--r--.gitlab/issue_templates/Feature proposal.md28
-rw-r--r--.gitlab/issue_templates/Test plan.md2
-rw-r--r--.prettierrc10
-rw-r--r--.rubocop.yml6
-rw-r--r--.stylelintrc2
-rw-r--r--CHANGELOG.md307
-rw-r--r--Dangerfile1
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile11
-rw-r--r--Gemfile.lock43
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/awards_handler.js6
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js20
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue22
-rw-r--r--app/assets/javascripts/clusters/constants.js1
-rw-r--r--app/assets/javascripts/commons/jquery.js4
-rw-r--r--app/assets/javascripts/commons/polyfills.js1
-rw-r--r--app/assets/javascripts/diffs/components/app.vue11
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions.vue4
-rw-r--r--app/assets/javascripts/diffs/components/diff_file.vue12
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_table_row.vue4
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue52
-rw-r--r--app/assets/javascripts/diffs/store/actions.js10
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js8
-rw-r--r--app/assets/javascripts/diffs/store/mutation_types.js1
-rw-r--r--app/assets/javascripts/diffs/store/mutations.js12
-rw-r--r--app/assets/javascripts/emoji/index.js15
-rw-r--r--app/assets/javascripts/environments/components/container.vue20
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue12
-rw-r--r--app/assets/javascripts/environments/components/environments_app.vue14
-rw-r--r--app/assets/javascripts/environments/components/environments_table.vue43
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_bundle.js3
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_view.vue11
-rw-r--r--app/assets/javascripts/environments/index.js3
-rw-r--r--app/assets/javascripts/environments/mixins/canary_callout_mixin.js5
-rw-r--r--app/assets/javascripts/environments/mixins/container_mixin.js29
-rw-r--r--app/assets/javascripts/environments/mixins/environment_item_mixin.js13
-rw-r--r--app/assets/javascripts/environments/mixins/environments_app_mixin.js32
-rw-r--r--app/assets/javascripts/environments/mixins/environments_folder_view_mixin.js29
-rw-r--r--app/assets/javascripts/environments/mixins/environments_mixin.js2
-rw-r--r--app/assets/javascripts/environments/mixins/environments_table_mixin.js10
-rw-r--r--app/assets/javascripts/environments/stores/environments_store.js25
-rw-r--r--app/assets/javascripts/environments/stores/helpers.js8
-rw-r--r--app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js12
-rw-r--r--app/assets/javascripts/filtered_search/available_dropdown_mappings.js31
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_manager.js13
-rw-r--r--app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js12
-rw-r--r--app/assets/javascripts/filtered_search/visual_token_value.js4
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue7
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/modal.vue2
-rw-r--r--app/assets/javascripts/ide/stores/modules/commit/getters.js4
-rw-r--r--app/assets/javascripts/jobs/components/stages_dropdown.vue51
-rw-r--r--app/assets/javascripts/lib/utils/autosave.js32
-rw-r--r--app/assets/javascripts/lib/utils/simple_poll.js4
-rw-r--r--app/assets/javascripts/lib/utils/webpack.js2
-rw-r--r--app/assets/javascripts/mirrors/ssh_mirror.js8
-rw-r--r--app/assets/javascripts/notes.js4
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter.vue2
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter_note.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue1
-rw-r--r--app/assets/javascripts/notes/components/note_actions/reply_button.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue21
-rw-r--r--app/assets/javascripts/notes/components/note_header.vue40
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue36
-rw-r--r--app/assets/javascripts/notes/discussion_filters.js13
-rw-r--r--app/assets/javascripts/notes/index.js7
-rw-r--r--app/assets/javascripts/pages/groups/details/index.js5
-rw-r--r--app/assets/javascripts/pages/groups/shared/group_details.js31
-rw-r--r--app/assets/javascripts/pages/groups/shared/group_tabs.js (renamed from app/assets/javascripts/pages/groups/show/group_tabs.js)0
-rw-r--r--app/assets/javascripts/pages/groups/show/index.js27
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js1
-rw-r--r--app/assets/javascripts/pipelines/components/graph/stage_column_component.vue5
-rw-r--r--app/assets/javascripts/pipelines/components/pipeline_url.vue8
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_table_row.vue3
-rw-r--r--app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js6
-rw-r--r--app/assets/javascripts/pipelines/mixins/stage_column_mixin.js7
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_bundle.js8
-rw-r--r--app/assets/javascripts/pipelines/pipeline_details_mediator.js16
-rw-r--r--app/assets/javascripts/pipelines/services/pipeline_service.js4
-rw-r--r--app/assets/javascripts/projects/project_new.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline.vue70
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue18
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue87
-rw-r--r--app/assets/javascripts/vue_shared/components/commit.vue70
-rw-r--r--app/assets/javascripts/vue_shared/components/loading_button.vue4
-rw-r--r--app/assets/stylesheets/application.scss2
-rw-r--r--app/assets/stylesheets/components/dashboard_skeleton.scss80
-rw-r--r--app/assets/stylesheets/framework/avatar.scss4
-rw-r--r--app/assets/stylesheets/framework/blank.scss28
-rw-r--r--app/assets/stylesheets/framework/buttons.scss3
-rw-r--r--app/assets/stylesheets/framework/common.scss16
-rw-r--r--app/assets/stylesheets/framework/filters.scss11
-rw-r--r--app/assets/stylesheets/framework/header.scss4
-rw-r--r--app/assets/stylesheets/framework/icons.scss1
-rw-r--r--app/assets/stylesheets/framework/lists.scss6
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss6
-rw-r--r--app/assets/stylesheets/framework/mixins.scss6
-rw-r--r--app/assets/stylesheets/framework/panels.scss1
-rw-r--r--app/assets/stylesheets/framework/secondary_navigation_elements.scss27
-rw-r--r--app/assets/stylesheets/framework/selects.scss10
-rw-r--r--app/assets/stylesheets/framework/system_messages.scss2
-rw-r--r--app/assets/stylesheets/framework/typography.scss12
-rw-r--r--app/assets/stylesheets/framework/variables.scss32
-rw-r--r--app/assets/stylesheets/framework/variables_overrides.scss5
-rw-r--r--app/assets/stylesheets/framework/vue_transitions.scss6
-rw-r--r--app/assets/stylesheets/pages/builds.scss12
-rw-r--r--app/assets/stylesheets/pages/diff.scss3
-rw-r--r--app/assets/stylesheets/pages/groups.scss49
-rw-r--r--app/assets/stylesheets/pages/import.scss12
-rw-r--r--app/assets/stylesheets/pages/labels.scss2
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss15
-rw-r--r--app/assets/stylesheets/pages/notes.scss14
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss7
-rw-r--r--app/assets/stylesheets/pages/profile.scss15
-rw-r--r--app/assets/stylesheets/pages/projects.scss7
-rw-r--r--app/assets/stylesheets/pages/settings.scss57
-rw-r--r--app/assets/stylesheets/pages/stat_graph.scss2
-rw-r--r--app/assets/stylesheets/pages/status.scss1
-rw-r--r--app/controllers/admin/appearances_controller.rb10
-rw-r--r--app/controllers/admin/application_settings_controller.rb8
-rw-r--r--app/controllers/admin/applications_controller.rb4
-rw-r--r--app/controllers/admin/broadcast_messages_controller.rb4
-rw-r--r--app/controllers/admin/deploy_keys_controller.rb2
-rw-r--r--app/controllers/admin/groups_controller.rb8
-rw-r--r--app/controllers/admin/hooks_controller.rb4
-rw-r--r--app/controllers/admin/identities_controller.rb8
-rw-r--r--app/controllers/admin/impersonation_tokens_controller.rb6
-rw-r--r--app/controllers/admin/keys_controller.rb4
-rw-r--r--app/controllers/admin/labels_controller.rb6
-rw-r--r--app/controllers/admin/projects_controller.rb4
-rw-r--r--app/controllers/admin/runners_controller.rb8
-rw-r--r--app/controllers/admin/spam_logs_controller.rb6
-rw-r--r--app/controllers/admin/users_controller.rb42
-rw-r--r--app/controllers/autocomplete_controller.rb9
-rw-r--r--app/controllers/concerns/boards_actions.rb38
-rw-r--r--app/controllers/concerns/notes_actions.rb2
-rw-r--r--app/controllers/dashboard/projects_controller.rb4
-rw-r--r--app/controllers/explore/projects_controller.rb6
-rw-r--r--app/controllers/groups/boards_controller.rb39
-rw-r--r--app/controllers/groups/group_members_controller.rb1
-rw-r--r--app/controllers/groups/settings/ci_cd_controller.rb18
-rw-r--r--app/controllers/groups_controller.rb36
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb4
-rw-r--r--app/controllers/profiles/accounts_controller.rb2
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb25
-rw-r--r--app/controllers/projects/boards_controller.rb39
-rw-r--r--app/controllers/projects/git_http_controller.rb5
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/controllers/projects/settings/operations_controller.rb5
-rw-r--r--app/controllers/search_controller.rb7
-rw-r--r--app/finders/issuable_finder.rb2
-rw-r--r--app/finders/merge_requests_finder.rb2
-rw-r--r--app/helpers/auth_helper.rb8
-rw-r--r--app/helpers/auto_devops_helper.rb13
-rw-r--r--app/helpers/clusters_helper.rb6
-rw-r--r--app/helpers/groups_helper.rb1
-rw-r--r--app/helpers/projects_helper.rb8
-rw-r--r--app/helpers/search_helper.rb10
-rw-r--r--app/models/application_setting.rb284
-rw-r--r--app/models/application_setting_implementation.rb281
-rw-r--r--app/models/broadcast_message.rb2
-rw-r--r--app/models/ci/build.rb26
-rw-r--r--app/models/ci/pipeline.rb46
-rw-r--r--app/models/ci/runner.rb2
-rw-r--r--app/models/ci/stage.rb7
-rw-r--r--app/models/clusters/applications/runner.rb2
-rw-r--r--app/models/clusters/platforms/kubernetes.rb2
-rw-r--r--app/models/commit_status.rb21
-rw-r--r--app/models/commit_status_enums.rb3
-rw-r--r--app/models/concerns/cache_markdown_field.rb22
-rw-r--r--app/models/concerns/has_status.rb17
-rw-r--r--app/models/concerns/token_authenticatable.rb19
-rw-r--r--app/models/deployment.rb4
-rw-r--r--app/models/diff_note.rb2
-rw-r--r--app/models/email.rb2
-rw-r--r--app/models/group.rb2
-rw-r--r--app/models/label.rb7
-rw-r--r--app/models/member.rb2
-rw-r--r--app/models/merge_request.rb35
-rw-r--r--app/models/merge_request_diff.rb17
-rw-r--r--app/models/merge_request_diff_file.rb2
-rw-r--r--app/models/milestone.rb1
-rw-r--r--app/models/namespace.rb17
-rw-r--r--app/models/note.rb8
-rw-r--r--app/models/project.rb40
-rw-r--r--app/models/project_services/jira_service.rb10
-rw-r--r--app/models/project_services/kubernetes_service.rb4
-rw-r--r--app/models/project_wiki.rb2
-rw-r--r--app/models/repository.rb19
-rw-r--r--app/models/user.rb10
-rw-r--r--app/models/user_interacted_project.rb18
-rw-r--r--app/policies/group_policy.rb3
-rw-r--r--app/policies/identity_provider_policy.rb15
-rw-r--r--app/policies/issuable_policy.rb1
-rw-r--r--app/policies/merge_request_policy.rb3
-rw-r--r--app/policies/project_policy.rb1
-rw-r--r--app/presenters/ci/pipeline_presenter.rb53
-rw-r--r--app/presenters/commit_status_presenter.rb3
-rw-r--r--app/presenters/merge_request_presenter.rb6
-rw-r--r--app/presenters/project_presenter.rb10
-rw-r--r--app/serializers/detailed_status_entity.rb16
-rw-r--r--app/serializers/diff_file_entity.rb2
-rw-r--r--app/serializers/environment_entity.rb2
-rw-r--r--app/serializers/merge_request_for_pipeline_entity.rb4
-rw-r--r--app/serializers/pipeline_entity.rb3
-rw-r--r--app/services/auth/container_registry_authentication_service.rb2
-rw-r--r--app/services/ci/prepare_build_service.rb25
-rw-r--r--app/services/groups/auto_devops_service.rb17
-rw-r--r--app/services/merge_requests/reopen_service.rb2
-rw-r--r--app/services/projects/lfs_pointers/lfs_download_link_list_service.rb16
-rw-r--r--app/services/projects/lfs_pointers/lfs_download_service.rb20
-rw-r--r--app/services/search/global_service.rb3
-rw-r--r--app/services/search/group_service.rb6
-rw-r--r--app/services/search/project_service.rb7
-rw-r--r--app/validators/devise_email_validator.rb36
-rw-r--r--app/validators/email_validator.rb7
-rw-r--r--app/validators/sha_validator.rb2
-rw-r--r--app/views/admin/application_settings/_account_and_limit.html.haml29
-rw-r--r--app/views/admin/application_settings/_ci_cd.html.haml29
-rw-r--r--app/views/admin/application_settings/_email.html.haml16
-rw-r--r--app/views/admin/application_settings/_help_page.html.haml12
-rw-r--r--app/views/admin/application_settings/_repository_mirrors_form.html.haml10
-rw-r--r--app/views/admin/application_settings/_visibility_and_access.html.haml16
-rw-r--r--app/views/admin/dashboard/index.html.haml7
-rw-r--r--app/views/admin/deploy_keys/edit.html.haml8
-rw-r--r--app/views/admin/deploy_keys/index.html.haml20
-rw-r--r--app/views/admin/users/show.html.haml5
-rw-r--r--app/views/clusters/clusters/_form.html.haml4
-rw-r--r--app/views/clusters/clusters/_sidebar.html.haml2
-rw-r--r--app/views/clusters/clusters/gcp/_form.html.haml8
-rw-r--r--app/views/clusters/clusters/show.html.haml4
-rw-r--r--app/views/clusters/clusters/user/_form.html.haml1
-rw-r--r--app/views/dashboard/activity.html.haml2
-rw-r--r--app/views/dashboard/groups/index.html.haml2
-rw-r--r--app/views/dashboard/issues.html.haml2
-rw-r--r--app/views/dashboard/merge_requests.html.haml2
-rw-r--r--app/views/dashboard/projects/index.html.haml2
-rw-r--r--app/views/dashboard/projects/starred.html.haml2
-rw-r--r--app/views/dashboard/todos/index.html.haml2
-rw-r--r--app/views/doorkeeper/applications/index.html.haml2
-rw-r--r--app/views/explore/groups/index.html.haml2
-rw-r--r--app/views/explore/projects/index.html.haml2
-rw-r--r--app/views/explore/projects/starred.html.haml2
-rw-r--r--app/views/explore/projects/trending.html.haml2
-rw-r--r--app/views/groups/_home_panel.html.haml4
-rw-r--r--app/views/groups/settings/ci_cd/_auto_devops_form.html.haml15
-rw-r--r--app/views/groups/settings/ci_cd/show.html.haml14
-rw-r--r--app/views/layouts/header/_new_dropdown.haml2
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml6
-rw-r--r--app/views/layouts/nav/sidebar/_admin.html.haml2
-rw-r--r--app/views/layouts/nav/sidebar/_group.html.haml11
-rw-r--r--app/views/profiles/_email_settings.html.haml16
-rw-r--r--app/views/profiles/accounts/_providers.html.haml21
-rw-r--r--app/views/profiles/accounts/show.html.haml19
-rw-r--r--app/views/profiles/notifications/_email_settings.html.haml6
-rw-r--r--app/views/profiles/notifications/show.html.haml4
-rw-r--r--app/views/profiles/show.html.haml13
-rw-r--r--app/views/projects/_flash_messages.html.haml3
-rw-r--r--app/views/projects/_home_panel.html.haml5
-rw-r--r--app/views/projects/_new_project_fields.html.haml8
-rw-r--r--app/views/projects/blob/_header_content.html.haml2
-rw-r--r--app/views/projects/commits/_commit.html.haml9
-rw-r--r--app/views/projects/empty.html.haml133
-rw-r--r--app/views/projects/merge_requests/_mr_title.html.haml6
-rw-r--r--app/views/projects/mirrors/_authentication_method.html.haml1
-rw-r--r--app/views/projects/pipelines/_info.html.haml14
-rw-r--r--app/views/projects/pipelines/_with_tabs.html.haml30
-rw-r--r--app/views/projects/pipelines/charts.html.haml2
-rw-r--r--app/views/projects/pipelines/index.html.haml4
-rw-r--r--app/views/projects/pipelines/new.html.haml8
-rw-r--r--app/views/projects/pipelines/show.html.haml10
-rw-r--r--app/views/projects/protected_branches/shared/_index.html.haml2
-rw-r--r--app/views/projects/settings/ci_cd/_autodevops_form.html.haml8
-rw-r--r--app/views/projects/settings/ci_cd/show.html.haml2
-rw-r--r--app/views/projects/settings/operations/_error_tracking.html.haml2
-rw-r--r--app/views/projects/settings/operations/show.html.haml1
-rw-r--r--app/views/search/_category.html.haml10
-rw-r--r--app/views/search/_results.html.haml2
-rw-r--r--app/views/search/results/_user.html.haml10
-rw-r--r--app/views/shared/_file_highlight.html.haml2
-rw-r--r--app/views/shared/deploy_keys/_form.html.haml11
-rw-r--r--app/views/shared/issuable/_search_bar.html.haml5
-rw-r--r--app/views/shared/notes/_note.html.haml15
-rw-r--r--app/views/shared/projects/_project.html.haml2
-rw-r--r--app/views/shared/snippets/_form.html.haml6
-rw-r--r--app/views/shared/snippets/_header.html.haml8
-rw-r--r--app/workers/all_queues.yml1
-rw-r--r--app/workers/ci/build_prepare_worker.rb16
-rw-r--r--app/workers/cluster_configure_worker.rb2
-rw-r--r--app/workers/cluster_project_configure_worker.rb2
-rw-r--r--app/workers/concerns/waitable_worker.rb8
-rw-r--r--app/workers/create_gpg_signature_worker.rb8
-rw-r--r--app/workers/emails_on_push_worker.rb34
-rw-r--r--app/workers/object_storage/migrate_uploads_worker.rb8
-rw-r--r--app/workers/pipeline_schedule_worker.rb19
-rw-r--r--app/workers/project_cache_worker.rb1
-rw-r--r--app/workers/remove_expired_members_worker.rb8
-rw-r--r--changelogs/unreleased/10029-env-item.yml5
-rw-r--r--changelogs/unreleased/10081-env-table.yml5
-rw-r--r--changelogs/unreleased/10095-job-getters.yml5
-rw-r--r--changelogs/unreleased/10097-number-utils.yml5
-rw-r--r--changelogs/unreleased/13784-simple-masking-of-protected-variables-in-logs.yml5
-rw-r--r--changelogs/unreleased/20084-update-the-spinner-component.yml5
-rw-r--r--changelogs/unreleased/24642-activity_service_optimization.yml5
-rw-r--r--changelogs/unreleased/24971-align-emailvalidator-to-validate_email-gem-implementation.yml5
-rw-r--r--changelogs/unreleased/25942-remove-fake-repository-path-response.yml5
-rw-r--r--changelogs/unreleased/27333-re-deploy-rollback-button-should-ask-for-confirmation-before-executing.yml5
-rw-r--r--changelogs/unreleased/2802-security-add-public-internal-groups-as-members-to-your-project-idor.yml6
-rw-r--r--changelogs/unreleased/32714-copying-comment-with-ordered-list-includes-extraneous-newlines.yml5
-rw-r--r--changelogs/unreleased/34555-empty-state-for-starred-projects.yml5
-rw-r--r--changelogs/unreleased/35638-move-language-setting-to-preferences.yml5
-rw-r--r--changelogs/unreleased/37673-minor-issue-with-apostrophe-single-quote-when-clicking-assign-to-me.yml5
-rw-r--r--changelogs/unreleased/39010-add-left-margin-to-1st-time-contributor-badge.yml5
-rw-r--r--changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml5
-rw-r--r--changelogs/unreleased/40396-sidekiq-in-process-group.yml5
-rw-r--r--changelogs/unreleased/40396-use-pgroups-for-background-jobs.yml5
-rw-r--r--changelogs/unreleased/40795-set-project-name-on-fork-api.yml5
-rw-r--r--changelogs/unreleased/41888-access-personal-snippets-by-api.yml5
-rw-r--r--changelogs/unreleased/42086-project-fetch-statistics-api-http-only.yml5
-rw-r--r--changelogs/unreleased/43297-authorized-application-count.yml5
-rw-r--r--changelogs/unreleased/44740-api-to-verify-a-given-user-has-right-to-merge-a-given-mergerequest.yml5
-rw-r--r--changelogs/unreleased/45035-force-push-api.yml5
-rw-r--r--changelogs/unreleased/45305-ci-status-icon-mismatch-on-merge-requests-page-and-the-mr-itself.yml5
-rw-r--r--changelogs/unreleased/46464-improve-stop-pipeline-modal.yml5
-rw-r--r--changelogs/unreleased/46750-ci-empty-environment-is-created-even-when-a-job-isn-t-run-when-manual.yml5
-rw-r--r--changelogs/unreleased/46787-create-project-label-window-is-cut-off-at-the-bottom.yml5
-rw-r--r--changelogs/unreleased/47150-update-sshkey.yml5
-rw-r--r--changelogs/unreleased/47869-jobs-tab-border-top-in-pipeline-s-page-is-1px-off.yml5
-rw-r--r--changelogs/unreleased/48297-fix-code-selection.yml6
-rw-r--r--changelogs/unreleased/48798-keybinding-mr-diff.yml5
-rw-r--r--changelogs/unreleased/49397-move-files-in-ide.yml5
-rw-r--r--changelogs/unreleased/49502-gpg-signature-api-endpoint.yml5
-rw-r--r--changelogs/unreleased/49663-branch-to-mr-connection.yml5
-rw-r--r--changelogs/unreleased/49863-ingress-ip-loading-state.yml5
-rw-r--r--changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml5
-rw-r--r--changelogs/unreleased/50313-use-kaniko-to-build-containers-in-autodevops.yml5
-rw-r--r--changelogs/unreleased/50433-make-emoji-picker-bigger.yml5
-rw-r--r--changelogs/unreleased/51819-show-feed-toggle-under-system-notes.yml5
-rw-r--r--changelogs/unreleased/51971-milestones-visibility.yml5
-rw-r--r--changelogs/unreleased/52198-timer-is-vertically-misaligned-for-delayed-jobs-in-pipeline-actions.yml5
-rw-r--r--changelogs/unreleased/52424-goodbye-hipchat.yml5
-rw-r--r--changelogs/unreleased/52447-auto-devops-at-group-level.yml5
-rw-r--r--changelogs/unreleased/52459-display-job-names-consistently-on-pipelines-and-environments-list.yml5
-rw-r--r--changelogs/unreleased/52734-styling-of-user-project-and-group-avatars.yml5
-rw-r--r--changelogs/unreleased/52778-don-t-display-pipeline-status-if-pipelines-are-disabled.yml5
-rw-r--r--changelogs/unreleased/52792-align-mirror-repository-button.yml5
-rw-r--r--changelogs/unreleased/52877-ios-publishing-blog-post-and-gitlab-ci-yml-template.yml5
-rw-r--r--changelogs/unreleased/53139-hide-tree-single-file.yml5
-rw-r--r--changelogs/unreleased/53325-admin-runners-page-fails-with-an-sql-statement-timeout.yml5
-rw-r--r--changelogs/unreleased/53336-improve-web-ide-launch-performance.yml5
-rw-r--r--changelogs/unreleased/53361-fresh-protected-branches.yml5
-rw-r--r--changelogs/unreleased/53411-remove_personal_access_tokens_token.yml5
-rw-r--r--changelogs/unreleased/53413-externalize-markdown-toolbar-tooltips.yml5
-rw-r--r--changelogs/unreleased/53861-api-promote-project-milestone-to-a-group-milestone.yml5
-rw-r--r--changelogs/unreleased/53966-make-hashed-storage-migration-safer-and-more-inviting.yml5
-rw-r--r--changelogs/unreleased/54643-lower_issuable_finder_complexity.yml5
-rw-r--r--changelogs/unreleased/54725-fix-emoji-button-active-state.yml5
-rw-r--r--changelogs/unreleased/54796-api-sort-tie-breaker-for-pagination.yml5
-rw-r--r--changelogs/unreleased/54850-pages-domain-show-view-is-not-protected-by-access-control.yml5
-rw-r--r--changelogs/unreleased/54924-refactor-notes-actions-params.yml5
-rw-r--r--changelogs/unreleased/55057-system-message-to-core.yml5
-rw-r--r--changelogs/unreleased/55109-jira-integration-api-doesn-t-respect-available-format.yml5
-rw-r--r--changelogs/unreleased/55209-tool-tip-hides-menu-item.yml5
-rw-r--r--changelogs/unreleased/55312-svg.yml5
-rw-r--r--changelogs/unreleased/55376-related_merge_requests-api-call-returns-merge-requests-that-are-not-related-to-the-issue.yml5
-rw-r--r--changelogs/unreleased/55447-validate-k8s-ca-cert.yml5
-rw-r--r--changelogs/unreleased/55703-md-image-borders.yml5
-rw-r--r--changelogs/unreleased/55893-artifacts-download.yml5
-rw-r--r--changelogs/unreleased/55925-if-there-is-only-one-changed-page-in-review-app-go-directly-there.yml5
-rw-r--r--changelogs/unreleased/56015-remove-remote-timeout.yml (renamed from changelogs/unreleased/57544-web-ide-new-directory-dialog-shows-file-templates.yml)4
-rw-r--r--changelogs/unreleased/56089-merge-gitlab-keys.yml5
-rw-r--r--changelogs/unreleased/56237-api-truncated-commit-title.yml5
-rw-r--r--changelogs/unreleased/56251-fix-issue-board-weekday-shift.yml5
-rw-r--r--changelogs/unreleased/56477-units-are-appended-to-y-axis-label-on-metrics-dashboard.yml5
-rw-r--r--changelogs/unreleased/56485-implement-graphql-mergerequestsresolver.yml5
-rw-r--r--changelogs/unreleased/56492-implement-new-arguments-state-closed_before-and-closed_after-for-issuesresolver-in-graphql.yml5
-rw-r--r--changelogs/unreleased/56618-hashed-storage-skip-validation.yml5
-rw-r--r--changelogs/unreleased/56694-mark-group-level-labels-in-label-api-as-such.yml5
-rw-r--r--changelogs/unreleased/56726-fix-n-1-in-issues-and-merge-requests-api.yml5
-rw-r--r--changelogs/unreleased/56787-realtime-validation-for-user-fullname-and-username.yml5
-rw-r--r--changelogs/unreleased/56809-graphql-version-api.yml5
-rw-r--r--changelogs/unreleased/56833-project-improve-empty-repository-state-ui-fe.yml5
-rw-r--r--changelogs/unreleased/56851-blank-values-in-reactive-cache.yml5
-rw-r--r--changelogs/unreleased/56851-error-tracking-page-seems-broken.yml5
-rw-r--r--changelogs/unreleased/56863-system-messages-in-email.yml5
-rw-r--r--changelogs/unreleased/56864-reopen-locked-mr.yml5
-rw-r--r--changelogs/unreleased/56871-list-issues-error.yml5
-rw-r--r--changelogs/unreleased/56873-only-load-syntax-highlighting-css-when-selected.yml5
-rw-r--r--changelogs/unreleased/56937-edit-knative-domain-after-it-has-been-deployed.yml5
-rw-r--r--changelogs/unreleased/56937-edit-knative-domain.yml5
-rw-r--r--changelogs/unreleased/56954-improve-knative-after-installing-tiller.yml5
-rw-r--r--changelogs/unreleased/57085-introduce-zoom-and-scroll-functionality-on-metrics-charts.yml5
-rw-r--r--changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml5
-rw-r--r--changelogs/unreleased/57115-just-in-time-k8s-resource-creation.yml5
-rw-r--r--changelogs/unreleased/57223-wiki-finder.yml5
-rw-r--r--changelogs/unreleased/57353-git-push-fails-on-large-lfs-files-where-the-push-take-a-long-time.yml5
-rw-r--r--changelogs/unreleased/57357-automate-base-domain-help-text.yml5
-rw-r--r--changelogs/unreleased/57409-loading-button-transition.yml5
-rw-r--r--changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml5
-rw-r--r--changelogs/unreleased/57534_filter_impersonated_sessions.yml6
-rw-r--r--changelogs/unreleased/57540-filename-trailing-space.yml5
-rw-r--r--changelogs/unreleased/57564-contributing-button-border.yml5
-rw-r--r--changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml5
-rw-r--r--changelogs/unreleased/57582-dropdown-icon-misalignment-on-issues-list-on-mobile-screen.yml5
-rw-r--r--changelogs/unreleased/57612-github-importer-ignores-milestone-due_date.yml5
-rw-r--r--changelogs/unreleased/57648-make-emoji-picker-full-width-on-mobile.yml5
-rw-r--r--changelogs/unreleased/57655-fix-markdown-tables-border.yml5
-rw-r--r--changelogs/unreleased/57671-fix_merge_request_base_pipeline.yml5
-rw-r--r--changelogs/unreleased/57712-project-import-error-user-expected-got-hash.yml5
-rw-r--r--changelogs/unreleased/57734-improve-label-dropdown-selection-performance.yml5
-rw-r--r--changelogs/unreleased/57768-remove-vertical-line.yml5
-rw-r--r--changelogs/unreleased/57784-make-closed-duplicate-and-closed-moved-button-a-link-to-target.yml5
-rw-r--r--changelogs/unreleased/57785-create-project-template-for-netlify.yml5
-rw-r--r--changelogs/unreleased/57788-project-labels-tooltip-missing.yml5
-rw-r--r--changelogs/unreleased/57794-project-template-for-net.yml5
-rw-r--r--changelogs/unreleased/57829-issuable-meta-line-ui-broken-on-mobile.yml5
-rw-r--r--changelogs/unreleased/57894-buttons-on-group-page-are-misaligned.yml5
-rw-r--r--changelogs/unreleased/57905-etag-caching-probably-broken-since-11-5-0.yml5
-rw-r--r--changelogs/unreleased/57984-store-branch-name.yml5
-rw-r--r--changelogs/unreleased/57991-frontend-pagination-needs-to-handle-cases-where-the-x-total-pages-header-isn-t-present.yml5
-rw-r--r--changelogs/unreleased/58010-mask-the-existing-variables.yml5
-rw-r--r--changelogs/unreleased/58020-fix-merge-api-endpoint-param.yml5
-rw-r--r--changelogs/unreleased/58023-add-Saturday-to-localization-first-day-of-the-week.yml5
-rw-r--r--changelogs/unreleased/58082-project-template-for-go-micro.yml5
-rw-r--r--changelogs/unreleased/58098-auto-devops-postgres-version-variable.yml5
-rw-r--r--changelogs/unreleased/58149-fix-read-list-board-policy.yml6
-rw-r--r--changelogs/unreleased/58208-explicitly-set-masterauth.yml6
-rw-r--r--changelogs/unreleased/58274-folder-icon-in-tags-page.yml5
-rw-r--r--changelogs/unreleased/58369-hide-squash-commit.yml5
-rw-r--r--changelogs/unreleased/58410-change-pixel-size-of-instance-header-footer-message-to-16px.yml5
-rw-r--r--changelogs/unreleased/58482-update-airminc-clair-local-scan-to-2-0-6.yml5
-rw-r--r--changelogs/unreleased/58570-fix-running-pipline-that-is-imported-via-dotnetcore-template.yml5
-rw-r--r--changelogs/unreleased/58648-project-template-for-ios.yml5
-rw-r--r--changelogs/unreleased/58739-hashed-storage-prevent-a-migration-and-rollback-running-at-the-same-time.yml5
-rw-r--r--changelogs/unreleased/58781-silent-progress-in-auto-devops.yml5
-rw-r--r--changelogs/unreleased/58789-some-system-notes-on-issuable-are-folded-on-mobile.yml5
-rw-r--r--changelogs/unreleased/58797-broken-ui-on-a-closed-merge-request-from-a-deleted-source-project.yml5
-rw-r--r--changelogs/unreleased/58883-fix-fetching-comments.yml5
-rw-r--r--changelogs/unreleased/58889-spinners-are-active-prematurely-in-bitbucket-cloud-import.yml5
-rw-r--r--changelogs/unreleased/58933-broken-ui-on-commits-on-mobile.yml5
-rw-r--r--changelogs/unreleased/59057-buttons-on-top-from-a-user-profile-page-on-mobile.yml5
-rw-r--r--changelogs/unreleased/59117-inconsistent-hover-behavior-on-navbar-items.yml5
-rw-r--r--changelogs/unreleased/59189-long-names-in-project-path-namespace-dropdown-breaks-past-container.yml5
-rw-r--r--changelogs/unreleased/8711-prep-frontend-single-repo.yml5
-rw-r--r--changelogs/unreleased/9841-geo-unable-to-compare-branches-on-secondary.yml5
-rw-r--r--changelogs/unreleased/add-project-level-config-for-prospective-merge-pipelines-ce.yml5
-rw-r--r--changelogs/unreleased/add-related-merge-request-count-to-api-response.yml5
-rw-r--r--changelogs/unreleased/add-title-attribute-to-file-row.yml5
-rw-r--r--changelogs/unreleased/add-youtrack-integration.yml5
-rw-r--r--changelogs/unreleased/add_ldap_tls_options.yml5
-rw-r--r--changelogs/unreleased/allow-filtering-labels-by-a-single-character.yml5
-rw-r--r--changelogs/unreleased/allow-maintainers-to-remove-pages.yml5
-rw-r--r--changelogs/unreleased/allow-to-recursively-include.yml5
-rw-r--r--changelogs/unreleased/an-peek-jaeger.yml5
-rw-r--r--changelogs/unreleased/auto-devops-tags.yml5
-rw-r--r--changelogs/unreleased/avoid_es_loading_project_ci_status.yml5
-rw-r--r--changelogs/unreleased/bvl-graphql-csrf.yml5
-rw-r--r--changelogs/unreleased/ce-56153-error-tracking-counts.yml5
-rw-r--r--changelogs/unreleased/change-badges-example-to-pipeline.yml5
-rw-r--r--changelogs/unreleased/changelogs-readme.yml5
-rw-r--r--changelogs/unreleased/consistent-pagination.yml5
-rw-r--r--changelogs/unreleased/deploy-keys-ext.yml5
-rw-r--r--changelogs/unreleased/deprecated-migration-inheritance.yml5
-rw-r--r--changelogs/unreleased/diff-tree-resizable.yml5
-rw-r--r--changelogs/unreleased/do-not-force-2fa.yml6
-rw-r--r--changelogs/unreleased/dz-sort-labels-alphabetically.yml5
-rw-r--r--changelogs/unreleased/enable-markup-highlighting.yml5
-rw-r--r--changelogs/unreleased/expand-diff-to-full-file.yml5
-rw-r--r--changelogs/unreleased/expose-additional-merge-request-pipeline-variables.yml5
-rw-r--r--changelogs/unreleased/expose-group-id-on-home-panel.yml5
-rw-r--r--changelogs/unreleased/expose-merge-ref-to-runner.yml5
-rw-r--r--changelogs/unreleased/expose-merge-request-entity-for-pipelines.yml5
-rw-r--r--changelogs/unreleased/fast-destroy-uploads.yml5
-rw-r--r--changelogs/unreleased/feature-api-delete-job-artifacts.yml5
-rw-r--r--changelogs/unreleased/feature-gb-add-serverless-cicd-template.yml5
-rw-r--r--changelogs/unreleased/feature-gb-enable-ci-persisted-stages-by-default.yml5
-rw-r--r--changelogs/unreleased/feature-runner-tag-filter-for-admin-view.yml5
-rw-r--r--changelogs/unreleased/feature-users-search-results.yml5
-rw-r--r--changelogs/unreleased/filter-confidential-issues.yml5
-rw-r--r--changelogs/unreleased/filter-merge-requests-by-target-branch.yml5
-rw-r--r--changelogs/unreleased/filter-note-parameters.yml5
-rw-r--r--changelogs/unreleased/fix-38010-sidebar-loads-and-collapses.yml5
-rw-r--r--changelogs/unreleased/fix-badges-logs.yml5
-rw-r--r--changelogs/unreleased/fix-blob-editor-deleting-content.yml5
-rw-r--r--changelogs/unreleased/fix-gb-fix-bridge-jobs-variables-policy.yml5
-rw-r--r--changelogs/unreleased/fix-group-without-owner.yml5
-rw-r--r--changelogs/unreleased/fix-ide-web-worker-relative-url.yml5
-rw-r--r--changelogs/unreleased/fix-missing-border.yml5
-rw-r--r--changelogs/unreleased/fix-new-merge-request-diff-headers-sticky-position.yml5
-rw-r--r--changelogs/unreleased/fix-pipeline-entity.yml5
-rw-r--r--changelogs/unreleased/fix-review-app-env-url.yml5
-rw-r--r--changelogs/unreleased/fix_-56347.yml5
-rw-r--r--changelogs/unreleased/gitaly-version-v1.26.0.yml5
-rw-r--r--changelogs/unreleased/gitaly-version-v1.29.0.yml5
-rw-r--r--changelogs/unreleased/gitlab_kubernetes_helm_bump.yml5
-rw-r--r--changelogs/unreleased/gokhanap-master-patch-03762.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-projects-commit.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-projects-pipelines.yml5
-rw-r--r--changelogs/unreleased/gt-update-activity-filter-for-issues.yml5
-rw-r--r--changelogs/unreleased/gt-update-new-password-breadcrumb.yml5
-rw-r--r--changelogs/unreleased/gt-update-operations-settings-breadcrumb-trail.yml5
-rw-r--r--changelogs/unreleased/helm-2-12-3.yml5
-rw-r--r--changelogs/unreleased/import-go-to-project-cta.yml5
-rw-r--r--changelogs/unreleased/improve-performance-for-diverging-commit-counts.yml5
-rw-r--r--changelogs/unreleased/improve-snippets-empty-state.yml5
-rw-r--r--changelogs/unreleased/include-ci-yaml.yml5
-rw-r--r--changelogs/unreleased/ingress-hostnames.yml5
-rw-r--r--changelogs/unreleased/jc-fix-set-project-writable.yml5
-rw-r--r--changelogs/unreleased/jej-feature-gates-can-be-set-by-group-path.yml5
-rw-r--r--changelogs/unreleased/jira-link-mention-compact.yml5
-rw-r--r--changelogs/unreleased/k8s_new_deployment_labels.yml5
-rw-r--r--changelogs/unreleased/kinolaev-master-patch-87865.yml5
-rw-r--r--changelogs/unreleased/move_chatops_to_core.yml5
-rw-r--r--changelogs/unreleased/nfriend-update-job-detail-view-sidebar.yml5
-rw-r--r--changelogs/unreleased/nfriend-update-merge-request-widget-pipeline-block.yml6
-rw-r--r--changelogs/unreleased/nfriend-update-pipeline-detail-view.yml5
-rw-r--r--changelogs/unreleased/nfriend-update-pipeline-list-view.yml5
-rw-r--r--changelogs/unreleased/only-counted-active-milestones-as-started.yml5
-rw-r--r--changelogs/unreleased/osw-create-and-store-merge-ref-for-mrs.yml5
-rw-r--r--changelogs/unreleased/osw-fetch-latest-version-when-creating-suggestions.yml5
-rw-r--r--changelogs/unreleased/osw-merge-refs-refreshing-api.yml5
-rw-r--r--changelogs/unreleased/osw-merge-to-ref-changes-for-ci-team.yml5
-rw-r--r--changelogs/unreleased/osw-multi-line-suggestions-parsing.yml5
-rw-r--r--changelogs/unreleased/patch-45.yml5
-rw-r--r--changelogs/unreleased/persist-source-sha-and-target-sha-for-pipelines.yml5
-rw-r--r--changelogs/unreleased/pravi-gitlab-ce-update-recaptcha.yml5
-rw-r--r--changelogs/unreleased/ravlen-fix-spaces-unicode.yml5
-rw-r--r--changelogs/unreleased/refactor-56367-extract-resolve-with-issue-button-component.yml5
-rw-r--r--changelogs/unreleased/refactor-merge-request-between-pipeline-and-build.yml5
-rw-r--r--changelogs/unreleased/remove-second-primary-button-on-wiki-edit.yml5
-rw-r--r--changelogs/unreleased/restrict-jupyter-login.yml5
-rw-r--r--changelogs/unreleased/rs-admin-user-case-insensitive.yml5
-rw-r--r--changelogs/unreleased/security-2774-milestones-detail.yml5
-rw-r--r--changelogs/unreleased/security-2797-milestone-mrs.yml5
-rw-r--r--changelogs/unreleased/security-2798-fix-boards-policy.yml5
-rw-r--r--changelogs/unreleased/security-2799-emails.yml5
-rw-r--r--changelogs/unreleased/security-50334.yml5
-rw-r--r--changelogs/unreleased/security-55468-check-validity-before-querying.yml5
-rw-r--r--changelogs/unreleased/security-56348.yml5
-rw-r--r--changelogs/unreleased/security-commit-private-related-mr.yml5
-rw-r--r--changelogs/unreleased/security-fj-diff-import-file-read-fix.yml5
-rw-r--r--changelogs/unreleased/security-id-fix-mr-visibility.yml5
-rw-r--r--changelogs/unreleased/security-id-restricted-access-to-private-repo.yml5
-rw-r--r--changelogs/unreleased/security-issue_54789_2.yml5
-rw-r--r--changelogs/unreleased/security-kubernetes-google-login-csrf.yml5
-rw-r--r--changelogs/unreleased/security-kubernetes-local-ssrf.yml5
-rw-r--r--changelogs/unreleased/security-mermaid.yml5
-rw-r--r--changelogs/unreleased/security-osw-stop-linking-to-packages.yml5
-rw-r--r--changelogs/unreleased/security-protect-private-repo-information.yml5
-rw-r--r--changelogs/unreleased/security-shared-project-private-group.yml5
-rw-r--r--changelogs/unreleased/security-tags-oracle.yml5
-rw-r--r--changelogs/unreleased/sh-bump-fog-gem.yml5
-rw-r--r--changelogs/unreleased/sh-cache-root-ref-asymetrically.yml5
-rw-r--r--changelogs/unreleased/sh-fix-cpp-templates-404.yml5
-rw-r--r--changelogs/unreleased/sh-fix-double-xhr-pipelines.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-58103.yml5
-rw-r--r--changelogs/unreleased/sh-fix-issue-59065.yml5
-rw-r--r--changelogs/unreleased/sh-include-project-path-for-internal-api.yml5
-rw-r--r--changelogs/unreleased/sh-log-rails-queue-duration.yml5
-rw-r--r--changelogs/unreleased/sh-optimize-calendar-activities.yml5
-rw-r--r--changelogs/unreleased/sh-optimize-todos-api.yml5
-rw-r--r--changelogs/unreleased/sh-reject-info-refs-head-requests.yml5
-rw-r--r--changelogs/unreleased/sh-remove-nplusone-admin-runners-tags.yml5
-rw-r--r--changelogs/unreleased/sh-rugged-commit-is-ancestor.yml5
-rw-r--r--changelogs/unreleased/sh-rugged-find-commit.yml5
-rw-r--r--changelogs/unreleased/sh-rugged-get-tree-entry.yml5
-rw-r--r--changelogs/unreleased/sh-rugged-tree-entries.yml5
-rw-r--r--changelogs/unreleased/sh-skip-sti-tables-reltuples.yml5
-rw-r--r--changelogs/unreleased/sh-wip-fix-duplicate-env-xhr.yml5
-rw-r--r--changelogs/unreleased/shell-8-7.yml5
-rw-r--r--changelogs/unreleased/support-only-changes-on-mr-pipelines.yml5
-rw-r--r--changelogs/unreleased/table-fix-scroll-and-block.yml5
-rw-r--r--changelogs/unreleased/tpresa-add-highest-role-to-user.yml5
-rw-r--r--changelogs/unreleased/tr-error-tracking-project-selection.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-2-0.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-3-0.yml5
-rw-r--r--changelogs/unreleased/update-rack-oauth2.yml5
-rw-r--r--changelogs/unreleased/use-date-for-upcoming-milestone-comparison.yml5
-rw-r--r--changelogs/unreleased/use-encrypted-runner-tokens.yml5
-rw-r--r--changelogs/unreleased/use-only-all-pipelines.yml5
-rw-r--r--changelogs/unreleased/web-ide-commit-header-icon-alignment-fix.yml5
-rw-r--r--changelogs/unreleased/web-ide-default-editor.yml5
-rw-r--r--changelogs/unreleased/winh-enable-reply_to_individual_notes.yml5
-rw-r--r--changelogs/unreleased/winh-toggle-comment-draft.yml5
-rw-r--r--changelogs/unreleased/zj-load-languages-from-database.yml5
-rw-r--r--config/application.rb2
-rw-r--r--config/gitlab.yml.example2
-rw-r--r--config/initializers/1_settings.rb1
-rw-r--r--config/initializers/console_message.rb2
-rw-r--r--config/initializers/rspec_profiling.rb50
-rw-r--r--config/initializers/sentry.rb15
-rw-r--r--config/initializers/trusted_proxies.rb6
-rw-r--r--config/karma.config.js66
-rw-r--r--config/routes.rb1
-rw-r--r--config/routes/group.rb2
-rw-r--r--config/webpack.config.js62
-rw-r--r--danger/documentation/Dangerfile24
-rw-r--r--danger/roulette/Dangerfile4
-rw-r--r--danger/single_codebase/Dangerfile56
-rw-r--r--db/fixtures/development/02_settings.rb (renamed from db/fixtures/development/03_settings.rb)0
-rw-r--r--db/fixtures/development/03_project.rb (renamed from db/fixtures/development/04_project.rb)2
-rw-r--r--db/fixtures/development/04_labels.rb49
-rw-r--r--db/fixtures/development/09_issues.rb6
-rw-r--r--db/fixtures/development/10_merge_requests.rb6
-rw-r--r--db/fixtures/development/22_labeled_issues_seed.rb103
-rw-r--r--db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb9
-rw-r--r--db/migrate/20190225152525_add_auto_dev_ops_enabled_to_namespaces.rb9
-rw-r--r--db/schema.rb1
-rw-r--r--doc/administration/build_artifacts.md4
-rw-r--r--doc/administration/container_registry.md1
-rw-r--r--doc/administration/git_protocol.md11
-rw-r--r--doc/administration/high_availability/redis.md3
-rw-r--r--doc/administration/index.md1
-rw-r--r--doc/administration/job_artifacts.md2
-rw-r--r--doc/administration/monitoring/index.md2
-rw-r--r--doc/administration/monitoring/performance/introduction.md4
-rw-r--r--doc/administration/monitoring/performance/prometheus.md4
-rw-r--r--doc/administration/monitoring/prometheus/index.md1
-rw-r--r--doc/administration/operations.md4
-rw-r--r--doc/administration/operations/speed_up_ssh.md4
-rw-r--r--doc/administration/pages/index.md1
-rw-r--r--doc/administration/raketasks/storage.md48
-rw-r--r--doc/administration/repository_storage_types.md114
-rw-r--r--doc/administration/repository_storages.md4
-rw-r--r--doc/administration/uploads.md2
-rw-r--r--doc/administration/user_settings.md35
-rw-r--r--doc/api/commits.md2
-rw-r--r--doc/api/group_milestones.md1
-rw-r--r--doc/api/namespaces.md12
-rw-r--r--doc/api/pipeline_schedules.md8
-rw-r--r--doc/api/project_snippets.md1
-rw-r--r--doc/api/releases/links.md10
-rw-r--r--doc/api/search.md71
-rw-r--r--doc/api/services.md7
-rw-r--r--doc/api/users.md3
-rw-r--r--doc/ci/README.md40
-rw-r--r--doc/ci/autodeploy/index.md4
-rw-r--r--doc/ci/autodeploy/quick_start_guide.md4
-rw-r--r--doc/ci/build_artifacts/README.md4
-rw-r--r--doc/ci/chatops/README.md6
-rw-r--r--doc/ci/docker/using_docker_build.md1
-rw-r--r--doc/ci/environments.md10
-rw-r--r--doc/ci/examples/README.md4
-rw-r--r--doc/ci/examples/container_scanning.md4
-rw-r--r--doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md2
-rw-r--r--doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md19
-rw-r--r--doc/ci/examples/end_to_end_testing_webdriverio/index.md4
-rw-r--r--doc/ci/examples/laravel_with_gitlab_and_envoy/index.md9
-rw-r--r--doc/ci/examples/sast_docker.md6
-rw-r--r--doc/ci/examples/test-scala-application.md9
-rw-r--r--doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md4
-rw-r--r--doc/ci/img/pipelines-goal.pngbin15284 -> 0 bytes
-rw-r--r--doc/ci/img/types-of-pipelines.pngbin12268 -> 0 bytes
-rw-r--r--doc/ci/introduction/img/gitlab_workflow_example.pngbin0 -> 55394 bytes
-rw-r--r--doc/ci/introduction/index.md11
-rw-r--r--doc/ci/junit_test_reports.md6
-rw-r--r--doc/ci/merge_request_pipelines/index.md54
-rw-r--r--doc/ci/permissions/README.md6
-rw-r--r--doc/ci/pipelines.md414
-rw-r--r--doc/ci/review_apps/index.md4
-rw-r--r--doc/ci/services/README.md2
-rw-r--r--doc/ci/services/postgres.md2
-rw-r--r--doc/ci/triggers/README.md13
-rw-r--r--doc/ci/variables/README.md9
-rw-r--r--doc/ci/variables/where_variables_can_be_used.md4
-rw-r--r--doc/ci/yaml/README.md32
-rw-r--r--doc/container_registry/README.md4
-rw-r--r--doc/container_registry/troubleshooting.md4
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/architecture.md12
-rw-r--r--doc/development/changelog.md2
-rw-r--r--doc/development/code_review.md17
-rw-r--r--doc/development/contributing/index.md1
-rw-r--r--doc/development/contributing/merge_request_workflow.md326
-rw-r--r--doc/development/documentation/feature-change-workflow.md2
-rw-r--r--doc/development/documentation/styleguide.md7
-rw-r--r--doc/development/fe_guide/event_tracking.md (renamed from doc/development/new_fe_guide/event_tracking.md)0
-rw-r--r--doc/development/fe_guide/index.md35
-rw-r--r--doc/development/fe_guide/principles.md15
-rw-r--r--doc/development/fe_guide/vue.md7
-rw-r--r--doc/development/fe_guide/vuex.md4
-rw-r--r--doc/development/git_object_deduplication.md261
-rw-r--r--doc/development/gitaly.md6
-rw-r--r--doc/development/go_guide/index.md33
-rw-r--r--doc/development/new_fe_guide/development/design_patterns.md3
-rw-r--r--doc/development/new_fe_guide/development/index.md12
-rw-r--r--doc/development/new_fe_guide/development/network_requests.md3
-rw-r--r--doc/development/new_fe_guide/development/security.md14
-rw-r--r--doc/development/new_fe_guide/index.md11
-rw-r--r--doc/development/new_fe_guide/initiatives.md3
-rw-r--r--doc/development/new_fe_guide/principles.md35
-rw-r--r--doc/development/new_fe_guide/style/html.md28
-rw-r--r--doc/development/new_fe_guide/style/javascript.md312
-rw-r--r--doc/development/new_fe_guide/tips.md16
-rw-r--r--doc/development/rolling_out_changes_using_feature_flags.md14
-rw-r--r--doc/development/testing_guide/end_to_end_tests.md61
-rw-r--r--doc/development/testing_guide/frontend_testing.md51
-rw-r--r--doc/development/testing_guide/img/qa_on_merge_requests_cicd_architecture.pngbin0 -> 64862 bytes
-rw-r--r--doc/development/testing_guide/review_apps.md4
-rw-r--r--doc/development/testing_guide/smoke.md14
-rw-r--r--doc/development/testing_guide/testing_levels.md4
-rw-r--r--doc/development/ux_guide/users.md4
-rw-r--r--doc/gitlab-basics/create-your-ssh-keys.md39
-rw-r--r--doc/gitlab-basics/img/profile_settings.pngbin2842 -> 0 bytes
-rw-r--r--doc/gitlab-basics/img/profile_settings_ssh_keys.pngbin16531 -> 0 bytes
-rw-r--r--doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.pngbin13436 -> 0 bytes
-rw-r--r--doc/gitlab-basics/img/profile_settings_ssh_keys_title.pngbin1867 -> 0 bytes
-rw-r--r--doc/hooks/custom_hooks.md4
-rw-r--r--doc/incoming_email/README.md4
-rw-r--r--doc/incoming_email/postfix.md4
-rw-r--r--doc/install/requirements.md3
-rw-r--r--doc/integration/chat_commands.md4
-rw-r--r--doc/integration/crowd.md4
-rw-r--r--doc/integration/external-issue-tracker.md17
-rw-r--r--doc/integration/jira.md4
-rw-r--r--doc/integration/ldap.md4
-rw-r--r--doc/integration/omniauth.md2
-rw-r--r--doc/integration/slack.md4
-rw-r--r--doc/logs/logs.md4
-rw-r--r--doc/monitoring/health_check.md4
-rw-r--r--doc/monitoring/performance/gitlab_configuration.md4
-rw-r--r--doc/monitoring/performance/grafana_configuration.md4
-rw-r--r--doc/monitoring/performance/influxdb_configuration.md4
-rw-r--r--doc/monitoring/performance/influxdb_schema.md4
-rw-r--r--doc/monitoring/performance/introduction.md4
-rw-r--r--doc/permissions/permissions.md4
-rw-r--r--doc/profile/README.md4
-rw-r--r--doc/profile/preferences.md4
-rw-r--r--doc/profile/two_factor_authentication.md4
-rw-r--r--doc/raketasks/backup_restore.md9
-rw-r--r--doc/topics/authentication/index.md2
-rw-r--r--doc/topics/autodevops/index.md68
-rw-r--r--doc/university/README.md7
-rw-r--r--doc/user/admin_area/settings/continuous_integration.md6
-rw-r--r--doc/user/discussions/index.md9
-rw-r--r--doc/user/gitlab_com/index.md2
-rw-r--r--doc/user/group/clusters/index.md27
-rw-r--r--doc/user/group/index.md24
-rw-r--r--doc/user/index.md38
-rw-r--r--doc/user/instance_statistics/convdev.md2
-rw-r--r--doc/user/markdown.md10
-rw-r--r--doc/user/permissions.md11
-rw-r--r--doc/user/profile/account/two_factor_authentication.md1
-rw-r--r--doc/user/profile/img/personal_access_tokens.pngbin18553 -> 0 bytes
-rw-r--r--doc/user/profile/personal_access_tokens.md18
-rw-r--r--doc/user/project/badges.md6
-rw-r--r--doc/user/project/bulk_editing.md1
-rw-r--r--doc/user/project/clusters/index.md48
-rw-r--r--doc/user/project/container_registry.md6
-rw-r--r--doc/user/project/index.md2
-rw-r--r--doc/user/project/integrations/img/issue_configuration.pngbin11882 -> 0 bytes
-rw-r--r--doc/user/project/integrations/jira.md2
-rw-r--r--doc/user/project/integrations/prometheus.md2
-rw-r--r--doc/user/project/integrations/redmine.md4
-rw-r--r--doc/user/project/integrations/webhooks.md21
-rw-r--r--doc/user/project/integrations/youtrack.md47
-rw-r--r--doc/user/project/issue_board.md10
-rw-r--r--doc/user/project/issues/confidential_issues.md7
-rw-r--r--doc/user/project/issues/crosslinking_issues.md9
-rw-r--r--doc/user/project/issues/due_dates.md7
-rw-r--r--doc/user/project/issues/index.md4
-rw-r--r--doc/user/project/merge_requests/img/filter_wip_merge_requests.pngbin6285 -> 28572 bytes
-rw-r--r--doc/user/project/merge_requests/img/wip_blocked_accept_button.pngbin4152 -> 7141 bytes
-rw-r--r--doc/user/project/merge_requests/img/wip_mark_as_wip.pngbin7961 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/img/wip_unmark_as_wip.pngbin8424 -> 0 bytes
-rw-r--r--doc/user/project/merge_requests/merge_when_pipeline_succeeds.md2
-rw-r--r--doc/user/project/merge_requests/versions.md1
-rw-r--r--doc/user/project/merge_requests/work_in_progress_merge_requests.md57
-rw-r--r--doc/user/project/milestones/index.md2
-rw-r--r--doc/user/project/new_ci_build_permissions_model.md6
-rw-r--r--doc/user/project/pages/getting_started_part_four.md2
-rw-r--r--doc/user/project/pages/introduction.md7
-rw-r--r--doc/user/project/pipelines/job_artifacts.md9
-rw-r--r--doc/user/project/pipelines/schedules.md3
-rw-r--r--doc/user/project/protected_branches.md4
-rw-r--r--doc/user/project/wiki/index.md13
-rw-r--r--doc/user/snippets.md2
-rw-r--r--doc/web_hooks/web_hooks.md4
-rw-r--r--doc/workflow/repository_mirroring.md8
-rw-r--r--jest.config.js1
-rw-r--r--lib/api/discussions.rb4
-rw-r--r--lib/api/entities.rb4
-rw-r--r--lib/api/groups.rb20
-rw-r--r--lib/api/helpers.rb11
-rw-r--r--lib/api/helpers/discussions_helpers.rb13
-rw-r--r--lib/api/helpers/notes_helpers.rb6
-rw-r--r--lib/api/helpers/resource_label_events_helpers.rb13
-rw-r--r--lib/api/helpers/search_helpers.rb22
-rw-r--r--lib/api/helpers/services_helpers.rb721
-rw-r--r--lib/api/internal.rb15
-rw-r--r--lib/api/notes.rb4
-rw-r--r--lib/api/project_milestones.rb16
-rw-r--r--lib/api/repositories.rb18
-rw-r--r--lib/api/resource_label_events.rb4
-rw-r--r--lib/api/search.rb38
-rw-r--r--lib/api/services.rb698
-rw-r--r--lib/api/settings.rb7
-rw-r--r--lib/api/triggers.rb2
-rw-r--r--lib/api/users.rb2
-rw-r--r--lib/api/variables.rb10
-rw-r--r--lib/backup/uploads.rb2
-rw-r--r--lib/banzai/filter/output_safety.rb11
-rw-r--r--lib/banzai/filter/reference_filter.rb5
-rw-r--r--lib/banzai/filter/suggestion_filter.rb18
-rw-r--r--lib/banzai/filter/syntax_highlight_filter.rb21
-rw-r--r--lib/gitlab/access.rb14
-rw-r--r--lib/gitlab/auth/ldap/person.rb2
-rw-r--r--lib/gitlab/auth/o_auth/user.rb17
-rw-r--r--lib/gitlab/authorized_keys.rb149
-rw-r--r--lib/gitlab/background_migration/archive_legacy_traces.rb9
-rw-r--r--lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb12
-rw-r--r--lib/gitlab/background_migration/populate_untracked_uploads.rb14
-rw-r--r--lib/gitlab/badge/pipeline/template.rb1
-rw-r--r--lib/gitlab/bitbucket_import/importer.rb142
-rw-r--r--lib/gitlab/bitbucket_server_import/importer.rb40
-rw-r--r--lib/gitlab/checks/branch_check.rb2
-rw-r--r--lib/gitlab/ci/build/artifacts/metadata.rb2
-rw-r--r--lib/gitlab/ci/build/prerequisite/base.rb27
-rw-r--r--lib/gitlab/ci/build/prerequisite/factory.rb33
-rw-r--r--lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb40
-rw-r--r--lib/gitlab/ci/model.rb2
-rw-r--r--lib/gitlab/ci/pipeline/chain/command.rb2
-rw-r--r--lib/gitlab/ci/status/build/factory.rb1
-rw-r--r--lib/gitlab/ci/status/build/failed.rb3
-rw-r--r--lib/gitlab/ci/status/build/preparing.rb28
-rw-r--r--lib/gitlab/ci/status/preparing.rb33
-rw-r--r--lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml1
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml16
-rw-r--r--lib/gitlab/ci/templates/Bash.gitlab-ci.yml14
-rw-r--r--lib/gitlab/ci/templates/C++.gitlab-ci.yml6
-rw-r--r--lib/gitlab/ci/templates/Chef.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Clojure.gitlab-ci.yml8
-rw-r--r--lib/gitlab/ci/templates/Code-Quality.gitlab-ci.yml17
-rw-r--r--lib/gitlab/ci/templates/Django.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Gradle.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Grails.gitlab-ci.yml10
-rw-r--r--lib/gitlab/ci/templates/Julia.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Laravel.gitlab-ci.yml15
-rw-r--r--lib/gitlab/ci/templates/Maven.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Mono.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml1
-rw-r--r--lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml1
-rw-r--r--lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml3
-rw-r--r--lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml13
-rw-r--r--lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml1
-rw-r--r--lib/gitlab/ci/templates/Python.gitlab-ci.yml4
-rw-r--r--lib/gitlab/ci/templates/Ruby.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml17
-rw-r--r--lib/gitlab/ci/templates/dotNET.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml2
-rw-r--r--lib/gitlab/danger/helper.rb2
-rw-r--r--lib/gitlab/danger/teammate.rb8
-rw-r--r--lib/gitlab/database.rb10
-rw-r--r--lib/gitlab/database/count/reltuples_count_strategy.rb18
-rw-r--r--lib/gitlab/database/count/tablesample_count_strategy.rb11
-rw-r--r--lib/gitlab/database/multi_threaded_migration.rb10
-rw-r--r--lib/gitlab/database/sha_attribute.rb2
-rw-r--r--lib/gitlab/diff/file.rb25
-rw-r--r--lib/gitlab/diff/suggestion_diff.rb37
-rw-r--r--lib/gitlab/diff/suggestions_parser.rb10
-rw-r--r--lib/gitlab/fake_application_settings.rb19
-rw-r--r--lib/gitlab/favicon.rb8
-rw-r--r--lib/gitlab/git/commit.rb14
-rw-r--r--lib/gitlab/git/repository.rb4
-rw-r--r--lib/gitlab/git/rugged_impl/commit.rb24
-rw-r--r--lib/gitlab/git/rugged_impl/repository.rb2
-rw-r--r--lib/gitlab/gitaly_client.rb28
-rw-r--r--lib/gitlab/gitaly_client/blobs_stitcher.rb20
-rw-r--r--lib/gitlab/gitaly_client/ref_service.rb11
-rw-r--r--lib/gitlab/gitaly_client/remote_service.rb2
-rw-r--r--lib/gitlab/github_import/importer/pull_request_importer.rb2
-rw-r--r--lib/gitlab/github_import/importer/repository_importer.rb16
-rw-r--r--lib/gitlab/group_search_results.rb30
-rw-r--r--lib/gitlab/hashed_storage/migrator.rb18
-rw-r--r--lib/gitlab/hook_data/issue_builder.rb2
-rw-r--r--lib/gitlab/hook_data/merge_request_builder.rb2
-rw-r--r--lib/gitlab/import_export/import_export.yml1
-rw-r--r--lib/gitlab/json_cache.rb19
-rw-r--r--lib/gitlab/kubernetes.rb24
-rw-r--r--lib/gitlab/legacy_github_import/importer.rb56
-rw-r--r--lib/gitlab/metrics/influx_db.rb6
-rw-r--r--lib/gitlab/middleware/basic_health_check.rb8
-rw-r--r--lib/gitlab/project_search_results.rb6
-rw-r--r--lib/gitlab/project_template.rb1
-rw-r--r--lib/gitlab/recaptcha.rb4
-rw-r--r--lib/gitlab/request_context.rb8
-rw-r--r--lib/gitlab/search_results.rb14
-rw-r--r--lib/gitlab/shell.rb133
-rw-r--r--lib/gitlab/sidekiq_config.rb4
-rw-r--r--lib/gitlab/sidekiq_middleware/memory_killer.rb2
-rw-r--r--lib/gitlab/sql/pattern.rb6
-rw-r--r--lib/gitlab/usage_data.rb1
-rw-r--r--lib/gitlab/user_extractor.rb5
-rw-r--r--lib/gitlab/utils.rb6
-rw-r--r--lib/google_api/cloud_platform/client.rb7
-rw-r--r--lib/sentry/client.rb2
-rw-r--r--lib/tasks/gitlab/artifacts/migrate.rake13
-rw-r--r--lib/tasks/gitlab/cleanup.rake2
-rw-r--r--lib/tasks/gitlab/info.rake6
-rw-r--r--lib/tasks/gitlab/lfs/migrate.rake11
-rw-r--r--lib/tasks/gitlab/shell.rake15
-rw-r--r--lib/tasks/gitlab/storage.rake15
-rw-r--r--lib/tasks/gitlab/traces.rake11
-rw-r--r--lib/tasks/karma.rake13
-rw-r--r--lib/tasks/lint.rake8
-rw-r--r--lib/tasks/migrate/migrate_iids.rake42
-rw-r--r--locale/gitlab.pot493
-rw-r--r--locale/uk/gitlab.po2252
-rw-r--r--package.json7
-rw-r--r--qa/.gitignore2
-rw-r--r--qa/Rakefile22
-rw-r--r--qa/load/artillery.yml22
-rw-r--r--qa/qa.rb11
-rw-r--r--qa/qa/page/component/select2.rb2
-rw-r--r--qa/qa/page/dashboard/projects.rb2
-rw-r--r--qa/qa/page/dashboard/snippet/index.rb21
-rw-r--r--qa/qa/page/dashboard/snippet/new.rb53
-rw-r--r--qa/qa/page/dashboard/snippet/show.rb63
-rw-r--r--qa/qa/page/main/menu.rb5
-rw-r--r--qa/qa/page/project/activity.rb2
-rw-r--r--qa/qa/page/project/menu.rb2
-rw-r--r--qa/qa/page/project/new.rb7
-rw-r--r--qa/qa/page/project/web_ide/edit.rb8
-rw-r--r--qa/qa/resource/snippet.rb30
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb4
-rw-r--r--qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb18
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb56
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb31
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb96
-rw-r--r--qa/qa/specs/helpers/quarantine.rb68
-rw-r--r--qa/qa/tools/generate_perf_testdata.rb12
-rw-r--r--qa/spec/spec_helper.rb47
-rw-r--r--qa/spec/spec_helper_spec.rb355
-rw-r--r--qa/spec/specs/helpers/quarantine_spec.rb271
-rwxr-xr-xscripts/build_assets_image19
-rw-r--r--scripts/frontend/postinstall.js2
-rw-r--r--scripts/frontend/prettier.js4
-rw-r--r--scripts/gitaly_test.rb16
-rwxr-xr-xscripts/insert-rspec-profiling-data47
-rwxr-xr-xscripts/merge-simplecov8
-rwxr-xr-xscripts/review_apps/review-apps.sh10
-rwxr-xr-xscripts/trigger-build2
-rw-r--r--spec/controllers/admin/projects_controller_spec.rb10
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb31
-rw-r--r--spec/controllers/dashboard/projects_controller_spec.rb26
-rw-r--r--spec/controllers/explore/projects_controller_spec.rb30
-rw-r--r--spec/controllers/groups/boards_controller_spec.rb22
-rw-r--r--spec/controllers/groups/settings/ci_cd_controller_spec.rb73
-rw-r--r--spec/controllers/groups_controller_spec.rb37
-rw-r--r--spec/controllers/omniauth_callbacks_controller_spec.rb27
-rw-r--r--spec/controllers/projects/boards_controller_spec.rb22
-rw-r--r--spec/controllers/projects/git_http_controller_spec.rb15
-rw-r--r--spec/controllers/projects/notes_controller_spec.rb31
-rw-r--r--spec/controllers/registrations_controller_spec.rb6
-rw-r--r--spec/factories/ci/builds.rb4
-rw-r--r--spec/factories/ci/pipelines.rb14
-rw-r--r--spec/factories/clusters/clusters.rb4
-rw-r--r--spec/factories/clusters/providers/gcp.rb4
-rw-r--r--spec/factories/commit_statuses.rb4
-rw-r--r--spec/factories/groups.rb8
-rw-r--r--spec/factories/merge_requests.rb25
-rw-r--r--spec/factories/projects.rb4
-rw-r--r--spec/features/clusters/cluster_detail_page_spec.rb6
-rw-r--r--spec/features/groups/settings/ci_cd_spec.rb45
-rw-r--r--spec/features/issues/gfm_autocomplete_spec.rb84
-rw-r--r--spec/features/issues_spec.rb11
-rw-r--r--spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb78
-rw-r--r--spec/features/merge_request/user_sees_merge_widget_spec.rb113
-rw-r--r--spec/features/merge_request/user_views_diffs_spec.rb10
-rw-r--r--spec/features/merge_requests/user_filters_by_target_branch_spec.rb45
-rw-r--r--spec/features/projects/badges/pipeline_badge_spec.rb19
-rw-r--r--spec/features/projects/clusters/applications_spec.rb2
-rw-r--r--spec/features/projects/jobs_spec.rb109
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb146
-rw-r--r--spec/features/projects/pipelines/pipelines_spec.rb119
-rw-r--r--spec/features/projects/settings/pipelines_settings_spec.rb31
-rw-r--r--spec/features/projects/show/user_sees_git_instructions_spec.rb2
-rw-r--r--spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb58
-rw-r--r--spec/features/search/user_searches_for_users_spec.rb83
-rw-r--r--spec/features/security/group/private_access_spec.rb15
-rw-r--r--spec/features/user_opens_link_to_comment.rb33
-rw-r--r--spec/features/users/login_spec.rb29
-rw-r--r--spec/finders/group_projects_finder_spec.rb39
-rw-r--r--spec/finders/issues_finder_spec.rb83
-rw-r--r--spec/finders/labels_finder_spec.rb6
-rw-r--r--spec/finders/merge_requests_finder_spec.rb139
-rw-r--r--spec/finders/snippets_finder_spec.rb221
-rw-r--r--spec/finders/users_finder_spec.rb31
-rw-r--r--spec/fixtures/api/schemas/board.json3
-rw-r--r--spec/fixtures/api/schemas/entities/issue.json3
-rw-r--r--spec/fixtures/api/schemas/entities/issue_boards.json3
-rw-r--r--spec/fixtures/api/schemas/entities/merge_request_widget.json4
-rw-r--r--spec/fixtures/api/schemas/issue.json5
-rw-r--r--spec/fixtures/api/schemas/issues.json3
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/merge_request.json124
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/merge_requests.json122
-rw-r--r--spec/frontend/behaviors/secret_values_spec.js (renamed from spec/javascripts/behaviors/secret_values_spec.js)0
-rw-r--r--spec/frontend/blob/blob_fork_suggestion_spec.js (renamed from spec/javascripts/blob/blob_fork_suggestion_spec.js)0
-rw-r--r--spec/frontend/boards/modal_store_spec.js (renamed from spec/javascripts/boards/modal_store_spec.js)0
-rw-r--r--spec/frontend/cycle_analytics/limit_warning_component_spec.js (renamed from spec/javascripts/cycle_analytics/limit_warning_component_spec.js)0
-rw-r--r--spec/frontend/diffs/components/diff_stats_spec.js (renamed from spec/javascripts/diffs/components/diff_stats_spec.js)0
-rw-r--r--spec/frontend/diffs/components/edit_button_spec.js (renamed from spec/javascripts/diffs/components/edit_button_spec.js)0
-rw-r--r--spec/frontend/diffs/components/hidden_files_warning_spec.js (renamed from spec/javascripts/diffs/components/hidden_files_warning_spec.js)0
-rw-r--r--spec/frontend/diffs/components/no_changes_spec.js (renamed from spec/javascripts/diffs/components/no_changes_spec.js)0
-rw-r--r--spec/frontend/error_tracking/components/error_tracking_list_spec.js (renamed from spec/javascripts/error_tracking/components/error_tracking_list_spec.js)0
-rw-r--r--spec/frontend/error_tracking/store/mutation_spec.js (renamed from spec/javascripts/error_tracking/store/mutation_spec.js)0
-rw-r--r--spec/frontend/filtered_search/filtered_search_token_keys_spec.js (renamed from spec/javascripts/filtered_search/filtered_search_token_keys_spec.js)0
-rw-r--r--spec/frontend/filtered_search/services/recent_searches_service_error_spec.js (renamed from spec/javascripts/filtered_search/services/recent_searches_service_error_spec.js)0
-rw-r--r--spec/frontend/filtered_search/stores/recent_searches_store_spec.js (renamed from spec/javascripts/filtered_search/stores/recent_searches_store_spec.js)0
-rw-r--r--spec/frontend/frequent_items/store/getters_spec.js (renamed from spec/javascripts/frequent_items/store/getters_spec.js)0
-rw-r--r--spec/frontend/gfm_auto_complete_spec.js103
-rw-r--r--spec/frontend/helpers/fixtures.js24
-rw-r--r--spec/frontend/helpers/timeout.js24
-rw-r--r--spec/frontend/helpers/vue_mount_component_helper.js38
-rw-r--r--spec/frontend/ide/lib/common/disposable_spec.js (renamed from spec/javascripts/ide/lib/common/disposable_spec.js)0
-rw-r--r--spec/frontend/ide/lib/diff/diff_spec.js (renamed from spec/javascripts/ide/lib/diff/diff_spec.js)0
-rw-r--r--spec/frontend/ide/lib/editor_options_spec.js (renamed from spec/javascripts/ide/lib/editor_options_spec.js)0
-rw-r--r--spec/frontend/ide/lib/files_spec.js (renamed from spec/javascripts/ide/lib/files_spec.js)0
-rw-r--r--spec/frontend/ide/stores/modules/commit/mutations_spec.js (renamed from spec/javascripts/ide/stores/modules/commit/mutations_spec.js)0
-rw-r--r--spec/frontend/ide/stores/modules/file_templates/getters_spec.js (renamed from spec/javascripts/ide/stores/modules/file_templates/getters_spec.js)0
-rw-r--r--spec/frontend/ide/stores/modules/file_templates/mutations_spec.js (renamed from spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js)0
-rw-r--r--spec/frontend/ide/stores/modules/pane/getters_spec.js (renamed from spec/javascripts/ide/stores/modules/pane/getters_spec.js)0
-rw-r--r--spec/frontend/ide/stores/modules/pane/mutations_spec.js (renamed from spec/javascripts/ide/stores/modules/pane/mutations_spec.js)0
-rw-r--r--spec/frontend/ide/stores/modules/pipelines/getters_spec.js (renamed from spec/javascripts/ide/stores/modules/pipelines/getters_spec.js)0
-rw-r--r--spec/frontend/ide/stores/mutations/branch_spec.js (renamed from spec/javascripts/ide/stores/mutations/branch_spec.js)0
-rw-r--r--spec/frontend/ide/stores/mutations/merge_request_spec.js (renamed from spec/javascripts/ide/stores/mutations/merge_request_spec.js)0
-rw-r--r--spec/frontend/image_diff/view_types_spec.js (renamed from spec/javascripts/image_diff/view_types_spec.js)0
-rw-r--r--spec/frontend/import_projects/store/getters_spec.js (renamed from spec/javascripts/import_projects/store/getters_spec.js)0
-rw-r--r--spec/frontend/import_projects/store/mutations_spec.js (renamed from spec/javascripts/import_projects/store/mutations_spec.js)0
-rw-r--r--spec/frontend/jobs/components/empty_state_spec.js (renamed from spec/javascripts/jobs/components/empty_state_spec.js)0
-rw-r--r--spec/frontend/jobs/components/erased_block_spec.js (renamed from spec/javascripts/jobs/components/erased_block_spec.js)0
-rw-r--r--spec/frontend/jobs/components/sidebar_detail_row_spec.js (renamed from spec/javascripts/jobs/components/sidebar_detail_row_spec.js)0
-rw-r--r--spec/frontend/jobs/components/stuck_block_spec.js (renamed from spec/javascripts/jobs/components/stuck_block_spec.js)0
-rw-r--r--spec/frontend/jobs/store/getters_spec.js (renamed from spec/javascripts/jobs/store/getters_spec.js)0
-rw-r--r--spec/frontend/jobs/store/mutations_spec.js (renamed from spec/javascripts/jobs/store/mutations_spec.js)0
-rw-r--r--spec/frontend/labels_select_spec.js (renamed from spec/javascripts/labels_select_spec.js)0
-rw-r--r--spec/frontend/lib/utils/autosave_spec.js64
-rw-r--r--spec/frontend/lib/utils/cache_spec.js (renamed from spec/javascripts/lib/utils/cache_spec.js)0
-rw-r--r--spec/frontend/lib/utils/grammar_spec.js (renamed from spec/javascripts/lib/utils/grammar_spec.js)0
-rw-r--r--spec/frontend/lib/utils/image_utility_spec.js (renamed from spec/javascripts/lib/utils/image_utility_spec.js)0
-rw-r--r--spec/frontend/lib/utils/number_utility_spec.js (renamed from spec/javascripts/lib/utils/number_utility_spec.js)0
-rw-r--r--spec/frontend/lib/utils/text_utility_spec.js (renamed from spec/javascripts/lib/utils/text_utility_spec.js)0
-rw-r--r--spec/frontend/locale/ensure_single_line_spec.js (renamed from spec/javascripts/locale/ensure_single_line_spec.js)0
-rw-r--r--spec/frontend/locale/sprintf_spec.js (renamed from spec/javascripts/locale/sprintf_spec.js)0
-rw-r--r--spec/frontend/notebook/lib/highlight_spec.js (renamed from spec/javascripts/notebook/lib/highlight_spec.js)0
-rw-r--r--spec/frontend/notes/components/discussion_reply_placeholder_spec.js (renamed from spec/javascripts/notes/components/discussion_reply_placeholder_spec.js)0
-rw-r--r--spec/frontend/notes/components/discussion_resolve_button_spec.js (renamed from spec/javascripts/notes/components/discussion_resolve_button_spec.js)0
-rw-r--r--spec/frontend/notes/components/note_attachment_spec.js (renamed from spec/javascripts/notes/components/note_attachment_spec.js)0
-rw-r--r--spec/frontend/notes/components/note_edited_text_spec.js (renamed from spec/javascripts/notes/components/note_edited_text_spec.js)0
-rw-r--r--spec/frontend/performance_bar/services/performance_bar_service_spec.js (renamed from spec/javascripts/performance_bar/services/performance_bar_service_spec.js)0
-rw-r--r--spec/frontend/pipelines/blank_state_spec.js (renamed from spec/javascripts/pipelines/blank_state_spec.js)0
-rw-r--r--spec/frontend/pipelines/empty_state_spec.js (renamed from spec/javascripts/pipelines/empty_state_spec.js)0
-rw-r--r--spec/frontend/pipelines/pipeline_store_spec.js (renamed from spec/javascripts/pipelines/pipeline_store_spec.js)0
-rw-r--r--spec/frontend/pipelines/pipelines_store_spec.js (renamed from spec/javascripts/pipelines/pipelines_store_spec.js)0
-rw-r--r--spec/frontend/registry/getters_spec.js (renamed from spec/javascripts/registry/getters_spec.js)0
-rw-r--r--spec/frontend/reports/components/report_link_spec.js (renamed from spec/javascripts/reports/components/report_link_spec.js)0
-rw-r--r--spec/frontend/reports/store/utils_spec.js (renamed from spec/javascripts/reports/store/utils_spec.js)0
-rw-r--r--spec/frontend/sidebar/confidential_edit_buttons_spec.js (renamed from spec/javascripts/sidebar/confidential_edit_buttons_spec.js)0
-rw-r--r--spec/frontend/sidebar/confidential_edit_form_buttons_spec.js (renamed from spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js)0
-rw-r--r--spec/frontend/sidebar/lock/edit_form_spec.js (renamed from spec/javascripts/sidebar/lock/edit_form_spec.js)0
-rw-r--r--spec/frontend/test_setup.js19
-rw-r--r--spec/frontend/u2f/util_spec.js (renamed from spec/javascripts/u2f/util_spec.js)0
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_container_spec.js (renamed from spec/javascripts/vue_mr_widget/components/mr_widget_container_spec.js)0
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_icon_spec.js (renamed from spec/javascripts/vue_mr_widget/components/mr_widget_icon_spec.js)0
-rw-r--r--spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js (renamed from spec/javascripts/vue_mr_widget/components/states/commit_edit_spec.js)0
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js (renamed from spec/javascripts/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js)0
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js (renamed from spec/javascripts/vue_mr_widget/components/states/mr_widget_commits_header_spec.js)26
-rw-r--r--spec/frontend/vue_mr_widget/stores/get_state_key_spec.js (renamed from spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js)0
-rw-r--r--spec/frontend/vue_shared/components/callout_spec.js (renamed from spec/javascripts/vue_shared/components/callout_spec.js)0
-rw-r--r--spec/frontend/vue_shared/components/code_block_spec.js (renamed from spec/javascripts/vue_shared/components/code_block_spec.js)0
-rw-r--r--spec/frontend/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js (renamed from spec/javascripts/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js)0
-rw-r--r--spec/frontend/vue_shared/components/identicon_spec.js (renamed from spec/javascripts/vue_shared/components/identicon_spec.js)0
-rw-r--r--spec/frontend/vue_shared/components/lib/utils/dom_utils_spec.js (renamed from spec/javascripts/vue_shared/components/lib/utils/dom_utils_spec.js)0
-rw-r--r--spec/frontend/vue_shared/components/pagination_links_spec.js (renamed from spec/javascripts/vue_shared/components/pagination_links_spec.js)0
-rw-r--r--spec/frontend/vue_shared/components/time_ago_tooltip_spec.js (renamed from spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js)0
-rw-r--r--spec/frontend/vuex_shared/modules/modal/mutations_spec.js (renamed from spec/javascripts/vuex_shared/modules/modal/mutations_spec.js)0
-rw-r--r--spec/helpers/auth_helper_spec.rb38
-rw-r--r--spec/helpers/auto_devops_helper_spec.rb138
-rw-r--r--spec/helpers/clusters_helper_spec.rb33
-rw-r--r--spec/javascripts/badges/components/badge_list_spec.js2
-rw-r--r--spec/javascripts/badges/components/badge_spec.js2
-rw-r--r--spec/javascripts/boards/board_list_spec.js2
-rw-r--r--spec/javascripts/clusters/clusters_bundle_spec.js80
-rw-r--r--spec/javascripts/clusters/components/applications_spec.js10
-rw-r--r--spec/javascripts/diffs/components/app_spec.js57
-rw-r--r--spec/javascripts/diffs/components/diff_file_spec.js25
-rw-r--r--spec/javascripts/diffs/store/actions_spec.js22
-rw-r--r--spec/javascripts/diffs/store/mutations_spec.js18
-rw-r--r--spec/javascripts/environments/environment_item_spec.js20
-rw-r--r--spec/javascripts/environments/environment_table_spec.js14
-rw-r--r--spec/javascripts/environments/environments_app_spec.js5
-rw-r--r--spec/javascripts/environments/environments_store_spec.js74
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js14
-rw-r--r--spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js4
-rw-r--r--spec/javascripts/filtered_search/visual_token_value_spec.js30
-rw-r--r--spec/javascripts/fixtures/ajax_loading_spinner.html.haml2
-rw-r--r--spec/javascripts/fixtures/autocomplete_sources.rb40
-rw-r--r--spec/javascripts/fixtures/balsamiq_viewer.html.haml1
-rw-r--r--spec/javascripts/fixtures/create_item_dropdown.html.haml13
-rw-r--r--spec/javascripts/fixtures/emojis.rb18
-rw-r--r--spec/javascripts/fixtures/event_filter.html.haml25
-rw-r--r--spec/javascripts/fixtures/gl_dropdown.html.haml17
-rw-r--r--spec/javascripts/fixtures/gl_field_errors.html.haml15
-rw-r--r--spec/javascripts/fixtures/issuable_filter.html.haml8
-rw-r--r--spec/javascripts/fixtures/issue_sidebar_label.html.haml16
-rw-r--r--spec/javascripts/fixtures/line_highlighter.html.haml11
-rw-r--r--spec/javascripts/fixtures/linked_tabs.html.haml13
-rw-r--r--spec/javascripts/fixtures/merge_requests_show.html.haml13
-rw-r--r--spec/javascripts/fixtures/mini_dropdown_graph.html.haml10
-rw-r--r--spec/javascripts/fixtures/notebook_viewer.html.haml1
-rw-r--r--spec/javascripts/fixtures/oauth_remember_me.html.haml6
-rw-r--r--spec/javascripts/fixtures/pdf_viewer.html.haml1
-rw-r--r--spec/javascripts/fixtures/pipeline_graph.html.haml14
-rw-r--r--spec/javascripts/fixtures/pipelines.html.haml12
-rw-r--r--spec/javascripts/fixtures/project_select_combo_button.html.haml6
-rw-r--r--spec/javascripts/fixtures/search_autocomplete.html.haml9
-rw-r--r--spec/javascripts/fixtures/signin_tabs.html.haml5
-rw-r--r--spec/javascripts/fixtures/sketch_viewer.html.haml2
-rw-r--r--spec/javascripts/fixtures/static/README.md3
-rw-r--r--spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw3
-rw-r--r--spec/javascripts/fixtures/static/balsamiq_viewer.html.raw1
-rw-r--r--spec/javascripts/fixtures/static/create_item_dropdown.html.raw11
-rw-r--r--spec/javascripts/fixtures/static/event_filter.html.raw44
-rw-r--r--spec/javascripts/fixtures/static/gl_dropdown.html.raw26
-rw-r--r--spec/javascripts/fixtures/static/gl_field_errors.html.raw22
-rw-r--r--spec/javascripts/fixtures/static/issuable_filter.html.raw9
-rw-r--r--spec/javascripts/fixtures/static/issue_sidebar_label.html.raw26
-rw-r--r--spec/javascripts/fixtures/static/line_highlighter.html.raw107
-rw-r--r--spec/javascripts/fixtures/static/linked_tabs.html.raw20
-rw-r--r--spec/javascripts/fixtures/static/merge_requests_show.html.raw15
-rw-r--r--spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw13
-rw-r--r--spec/javascripts/fixtures/static/notebook_viewer.html.raw1
-rw-r--r--spec/javascripts/fixtures/static/oauth_remember_me.html.raw6
-rw-r--r--spec/javascripts/fixtures/static/pdf_viewer.html.raw1
-rw-r--r--spec/javascripts/fixtures/static/pipeline_graph.html.raw24
-rw-r--r--spec/javascripts/fixtures/static/pipelines.html.raw3
-rw-r--r--spec/javascripts/fixtures/static/project_select_combo_button.html.raw9
-rw-r--r--spec/javascripts/fixtures/static/search_autocomplete.html.raw15
-rw-r--r--spec/javascripts/fixtures/static/signin_tabs.html.raw8
-rw-r--r--spec/javascripts/fixtures/static/sketch_viewer.html.raw3
-rw-r--r--spec/javascripts/fixtures/static_fixtures.rb24
-rw-r--r--spec/javascripts/frequent_items/components/app_spec.js2
-rw-r--r--spec/javascripts/groups/components/app_spec.js2
-rw-r--r--spec/javascripts/helpers/filtered_search_spec_helper.js2
-rw-r--r--spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js7
-rw-r--r--spec/javascripts/ide/components/new_dropdown/modal_spec.js12
-rw-r--r--spec/javascripts/ide/stores/modules/commit/actions_spec.js2
-rw-r--r--spec/javascripts/ide/stores/modules/commit/getters_spec.js8
-rw-r--r--spec/javascripts/jobs/components/stages_dropdown_spec.js188
-rw-r--r--spec/javascripts/notes/components/note_form_spec.js94
-rw-r--r--spec/javascripts/notes/components/noteable_discussion_spec.js14
-rw-r--r--spec/javascripts/pipelines/graph/stage_column_component_spec.js2
-rw-r--r--spec/javascripts/pipelines/pipeline_url_spec.js12
-rw-r--r--spec/javascripts/registry/components/app_spec.js2
-rw-r--r--spec/javascripts/reports/components/grouped_test_reports_app_spec.js10
-rw-r--r--spec/javascripts/test_bundle.js72
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_pipeline_spec.js83
-rw-r--r--spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js2
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js141
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js11
-rw-r--r--spec/javascripts/vue_shared/components/commit_spec.js90
-rw-r--r--spec/javascripts/vue_shared/components/file_icon_spec.js7
-rw-r--r--spec/javascripts/vue_shared/components/header_ci_component_spec.js2
-rw-r--r--spec/lib/backup/uploads_spec.rb18
-rw-r--r--spec/lib/banzai/filter/output_safety_spec.rb29
-rw-r--r--spec/lib/banzai/filter/suggestion_filter_spec.rb33
-rw-r--r--spec/lib/banzai/filter/syntax_highlight_filter_spec.rb32
-rw-r--r--spec/lib/gitlab/auth/o_auth/user_spec.rb11
-rw-r--r--spec/lib/gitlab/authorized_keys_spec.rb194
-rw-r--r--spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb6
-rw-r--r--spec/lib/gitlab/badge/pipeline/template_spec.rb10
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb20
-rw-r--r--spec/lib/gitlab/checks/branch_check_spec.rb80
-rw-r--r--spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb34
-rw-r--r--spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb85
-rw-r--r--spec/lib/gitlab/ci/status/build/preparing_spec.rb33
-rw-r--r--spec/lib/gitlab/ci/status/preparing_spec.rb29
-rw-r--r--spec/lib/gitlab/danger/teammate_spec.rb39
-rw-r--r--spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb19
-rw-r--r--spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb16
-rw-r--r--spec/lib/gitlab/database_spec.rb52
-rw-r--r--spec/lib/gitlab/diff/file_spec.rb7
-rw-r--r--spec/lib/gitlab/diff/suggestion_diff_spec.rb55
-rw-r--r--spec/lib/gitlab/fake_application_settings_spec.rb31
-rw-r--r--spec/lib/gitlab/git/repository_cleaner_spec.rb12
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb5
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb38
-rw-r--r--spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb5
-rw-r--r--spec/lib/gitlab/github_import/importer/repository_importer_spec.rb11
-rw-r--r--spec/lib/gitlab/group_search_results_spec.rb69
-rw-r--r--spec/lib/gitlab/hashed_storage/migrator_spec.rb52
-rw-r--r--spec/lib/gitlab/json_cache_spec.rb54
-rw-r--r--spec/lib/gitlab/kubernetes_spec.rb34
-rw-r--r--spec/lib/gitlab/middleware/basic_health_check_spec.rb29
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb32
-rw-r--r--spec/lib/gitlab/project_template_spec.rb1
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb2
-rw-r--r--spec/lib/gitlab/request_context_spec.rb27
-rw-r--r--spec/lib/gitlab/search_results_spec.rb16
-rw-r--r--spec/lib/gitlab/shell_spec.rb584
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb2
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb4
-rw-r--r--spec/lib/gitlab/user_extractor_spec.rb20
-rw-r--r--spec/lib/gitlab/utils_spec.rb18
-rw-r--r--spec/lib/google_api/cloud_platform/client_spec.rb12
-rw-r--r--spec/migrations/add_foreign_keys_to_todos_spec.rb2
-rw-r--r--spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb24
-rw-r--r--spec/migrations/calculate_conv_dev_index_percentages_spec.rb22
-rw-r--r--spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb19
-rw-r--r--spec/migrations/delete_inconsistent_internal_id_records_spec.rb83
-rw-r--r--spec/migrations/issues_moved_to_id_foreign_key_spec.rb15
-rw-r--r--spec/migrations/migrate_old_artifacts_spec.rb32
-rw-r--r--spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb6
-rw-r--r--spec/migrations/migrate_user_project_view_spec.rb6
-rw-r--r--spec/migrations/move_personal_snippets_files_spec.rb35
-rw-r--r--spec/models/application_setting_spec.rb232
-rw-r--r--spec/models/broadcast_message_spec.rb6
-rw-r--r--spec/models/ci/build_spec.rb91
-rw-r--r--spec/models/ci/pipeline_spec.rb262
-rw-r--r--spec/models/clusters/applications/runner_spec.rb4
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb6
-rw-r--r--spec/models/commit_spec.rb16
-rw-r--r--spec/models/commit_status_spec.rb23
-rw-r--r--spec/models/concerns/cache_markdown_field_spec.rb14
-rw-r--r--spec/models/concerns/has_ref_spec.rb4
-rw-r--r--spec/models/concerns/has_status_spec.rb22
-rw-r--r--spec/models/deployment_spec.rb28
-rw-r--r--spec/models/diff_note_spec.rb10
-rw-r--r--spec/models/environment_status_spec.rb4
-rw-r--r--spec/models/group_spec.rb121
-rw-r--r--spec/models/merge_request_diff_spec.rb21
-rw-r--r--spec/models/merge_request_spec.rb99
-rw-r--r--spec/models/namespace_spec.rb24
-rw-r--r--spec/models/note_spec.rb18
-rw-r--r--spec/models/project_services/kubernetes_service_spec.rb12
-rw-r--r--spec/models/project_spec.rb182
-rw-r--r--spec/models/project_wiki_spec.rb8
-rw-r--r--spec/models/repository_spec.rb60
-rw-r--r--spec/models/user_spec.rb62
-rw-r--r--spec/policies/group_policy_spec.rb13
-rw-r--r--spec/policies/identity_provider_policy_spec.rb30
-rw-r--r--spec/policies/issuable_policy_spec.rb6
-rw-r--r--spec/policies/merge_request_policy_spec.rb50
-rw-r--r--spec/presenters/ci/pipeline_presenter_spec.rb134
-rw-r--r--spec/presenters/merge_request_presenter_spec.rb70
-rw-r--r--spec/rack_servers/puma_spec.rb8
-rw-r--r--spec/requests/api/internal_spec.rb27
-rw-r--r--spec/requests/api/merge_requests_spec.rb407
-rw-r--r--spec/requests/api/project_clusters_spec.rb1
-rw-r--r--spec/requests/api/runner_spec.rb9
-rw-r--r--spec/requests/api/search_spec.rb79
-rw-r--r--spec/requests/api/users_spec.rb20
-rw-r--r--spec/routing/api_routing_spec.rb14
-rw-r--r--spec/routing/group_routing_spec.rb4
-rw-r--r--spec/serializers/pipeline_entity_spec.rb25
-rw-r--r--spec/serializers/pipeline_serializer_spec.rb10
-rw-r--r--spec/services/auth/container_registry_authentication_service_spec.rb93
-rw-r--r--spec/services/ci/prepare_build_service_spec.rb54
-rw-r--r--spec/services/emails/create_service_spec.rb2
-rw-r--r--spec/services/emails/destroy_service_spec.rb2
-rw-r--r--spec/services/groups/auto_devops_service_spec.rb62
-rw-r--r--spec/services/projects/create_service_spec.rb1
-rw-r--r--spec/services/projects/destroy_service_spec.rb6
-rw-r--r--spec/services/projects/transfer_service_spec.rb1
-rw-r--r--spec/support/api/schema_matcher.rb24
-rw-r--r--spec/support/api/time_tracking_shared_examples.rb2
-rw-r--r--spec/support/helpers/graphql_helpers.rb10
-rw-r--r--spec/support/helpers/javascript_fixtures_helpers.rb28
-rw-r--r--spec/support/helpers/kubernetes_helpers.rb16
-rw-r--r--spec/support/helpers/repo_helpers.rb14
-rw-r--r--spec/support/helpers/stub_configuration.rb4
-rw-r--r--spec/support/helpers/stub_object_storage.rb12
-rw-r--r--spec/support/helpers/test_env.rb10
-rw-r--r--spec/support/pg_stat_activity.rb19
-rw-r--r--spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb24
-rw-r--r--spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb44
-rw-r--r--spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb65
-rw-r--r--spec/support/shared_contexts/finders/users_finder_shared_contexts.rb8
-rw-r--r--spec/support/shared_examples/application_setting_examples.rb252
-rw-r--r--spec/support/shared_examples/requests/api/merge_requests_list.rb366
-rw-r--r--spec/support/shared_examples/snippet_visibility.rb322
-rw-r--r--spec/support/shared_examples/snippet_visibility_shared_examples.rb306
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb4
-rw-r--r--spec/tasks/gitlab/storage_rake_spec.rb108
-rw-r--r--spec/validators/devise_email_validator_spec.rb94
-rw-r--r--spec/validators/sha_validator_spec.rb9
-rw-r--r--spec/views/groups/_home_panel.html.haml_spec.rb15
-rw-r--r--spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb1
-rw-r--r--spec/views/projects/settings/operations/show.html.haml_spec.rb1
-rw-r--r--spec/workers/ci/build_prepare_worker_spec.rb30
-rw-r--r--spec/workers/cluster_configure_worker_spec.rb16
-rw-r--r--spec/workers/cluster_project_configure_worker_spec.rb21
-rw-r--r--vendor/assets/javascripts/jquery.atwho.js1202
-rw-r--r--vendor/assets/javascripts/jquery.caret.js436
-rw-r--r--vendor/licenses.csv1
-rw-r--r--vendor/project_templates/android.tar.gzbin0 -> 132592 bytes
-rw-r--r--yarn.lock28
1305 files changed, 18495 insertions, 10772 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 26087b2cc4d..33559812872 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -1,5 +1,8 @@
image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-golang-1.11-git-2.18-chrome-71.0-node-10.x-yarn-1.12-postgresql-9.6-graphicsmagick-1.3.29"
+include:
+ - local: /lib/gitlab/ci/templates/Code-Quality.gitlab-ci.yml
+
.dedicated-runner: &dedicated-runner
retry: 1
tags:
@@ -66,6 +69,7 @@ stages:
paths:
- knapsack/
- rspec_flaky/
+ - rspec_profiling/
.use-pg: &use-pg
services:
@@ -159,6 +163,7 @@ stages:
- coverage/
- knapsack/
- rspec_flaky/
+ - rspec_profiling/
- tmp/capybara/
reports:
junit: junit_rspec.xml
@@ -336,6 +341,7 @@ retrieve-tests-metadata:
- wget -O $KNAPSACK_RSPEC_SUITE_REPORT_PATH http://${TESTS_METADATA_S3_BUCKET}.s3.amazonaws.com/$KNAPSACK_RSPEC_SUITE_REPORT_PATH || rm $KNAPSACK_RSPEC_SUITE_REPORT_PATH
- '[[ -f $KNAPSACK_RSPEC_SUITE_REPORT_PATH ]] || echo "{}" > ${KNAPSACK_RSPEC_SUITE_REPORT_PATH}'
- mkdir -p rspec_flaky/
+ - mkdir -p rspec_profiling/
- wget -O $FLAKY_RSPEC_SUITE_REPORT_PATH http://${TESTS_METADATA_S3_BUCKET}.s3.amazonaws.com/$FLAKY_RSPEC_SUITE_REPORT_PATH || rm $FLAKY_RSPEC_SUITE_REPORT_PATH
- '[[ -f $FLAKY_RSPEC_SUITE_REPORT_PATH ]] || echo "{}" > ${FLAKY_RSPEC_SUITE_REPORT_PATH}'
@@ -350,7 +356,7 @@ update-tests-metadata:
- rspec_flaky/
policy: push
script:
- - retry gem install fog-aws mime-types activesupport --no-document
+ - retry gem install fog-aws mime-types activesupport rspec_profiling postgres-copy --no-document
- scripts/merge-reports ${KNAPSACK_RSPEC_SUITE_REPORT_PATH} knapsack/${CI_PROJECT_NAME}/rspec-pg_node_*.json
- scripts/merge-reports ${FLAKY_RSPEC_SUITE_REPORT_PATH} rspec_flaky/all_*_*.json
- FLAKY_RSPEC_GENERATE_REPORT=1 scripts/prune-old-flaky-specs ${FLAKY_RSPEC_SUITE_REPORT_PATH}
@@ -358,6 +364,7 @@ update-tests-metadata:
- '[[ -z ${TESTS_METADATA_S3_BUCKET} ]] || scripts/sync-reports put $TESTS_METADATA_S3_BUCKET $FLAKY_RSPEC_SUITE_REPORT_PATH'
- rm -f knapsack/${CI_PROJECT_NAME}/*_node_*.json
- rm -f rspec_flaky/all_*.json rspec_flaky/new_*.json
+ - scripts/insert-rspec-profiling-data
flaky-examples-check:
<<: *dedicated-runner
@@ -484,6 +491,9 @@ setup-test-env:
build-qa-image:
<<: *review-docker
+ variables:
+ <<: *review-docker-variables
+ GIT_DEPTH: "20"
stage: prepare
script:
- time docker build --cache-from ${LATEST_QA_IMAGE} --tag ${QA_IMAGE} ./qa/
@@ -684,10 +694,10 @@ gitlab:assets:compile:
- gitlab-org
gitlab:ui:visual:
- <<: *except-docs
tags:
- gitlab-org
before_script: []
+ allow_failure: true
dependencies:
- compile-assets
script:
@@ -705,6 +715,7 @@ gitlab:ui:visual:
- app/assets/stylesheets/**/**/*.scss
except:
refs:
+ - /(^docs[\/-].*|.*-docs$)/
- master
variables:
- $CI_COMMIT_MESSAGE =~ /\[skip visual\]/i
@@ -766,31 +777,14 @@ jest:
code_quality:
<<: *dedicated-no-docs-no-db-pull-cache-job
- image: docker:stable
- allow_failure: true
# gitlab-org runners set `privileged: false` but we need to have it set to true
# since we're using Docker in Docker
tags: []
before_script: []
- services:
- - docker:stable-dind
- variables:
- SETUP_DB: "false"
- DOCKER_DRIVER: overlay2
cache: {}
dependencies: []
- script:
- # Extract "MAJOR.MINOR" from CI_SERVER_VERSION and generate "MAJOR-MINOR-stable" for Security Products
- - export SP_VERSION=$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')
- - docker run
- --env SOURCE_CODE="$PWD"
- --volume "$PWD":/code
- --volume /var/run/docker.sock:/var/run/docker.sock
- "registry.gitlab.com/gitlab-org/security-products/codequality:$SP_VERSION" /code
- artifacts:
- reports:
- codequality: gl-code-quality-report.json
- expire_in: 1 week
+ variables:
+ SETUP_DB: "false"
sast:
<<: *dedicated-no-docs-no-db-pull-cache-job
@@ -1006,7 +1000,6 @@ no_ee_check:
dependencies: []
cache: {}
variables:
- GIT_DEPTH: "1"
API_TOKEN: "${GITLAB_BOT_MULTI_PROJECT_PIPELINE_POLLING_TOKEN}"
script:
- apk add --update openssl curl jq
@@ -1045,15 +1038,7 @@ schedule:review-build-cng:
- source ./scripts/review_apps/review-apps.sh
script:
- wait_for_job_to_be_done "review-build-cng"
- after_script:
- - source ./scripts/review_apps/review-apps.sh
- - check_kube_domain
- - download_gitlab_chart
- - ensure_namespace
- - install_tiller
- - install_external_dns
- - time deploy
- - add_license
+ - perform_review_app_deployment
review-deploy:
<<: *review-deploy-base
@@ -1063,6 +1048,7 @@ schedule:review-deploy:
<<: *review-schedules-only
script:
- wait_for_job_to_be_done "schedule:review-build-cng"
+ - perform_review_app_deployment
.review-qa-base: &review-qa-base
<<: *review-docker
@@ -1109,8 +1095,7 @@ review-qa-all:
<<: *review-qa-base
script:
- wait_for_job_to_be_done "review-deploy"
- after_script:
- - mkdir gitlab-exporter
+ - mkdir -p gitlab-exporter
- wget -O ./gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/master/index.js
- mkdir sitespeed-results
- docker run --shm-size=1g --rm -v "$(pwd)":/sitespeed.io sitespeedio/sitespeed.io:6.3.1 --plugins.add ./gitlab-exporter --outputFolder sitespeed-results "$CI_ENVIRONMENT_URL"
diff --git a/.gitlab/issue_templates/Database Reviewer.md b/.gitlab/issue_templates/Database Reviewer.md
new file mode 100644
index 00000000000..a5e7e42fd14
--- /dev/null
+++ b/.gitlab/issue_templates/Database Reviewer.md
@@ -0,0 +1,32 @@
+#### Database Reviewer Checklist
+
+Thank you for becoming a ~database reviewer! Please work on the list below to complete your setup. For any question, reach out to #database an mention @abrandl.
+
+- [ ] Change issue title to include your name: `Database Reviewer Checklist: Your Name`
+- [ ] Review general [code review guide](https://docs.gitlab.com/ee/development/code_review.html)
+- [ ] Review [database review documentation](https://about.gitlab.com/handbook/engineering/workflow/code-review/database.html)
+- [ ] Familiarize with [migration helpers](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/gitlab/database/migration_helpers.rb) and review usage in existing migrations
+- [ ] Read [database migration style guide](https://docs.gitlab.com/ee/development/migration_style_guide.html)
+- [ ] Familiarize with best practices in [database guides](https://docs.gitlab.com/ee/development/#database-guides)
+- [ ] Watch [Optimising Rails Database Queries: Episode 1](https://www.youtube.com/watch?v=79GurlaxhsI)
+- [ ] Read [Understanding EXPLAIN plans](https://docs.gitlab.com/ee/development/understanding_explain_plans.html)
+- [ ] Review [database best practices](https://docs.gitlab.com/ee/development/#best-practices)
+- [ ] Review how we use [database instances restored from a backup](https://ops.gitlab.net/gitlab-com/gl-infra/gitlab-restore/postgres-gprd) for testing and make sure you're set up to execute pipelines (check [README.md](https://ops.gitlab.net/gitlab-com/gl-infra/gitlab-restore/postgres-gprd/blob/master/README.md) and reach out to @abrandl since this is currently subject to being changed)
+- [ ] Get yourself added to [@gl-database](https://gitlab.com/groups/gl-database/-/group_members) group and respond to @-mentions to the group (reach out to any maintainer on the group to get added). You will get TODOs on gitlab.com for group mentions.
+- [ ] Make sure you have proper access to at least a read-only replica in staging and production
+- [ ] Indicate in `data/team.yml` your role as a database reviewer ([example MR](https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/19600/diffs)). Assign MR to your manager for merge.
+- [ ] Send one MR to improve the [review documentation](https://about.gitlab.com/handbook/engineering/workflow/code-review/database.html) or the [issue template](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab/issue_templates/Database%20Reviewer.md)
+
+Note that *approving and accepting* merge requests is *restricted* to
+Database Maintainers only. As a reviewer, pass the MR to a maintainer
+for approval.
+
+You're all set! Watch out for TODOs on GitLab.com.
+
+###### Where to go for questions?
+
+Reach out to `#database` on Slack and mention @abrandl for any questions.
+
+cc @abrandl
+
+/label ~meta ~database
diff --git a/.gitlab/issue_templates/Feature proposal.md b/.gitlab/issue_templates/Feature proposal.md
index eef1e877ff2..b4007c1ba7b 100644
--- a/.gitlab/issue_templates/Feature proposal.md
+++ b/.gitlab/issue_templates/Feature proposal.md
@@ -2,32 +2,10 @@
<!-- What problem do we solve? -->
-### Target audience
+### Intended users
-<!--- For whom are we doing this? Include a [persona](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/)
-listed below, if applicable, along with its [label](https://gitlab.com/groups/gitlab-org/-/labels?utf8=%E2%9C%93&subscribed=&search=persona%3A),
-or define a specific company role, e.g. "Release Manager".
-
-Existing personas are: (copy relevant personas out of this comment, and delete any persona that does not apply)
-
-- Parker, Product Manager, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#parker-product-manager
-/label ~"Persona: Product Manager"
-
-- Delaney, Development Team Lead, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#delaney-development-team-lead
-/label ~"Persona: Development Team Lead"
-
-- Sasha, Software Developer, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#sasha-software-developer
-/label ~"Persona: Software developer"
-
-- Devon, DevOps Engineer, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#devon-devops-engineer
-/label ~"Persona: DevOps Engineer"
-
-- Sidney, Systems Administrator, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#sidney-systems-administrator
-/label ~"Persona: Systems Administrator"
-
-- Sam, Security Analyst, https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas#sam-security-analyst
-/label ~"Persona: Security Analyst"
--->
+<!-- Who will use this feature? If known, include any of the following: types of users (e.g. Developer), personas, or specific company roles (e.g. Release Manager). It's okay to write "Unknown" and fill this field in later.
+Personas can be found at https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/ -->
### Further details
diff --git a/.gitlab/issue_templates/Test plan.md b/.gitlab/issue_templates/Test plan.md
index a3c3f4a6509..3aedd5859d3 100644
--- a/.gitlab/issue_templates/Test plan.md
+++ b/.gitlab/issue_templates/Test plan.md
@@ -93,4 +93,4 @@ When adding new automated tests, please keep [testing levels](https://docs.gitla
in mind.
-->
-/label ~Quality ~"test plan"
+/label ~Quality ~"test\-plan"
diff --git a/.prettierrc b/.prettierrc
index 3384551aea5..5e2863a11f6 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -1,13 +1,5 @@
{
"printWidth": 100,
"singleQuote": true,
- "trailingComma": "es5",
- "overrides": [
- {
- "files": ["**/app/**/*", "**/spec/**/*"],
- "options": {
- "trailingComma": "all"
- }
- }
- ]
+ "trailingComma": "all"
}
diff --git a/.rubocop.yml b/.rubocop.yml
index 2985c1446e4..9143966b864 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -8,6 +8,7 @@ require:
- rubocop-rspec
AllCops:
+ TargetRubyVersion: 2.5
TargetRailsVersion: 5.0
Exclude:
- 'vendor/**/*'
@@ -184,3 +185,8 @@ Cop/InjectEnterpriseEditionModule:
Style/ReturnNil:
Enabled: true
+
+# It isn't always safe to replace `=~` with `.match?`, especially when there are
+# nil values on the left hand side
+Performance/RegexpMatch:
+ Enabled: false
diff --git a/.stylelintrc b/.stylelintrc
index 04784a0a11a..c0f21aed292 100644
--- a/.stylelintrc
+++ b/.stylelintrc
@@ -102,6 +102,6 @@
"selector-pseudo-element-no-unknown":true,
"shorthand-property-no-redundant-values":true,
"string-quotes":"single",
- "value-no-vendor-prefix":true
+ "value-no-vendor-prefix":[true, { ignoreValues: ["sticky"] }]
}
}
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8a4a6c9ff13..3d7e8e10280 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,305 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.9.1 (2019-03-25)
+
+### Fixed (7 changes)
+
+- Fix issue that caused the "Show all activity" button to appear on top of the mini pipeline status dropdown on the merge request page. !26274
+- Fix duplicated bottom match line on merge request parallel diff view. !26402
+- Allow users who can push to protected branches to create protected branches via CLI. !26413
+- Add missing .gitlab-ci.yml to Android template. !26415
+- Refresh commit count after repository head changes. !26473
+- Set proper default-branch for repository on GitHub Import. !26476
+- GitHub importer: Use the project creator to create branches from forks. !26510
+
+### Changed (1 change)
+
+- Upgrade to Gitaly v1.27.1. !26533
+
+
+## 11.9.0 (2019-03-22)
+
+### Security (24 changes)
+
+- Use encrypted runner tokens. !25532
+- Stop linking to unrecognized package sources. !55518
+- Disable issue boards API when issues are disabled.
+- Forbid creating discussions for users with restricted access.
+- Fix leaking private repository information in API.
+- Fixed ability to see private groups by users not belonging to given group.
+- Prevent releases links API to leak tag existance.
+- Display the correct number of MRs a user has access to.
+- Block local URLs for Kubernetes integration.
+- Fix arbitrary file read via diffs during import.
+- Check if desired milestone for an issue is available.
+- Don't allow non-members to see private related MRs.
+- Check snippet attached file to be moved is within designated directory.
+- Fix blind SSRF in Prometheus integration by checking URL before querying.
+- Fix git clone revealing private repo's presence.
+- Remove project serialization in quick actions response.
+- Don't show new issue link after move when a user does not have permissions.
+- Limit mermaid rendering to 5K characters.
+- Show only merge requests visible to user on milestone detail page.
+- Display only information visible to current user on the Milestone page.
+- Do not display impersonated sessions under active sessions and remove ability to revoke session.
+- Validate session key when authorizing with GCP to create a cluster.
+- Do not disclose milestone titles for unauthorized users.
+- Remove the possibility to share a project with a group that a user is not a member of.
+
+### Removed (1 change)
+
+- Remove HipChat integration from GitLab. !22223
+
+### Fixed (86 changes, 21 of them are from the community)
+
+- Fixes issue with AWS V4 signatures not working with some S3 providers. !21788
+- Validate 'include' keywords in gitlab-ci.yml configuration files. !24098 (Paul Bonaud)
+- Close More Actions tooltip when menu opens. !24285
+- API: Support Jira transition ID as string. !24400 (Robert Schilling)
+- Fixed navigation sidebar flashing open on page load. !24555
+- Fix username escaping when using assign to me for issues. !24673
+- commit page info-well overflow fix #56436. !24799 (Gokhan Apaydin)
+- Fix error tracking list page. !24806
+- Fix overlapping empty-header logo. !24868 (Jonas L.)
+- Resolve Jobs tab border top in pipeline's page is 1px off. !24878
+- Require maintainer access to show pages domain settings. !24926
+- Display error message when API call to list Sentry issues fails. !24936
+- Fix rollout status for statefulsets and daemonsets. !24972 (Sergej Nikolaev <kinolaev@gmail.com>)
+- Display job names consistently on pipelines and environments list. !24984
+- Update new password breadcrumb. !25037 (George Tsiolis)
+- Fixes functions finder for upgraded Knative app. !25067
+- Provide expires_in in LFS authentication payload. !25082
+- Fix validation of certain ed25519 keys. !25115 (Merlijn B. W. Wajer)
+- Timer and action name aligned vertically for delayed jobs in pipeline actions. !25117 (Gokhan Apaydin)
+- Fix the border style of CONTRIBUTING button when it exists. !25124 (Takuya Noguchi)
+- Change badges.svg example to pipeline.svg. !25157 (Aviad Levy)
+- API: Fix docs and parameters for hangouts-chat service. !25180 (Robert Schilling)
+- API: Expose full commit title. !25189 (Robert Schilling)
+- API: Require only one parameter when updating a wiki. !25191 (Robert Schilling)
+- Hide pipeline status when pipelines are disabled on project. !25204
+- Fix alignment of dropdown icon on issuable on mobile. !25205 (Takuya Noguchi)
+- Add left margin to 1st time contributor badge. !25216 (Gokhan Apaydin)
+- Use limited counter for runner build count in admin page. !25220
+- API: Ensure that related merge requests are referenced cross-project. !25222 (Robert Schilling)
+- Ensure the base pipeline of a Merge Request belongs to its target branch. !25226
+- Fix import_jid error on project import. !25239
+- Fix commenting on commits having SHA1 starting with a large number. !25278
+- Allow empty values such as [] to be stored in reactive cache. !25283
+- Remove vertical connecting line placeholder from diff discussion notes. !25292
+- Fix hover and active state colors of award emoji button. !25295
+- Fix author layouts in issuable meta line UIs on mobile. !25332 (Takuya Noguchi)
+- Fix bug where project topics truncate. !25398
+- Fix ETag caching not being used for AJAX requests. !25400
+- Doc - fix the url of pipeline status badge. !25404 (Aviad Levy)
+- Fix pipeline status icon mismatch. !25407
+- Allow users to compare branches on a read-only instance. !25414
+- Fix 404s when C++ .gitignore template selected. !25416
+- Always fetch MR latest version when creating suggestions. !25441
+- Only show borders for markdown images in notes. !25448
+- Bring back Rugged implementation of find_commit. !25477
+- Remove duplicate units from metrics graph. !25485
+- Fix project import error importing releases. !25495
+- Remove duplicate XHR request when requesting new pipeline page. !25506
+- Properly handle multiple X-Forwarded-For addresses in runner IP. !25511
+- Fix weekday shift in issue board cards for UTC+X timezones by removing local timezone to UTC conversion. !25512 (Elias Werberich)
+- Fix large table horizontal scroll and prevent side-by-side tables. !25520 (Dany Jupille)
+- Fix error when viewing group issue boards when user doesn't have explicit group permissions. !25524
+- Respect the should_remove_source_branch parameter to the merge API. !25525
+- Externalize markdown toolbar buttons tooltips. !25529
+- Fix method to mark a project repository as writable. !25546
+- fix group without owner after transfer. !25573 (Peter Marko)
+- Fix pagination and duplicate requests in environments page. !25582
+- Improve the JS pagination to handle the case when the `X-Total` and `X-Total-Pages` headers aren't present. !25601
+- Add right padding to the repository mirror action buttons. !25606
+- Use 'folder-open' from sprite icons for Browse Files button in Tag page. !25635
+- Make merge to refs/merge-requests/:iid/merge not raise when FF-only enabled. !25653
+- Fixed "Copying comment with ordered list includes extraneous newlines". !25695
+- Fix bridge jobs only/except variables policy. !25710
+- Allow GraphQL requests without CSRF token. !25719
+- Skip Project validation during Hashed Storage migration or rollback. !25753
+- Resolve showing squash commit edit issue when only single commit is present. !25807
+- Fix the last-ditch memory killer pgroup SIGKILL. !25940
+- Disable timeout on merge request merging poll. !25988
+- Allow modifying squash commit message for fast-forward only merge method. !26017
+- Fix bug in BitBucket imports with SHA shorter than 40 chars. !26050
+- Fix health checks not working behind load balancers. !26055
+- Fix 500 error caused by CODEOWNERS with no matches. !26072
+- Fix notes being marked as edited after resolving. !26143
+- Fix error creating a merge request when diff includes a null byte. !26190
+- Fix undefined variable error on json project views. !26297
+- GitHub import: Create new branches as project owner. !26335
+- Gracefully handles excluded fields from attributes during serialization on JsonCache. !26368
+- Admin section finds users case-insensitively.
+- Fixes not working dropdowns in pipelines page.
+- Do not show file templates when creating a new directory in WebIDE.
+- Allow project members to see private group if the project is in the group namespace.
+- Allow maintainers to remove pages.
+- Fix inconsistent pagination styles.
+- Fixed blob editor deleting file content for certain file paths.
+- Fix upcoming milestone when there are milestones with far-future due dates.
+- Fixed alignment of changed icon in Web IDE.
+
+### Changed (31 changes, 10 of them are from the community)
+
+- Improve snippets empty state. !18348 (George Tsiolis)
+- Remove second primary button on wiki edit. !19959 (George Tsiolis)
+- Allow raw `tls_options` to be passed in LDAP configuration. !20678
+- Remove undigested token column from personal_access_tokens table from the database. !22743
+- Update activity filter for issues. !23423 (George Tsiolis)
+- Use auto-build-image for build job in Auto-DevOps.gitlab-ci.yml. !24279
+- Error tracking configuration - add a Sentry project selection dropdown. !24701
+- Move ChatOps to Core. !24780
+- Implement new arguments `state`, `closed_before` and `closed_after` for `IssuesResolver` in GraphQL. !24910
+- Validate kubernetes cluster CA certificate. !24990
+- Review App Link to Changed Page if Only One Change Present. !25048
+- Show pipeline ID, commit, and branch name on modal while stopping pipeline. !25059
+- Improve empty state for starred projects. !25138
+- Capture due date when importing milestones from Github. !25182 (dstanley)
+- Add a spinner icon which is rendered using pure css. !25186
+- Make emoji picker bigger. !25187 (Jacopo Beschi @jacopo-beschi)
+- API: Sort tie breaker with id DESC. !25311 (Nermin Vehabovic)
+- Add iOS-fastlane template for .gitlab-ci.yml. !25395
+- Move language setting to preferences. !25427 (Fabian Schneider @fabsrc)
+- Resolve Create Project Template for Netlify. !25453
+- Sort labels alphabetically on issues and merge requests list. !25470
+- Add Project template for .NET Core. !25486
+- Update operations settings breadcrumb trail. !25539 (George Tsiolis)
+- Add Project template for go-micro. !25553
+- Jira: make issue links title compact. !25609 (Elan Ruusamäe @glensc)
+- Project level filtering for JupyterHub. !25684 (Amit Rathi (amit1rrr))
+- Clean up vendored templates. !25794
+- Mask all TOKEN and PASSWORD CI variables. !25868
+- Add project template for Android. !25870
+- Add iOS project template. !25872
+- Upgrade to Gitaly v1.26.0. !25890
+
+### Performance (11 changes)
+
+- Improve performance for diverging commit counts. !24287
+- Optimize Redis usage in User::ActivityService. !25005
+- Only load syntax highlight CSS of selected theme. !25232
+- Improve label select rendering. !25281
+- Enable persisted pipeline stages by default. !25347
+- Speed up group issue search counts. !25411
+- Load repository language from the database if detected before. !25518
+- Remove N+1 query for tags in /admin/runners page. !25572
+- Eliminate most N+1 queries loading UserController#calendar_activities. !25697
+- Improve Web IDE launch performance. !25700
+- Significantly reduce N+1 queries in /api/v4/todos endpoint. !25711
+
+### Added (55 changes, 18 of them are from the community)
+
+- Add a tag filter to the admin runners view. !19740 (Alexis Reigel)
+- Add project fetch statistics. !23596 (Jacopo Beschi @jacopo-beschi)
+- Hashed Storage rollback mechanism. !23955
+- Allow to recursively expand includes. !24356
+- Allow expanding a diff to display full file. !24406
+- Support `only: changes:` on MR pipelines. !24490 (Hiroyuki Sato)
+- Expose additional merge request pipeline variables. !24595 (Hiroyuki Sato)
+- Add metadata about the GitLab server to GraphQL. !24636
+- Support merge ref writing (without merging to target branch). !24692
+- Add field mergeRequests for project in GraphQL. !24805
+- API support for MR merge to temporary merge ref path. !24918
+- Ability to filter confidential issues. !24960 (Robert Schilling)
+- Allow creation of branches that match a wildcard protection, except directly through git. !24969
+- Add related merge request count to api response. !24974
+- Add realtime validation for user fullname and username on validation. !25017 (Ehsan Abdulqader @EhsanZ)
+- Allow setting feature flags per GitLab group through the API. !25022
+- Add API endpoint to get a commit's GPG signature. !25032
+- Add support for FTP assets for releases. !25071 (Robert Schilling)
+- Add Confirmation Modal to Rollback on Environment. !25110
+- add title attribute to display file name. !25154 (Satoshi Nakamatsu @satoshicano)
+- API: Expose text_color for project and group labels. !25172 (Robert Schilling)
+- Added support for ingress hostnames. !25181 (walkafwalka)
+- API: Promote project milestone to a group milestone. !25203 (Nermin Vehabovic)
+- API: Expose if the current user can merge a MR. !25207 (Robert Schilling)
+- add readme to changelogs directory. !25209 (@glensc)
+- API: Indicate if label is a project label. !25219 (Robert Schilling)
+- Expose refspecs and depth to runner. !25233
+- Port System Header and Footer feature to Core. !25241
+- Sort Environments by Last Updated. !25260
+- Accept force option to overwrite branch on commit via API. !25286
+- Add support for masking CI variables. !25293
+- Add Link from Closed (moved) Issues to Moved Issue. !25300
+- Next/previous navigation between files in MR review. !25355
+- Add YouTrack integration service. !25361 (Yauhen Kotau @bessorion)
+- Add ability to set path and name for project on fork using API. !25363
+- Add project level config for merge pipelines. !25385
+- Edit Knative domain after it has been deployed. !25386
+- Add zoom and scroll to metrics dashboard. !25388
+- Persist source sha and target sha for merge pipelines. !25417
+- Add support for toggling discussion filter from notes section. !25426
+- Resolve Move files in the Web IDE. !25431
+- Show header and footer system messages in email. !25474
+- Allow configuring POSTGRES_VERSION in Auto DevOps. !25500
+- Add Saturday to Localization first day of the week. !25509 (Ahmad Haghighi)
+- Extend the Gitlab API for deletion of job_artifacts of a single job. !25522 (rroger)
+- Simplify CI/CD configuration on serverless projects. !25523
+- Add button to start discussion from single comment. !25575
+- sidekiq: terminate child processes at shutdown. !25669
+- Expose merge request entity for pipelines. !25679
+- Link to most recent MR from a branch. !25689
+- Adds Auto DevOps build job for tags. !25718 (walkafwalka)
+- Allow all snippets to be accessed by API. !25772
+- Make file tree in merge requests resizable.
+- Make the Web IDE the default editor.
+- File uploads are deleted asynchronously when deleting a project or group.
+
+### Other (28 changes, 6 of them are from the community)
+
+- Improve GitHub and Gitea project import table UI. !24606
+- Externalize strings from `/app/views/projects/commit`. !24668 (George Tsiolis)
+- Correct non-standard unicode spaces to regular unicode. !24795 (Marcel Amirault)
+- Provide a performance bar link to the Jaeger UI. !24902
+- Remove BATCH_SIZE from WikiFileFinder. !24933
+- Use export-import svgs from gitlab-svgs. !24954
+- Fix N+1 query in Issues and MergeRequest API when issuable_metadata is present. !25042 (Alex Koval)
+- Directly inheriting from ActiveRecord::Migration is deprecated. !25066 (Jasper Maes)
+- Bump Helm and kubectl in Auto DevOps to 2.12.3 and 1.11.7 respectively. !25072
+- Log queue duration in production_json.log. !25075
+- Extracted ResolveWithIssueButton to its own component. !25093 (Martin Hobert)
+- Add rectangular project and group avatars. !25098
+- Include note in the Rails filter_parameters configuration. !25238
+- Bump Helm and kubectl used in Kubernetes integration to 2.12.3 and 1.11.7 respectively. !25268
+- Include gl_project_path in API /internal/allowed response. !25314
+- Fix incorrect Pages Domains checkbox description. !25392 (Anton Melser)
+- Update GitLab Runner Helm Chart to 0.2.0. !25493
+- Add suffix (`_event`) to merge request source. !25508
+- Creates a helper function to check if repo is EE. !25647
+- If chpst is available, make fron-source installations run sidekiq as a process group leader. !25654
+- Bring back Rugged implementation of GetTreeEntries. !25674
+- Moves EE util into the CE file. !25680
+- Bring back Rugged implementation of CommitIsAncestor. !25702
+- Bring back Rugged implementation of TreeEntry. !25706
+- Enable syntax highlighting to other supported markups. !25761
+- Update GitLab Shell to v8.7.1. !25801
+- Bring back Rugged implementation of commit_tree_entry. !25896
+- Removes EE differences for jobs/getters.js.
+
+
+## 11.8.3 (2019-03-19)
+
+### Security (1 change)
+
+- Remove project serialization in quick actions response.
+
+
+## 11.8.2 (2019-03-13)
+
+### Security (1 change)
+
+- Fixed ability to see private groups by users not belonging to given group.
+
+### Fixed (5 changes)
+
+- Fix import_jid error on project import. !25239
+- Properly handle multiple X-Forwarded-For addresses in runner IP. !25511
+- Fix error when viewing group issue boards when user doesn't have explicit group permissions. !25524
+- Fix method to mark a project repository as writable. !25546
+- Allow project members to see private group if the project is in the group namespace.
+
+
## 11.8.0 (2019-02-22)
### Security (7 changes, 1 of them is from the community)
@@ -249,6 +548,14 @@ entry.
- Creates mixin to reduce code duplication between CE and EE in graph component.
+## 11.7.7 (2019-03-19)
+
+### Security (2 changes)
+
+- Remove project serialization in quick actions response.
+- Fixed ability to see private groups by users not belonging to given group.
+
+
## 11.7.5 (2019-02-06)
### Fixed (8 changes)
diff --git a/Dangerfile b/Dangerfile
index 715a2bcbbae..32f4b4d23c3 100644
--- a/Dangerfile
+++ b/Dangerfile
@@ -12,3 +12,4 @@ danger.import_dangerfile(path: 'danger/duplicate_yarn_dependencies')
danger.import_dangerfile(path: 'danger/prettier')
danger.import_dangerfile(path: 'danger/eslint')
danger.import_dangerfile(path: 'danger/roulette')
+danger.import_dangerfile(path: 'danger/single_codebase')
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 5ff8c4f5d2a..5e57fb89558 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.26.0
+1.29.0
diff --git a/Gemfile b/Gemfile
index caa40e99307..d44a99b2913 100644
--- a/Gemfile
+++ b/Gemfile
@@ -18,7 +18,7 @@ gem 'gitlab-default_value_for', '~> 3.1.1', require: 'default_value_for'
gem 'mysql2', '~> 0.4.10', group: :mysql
gem 'pg', '~> 1.1', group: :postgres
-gem 'rugged', '~> 0.27'
+gem 'rugged', '~> 0.28'
gem 'grape-path-helpers', '~> 1.0'
gem 'faraday', '~> 0.12'
@@ -42,11 +42,11 @@ gem 'omniauth-shibboleth', '~> 1.3.0'
gem 'omniauth-twitter', '~> 1.4'
gem 'omniauth_crowd', '~> 2.2.0'
gem 'omniauth-authentiq', '~> 0.3.3'
-gem 'rack-oauth2', '~> 1.2.1'
+gem 'rack-oauth2', '~> 1.9.3'
gem 'jwt', '~> 2.1.0'
# Spam and anti-bot protection
-gem 'recaptcha', '~> 3.0', require: 'recaptcha/rails'
+gem 'recaptcha', '~> 4.11', require: 'recaptcha/rails'
gem 'akismet', '~> 2.0'
# Two-factor authentication
@@ -158,7 +158,7 @@ end
gem 'state_machines-activerecord', '~> 0.5.1'
# Issue tags
-gem 'acts-as-taggable-on', '~> 5.0'
+gem 'acts-as-taggable-on', '~> 6.0'
# Background jobs
gem 'sidekiq', '~> 5.2.1'
@@ -170,7 +170,7 @@ gem 'gitlab-sidekiq-fetcher', '~> 0.4.0', require: 'sidekiq-reliable-fetch'
gem 'fugit', '~> 1.1'
# HTTP requests
-gem 'httparty', '~> 0.13.3'
+gem 'httparty', '~> 0.16.4'
# Colored output to console
gem 'rainbow', '~> 3.0'
@@ -265,7 +265,6 @@ gem 'addressable', '~> 2.5.2'
gem 'font-awesome-rails', '~> 4.7'
gem 'gemojione', '~> 3.3'
gem 'gon', '~> 6.2'
-gem 'jquery-atwho-rails', '~> 1.3.2'
gem 'request_store', '~> 1.3'
gem 'virtus', '~> 1.0.1'
gem 'base32', '~> 0.3.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 278a1a64472..66e12efa4b3 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -43,8 +43,8 @@ GEM
i18n (>= 0.7, < 2)
minitest (~> 5.1)
tzinfo (~> 1.1)
- acts-as-taggable-on (5.0.0)
- activerecord (>= 4.2.8)
+ acts-as-taggable-on (6.0.0)
+ activerecord (~> 5.0)
adamantium (0.2.0)
ice_nine (~> 0.11.0)
memoizable (~> 0.4.0)
@@ -65,7 +65,7 @@ GEM
atomic (1.1.99)
attr_encrypted (3.1.0)
encryptor (~> 3.0.0)
- attr_required (1.0.0)
+ attr_required (1.0.1)
awesome_print (1.8.0)
axiom-types (0.1.1)
descendants_tracker (~> 0.0.4)
@@ -379,8 +379,8 @@ GEM
domain_name (~> 0.5)
http-form_data (2.1.1)
http_parser.rb (0.6.0)
- httparty (0.13.7)
- json (~> 1.8)
+ httparty (0.16.4)
+ mime-types (~> 3.0)
multi_xml (>= 0.5.2)
httpclient (2.8.3)
i18n (1.2.0)
@@ -398,7 +398,6 @@ GEM
activesupport
multipart-post
oauth (~> 0.5, >= 0.5.0)
- jquery-atwho-rails (1.3.2)
js_regex (3.1.1)
character_set (~> 1.1)
regexp_parser (~> 1.1)
@@ -584,7 +583,7 @@ GEM
atomic (>= 1.0.0)
peek
redis
- pg (1.1.3)
+ pg (1.1.4)
po_to_json (1.0.1)
json (>= 1.6.0)
powerpack (0.1.1)
@@ -622,12 +621,12 @@ GEM
rack-attack (4.4.1)
rack
rack-cors (1.0.2)
- rack-oauth2 (1.2.3)
- activesupport (>= 2.3)
- attr_required (>= 0.0.5)
- httpclient (>= 2.4)
- multi_json (>= 1.3.6)
- rack (>= 1.1)
+ rack-oauth2 (1.9.3)
+ activesupport
+ attr_required
+ httpclient
+ json-jwt (>= 1.9.0)
+ rack
rack-protection (2.0.5)
rack
rack-proxy (0.6.0)
@@ -680,7 +679,7 @@ GEM
optimist (>= 3.0.0)
rdoc (6.0.4)
re2 (1.1.1)
- recaptcha (3.0.0)
+ recaptcha (4.13.1)
json
recursive-open-struct (1.1.0)
redis (3.3.5)
@@ -785,7 +784,7 @@ GEM
rubyntlm (0.6.2)
rubypants (0.2.0)
rubyzip (1.2.2)
- rugged (0.27.5)
+ rugged (0.28.0)
safe_yaml (1.0.4)
sanitize (4.6.6)
crass (~> 1.0.2)
@@ -814,7 +813,7 @@ GEM
selenium-webdriver (3.12.0)
childprocess (~> 0.5)
rubyzip (~> 1.2)
- sentry-raven (2.7.4)
+ sentry-raven (2.9.0)
faraday (>= 0.7.6, < 1.0)
settingslogic (2.0.9)
sexp_processor (4.11.0)
@@ -949,7 +948,7 @@ DEPENDENCIES
RedCloth (~> 4.3.2)
ace-rails-ap (~> 4.1.0)
activerecord_sane_schema_dumper (= 1.0)
- acts-as-taggable-on (~> 5.0)
+ acts-as-taggable-on (~> 6.0)
addressable (~> 2.5.2)
akismet (~> 2.0)
asana (~> 0.8.1)
@@ -1041,12 +1040,11 @@ DEPENDENCIES
health_check (~> 2.6.0)
html-pipeline (~> 2.8)
html2text
- httparty (~> 0.13.3)
+ httparty (~> 0.16.4)
icalendar
influxdb (~> 0.2)
jaeger-client (~> 0.10.0)
jira-ruby (~> 1.4)
- jquery-atwho-rails (~> 1.3.2)
js_regex (~> 3.1)
json-schema (~> 2.8.0)
jwt (~> 2.1.0)
@@ -1103,7 +1101,7 @@ DEPENDENCIES
rack (= 2.0.6)
rack-attack (~> 4.4.1)
rack-cors (~> 1.0.0)
- rack-oauth2 (~> 1.2.1)
+ rack-oauth2 (~> 1.9.3)
rack-proxy (~> 0.6.0)
rails (= 5.0.7.1)
rails-controller-testing
@@ -1115,7 +1113,7 @@ DEPENDENCIES
rbtrace (~> 0.4)
rdoc (~> 6.0)
re2 (~> 1.1.1)
- recaptcha (~> 3.0)
+ recaptcha (~> 4.11)
redis (~> 3.2)
redis-namespace (~> 1.6.0)
redis-rails (~> 5.0.2)
@@ -1136,13 +1134,12 @@ DEPENDENCIES
ruby-progressbar
ruby_parser (~> 3.8)
rubyzip (~> 1.2.2)
- rugged (~> 0.27)
+ rugged (~> 0.28)
sanitize (~> 4.6)
sass (~> 3.5)
sass-rails (~> 5.0.6)
scss_lint (~> 0.56.0)
seed-fu (~> 2.3.7)
- select2-rails (~> 3.5.9)
selenium-webdriver (~> 3.12)
sentry-raven (~> 2.7)
settingslogic (~> 2.0.9)
diff --git a/VERSION b/VERSION
index 00a946e3be3..1a954017bf3 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-11.9.0-pre
+11.10.0-pre
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index aeb88715c11..3826ecd1ac1 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -8,6 +8,7 @@ import { updateTooltipTitle } from './lib/utils/common_utils';
import { isInVueNoteablePage } from './lib/utils/dom_utils';
import flash from './flash';
import axios from './lib/utils/axios_utils';
+import bp from './breakpoints';
const animationEndEventString = 'animationend webkitAnimationEnd MSAnimationEnd oAnimationEnd';
const transitionEndEventString = 'transitionend webkitTransitionEnd oTransitionEnd MSTransitionEnd';
@@ -264,7 +265,10 @@ export class AwardsHandler {
const css = {
top: `${$addBtn.offset().top + $addBtn.outerHeight()}px`,
};
- if (position === 'right') {
+ // for xs screen we position the element on center
+ if (bp.getBreakpointSize() === 'xs') {
+ css.left = '5%';
+ } else if (position === 'right') {
css.left = `${$addBtn.offset().left - $menu.outerWidth() + 20}px`;
$menu.addClass('is-aligned-right');
} else {
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index 388f674f643..c95d7608e37 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -12,6 +12,8 @@ import {
REQUEST_FAILURE,
UPGRADE_REQUESTED,
UPGRADE_REQUEST_FAILURE,
+ INGRESS,
+ INGRESS_DOMAIN_SUFFIX,
} from './constants';
import ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store';
@@ -76,6 +78,10 @@ export default class Clusters {
this.successApplicationContainer = document.querySelector('.js-cluster-application-notice');
this.showTokenButton = document.querySelector('.js-show-cluster-token');
this.tokenField = document.querySelector('.js-cluster-token');
+ this.ingressDomainHelpText = document.querySelector('.js-ingress-domain-help-text');
+ this.ingressDomainSnippet = this.ingressDomainHelpText.querySelector(
+ '.js-ingress-domain-snippet',
+ );
Clusters.initDismissableCallout();
initSettingsPanels();
@@ -182,6 +188,10 @@ export default class Clusters {
this.checkForNewInstalls(prevApplicationMap, this.store.state.applications);
this.updateContainer(prevStatus, this.store.state.status, this.store.state.statusReason);
+ this.toggleIngressDomainHelpText(
+ prevApplicationMap[INGRESS],
+ this.store.state.applications[INGRESS],
+ );
}
showToken() {
@@ -277,6 +287,16 @@ export default class Clusters {
this.store.updateAppProperty(appId, 'requestStatus', null);
}
+ toggleIngressDomainHelpText(ingressPreviousState, ingressNewState) {
+ const helpTextHidden = ingressNewState.status !== APPLICATION_STATUS.INSTALLED;
+ const domainSnippetText = `${ingressNewState.externalIp}${INGRESS_DOMAIN_SUFFIX}`;
+
+ if (ingressPreviousState.status !== ingressNewState.status) {
+ this.ingressDomainHelpText.classList.toggle('hide', helpTextHidden);
+ this.ingressDomainSnippet.textContent = domainSnippetText;
+ }
+ }
+
saveKnativeDomain(data) {
const appId = data.id;
this.store.updateAppProperty(appId, 'status', APPLICATION_STATUS.UPDATING);
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 13e8617c515..5b206b82fe0 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -1,6 +1,7 @@
<script>
import _ from 'underscore';
import helmInstallIllustration from '@gitlab/svgs/dist/illustrations/kubernetes-installation.svg';
+import { GlLoadingIcon } from '@gitlab/ui';
import elasticsearchLogo from 'images/cluster_app_logos/elasticsearch.png';
import gitlabLogo from 'images/cluster_app_logos/gitlab.png';
import helmLogo from 'images/cluster_app_logos/helm.png';
@@ -23,6 +24,7 @@ export default {
applicationRow,
clipboardButton,
LoadingButton,
+ GlLoadingIcon,
},
props: {
type: {
@@ -296,7 +298,12 @@ export default {
/>
</span>
</div>
- <input v-else type="text" class="form-control js-endpoint" readonly value="?" />
+ <div v-else class="input-group">
+ <input type="text" class="form-control js-endpoint" readonly />
+ <gl-loading-icon
+ class="position-absolute align-self-center ml-2 js-ingress-ip-loading-icon"
+ />
+ </div>
<p class="form-text text-muted">
{{
s__(`ClusterIntegration|Point a wildcard DNS to this
@@ -545,13 +552,12 @@ export default {
/>
</span>
</div>
- <input
- v-else
- type="text"
- class="form-control js-knative-endpoint"
- readonly
- value="?"
- />
+ <div v-else class="input-group">
+ <input type="text" class="form-control js-endpoint" readonly />
+ <gl-loading-icon
+ class="position-absolute align-self-center ml-2 js-knative-ip-loading-icon"
+ />
+ </div>
</div>
<p class="form-text text-muted col-12">
diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js
index 39022879d91..67f481f2afb 100644
--- a/app/assets/javascripts/clusters/constants.js
+++ b/app/assets/javascripts/clusters/constants.js
@@ -28,3 +28,4 @@ export const JUPYTER = 'jupyter';
export const KNATIVE = 'knative';
export const RUNNER = 'runner';
export const CERT_MANAGER = 'cert_manager';
+export const INGRESS_DOMAIN_SUFFIX = '.nip.io';
diff --git a/app/assets/javascripts/commons/jquery.js b/app/assets/javascripts/commons/jquery.js
index 009153d0703..2f268419bff 100644
--- a/app/assets/javascripts/commons/jquery.js
+++ b/app/assets/javascripts/commons/jquery.js
@@ -3,7 +3,7 @@ import 'jquery';
// common jQuery plugins
import 'jquery-ujs';
import 'vendor/jquery.endless-scroll';
-import 'vendor/jquery.caret';
-import 'vendor/jquery.atwho';
+import 'jquery.caret'; // must be imported before at.js
+import 'at.js';
import 'vendor/jquery.scrollTo';
import 'jquery.waitforimages';
diff --git a/app/assets/javascripts/commons/polyfills.js b/app/assets/javascripts/commons/polyfills.js
index bffc025ced3..a0ca44caa07 100644
--- a/app/assets/javascripts/commons/polyfills.js
+++ b/app/assets/javascripts/commons/polyfills.js
@@ -7,6 +7,7 @@ import 'core-js/fn/array/includes';
import 'core-js/fn/object/assign';
import 'core-js/fn/object/values';
import 'core-js/fn/promise';
+import 'core-js/fn/promise/finally';
import 'core-js/fn/string/code-point-at';
import 'core-js/fn/string/from-code-point';
import 'core-js/fn/string/includes';
diff --git a/app/assets/javascripts/diffs/components/app.vue b/app/assets/javascripts/diffs/components/app.vue
index 1fc2b7fe859..e8f8c09152a 100644
--- a/app/assets/javascripts/diffs/components/app.vue
+++ b/app/assets/javascripts/diffs/components/app.vue
@@ -19,6 +19,7 @@ import {
MIN_TREE_WIDTH,
MAX_TREE_WIDTH,
TREE_HIDE_STATS_WIDTH,
+ MR_TREE_SHOW_KEY,
} from '../constants';
export default {
@@ -162,10 +163,13 @@ export default {
'setHighlightedRow',
'cacheTreeListWidth',
'scrollToFile',
+ 'toggleShowTreeList',
]),
fetchData() {
this.fetchDiffFiles()
.then(() => {
+ this.hideTreeListIfJustOneFile();
+
requestIdleCallback(
() => {
this.setDiscussions();
@@ -231,6 +235,13 @@ export default {
this.scrollToFile(this.diffFiles[targetIndex].file_path);
}
},
+ hideTreeListIfJustOneFile() {
+ const storedTreeShow = localStorage.getItem(MR_TREE_SHOW_KEY);
+
+ if ((storedTreeShow === null && this.diffFiles.length <= 1) || storedTreeShow === 'false') {
+ this.toggleShowTreeList(false);
+ }
+ },
},
minTreeWidth: MIN_TREE_WIDTH,
maxTreeWidth: MAX_TREE_WIDTH,
diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue
index 0bf2dde8b96..fe49dfff10b 100644
--- a/app/assets/javascripts/diffs/components/compare_versions.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions.vue
@@ -125,9 +125,9 @@ export default {
>
{{ __('Show latest version') }}
</gl-button>
- <a v-show="hasCollapsedFile" class="btn btn-default append-right-8" @click="expandAllFiles">
+ <gl-button v-show="hasCollapsedFile" class="append-right-8" @click="expandAllFiles">
{{ __('Expand all') }}
- </a>
+ </gl-button>
<settings-dropdown />
</div>
</div>
diff --git a/app/assets/javascripts/diffs/components/diff_file.vue b/app/assets/javascripts/diffs/components/diff_file.vue
index 1141a197c6a..0e779e1be9a 100644
--- a/app/assets/javascripts/diffs/components/diff_file.vue
+++ b/app/assets/javascripts/diffs/components/diff_file.vue
@@ -73,13 +73,23 @@ export default {
if (!newVal && oldVal && !this.hasDiffLines) {
this.handleLoadCollapsedDiff();
}
+
+ this.setFileCollapsed({ filePath: this.file.file_path, collapsed: newVal });
+ },
+ 'file.viewer.collapsed': function setIsCollapsed(newVal) {
+ this.isCollapsed = newVal;
},
},
created() {
eventHub.$on(`loadCollapsedDiff/${this.file.file_hash}`, this.handleLoadCollapsedDiff);
},
methods: {
- ...mapActions('diffs', ['loadCollapsedDiff', 'assignDiscussionsToDiff', 'setRenderIt']),
+ ...mapActions('diffs', [
+ 'loadCollapsedDiff',
+ 'assignDiscussionsToDiff',
+ 'setRenderIt',
+ 'setFileCollapsed',
+ ]),
handleToggle() {
if (!this.hasDiffLines) {
this.handleLoadCollapsedDiff();
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
index caf0df8a4e3..c60246bf8ef 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_table_row.vue
@@ -140,7 +140,7 @@ export default {
:id="line.left.line_code"
:class="parallelViewLeftLineType"
class="line_content parallel left-side"
- @mousedown.native="handleParallelLineMouseDown"
+ @mousedown="handleParallelLineMouseDown"
v-html="line.left.rich_text"
></td>
</template>
@@ -171,7 +171,7 @@ export default {
},
]"
class="line_content parallel right-side"
- @mousedown.native="handleParallelLineMouseDown"
+ @mousedown="handleParallelLineMouseDown"
v-html="line.right.rich_text"
></td>
</template>
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index 8fc3af15bea..384f33e0983 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -30,8 +30,9 @@ export default {
filteredTreeList() {
const search = this.search.toLowerCase().trim();
- if (search === '' || this.$options.fuzzyFileFinderEnabled)
+ if (search === '') {
return this.renderTreeList ? this.tree : this.allBlobs;
+ }
return this.allBlobs.reduce((acc, folder) => {
const tree = folder.tree.filter(f => f.path.toLowerCase().indexOf(search) >= 0);
@@ -51,13 +52,11 @@ export default {
},
},
methods: {
- ...mapActions('diffs', ['toggleTreeOpen', 'scrollToFile', 'toggleFileFinder']),
+ ...mapActions('diffs', ['toggleTreeOpen', 'scrollToFile']),
clearSearch() {
this.search = '';
},
},
- shortcutKeyCharacter: `${/Mac/i.test(navigator.userAgent) ? '&#8984;' : 'Ctrl'}+P`,
- diffTreeFiltering: gon.features && gon.features.diffTreeFiltering,
};
</script>
@@ -66,36 +65,21 @@ export default {
<div class="append-bottom-8 position-relative tree-list-search d-flex">
<div class="flex-fill d-flex">
<icon name="search" class="position-absolute tree-list-icon" />
- <template v-if="$options.diffTreeFiltering">
- <input
- v-model="search"
- :placeholder="s__('MergeRequest|Filter files')"
- type="search"
- class="form-control"
- />
- <button
- v-show="search"
- :aria-label="__('Clear search')"
- type="button"
- class="position-absolute bg-transparent tree-list-icon tree-list-clear-icon border-0 p-0"
- @click="clearSearch"
- >
- <icon name="close" />
- </button>
- </template>
- <template v-else>
- <button
- type="button"
- class="form-control text-left text-secondary"
- @click="toggleFileFinder(true)"
- >
- {{ s__('MergeRequest|Search files') }}
- </button>
- <span
- class="position-absolute text-secondary diff-tree-search-shortcut"
- v-html="$options.shortcutKeyCharacter"
- ></span>
- </template>
+ <input
+ v-model="search"
+ :placeholder="s__('MergeRequest|Filter files')"
+ type="search"
+ class="form-control"
+ />
+ <button
+ v-show="search"
+ :aria-label="__('Clear search')"
+ type="button"
+ class="position-absolute bg-transparent tree-list-icon tree-list-clear-icon border-0 p-0"
+ @click="clearSearch"
+ >
+ <icon name="close" />
+ </button>
</div>
</div>
<div :class="{ 'pt-0 tree-list-blobs': !renderTreeList }" class="tree-list-scroll">
diff --git a/app/assets/javascripts/diffs/store/actions.js b/app/assets/javascripts/diffs/store/actions.js
index 57ddc923a3e..b58ae0d248c 100644
--- a/app/assets/javascripts/diffs/store/actions.js
+++ b/app/assets/javascripts/diffs/store/actions.js
@@ -266,9 +266,12 @@ export const scrollToFile = ({ state, commit }, path) => {
commit(types.UPDATE_CURRENT_DIFF_FILE_ID, fileHash);
};
-export const toggleShowTreeList = ({ commit, state }) => {
+export const toggleShowTreeList = ({ commit, state }, saving = true) => {
commit(types.TOGGLE_SHOW_TREE_LIST);
- localStorage.setItem(MR_TREE_SHOW_KEY, state.showTreeList);
+
+ if (saving) {
+ localStorage.setItem(MR_TREE_SHOW_KEY, state.showTreeList);
+ }
};
export const openDiffFileCommentForm = ({ commit, getters }, formData) => {
@@ -344,5 +347,8 @@ export const toggleFullDiff = ({ dispatch, getters, state }, filePath) => {
}
};
+export const setFileCollapsed = ({ commit }, { filePath, collapsed }) =>
+ commit(types.SET_FILE_COLLAPSED, { filePath, collapsed });
+
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index 47f78a5db54..cf4dd93dbfb 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -1,13 +1,10 @@
import Cookies from 'js-cookie';
import { getParameterValues } from '~/lib/utils/url_utility';
-import bp from '~/breakpoints';
-import { parseBoolean } from '~/lib/utils/common_utils';
-import { INLINE_DIFF_VIEW_TYPE, DIFF_VIEW_COOKIE_NAME, MR_TREE_SHOW_KEY } from '../../constants';
+import { INLINE_DIFF_VIEW_TYPE, DIFF_VIEW_COOKIE_NAME } from '../../constants';
const viewTypeFromQueryString = getParameterValues('view')[0];
const viewTypeFromCookie = Cookies.get(DIFF_VIEW_COOKIE_NAME);
const defaultViewType = INLINE_DIFF_VIEW_TYPE;
-const storedTreeShow = localStorage.getItem(MR_TREE_SHOW_KEY);
export default () => ({
isLoading: true,
@@ -23,8 +20,7 @@ export default () => ({
diffViewType: viewTypeFromQueryString || viewTypeFromCookie || defaultViewType,
tree: [],
treeEntries: {},
- showTreeList:
- storedTreeShow === null ? bp.getBreakpointSize() !== 'xs' : parseBoolean(storedTreeShow),
+ showTreeList: true,
currentDiffFileId: '',
projectPath: '',
commentForms: [],
diff --git a/app/assets/javascripts/diffs/store/mutation_types.js b/app/assets/javascripts/diffs/store/mutation_types.js
index b441b1de451..adf56eba3f8 100644
--- a/app/assets/javascripts/diffs/store/mutation_types.js
+++ b/app/assets/javascripts/diffs/store/mutation_types.js
@@ -27,3 +27,4 @@ export const TOGGLE_FILE_FINDER_VISIBLE = 'TOGGLE_FILE_FINDER_VISIBLE';
export const REQUEST_FULL_DIFF = 'REQUEST_FULL_DIFF';
export const RECEIVE_FULL_DIFF_SUCCESS = 'RECEIVE_FULL_DIFF_SUCCESS';
export const RECEIVE_FULL_DIFF_ERROR = 'RECEIVE_FULL_DIFF_ERROR';
+export const SET_FILE_COLLAPSED = 'SET_FILE_COLLAPSED';
diff --git a/app/assets/javascripts/diffs/store/mutations.js b/app/assets/javascripts/diffs/store/mutations.js
index 45187d93fef..856f4eaf00a 100644
--- a/app/assets/javascripts/diffs/store/mutations.js
+++ b/app/assets/javascripts/diffs/store/mutations.js
@@ -104,7 +104,10 @@ export default {
[types.EXPAND_ALL_FILES](state) {
state.diffFiles = state.diffFiles.map(file => ({
...file,
- collapsed: false,
+ viewer: {
+ ...file.viewer,
+ collapsed: false,
+ },
}));
},
@@ -300,4 +303,11 @@ export default {
}),
});
},
+ [types.SET_FILE_COLLAPSED](state, { filePath, collapsed }) {
+ const file = state.diffFiles.find(f => f.file_path === filePath);
+
+ if (file && file.viewer) {
+ file.viewer.collapsed = collapsed;
+ }
+ },
};
diff --git a/app/assets/javascripts/emoji/index.js b/app/assets/javascripts/emoji/index.js
index 36542315c4c..b9b3b344524 100644
--- a/app/assets/javascripts/emoji/index.js
+++ b/app/assets/javascripts/emoji/index.js
@@ -3,6 +3,7 @@ import createFlash from '~/flash';
import { s__ } from '~/locale';
import emojiAliases from 'emojis/aliases.json';
import axios from '../lib/utils/axios_utils';
+import csrf from '../lib/utils/csrf';
import AccessorUtilities from '../lib/utils/accessor';
@@ -24,11 +25,15 @@ export function initEmojiMap() {
resolve(emojiMap);
} else {
// We load the JSON from server
- axios
- .get(
- `${gon.asset_host || ''}${gon.relative_url_root ||
- ''}/-/emojis/${EMOJI_VERSION}/emojis.json`,
- )
+ const axiosInstance = axios.create();
+
+ // If the static JSON file is on a CDN we don't want to send the default CSRF token
+ if (gon.asset_host) {
+ delete axiosInstance.defaults.headers.common[csrf.headerKey];
+ }
+
+ axiosInstance
+ .get(`${gon.relative_url_root || ''}/-/emojis/${EMOJI_VERSION}/emojis.json`)
.then(({ data }) => {
emojiMap = data;
validEmojiNames = [...Object.keys(emojiMap), ...Object.keys(emojiAliases)];
diff --git a/app/assets/javascripts/environments/components/container.vue b/app/assets/javascripts/environments/components/container.vue
index 6ece8b92a30..be80661223c 100644
--- a/app/assets/javascripts/environments/components/container.vue
+++ b/app/assets/javascripts/environments/components/container.vue
@@ -1,14 +1,16 @@
<script>
import { GlLoadingIcon } from '@gitlab/ui';
-import tablePagination from '../../vue_shared/components/table_pagination.vue';
-import environmentTable from '../components/environments_table.vue';
+import TablePagination from '~/vue_shared/components/table_pagination.vue';
+import containerMixin from 'ee_else_ce/environments/mixins/container_mixin';
+import EnvironmentTable from '../components/environments_table.vue';
export default {
components: {
- environmentTable,
- tablePagination,
+ EnvironmentTable,
+ TablePagination,
GlLoadingIcon,
},
+ mixins: [containerMixin],
props: {
isLoading: {
type: Boolean,
@@ -47,7 +49,15 @@ export default {
<slot name="emptyState"></slot>
<div v-if="!isLoading && environments.length > 0" class="table-holder">
- <environment-table :environments="environments" :can-read-environment="canReadEnvironment" />
+ <environment-table
+ :environments="environments"
+ :can-read-environment="canReadEnvironment"
+ :canary-deployment-feature-id="canaryDeploymentFeatureId"
+ :show-canary-deployment-callout="showCanaryDeploymentCallout"
+ :user-callouts-path="userCalloutsPath"
+ :lock-promotion-svg-path="lockPromotionSvgPath"
+ :help-canary-deployments-path="helpCanaryDeploymentsPath"
+ />
<table-pagination
v-if="pagination && pagination.totalPages > 1"
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index 1e89dce69cb..a092bdfbc6c 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -4,6 +4,7 @@ import _ from 'underscore';
import { GlTooltipDirective } from '@gitlab/ui';
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
import Icon from '~/vue_shared/components/icon.vue';
+import environmentItemMixin from 'ee_else_ce/environments/mixins/environment_item_mixin';
import ActionsComponent from './environment_actions.vue';
import ExternalUrlComponent from './environment_external_url.vue';
import StopComponent from './environment_stop.vue';
@@ -34,10 +35,10 @@ export default {
TerminalButtonComponent,
MonitoringButtonComponent,
},
-
directives: {
GlTooltip: GlTooltipDirective,
},
+ mixins: [environmentItemMixin],
props: {
model: {
@@ -467,9 +468,18 @@ export default {
<div v-if="!model.isFolder" class="table-mobile-header" role="rowheader">
{{ s__('Environments|Environment') }}
</div>
+
+ <span v-if="shouldRenderDeployBoard" class="deploy-board-icon" @click="toggleDeployBoard">
+ <icon :name="deployIconName" />
+ </span>
+
<span v-if="!model.isFolder" class="environment-name table-mobile-content">
<a class="qa-environment-link" :href="environmentPath"> {{ model.name }} </a>
+ <span v-if="isProtected" class="badge badge-success">
+ {{ s__('Environments|protected') }}
+ </span>
</span>
+
<span v-else class="folder-name" role="button" @click="onClickFolder">
<icon :name="folderIconName" class="folder-icon" />
diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue
index 6e55c3f901a..ec78240217b 100644
--- a/app/assets/javascripts/environments/components/environments_app.vue
+++ b/app/assets/javascripts/environments/components/environments_app.vue
@@ -1,4 +1,5 @@
<script>
+import envrionmentsAppMixin from 'ee_else_ce/environments/mixins/environments_app_mixin';
import Flash from '../../flash';
import { s__ } from '../../locale';
import emptyState from './empty_state.vue';
@@ -15,7 +16,7 @@ export default {
ConfirmRollbackModal,
},
- mixins: [CIPaginationMixin, environmentsMixin],
+ mixins: [CIPaginationMixin, environmentsMixin, envrionmentsAppMixin],
props: {
endpoint: {
@@ -95,9 +96,9 @@ export default {
<tabs :tabs="tabs" scope="environments" @onChangeTab="onChangeTab" />
<div v-if="canCreateEnvironment && !isLoading" class="nav-controls">
- <a :href="newEnvironmentPath" class="btn btn-success">{{
- s__('Environments|New environment')
- }}</a>
+ <a :href="newEnvironmentPath" class="btn btn-success">
+ {{ s__('Environments|New environment') }}
+ </a>
</div>
</div>
@@ -106,6 +107,11 @@ export default {
:environments="state.environments"
:pagination="state.paginationInformation"
:can-read-environment="canReadEnvironment"
+ :canary-deployment-feature-id="canaryDeploymentFeatureId"
+ :show-canary-deployment-callout="showCanaryDeploymentCallout"
+ :user-callouts-path="userCalloutsPath"
+ :lock-promotion-svg-path="lockPromotionSvgPath"
+ :help-canary-deployments-path="helpCanaryDeploymentsPath"
@onChangePage="onChangePage"
>
<empty-state
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index eef141a07ba..ff4e16178e8 100644
--- a/app/assets/javascripts/environments/components/environments_table.vue
+++ b/app/assets/javascripts/environments/components/environments_table.vue
@@ -4,21 +4,24 @@
*/
import { GlLoadingIcon } from '@gitlab/ui';
import _ from 'underscore';
-import environmentItem from './environment_item.vue';
+import environmentTableMixin from 'ee_else_ce/environments/mixins/environments_table_mixin';
+import EnvironmentItem from './environment_item.vue';
export default {
components: {
- environmentItem,
+ EnvironmentItem,
GlLoadingIcon,
+ DeployBoard: () => import('ee_component/environments/components/deploy_board_component.vue'),
+ CanaryDeploymentCallout: () =>
+ import('ee_component/environments/components/canary_deployment_callout.vue'),
},
-
+ mixins: [environmentTableMixin],
props: {
environments: {
type: Array,
required: true,
default: () => [],
},
-
canReadEnvironment: {
type: Boolean,
required: false,
@@ -95,6 +98,21 @@ export default {
:can-read-environment="canReadEnvironment"
/>
+ <div
+ v-if="shouldRenderDeployBoard"
+ :key="`deploy-board-row-${i}`"
+ class="js-deploy-board-row"
+ >
+ <div class="deploy-board-container">
+ <deploy-board
+ :deploy-board-data="model.deployBoardData"
+ :is-loading="model.isLoadingDeployBoard"
+ :is-empty="model.isEmptyDeployBoard"
+ :logs-path="model.logs_path"
+ />
+ </div>
+ </div>
+
<template v-if="shouldRenderFolderContent(model)">
<div v-if="model.isLoadingFolderContent" :key="`loading-item-${i}`">
<gl-loading-icon :size="2" class="prepend-top-16" />
@@ -111,13 +129,24 @@ export default {
<div :key="`sub-div-${i}`">
<div class="text-center prepend-top-10">
- <a :href="folderUrl(model)" class="btn btn-default">{{
- s__('Environments|Show all')
- }}</a>
+ <a :href="folderUrl(model)" class="btn btn-default">
+ {{ s__('Environments|Show all') }}
+ </a>
</div>
</div>
</template>
</template>
+
+ <template v-if="shouldShowCanaryCallout(model)">
+ <canary-deployment-callout
+ :key="`canary-promo-${i}`"
+ :canary-deployment-feature-id="canaryDeploymentFeatureId"
+ :user-callouts-path="userCalloutsPath"
+ :lock-promotion-svg-path="lockPromotionSvgPath"
+ :help-canary-deployments-path="helpCanaryDeploymentsPath"
+ :data-js-canary-promo-key="i"
+ />
+ </template>
</template>
</div>
</template>
diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
index 56e7f69cad6..c1bfe8d05fe 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js
+++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import canaryCalloutMixin from 'ee_else_ce/environments/mixins/canary_callout_mixin';
import environmentsFolderApp from './environments_folder_view.vue';
import { parseBoolean } from '../../lib/utils/common_utils';
import Translate from '../../vue_shared/translate';
@@ -11,6 +12,7 @@ export default () =>
components: {
environmentsFolderApp,
},
+ mixins: [canaryCalloutMixin],
data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
@@ -28,6 +30,7 @@ export default () =>
folderName: this.folderName,
cssContainerClass: this.cssContainerClass,
canReadEnvironment: this.canReadEnvironment,
+ ...this.canaryCalloutProps,
},
});
},
diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue
index 80f0e00400b..6fd0561f682 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_view.vue
+++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue
@@ -1,4 +1,5 @@
<script>
+import folderMixin from 'ee_else_ce/environments/mixins/environments_folder_view_mixin';
import environmentsMixin from '../mixins/environments_mixin';
import CIPaginationMixin from '../../vue_shared/mixins/ci_pagination_api_mixin';
import StopEnvironmentModal from '../components/stop_environment_modal.vue';
@@ -8,7 +9,7 @@ export default {
StopEnvironmentModal,
},
- mixins: [environmentsMixin, CIPaginationMixin],
+ mixins: [environmentsMixin, CIPaginationMixin, folderMixin],
props: {
endpoint: {
@@ -41,7 +42,8 @@ export default {
<div v-if="!isLoading" class="top-area">
<h4 class="js-folder-name environments-folder-name">
- {{ s__('Environments|Environments') }} / <b>{{ folderName }}</b>
+ {{ s__('Environments|Environments') }} /
+ <b>{{ folderName }}</b>
</h4>
<tabs :tabs="tabs" scope="environments" @onChangeTab="onChangeTab" />
@@ -52,6 +54,11 @@ export default {
:environments="state.environments"
:pagination="state.paginationInformation"
:can-read-environment="canReadEnvironment"
+ :canary-deployment-feature-id="canaryDeploymentFeatureId"
+ :show-canary-deployment-callout="showCanaryDeploymentCallout"
+ :user-callouts-path="userCalloutsPath"
+ :lock-promotion-svg-path="lockPromotionSvgPath"
+ :help-canary-deployments-path="helpCanaryDeploymentsPath"
@onChangePage="onChangePage"
/>
</div>
diff --git a/app/assets/javascripts/environments/index.js b/app/assets/javascripts/environments/index.js
index 6af66d0f86e..b53d42f202b 100644
--- a/app/assets/javascripts/environments/index.js
+++ b/app/assets/javascripts/environments/index.js
@@ -1,4 +1,5 @@
import Vue from 'vue';
+import canaryCalloutMixin from 'ee_else_ce/environments/mixins/canary_callout_mixin';
import environmentsComponent from './components/environments_app.vue';
import { parseBoolean } from '../lib/utils/common_utils';
import Translate from '../vue_shared/translate';
@@ -11,6 +12,7 @@ export default () =>
components: {
environmentsComponent,
},
+ mixins: [canaryCalloutMixin],
data() {
const environmentsData = document.querySelector(this.$options.el).dataset;
@@ -32,6 +34,7 @@ export default () =>
cssContainerClass: this.cssContainerClass,
canCreateEnvironment: this.canCreateEnvironment,
canReadEnvironment: this.canReadEnvironment,
+ ...this.canaryCalloutProps,
},
});
},
diff --git a/app/assets/javascripts/environments/mixins/canary_callout_mixin.js b/app/assets/javascripts/environments/mixins/canary_callout_mixin.js
new file mode 100644
index 00000000000..f6d3d67b777
--- /dev/null
+++ b/app/assets/javascripts/environments/mixins/canary_callout_mixin.js
@@ -0,0 +1,5 @@
+export default {
+ computed: {
+ canaryCalloutProps() {},
+ },
+};
diff --git a/app/assets/javascripts/environments/mixins/container_mixin.js b/app/assets/javascripts/environments/mixins/container_mixin.js
new file mode 100644
index 00000000000..f2907c120f8
--- /dev/null
+++ b/app/assets/javascripts/environments/mixins/container_mixin.js
@@ -0,0 +1,29 @@
+export default {
+ props: {
+ canaryDeploymentFeatureId: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ showCanaryDeploymentCallout: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ userCalloutsPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ lockPromotionSvgPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ helpCanaryDeploymentsPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
+ },
+};
diff --git a/app/assets/javascripts/environments/mixins/environment_item_mixin.js b/app/assets/javascripts/environments/mixins/environment_item_mixin.js
new file mode 100644
index 00000000000..2dfed36ec99
--- /dev/null
+++ b/app/assets/javascripts/environments/mixins/environment_item_mixin.js
@@ -0,0 +1,13 @@
+export default {
+ computed: {
+ deployIconName() {
+ return '';
+ },
+ shouldRenderDeployBoard() {
+ return false;
+ },
+ },
+ methods: {
+ toggleDeployBoard() {},
+ },
+};
diff --git a/app/assets/javascripts/environments/mixins/environments_app_mixin.js b/app/assets/javascripts/environments/mixins/environments_app_mixin.js
new file mode 100644
index 00000000000..fc805b9235a
--- /dev/null
+++ b/app/assets/javascripts/environments/mixins/environments_app_mixin.js
@@ -0,0 +1,32 @@
+export default {
+ props: {
+ canaryDeploymentFeatureId: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ showCanaryDeploymentCallout: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ userCalloutsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ lockPromotionSvgPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ helpCanaryDeploymentsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+ metods: {
+ toggleDeployBoard() {},
+ },
+};
diff --git a/app/assets/javascripts/environments/mixins/environments_folder_view_mixin.js b/app/assets/javascripts/environments/mixins/environments_folder_view_mixin.js
new file mode 100644
index 00000000000..e793a7cadf2
--- /dev/null
+++ b/app/assets/javascripts/environments/mixins/environments_folder_view_mixin.js
@@ -0,0 +1,29 @@
+export default {
+ props: {
+ canaryDeploymentFeatureId: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ showCanaryDeploymentCallout: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ userCalloutsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ lockPromotionSvgPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ helpCanaryDeploymentsPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ },
+};
diff --git a/app/assets/javascripts/environments/mixins/environments_mixin.js b/app/assets/javascripts/environments/mixins/environments_mixin.js
index 71b6b578196..a5812b173dc 100644
--- a/app/assets/javascripts/environments/mixins/environments_mixin.js
+++ b/app/assets/javascripts/environments/mixins/environments_mixin.js
@@ -3,13 +3,13 @@
*/
import _ from 'underscore';
import Visibility from 'visibilityjs';
+import EnvironmentsStore from 'ee_else_ce/environments/stores/environments_store';
import Poll from '../../lib/utils/poll';
import { getParameterByName } from '../../lib/utils/common_utils';
import { s__ } from '../../locale';
import Flash from '../../flash';
import eventHub from '../event_hub';
-import EnvironmentsStore from '../stores/environments_store';
import EnvironmentsService from '../services/environments_service';
import tablePagination from '../../vue_shared/components/table_pagination.vue';
import environmentTable from '../components/environments_table.vue';
diff --git a/app/assets/javascripts/environments/mixins/environments_table_mixin.js b/app/assets/javascripts/environments/mixins/environments_table_mixin.js
new file mode 100644
index 00000000000..208f1a7373d
--- /dev/null
+++ b/app/assets/javascripts/environments/mixins/environments_table_mixin.js
@@ -0,0 +1,10 @@
+export default {
+ methods: {
+ shouldShowCanaryCallout() {
+ return false;
+ },
+ shouldRenderDeployBoard() {
+ return false;
+ },
+ },
+};
diff --git a/app/assets/javascripts/environments/stores/environments_store.js b/app/assets/javascripts/environments/stores/environments_store.js
index ac9a31c202c..5fb420e9da5 100644
--- a/app/assets/javascripts/environments/stores/environments_store.js
+++ b/app/assets/javascripts/environments/stores/environments_store.js
@@ -1,4 +1,6 @@
import { parseIntPagination, normalizeHeaders } from '~/lib/utils/common_utils';
+import { setDeployBoard } from 'ee_else_ce/environments/stores/helpers';
+
/**
* Environments Store.
*
@@ -31,6 +33,14 @@ export default class EnvironmentsStore {
* If the `size` is bigger than 1, it means it should be rendered as a folder.
* In those cases we add `isFolder` key in order to render it properly.
*
+ * Top level environments - when the size is 1 - with `rollout_status`
+ * can render a deploy board. We add `isDeployBoardVisible` and `deployBoardData`
+ * keys to those environments.
+ * The first key will let's us know if we should or not render the deploy board.
+ * It will be toggled when the user clicks to seee the deploy board.
+ *
+ * The second key will allow us to update the environment with the received deploy board data.
+ *
* @param {Array} environments
* @returns {Array}
*/
@@ -63,6 +73,7 @@ export default class EnvironmentsStore {
filtered = Object.assign(filtered, env);
}
+ filtered = setDeployBoard(oldEnvironmentState, filtered);
return filtered;
});
@@ -71,6 +82,20 @@ export default class EnvironmentsStore {
return filteredEnvironments;
}
+ /**
+ * Stores the pagination information needed to render the pagination for the
+ * table.
+ *
+ * Normalizes the headers to uppercase since they can be provided either
+ * in uppercase or lowercase.
+ *
+ * Parses to an integer the normalized ones needed for the pagination component.
+ *
+ * Stores the normalized and parsed information.
+ *
+ * @param {Object} pagination = {}
+ * @return {Object}
+ */
setPagination(pagination = {}) {
const normalizedHeaders = normalizeHeaders(pagination);
const paginationInformation = parseIntPagination(normalizedHeaders);
diff --git a/app/assets/javascripts/environments/stores/helpers.js b/app/assets/javascripts/environments/stores/helpers.js
new file mode 100644
index 00000000000..8eba6c00601
--- /dev/null
+++ b/app/assets/javascripts/environments/stores/helpers.js
@@ -0,0 +1,8 @@
+/**
+ * Deploy boards are EE only.
+ *
+ * @param {Object} environment
+ * @returns {Object}
+ */
+// eslint-disable-next-line import/prefer-default-export
+export const setDeployBoard = (oldEnvironmentState, environment) => environment;
diff --git a/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js b/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js
index 54ea936252e..0b24d9fc920 100644
--- a/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js
+++ b/app/assets/javascripts/filtered_search/add_extra_tokens_for_merge_requests.js
@@ -13,4 +13,16 @@ export default IssuableTokenKeys => {
IssuableTokenKeys.tokenKeys.push(wipToken);
IssuableTokenKeys.tokenKeysWithAlternative.push(wipToken);
+
+ const targetBranchToken = {
+ key: 'target-branch',
+ type: 'string',
+ param: '',
+ symbol: '',
+ icon: 'arrow-right',
+ tag: 'branch',
+ };
+
+ IssuableTokenKeys.tokenKeys.push(targetBranchToken);
+ IssuableTokenKeys.tokenKeysWithAlternative.push(targetBranchToken);
};
diff --git a/app/assets/javascripts/filtered_search/available_dropdown_mappings.js b/app/assets/javascripts/filtered_search/available_dropdown_mappings.js
index e2f9c03ee65..be867a3838d 100644
--- a/app/assets/javascripts/filtered_search/available_dropdown_mappings.js
+++ b/app/assets/javascripts/filtered_search/available_dropdown_mappings.js
@@ -5,6 +5,7 @@ import DropdownEmoji from './dropdown_emoji';
import NullDropdown from './null_dropdown';
import DropdownAjaxFilter from './dropdown_ajax_filter';
import DropdownUtils from './dropdown_utils';
+import { mergeUrlParams } from '../lib/utils/url_utility';
export default class AvailableDropdownMappings {
constructor(container, baseEndpoint, groupsOnly, includeAncestorGroups, includeDescendantGroups) {
@@ -13,6 +14,7 @@ export default class AvailableDropdownMappings {
this.groupsOnly = groupsOnly;
this.includeAncestorGroups = includeAncestorGroups;
this.includeDescendantGroups = includeDescendantGroups;
+ this.filteredSearchInput = this.container.querySelector('.filtered-search');
}
getAllowedMappings(supportedTokens) {
@@ -102,6 +104,15 @@ export default class AvailableDropdownMappings {
},
element: this.container.querySelector('#js-dropdown-runner-tag'),
},
+ 'target-branch': {
+ reference: null,
+ gl: DropdownNonUser,
+ extraArguments: {
+ endpoint: this.getMergeRequestTargetBranchesEndpoint(),
+ symbol: '',
+ },
+ element: this.container.querySelector('#js-dropdown-target-branch'),
+ },
};
}
@@ -130,4 +141,24 @@ export default class AvailableDropdownMappings {
getRunnerTagsEndpoint() {
return `${this.baseEndpoint}/admin/runners/tag_list.json`;
}
+
+ getMergeRequestTargetBranchesEndpoint() {
+ const endpoint = `${gon.relative_url_root ||
+ ''}/autocomplete/merge_request_target_branches.json`;
+
+ const params = {
+ group_id: this.getGroupId(),
+ project_id: this.getProjectId(),
+ };
+
+ return mergeUrlParams(params, endpoint);
+ }
+
+ getGroupId() {
+ return this.filteredSearchInput.getAttribute('data-group-id') || '';
+ }
+
+ getProjectId() {
+ return this.filteredSearchInput.getAttribute('data-project-id') || '';
+ }
}
diff --git a/app/assets/javascripts/filtered_search/filtered_search_manager.js b/app/assets/javascripts/filtered_search/filtered_search_manager.js
index 33c82778c79..0c2e87521d9 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_manager.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_manager.js
@@ -504,14 +504,7 @@ export default class FilteredSearchManager {
const match = this.filteredSearchTokenKeys.searchByKeyParam(keyParam);
if (match) {
- // Use lastIndexOf because the token key is allowed to contain underscore
- // e.g. 'my_reaction' is the token key of 'my_reaction_emoji'
- const lastIndexOf = keyParam.lastIndexOf('_');
- let sanitizedKey = lastIndexOf !== -1 ? keyParam.slice(0, lastIndexOf) : keyParam;
- // Replace underscore with hyphen in the sanitizedkey.
- // e.g. 'my_reaction' => 'my-reaction'
- sanitizedKey = sanitizedKey.replace('_', '-');
- const { symbol } = match;
+ const { key, symbol } = match;
let quotationsToUse = '';
if (sanitizedValue.indexOf(' ') !== -1) {
@@ -520,10 +513,10 @@ export default class FilteredSearchManager {
}
hasFilteredSearch = true;
- const canEdit = this.canEdit && this.canEdit(sanitizedKey, sanitizedValue);
+ const canEdit = this.canEdit && this.canEdit(key, sanitizedValue);
const { uppercaseTokenName, capitalizeTokenValue } = match;
FilteredSearchVisualTokens.addFilterVisualToken(
- sanitizedKey,
+ key,
`${symbol}${quotationsToUse}${sanitizedValue}${quotationsToUse}`,
{
canEdit,
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 7746908714e..315cd6f64da 100644
--- a/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
+++ b/app/assets/javascripts/filtered_search/filtered_search_visual_tokens.js
@@ -69,11 +69,21 @@ export default class FilteredSearchVisualTokens {
}
static addVisualTokenElement(name, value, options = {}) {
- const { isSearchTerm = false, canEdit, uppercaseTokenName, capitalizeTokenValue } = options;
+ const {
+ isSearchTerm = false,
+ canEdit,
+ uppercaseTokenName,
+ capitalizeTokenValue,
+ tokenClass = `search-token-${name.toLowerCase()}`,
+ } = options;
const li = document.createElement('li');
li.classList.add('js-visual-token');
li.classList.add(isSearchTerm ? 'filtered-search-term' : 'filtered-search-token');
+ if (!isSearchTerm) {
+ li.classList.add(tokenClass);
+ }
+
if (value) {
li.innerHTML = FilteredSearchVisualTokens.createVisualTokenElementHTML({
canEdit,
diff --git a/app/assets/javascripts/filtered_search/visual_token_value.js b/app/assets/javascripts/filtered_search/visual_token_value.js
index 7f6f41c18f7..24532d88cf3 100644
--- a/app/assets/javascripts/filtered_search/visual_token_value.js
+++ b/app/assets/javascripts/filtered_search/visual_token_value.js
@@ -13,9 +13,9 @@ export default class VisualTokenValue {
}
render(tokenValueContainer, tokenValueElement) {
- const { tokenType } = this;
+ const { tokenType, tokenValue } = this;
- if (['none', 'any'].includes(tokenType)) {
+ if (['none', 'any'].includes(tokenValue.toLowerCase())) {
return;
}
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
index 2b44438f849..9161eb3d9b1 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
@@ -38,8 +38,8 @@ export default {
},
},
computed: {
- ...mapState('commit', ['commitAction']),
- ...mapGetters('commit', ['newBranchName']),
+ ...mapState('commit', ['commitAction', 'newBranchName']),
+ ...mapGetters('commit', ['placeholderBranchName']),
tooltipTitle() {
return this.disabled ? this.title : '';
},
@@ -73,7 +73,8 @@ export default {
</label>
<div v-if="commitAction === value && showInput" class="ide-commit-new-branch">
<input
- :placeholder="newBranchName"
+ :placeholder="placeholderBranchName"
+ :value="newBranchName"
type="text"
class="form-control monospace"
@input="updateBranchName($event.target.value)"
diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
index ba6bbdfef4b..412b07553dc 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
@@ -29,7 +29,7 @@ export default {
return this.name || (entryPath ? `${entryPath}/` : '');
},
set(val) {
- this.name = val;
+ this.name = val.trim();
},
},
modalTitle() {
diff --git a/app/assets/javascripts/ide/stores/modules/commit/getters.js b/app/assets/javascripts/ide/stores/modules/commit/getters.js
index 03777e6c10b..bbe40b2ec2f 100644
--- a/app/assets/javascripts/ide/stores/modules/commit/getters.js
+++ b/app/assets/javascripts/ide/stores/modules/commit/getters.js
@@ -14,7 +14,7 @@ const createTranslatedTextForFiles = (files, text) => {
export const discardDraftButtonDisabled = state =>
state.commitMessage === '' || state.submitCommitLoading;
-export const newBranchName = (state, _, rootState) =>
+export const placeholderBranchName = (state, _, rootState) =>
`${gon.current_username}-${rootState.currentBranchId}-patch-${`${new Date().getTime()}`.substr(
-BRANCH_SUFFIX_COUNT,
)}`;
@@ -25,7 +25,7 @@ export const branchName = (state, getters, rootState) => {
state.commitAction === consts.COMMIT_TO_NEW_BRANCH_MR
) {
if (state.newBranchName === '') {
- return getters.newBranchName;
+ return getters.placeholderBranchName;
}
return state.newBranchName;
diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue
index c5076d65ff9..6e92b599b0a 100644
--- a/app/assets/javascripts/jobs/components/stages_dropdown.vue
+++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue
@@ -1,5 +1,6 @@
<script>
import _ from 'underscore';
+import { GlLink } from '@gitlab/ui';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
import Icon from '~/vue_shared/components/icon.vue';
@@ -7,6 +8,7 @@ export default {
components: {
CiIcon,
Icon,
+ GlLink,
},
props: {
pipeline: {
@@ -26,6 +28,12 @@ export default {
hasRef() {
return !_.isEmpty(this.pipeline.ref);
},
+ isTriggeredByMergeRequest() {
+ return Boolean(this.pipeline.merge_request);
+ },
+ isMergeRequestPipeline() {
+ return Boolean(this.pipeline.flags && this.pipeline.flags.merge_request_pipeline);
+ },
},
methods: {
onStageClick(stage) {
@@ -36,16 +44,41 @@ export default {
</script>
<template>
<div class="block-last dropdown">
- <ci-icon :status="pipeline.details.status" class="vertical-align-middle" />
+ <div class="js-pipeline-info">
+ <ci-icon :status="pipeline.details.status" class="vertical-align-middle" />
- <span class="font-weight-bold">{{ __('Pipeline') }}</span>
- <a :href="pipeline.path" class="js-pipeline-path link-commit qa-pipeline-path"
- >#{{ pipeline.id }}</a
- >
- <template v-if="hasRef">
- {{ __('from') }}
- <a :href="pipeline.ref.path" class="link-commit ref-name">{{ pipeline.ref.name }}</a>
- </template>
+ <span class="font-weight-bold">{{ s__('Job|Pipeline') }}</span>
+ <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') }}
+
+ <template v-if="isTriggeredByMergeRequest">
+ <gl-link :href="pipeline.merge_request.path" class="link-commit ref-name js-mr-link"
+ >!{{ pipeline.merge_request.iid }}</gl-link
+ >
+ {{ s__('Job|with') }}
+ <gl-link
+ :href="pipeline.merge_request.source_branch_path"
+ class="link-commit ref-name js-source-branch-link"
+ >{{ pipeline.merge_request.source_branch }}</gl-link
+ >
+
+ <template v-if="isMergeRequestPipeline">
+ {{ s__('Job|into') }}
+ <gl-link
+ :href="pipeline.merge_request.target_branch_path"
+ class="link-commit ref-name js-target-branch-link"
+ >{{ pipeline.merge_request.target_branch }}</gl-link
+ >
+ </template>
+ </template>
+ <gl-link v-else :href="pipeline.ref.path" class="link-commit ref-name">{{
+ pipeline.ref.name
+ }}</gl-link>
+ </template>
+ </div>
<button
type="button"
diff --git a/app/assets/javascripts/lib/utils/autosave.js b/app/assets/javascripts/lib/utils/autosave.js
new file mode 100644
index 00000000000..023c336db02
--- /dev/null
+++ b/app/assets/javascripts/lib/utils/autosave.js
@@ -0,0 +1,32 @@
+import { capitalizeFirstCharacter } from '~/lib/utils/text_utility';
+
+export const clearDraft = autosaveKey => {
+ try {
+ window.localStorage.removeItem(`autosave/${autosaveKey}`);
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error(e);
+ }
+};
+
+export const getDraft = autosaveKey => {
+ try {
+ return window.localStorage.getItem(`autosave/${autosaveKey}`);
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error(e);
+ return null;
+ }
+};
+
+export const updateDraft = (autosaveKey, text) => {
+ try {
+ window.localStorage.setItem(`autosave/${autosaveKey}`, text);
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error(e);
+ }
+};
+
+export const getDiscussionReplyKey = (noteableType, discussionId) =>
+ ['Note', capitalizeFirstCharacter(noteableType), discussionId, 'Reply'].join('/');
diff --git a/app/assets/javascripts/lib/utils/simple_poll.js b/app/assets/javascripts/lib/utils/simple_poll.js
index 473f179ad86..576a9ec880c 100644
--- a/app/assets/javascripts/lib/utils/simple_poll.js
+++ b/app/assets/javascripts/lib/utils/simple_poll.js
@@ -1,10 +1,10 @@
-export default (fn, interval = 2000, timeout = 60000) => {
+export default (fn, { interval = 2000, timeout = 60000 } = {}) => {
const startTime = Date.now();
return new Promise((resolve, reject) => {
const stop = arg => (arg instanceof Error ? reject(arg) : resolve(arg));
const next = () => {
- if (Date.now() - startTime < timeout) {
+ if (timeout === 0 || Date.now() - startTime < timeout) {
setTimeout(fn.bind(null, next, stop), interval);
} else {
reject(new Error('SIMPLE_POLL_TIMEOUT'));
diff --git a/app/assets/javascripts/lib/utils/webpack.js b/app/assets/javascripts/lib/utils/webpack.js
index 308ad9784e4..a4dad6f1615 100644
--- a/app/assets/javascripts/lib/utils/webpack.js
+++ b/app/assets/javascripts/lib/utils/webpack.js
@@ -6,5 +6,5 @@ export function resetServiceWorkersPublicPath() {
// see: https://webpack.js.org/guides/public-path/
const relativeRootPath = (gon && gon.relative_url_root) || '';
const webpackAssetPath = `${relativeRootPath}/assets/webpack/`;
- __webpack_public_path__ = webpackAssetPath; // eslint-disable-line camelcase
+ window.__webpack_public_path__ = webpackAssetPath; // eslint-disable-line
}
diff --git a/app/assets/javascripts/mirrors/ssh_mirror.js b/app/assets/javascripts/mirrors/ssh_mirror.js
index 5bdf5d6277a..547c078ec55 100644
--- a/app/assets/javascripts/mirrors/ssh_mirror.js
+++ b/app/assets/javascripts/mirrors/ssh_mirror.js
@@ -20,6 +20,7 @@ export default class SSHMirror {
this.$btnDetectHostKeys = this.$form.find('.js-detect-host-keys');
this.$btnSSHHostsShowAdvanced = this.$form.find('.btn-show-advanced');
this.$dropdownAuthType = this.$form.find('.js-mirror-auth-type');
+ this.$hiddenAuthType = this.$form.find('.js-hidden-mirror-auth-type');
this.$wellAuthTypeChanging = this.$form.find('.js-well-changing-auth');
this.$wellPasswordAuth = this.$form.find('.js-well-password-auth');
@@ -167,6 +168,7 @@ export default class SSHMirror {
this.$wellPasswordAuth.collapse('hide');
this.$wellSSHAuth.collapse('hide');
+ this.updateHiddenAuthType(selectedAuthType);
// This request should happen only if selected Auth type was SSH
// and SSH Public key was not present on page load
@@ -234,6 +236,12 @@ export default class SSHMirror {
toggleAuthWell(authType) {
this.$wellPasswordAuth.collapse(authType === AUTH_METHOD.PASSWORD ? 'show' : 'hide');
this.$wellSSHAuth.collapse(authType === AUTH_METHOD.SSH ? 'show' : 'hide');
+ this.updateHiddenAuthType(authType);
+ }
+
+ updateHiddenAuthType(authType) {
+ this.$hiddenAuthType.val(authType);
+ this.$hiddenAuthType.prop('disabled', authType === AUTH_METHOD.SSH);
}
/**
diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js
index c9c01354333..94d2e2b53e9 100644
--- a/app/assets/javascripts/notes.js
+++ b/app/assets/javascripts/notes.js
@@ -11,8 +11,8 @@ import $ from 'jquery';
import _ from 'underscore';
import Cookies from 'js-cookie';
import Autosize from 'autosize';
-import 'vendor/jquery.caret'; // required by jquery.atwho
-import 'vendor/jquery.atwho';
+import 'jquery.caret'; // required by at.js
+import 'at.js';
import AjaxCache from '~/lib/utils/ajax_cache';
import Vue from 'vue';
import syntaxHighlight from '~/syntax_highlight';
diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue
index 31164f74201..47951591e82 100644
--- a/app/assets/javascripts/notes/components/discussion_filter.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter.vue
@@ -22,7 +22,7 @@ export default {
},
selectedValue: {
type: Number,
- default: null,
+ default: DISCUSSION_FILTERS_DEFAULT_VALUE,
required: false,
},
},
diff --git a/app/assets/javascripts/notes/components/discussion_filter_note.vue b/app/assets/javascripts/notes/components/discussion_filter_note.vue
index 46661e06f6d..889731df180 100644
--- a/app/assets/javascripts/notes/components/discussion_filter_note.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter_note.vue
@@ -34,7 +34,7 @@ export default {
<template>
<li class="timeline-entry note note-wrapper discussion-filter-note js-discussion-filter-note">
- <div class="timeline-icon">
+ <div class="timeline-icon d-none d-lg-flex">
<icon name="comment" />
</div>
<div class="timeline-content">
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index de1ea0f58d6..fc73726857d 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -158,7 +158,6 @@ export default {
href="#"
title="Add reaction"
>
- <gl-loading-icon inline />
<icon
css-classes="link-highlight award-control-icon-neutral"
name="emoji_slightly_smiling_face"
diff --git a/app/assets/javascripts/notes/components/note_actions/reply_button.vue b/app/assets/javascripts/notes/components/note_actions/reply_button.vue
index f50cab81efe..be8e42af9ea 100644
--- a/app/assets/javascripts/notes/components/note_actions/reply_button.vue
+++ b/app/assets/javascripts/notes/components/note_actions/reply_button.vue
@@ -18,7 +18,7 @@ export default {
<div class="note-actions-item">
<gl-button
ref="button"
- v-gl-tooltip.bottom
+ v-gl-tooltip
class="note-action-button"
variant="transparent"
:title="__('Reply to comment')"
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index 92258a25438..57d6b181bd7 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -7,6 +7,7 @@ import markdownField from '../../vue_shared/components/markdown/field.vue';
import issuableStateMixin from '../mixins/issuable_state';
import resolvable from '../mixins/resolvable';
import { __ } from '~/locale';
+import { getDraft, updateDraft } from '~/lib/utils/autosave';
export default {
name: 'NoteForm',
@@ -65,10 +66,21 @@ export default {
required: false,
default: '',
},
+ autosaveKey: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
data() {
+ let updatedNoteBody = this.noteBody;
+
+ if (!updatedNoteBody && this.autosaveKey) {
+ updatedNoteBody = getDraft(this.autosaveKey) || '';
+ }
+
return {
- updatedNoteBody: this.noteBody,
+ updatedNoteBody,
conflictWhileEditing: false,
isSubmitting: false,
isResolving: this.resolveDiscussion,
@@ -175,6 +187,12 @@ export default {
// Sends information about confirm message and if the textarea has changed
this.$emit('cancelForm', shouldConfirm, this.noteBody !== this.updatedNoteBody);
},
+ onInput() {
+ if (this.autosaveKey) {
+ const { autosaveKey, updatedNoteBody: text } = this;
+ updateDraft(autosaveKey, text);
+ }
+ },
},
};
</script>
@@ -218,6 +236,7 @@ export default {
@keydown.ctrl.enter="handleKeySubmit()"
@keydown.up="editMyLastNote()"
@keydown.esc="cancelHandler(true)"
+ @input="onInput"
></textarea>
</markdown-field>
<div class="note-form-actions clearfix">
diff --git a/app/assets/javascripts/notes/components/note_header.vue b/app/assets/javascripts/notes/components/note_header.vue
index 68b753a4abf..5c59c0c32dd 100644
--- a/app/assets/javascripts/notes/components/note_header.vue
+++ b/app/assets/javascripts/notes/components/note_header.vue
@@ -87,27 +87,25 @@ export default {
<span class="note-headline-light">@{{ author.username }}</span>
</a>
<span v-else>{{ __('A deleted user') }}</span>
- <span class="note-headline-light">
- <span class="note-headline-meta">
- <span class="system-note-message"> <slot></slot> </span>
- <template v-if="createdAt">
- <span class="system-note-separator">
- <template v-if="actionText">{{ actionText }}</template>
- </span>
- <a
- :href="noteTimestampLink"
- class="note-timestamp system-note-separator"
- @click="updateTargetNoteHash"
- >
- <time-ago-tooltip :time="createdAt" tooltip-placement="bottom" />
- </a>
- </template>
- <i
- class="fa fa-spinner fa-spin editing-spinner"
- aria-label="Comment is being updated"
- aria-hidden="true"
- ></i>
- </span>
+ <span class="note-headline-light note-headline-meta">
+ <span class="system-note-message"> <slot></slot> </span>
+ <template v-if="createdAt">
+ <span class="system-note-separator">
+ <template v-if="actionText">{{ actionText }}</template>
+ </span>
+ <a
+ :href="noteTimestampLink"
+ class="note-timestamp system-note-separator"
+ @click="updateTargetNoteHash"
+ >
+ <time-ago-tooltip :time="createdAt" tooltip-placement="bottom" />
+ </a>
+ </template>
+ <i
+ class="fa fa-spinner fa-spin editing-spinner"
+ aria-label="Comment is being updated"
+ aria-hidden="true"
+ ></i>
</span>
</div>
</template>
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index fc51998935d..0fabbfb06b5 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -4,6 +4,7 @@ import { mapActions, mapGetters } from 'vuex';
import { GlTooltipDirective } from '@gitlab/ui';
import { truncateSha } from '~/lib/utils/text_utility';
import { s__, __, sprintf } from '~/locale';
+import { clearDraft, getDiscussionReplyKey } from '~/lib/utils/autosave';
import systemNote from '~/vue_shared/components/notes/system_note.vue';
import icon from '~/vue_shared/components/icon.vue';
import diffLineNoteFormMixin from 'ee_else_ce/notes/mixins/diff_line_note_form';
@@ -21,7 +22,6 @@ import noteForm from './note_form.vue';
import diffWithNote from './diff_with_note.vue';
import placeholderNote from '../../vue_shared/components/notes/placeholder_note.vue';
import placeholderSystemNote from '../../vue_shared/components/notes/placeholder_system_note.vue';
-import autosave from '../mixins/autosave';
import noteable from '../mixins/noteable';
import resolvable from '../mixins/resolvable';
import discussionNavigation from '../mixins/discussion_navigation';
@@ -54,7 +54,7 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
- mixins: [autosave, noteable, resolvable, discussionNavigation, diffLineNoteFormMixin],
+ mixins: [noteable, resolvable, discussionNavigation, diffLineNoteFormMixin],
props: {
discussion: {
type: Object,
@@ -106,7 +106,10 @@ export default {
'showJumpToNextDiscussion',
]),
author() {
- return this.initialDiscussion.author;
+ return this.firstNote.author;
+ },
+ autosaveKey() {
+ return getDiscussionReplyKey(this.firstNote.noteable_type, this.discussion.id);
},
canReply() {
return this.getNoteableData.current_user.can_create_note;
@@ -117,7 +120,7 @@ export default {
hasReplies() {
return this.discussion.notes.length > 1;
},
- initialDiscussion() {
+ firstNote() {
return this.discussion.notes.slice(0, 1)[0];
},
replies() {
@@ -242,18 +245,6 @@ export default {
return !this.discussionResolved && this.discussion.resolve_with_issue_path;
},
},
- watch: {
- isReplying() {
- if (this.isReplying) {
- this.$nextTick(() => {
- // Pass an extra key to separate reply and note edit forms
- this.initAutoSave({ ...this.initialDiscussion, ...this.discussion }, ['Reply']);
- });
- } else {
- this.disposeAutoSave();
- }
- },
- },
created() {
eventHub.$on('startReplying', this.onStartReplying);
},
@@ -312,7 +303,7 @@ export default {
}
this.isReplying = false;
- this.resetAutoSave();
+ clearDraft(this.autosaveKey);
},
saveReply(noteText, form, callback) {
const postData = {
@@ -338,7 +329,7 @@ export default {
this.isReplying = false;
this.saveNote(replyData)
.then(() => {
- this.resetAutoSave();
+ clearDraft(this.autosaveKey);
callback();
})
.catch(err => {
@@ -390,8 +381,8 @@ Please check your network connection and try again.`;
<div class="timeline-content">
<note-header
:author="author"
- :created-at="initialDiscussion.created_at"
- :note-id="initialDiscussion.id"
+ :created-at="firstNote.created_at"
+ :note-id="firstNote.id"
:include-toggle="true"
:expanded="discussion.expanded"
@toggleHandler="toggleDiscussionHandler"
@@ -424,8 +415,8 @@ Please check your network connection and try again.`;
<ul class="notes">
<template v-if="shouldGroupReplies">
<component
- :is="componentName(initialDiscussion)"
- :note="componentData(initialDiscussion)"
+ :is="componentName(firstNote)"
+ :note="componentData(firstNote)"
:line="line"
:commit="commit"
:help-page-path="helpPagePath"
@@ -512,6 +503,7 @@ Please check your network connection and try again.`;
:is-editing="false"
:line="diffLine"
save-button-title="Comment"
+ :autosave-key="autosaveKey"
@handleFormUpdateAddToReview="addReplyToReview"
@handleFormUpdate="saveReply"
@cancelForm="cancelReplyForm"
diff --git a/app/assets/javascripts/notes/discussion_filters.js b/app/assets/javascripts/notes/discussion_filters.js
index 5c5f38a3fb0..cdf9a46c5aa 100644
--- a/app/assets/javascripts/notes/discussion_filters.js
+++ b/app/assets/javascripts/notes/discussion_filters.js
@@ -6,12 +6,16 @@ export default store => {
if (discussionFilterEl) {
const { defaultFilter, notesFilters } = discussionFilterEl.dataset;
- const selectedValue = defaultFilter ? parseInt(defaultFilter, 10) : null;
const filterValues = notesFilters ? JSON.parse(notesFilters) : {};
const filters = Object.keys(filterValues).map(entry => ({
title: entry,
value: filterValues[entry],
}));
+ const props = { filters };
+
+ if (defaultFilter) {
+ props.selectedValue = parseInt(defaultFilter, 10);
+ }
return new Vue({
el: discussionFilterEl,
@@ -21,12 +25,7 @@ export default store => {
},
store,
render(createElement) {
- return createElement('discussion-filter', {
- props: {
- filters,
- selectedValue,
- },
- });
+ return createElement('discussion-filter', { props });
},
});
}
diff --git a/app/assets/javascripts/notes/index.js b/app/assets/javascripts/notes/index.js
index 4883266dae5..30372103590 100644
--- a/app/assets/javascripts/notes/index.js
+++ b/app/assets/javascripts/notes/index.js
@@ -6,9 +6,8 @@ import createStore from './stores';
document.addEventListener('DOMContentLoaded', () => {
const store = createStore();
- initDiscussionFilters(store);
-
- return new Vue({
+ // eslint-disable-next-line no-new
+ new Vue({
el: '#js-vue-notes',
components: {
notesApp,
@@ -49,4 +48,6 @@ document.addEventListener('DOMContentLoaded', () => {
});
},
});
+
+ initDiscussionFilters(store);
});
diff --git a/app/assets/javascripts/pages/groups/details/index.js b/app/assets/javascripts/pages/groups/details/index.js
new file mode 100644
index 00000000000..3bcaa0f0232
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/details/index.js
@@ -0,0 +1,5 @@
+import initGroupDetails from '../shared/group_details';
+
+document.addEventListener('DOMContentLoaded', () => {
+ initGroupDetails('details');
+});
diff --git a/app/assets/javascripts/pages/groups/shared/group_details.js b/app/assets/javascripts/pages/groups/shared/group_details.js
new file mode 100644
index 00000000000..01ef3f1db2b
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/shared/group_details.js
@@ -0,0 +1,31 @@
+/* eslint-disable no-new */
+
+import { getPagePath } from '~/lib/utils/common_utils';
+import { ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED } from '~/groups/constants';
+import NewGroupChild from '~/groups/new_group_child';
+import notificationsDropdown from '~/notifications_dropdown';
+import NotificationsForm from '~/notifications_form';
+import ProjectsList from '~/projects_list';
+import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
+import GroupTabs from './group_tabs';
+
+export default function initGroupDetails(actionName = 'show') {
+ const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
+ const loadableActions = [ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED];
+ const paths = window.location.pathname.split('/');
+ const subpath = paths[paths.length - 1];
+ let action = loadableActions.includes(subpath) ? subpath : getPagePath(1);
+ if (actionName && action === actionName) {
+ action = 'show'; // 'show' resets GroupTabs to default action through base class
+ }
+
+ new GroupTabs({ parentEl: '.groups-listing', action });
+ new ShortcutsNavigation();
+ new NotificationsForm();
+ notificationsDropdown();
+ new ProjectsList();
+
+ if (newGroupChildWrapper) {
+ new NewGroupChild(newGroupChildWrapper);
+ }
+}
diff --git a/app/assets/javascripts/pages/groups/show/group_tabs.js b/app/assets/javascripts/pages/groups/shared/group_tabs.js
index c6fe61d2bd9..c6fe61d2bd9 100644
--- a/app/assets/javascripts/pages/groups/show/group_tabs.js
+++ b/app/assets/javascripts/pages/groups/shared/group_tabs.js
diff --git a/app/assets/javascripts/pages/groups/show/index.js b/app/assets/javascripts/pages/groups/show/index.js
index 3a45fd70d02..af924e74f1f 100644
--- a/app/assets/javascripts/pages/groups/show/index.js
+++ b/app/assets/javascripts/pages/groups/show/index.js
@@ -1,28 +1,5 @@
-/* eslint-disable no-new */
-
-import { getPagePath } from '~/lib/utils/common_utils';
-import { ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED } from '~/groups/constants';
-import NewGroupChild from '~/groups/new_group_child';
-import notificationsDropdown from '~/notifications_dropdown';
-import NotificationsForm from '~/notifications_form';
-import ProjectsList from '~/projects_list';
-import ShortcutsNavigation from '~/behaviors/shortcuts/shortcuts_navigation';
-import GroupTabs from './group_tabs';
+import initGroupDetails from '../shared/group_details';
document.addEventListener('DOMContentLoaded', () => {
- const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
- const loadableActions = [ACTIVE_TAB_SHARED, ACTIVE_TAB_ARCHIVED];
- const paths = window.location.pathname.split('/');
- const subpath = paths[paths.length - 1];
- const action = loadableActions.includes(subpath) ? subpath : getPagePath(1);
-
- new GroupTabs({ parentEl: '.groups-listing', action });
- new ShortcutsNavigation();
- new NotificationsForm();
- notificationsDropdown();
- new ProjectsList();
-
- if (newGroupChildWrapper) {
- new NewGroupChild(newGroupChildWrapper);
- }
+ initGroupDetails();
});
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index 636308c5401..7f800d20835 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -91,6 +91,7 @@ export default class UserTabs {
this.actions = Object.keys(this.loaded);
this.bindEvents();
+ // TODO: refactor to make this configurable via constructor params with a default value of 'show'
if (this.action === 'show') {
this.action = this.defaultAction;
}
diff --git a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
index 09a50d25020..348c407f1b5 100644
--- a/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/stage_column_component.vue
@@ -1,5 +1,6 @@
<script>
import _ from 'underscore';
+import stageColumnMixin from 'ee_else_ce/pipelines/mixins/stage_column_mixin';
import JobItem from './job_item.vue';
import JobGroupDropdown from './job_group_dropdown.vue';
@@ -8,6 +9,7 @@ export default {
JobItem,
JobGroupDropdown,
},
+ mixins: [stageColumnMixin],
props: {
title: {
type: String,
@@ -32,9 +34,6 @@ export default {
groupId(group) {
return `ci-badge-${_.escape(group.name)}`;
},
- buildConnnectorClass(index) {
- return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
- },
pipelineActionRequestComplete() {
this.$emit('refreshPipelineGraph');
},
diff --git a/app/assets/javascripts/pipelines/components/pipeline_url.vue b/app/assets/javascripts/pipelines/components/pipeline_url.vue
index 918622ef8dc..3e7bf20470c 100644
--- a/app/assets/javascripts/pipelines/components/pipeline_url.vue
+++ b/app/assets/javascripts/pipelines/components/pipeline_url.vue
@@ -110,12 +110,12 @@ export default {
{{ __('stuck') }}
</span>
<span
- v-if="pipeline.flags.merge_request"
+ v-if="pipeline.flags.detached_merge_request_pipeline"
v-gl-tooltip
- :title="__('This pipeline is run in a merge request context')"
- class="js-pipeline-url-mergerequest badge badge-info"
+ :title="__('This pipeline is run on the source branch')"
+ class="js-pipeline-url-detached badge badge-info"
>
- {{ __('merge request') }}
+ {{ __('detached') }}
</span>
</div>
</div>
diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
index f6454a84ea5..1c44427e720 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue
@@ -272,10 +272,11 @@ export default {
:tag="commitTag"
:commit-ref="commitRef"
:commit-url="commitUrl"
+ :merge-request-ref="pipeline.merge_request"
:short-sha="commitShortSha"
:title="commitTitle"
:author="commitAuthor"
- :show-branch="!isChildView"
+ :show-ref-info="!isChildView"
/>
</div>
</div>
diff --git a/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js b/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js
new file mode 100644
index 00000000000..9177943f88a
--- /dev/null
+++ b/app/assets/javascripts/pipelines/mixins/graph_pipeline_bundle_mixin.js
@@ -0,0 +1,6 @@
+export default {
+ methods: {
+ clickTriggeredByPipeline() {},
+ clickTriggeredPipeline() {},
+ },
+};
diff --git a/app/assets/javascripts/pipelines/mixins/stage_column_mixin.js b/app/assets/javascripts/pipelines/mixins/stage_column_mixin.js
new file mode 100644
index 00000000000..64283ed0e58
--- /dev/null
+++ b/app/assets/javascripts/pipelines/mixins/stage_column_mixin.js
@@ -0,0 +1,7 @@
+export default {
+ methods: {
+ buildConnnectorClass(index) {
+ return index === 0 && !this.isFirstColumn ? 'left-connector' : '';
+ },
+ },
+};
diff --git a/app/assets/javascripts/pipelines/pipeline_details_bundle.js b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
index dc9befe6349..8adbd39edd4 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_bundle.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_bundle.js
@@ -2,8 +2,9 @@ import Vue from 'vue';
import Flash from '~/flash';
import Translate from '~/vue_shared/translate';
import { __ } from '~/locale';
+import pipelineGraph from 'ee_else_ce/pipelines/components/graph/graph_component.vue';
+import GraphEEMixin from 'ee_else_ce/pipelines/mixins/graph_pipeline_bundle_mixin';
import PipelinesMediator from './pipeline_details_mediator';
-import pipelineGraph from './components/graph/graph_component.vue';
import pipelineHeader from './components/header_component.vue';
import eventHub from './event_hub';
@@ -22,6 +23,7 @@ export default () => {
components: {
pipelineGraph,
},
+ mixins: [GraphEEMixin],
data() {
return {
mediator,
@@ -44,6 +46,10 @@ export default () => {
},
on: {
refreshPipelineGraph: this.requestRefreshPipelineGraph,
+ onClickTriggeredBy: (parentPipeline, pipeline) =>
+ this.clickTriggeredByPipeline(parentPipeline, pipeline),
+ onClickTriggered: (parentPipeline, pipeline) =>
+ this.clickTriggeredPipeline(parentPipeline, pipeline),
},
});
},
diff --git a/app/assets/javascripts/pipelines/pipeline_details_mediator.js b/app/assets/javascripts/pipelines/pipeline_details_mediator.js
index bd1e1895660..d67d88c4dba 100644
--- a/app/assets/javascripts/pipelines/pipeline_details_mediator.js
+++ b/app/assets/javascripts/pipelines/pipeline_details_mediator.js
@@ -19,6 +19,7 @@ export default class pipelinesMediator {
this.poll = new Poll({
resource: this.service,
method: 'getPipeline',
+ data: this.store.state.expandedPipelines ? this.getExpandedParameters() : undefined,
successCallback: this.successCallback.bind(this),
errorCallback: this.errorCallback.bind(this),
});
@@ -56,6 +57,19 @@ export default class pipelinesMediator {
.getPipeline()
.then(response => this.successCallback(response))
.catch(() => this.errorCallback())
- .finally(() => this.poll.restart());
+ .finally(() =>
+ this.poll.restart(
+ this.store.state.expandedPipelines ? this.getExpandedParameters() : undefined,
+ ),
+ );
+ }
+
+ /**
+ * Backend expects paramets in the following format: `expanded[]=id&expanded[]=id`
+ */
+ getExpandedParameters() {
+ return {
+ expanded: this.store.state.expandedPipelines,
+ };
}
}
diff --git a/app/assets/javascripts/pipelines/services/pipeline_service.js b/app/assets/javascripts/pipelines/services/pipeline_service.js
index a53a9cc8365..e44eb9cdfd1 100644
--- a/app/assets/javascripts/pipelines/services/pipeline_service.js
+++ b/app/assets/javascripts/pipelines/services/pipeline_service.js
@@ -5,8 +5,8 @@ export default class PipelineService {
this.pipeline = endpoint;
}
- getPipeline() {
- return axios.get(this.pipeline);
+ getPipeline(params) {
+ return axios.get(this.pipeline, { params });
}
// eslint-disable-next-line class-methods-use-this
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index 796ecbccde7..784eec1ea55 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -133,6 +133,10 @@ const bindEvents = () => {
text: '.NET Core',
icon: '.template-option .icon-dotnet',
},
+ android: {
+ text: 'Android',
+ icon: '.template-option svg.icon-android',
+ },
gomicro: {
text: 'Go Micro',
icon: '.template-option .icon-gomicro',
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 9e63aa00341..f5a1ff2f6fd 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
@@ -1,5 +1,6 @@
<script>
/* eslint-disable vue/require-default-prop */
+import { GlTooltipDirective, GlLink } from '@gitlab/ui';
import { sprintf, __ } from '~/locale';
import PipelineStage from '~/pipelines/components/stage.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
@@ -14,9 +15,13 @@ export default {
CiIcon,
Icon,
TooltipOnTruncate,
+ GlLink,
LinkedPipelinesMiniList: () =>
import('ee_component/vue_shared/components/linked_pipelines_mini_list.vue'),
},
+ directives: {
+ GlTooltip: GlTooltipDirective,
+ },
mixins: [mrWidgetPipelineMixin],
props: {
pipeline: {
@@ -78,12 +83,18 @@ export default {
false,
);
},
+ isTriggeredByMergeRequest() {
+ return Boolean(this.pipeline.merge_request);
+ },
+ isMergeRequestPipeline() {
+ return Boolean(this.pipeline.flags && this.pipeline.flags.merge_request_pipeline);
+ },
},
};
</script>
<template>
- <div v-if="hasPipeline || hasCIError" class="ci-widget media">
+ <div v-if="hasPipeline || hasCIError" class="ci-widget media js-ci-widget">
<template v-if="hasCIError">
<div
class="add-border ci-status-icon ci-status-icon-failed ci-error js-ci-error append-right-default"
@@ -99,21 +110,58 @@ export default {
<div class="ci-widget-container d-flex">
<div class="ci-widget-content">
<div class="media-body">
- <div class="font-weight-bold">
- Pipeline
- <a :href="pipeline.path" class="pipeline-id font-weight-normal pipeline-number"
- >#{{ pipeline.id }}</a
+ <div class="font-weight-bold js-pipeline-info-container">
+ {{ s__('Pipeline|Pipeline') }}
+ <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">
- for
- <a
+ {{ s__('Pipeline|for') }}
+ <gl-link
:href="pipeline.commit.commit_path"
class="commit-sha js-commit-link font-weight-normal"
- >{{ pipeline.commit.short_id }}</a
+ >{{ pipeline.commit.short_id }}</gl-link
>
- on
+ {{ s__('Pipeline|on') }}
+ <template v-if="isTriggeredByMergeRequest">
+ <gl-link
+ v-gl-tooltip
+ :href="pipeline.merge_request.path"
+ :title="pipeline.merge_request.title"
+ class="font-weight-normal"
+ >!{{ pipeline.merge_request.iid }}</gl-link
+ >
+ {{ s__('Pipeline|with') }}
+ <tooltip-on-truncate
+ :title="pipeline.merge_request.source_branch"
+ truncate-target="child"
+ class="label-branch label-truncate"
+ >
+ <gl-link
+ :href="pipeline.merge_request.source_branch_path"
+ class="font-weight-normal"
+ >{{ pipeline.merge_request.source_branch }}</gl-link
+ >
+ </tooltip-on-truncate>
+
+ <template v-if="isMergeRequestPipeline">
+ {{ s__('Pipeline|into') }}
+ <tooltip-on-truncate
+ :title="pipeline.merge_request.target_branch"
+ truncate-target="child"
+ class="label-branch label-truncate"
+ >
+ <gl-link
+ :href="pipeline.merge_request.target_branch_path"
+ class="font-weight-normal"
+ >{{ pipeline.merge_request.target_branch }}</gl-link
+ >
+ </tooltip-on-truncate>
+ </template>
+ </template>
<tooltip-on-truncate
+ v-else
:title="sourceBranch"
truncate-target="child"
class="label-branch label-truncate"
@@ -121,7 +169,9 @@ export default {
/>
</template>
</div>
- <div v-if="pipeline.coverage" class="coverage">Coverage {{ pipeline.coverage }}%</div>
+ <div v-if="pipeline.coverage" class="coverage">
+ {{ s__('Pipeline|Coverage') }} {{ pipeline.coverage }}%
+ </div>
</div>
</div>
<div>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
index 33963d5e1e6..0312b147b62 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
@@ -14,6 +14,10 @@ export default {
type: Boolean,
required: true,
},
+ isFastForwardEnabled: {
+ type: Boolean,
+ required: true,
+ },
commitsCount: {
type: Number,
required: false,
@@ -37,16 +41,22 @@ export default {
return n__(__('%d commit'), __('%d commits'), this.isSquashEnabled ? 1 : this.commitsCount);
},
modifyLinkMessage() {
- return this.isSquashEnabled ? __('Modify commit messages') : __('Modify merge commit');
+ if (this.isFastForwardEnabled) return __('Modify commit message');
+ else if (this.isSquashEnabled) return __('Modify commit messages');
+ return __('Modify merge commit');
},
ariaLabel() {
return this.expanded ? __('Collapse') : __('Expand');
},
message() {
+ const message = this.isFastForwardEnabled
+ ? s__('mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}.')
+ : s__(
+ 'mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}.',
+ );
+
return sprintf(
- s__(
- 'mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}.',
- ),
+ message,
{
commitCount: `<strong class="commits-count-message">${this.commitsCountMessage}</strong>`,
mergeCommitCount: `<strong>${s__('mrWidgetCommitsAdded|1 merge commit')}</strong>`,
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index 9b4e80ee3a3..bb76eb1030d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -113,6 +113,12 @@ export default {
shouldShowMergeControls() {
return this.mr.isMergeAllowed || this.shouldShowMergeWhenPipelineSucceedsText;
},
+ shouldShowSquashEdit() {
+ return this.squashBeforeMerge && this.shouldShowSquashBeforeMerge;
+ },
+ shouldShowMergeEdit() {
+ return !this.mr.ffOnlyEnabled;
+ },
},
methods: {
updateMergeCommitMessage(includeDescription) {
@@ -159,9 +165,12 @@ export default {
});
},
initiateMergePolling() {
- simplePoll((continuePolling, stopPolling) => {
- this.handleMergePolling(continuePolling, stopPolling);
- });
+ simplePoll(
+ (continuePolling, stopPolling) => {
+ this.handleMergePolling(continuePolling, stopPolling);
+ },
+ { timeout: 0 },
+ );
},
handleMergePolling(continuePolling, stopPolling) {
this.service
@@ -192,6 +201,7 @@ export default {
})
.catch(() => {
new Flash(__('Something went wrong while merging this merge request. Please try again.')); // eslint-disable-line
+ stopPolling();
});
},
initiateRemoveSourceBranchPolling() {
@@ -321,43 +331,44 @@ export default {
<div v-if="mr.ffOnlyEnabled" class="mr-fast-forward-message">
{{ __('Fast-forward merge without a merge commit') }}
</div>
- <template v-else>
- <commits-header
- :is-squash-enabled="squashBeforeMerge"
- :commits-count="mr.commitsCount"
- :target-branch="mr.targetBranch"
- >
- <ul class="border-top content-list commits-list flex-list">
- <commit-edit
- v-if="squashBeforeMerge && shouldShowSquashBeforeMerge"
+ <commits-header
+ v-if="shouldShowSquashEdit || shouldShowMergeEdit"
+ :is-squash-enabled="squashBeforeMerge"
+ :commits-count="mr.commitsCount"
+ :target-branch="mr.targetBranch"
+ :is-fast-forward-enabled="mr.ffOnlyEnabled"
+ >
+ <ul class="border-top content-list commits-list flex-list">
+ <commit-edit
+ v-if="shouldShowSquashEdit"
+ v-model="squashCommitMessage"
+ :label="__('Squash commit message')"
+ input-id="squash-message-edit"
+ squash
+ >
+ <commit-message-dropdown
+ slot="header"
v-model="squashCommitMessage"
- :label="__('Squash commit message')"
- input-id="squash-message-edit"
- squash
- >
- <commit-message-dropdown
- slot="header"
- v-model="squashCommitMessage"
- :commits="mr.commits"
+ :commits="mr.commits"
+ />
+ </commit-edit>
+ <commit-edit
+ v-if="shouldShowMergeEdit"
+ v-model="commitMessage"
+ :label="__('Merge commit message')"
+ input-id="merge-message-edit"
+ >
+ <label slot="checkbox">
+ <input
+ id="include-description"
+ type="checkbox"
+ @change="updateMergeCommitMessage($event.target.checked)"
/>
- </commit-edit>
- <commit-edit
- v-model="commitMessage"
- :label="__('Merge commit message')"
- input-id="merge-message-edit"
- >
- <label slot="checkbox">
- <input
- id="include-description"
- type="checkbox"
- @change="updateMergeCommitMessage($event.target.checked)"
- />
- {{ __('Include merge request description') }}
- </label>
- </commit-edit>
- </ul>
- </commits-header>
- </template>
+ {{ __('Include merge request description') }}
+ </label>
+ </commit-edit>
+ </ul>
+ </commits-header>
</template>
</div>
</template>
diff --git a/app/assets/javascripts/vue_shared/components/commit.vue b/app/assets/javascripts/vue_shared/components/commit.vue
index ee685a4b8cd..3f282138bdf 100644
--- a/app/assets/javascripts/vue_shared/components/commit.vue
+++ b/app/assets/javascripts/vue_shared/components/commit.vue
@@ -1,5 +1,6 @@
<script>
-import { GlTooltipDirective } from '@gitlab/ui';
+import _ from 'underscore';
+import { GlTooltipDirective, GlLink } from '@gitlab/ui';
import UserAvatarLink from './user_avatar/user_avatar_link.vue';
import Icon from '../../vue_shared/components/icon.vue';
@@ -10,6 +11,7 @@ export default {
components: {
UserAvatarLink,
Icon,
+ GlLink,
},
props: {
/**
@@ -33,6 +35,27 @@ export default {
required: false,
default: () => ({}),
},
+
+ /**
+ * If provided, is used the render the MR IID and link
+ * in place of the branch name. Must contains the
+ * following properties:
+ * - iid (number)
+ * - path (non-empty string)
+ *
+ * May optionally contain the following properties:
+ * - title (string): used in a tooltip if provided
+ *
+ * Any additional properties are ignored.
+ */
+ mergeRequestRef: {
+ type: Object,
+ required: false,
+ default: undefined,
+ validator: ref =>
+ _.isUndefined(ref) || (_.isFinite(ref.iid) && _.isString(ref.path) && !_.isEmpty(ref.path)),
+ },
+
/**
* Used to link to the commit sha.
*/
@@ -70,7 +93,11 @@ export default {
required: false,
default: () => ({}),
},
- showBranch: {
+
+ /**
+ * Indicates whether or not to show the branch/MR ref info
+ */
+ showRefInfo: {
type: Boolean,
required: false,
default: true,
@@ -78,14 +105,12 @@ export default {
},
computed: {
/**
- * Used to verify if all the properties needed to render the commit
- * ref section were provided.
- *
- * @returns {Boolean}
+ * Determines if we shoud render the ref info section based
*/
- hasCommitRef() {
- return this.commitRef && this.commitRef.name && this.commitRef.ref_url;
+ shouldShowRefInfo() {
+ return this.showRefInfo && (this.commitRef || this.mergeRequestRef);
},
+
/**
* Used to verify if all the properties needed to render the commit
* author section were provided.
@@ -109,18 +134,35 @@ export default {
</script>
<template>
<div class="branch-commit">
- <template v-if="hasCommitRef && showBranch">
+ <template v-if="shouldShowRefInfo">
<div class="icon-container">
- <i v-if="tag" class="fa fa-tag" aria-hidden="true"> </i> <icon v-if="!tag" name="fork" />
+ <icon v-if="tag" name="tag" />
+ <icon v-else-if="mergeRequestRef" name="git-merge" />
+ <icon v-else name="branch" />
</div>
- <a v-gl-tooltip :href="commitRef.ref_url" :title="commitRef.name" class="ref-name">
+ <gl-link
+ v-if="mergeRequestRef"
+ v-gl-tooltip
+ :href="mergeRequestRef.path"
+ :title="mergeRequestRef.title"
+ class="ref-name"
+ >
+ {{ mergeRequestRef.iid }}
+ </gl-link>
+ <gl-link
+ v-else
+ v-gl-tooltip
+ :href="commitRef.ref_url"
+ :title="commitRef.name"
+ class="ref-name"
+ >
{{ commitRef.name }}
- </a>
+ </gl-link>
</template>
<icon name="commit" class="commit-icon js-commit-icon" />
- <a :href="commitUrl" class="commit-sha"> {{ shortSha }} </a>
+ <gl-link :href="commitUrl" class="commit-sha"> {{ shortSha }} </gl-link>
<div class="commit-title flex-truncate-parent">
<span v-if="title" class="flex-truncate-child">
@@ -132,7 +174,7 @@ export default {
:tooltip-text="author.username"
class="avatar-image-container"
/>
- <a :href="commitUrl" class="commit-row-message"> {{ title }} </a>
+ <gl-link :href="commitUrl" class="commit-row-message"> {{ title }} </gl-link>
</span>
<span v-else> Can't find HEAD commit for this branch </span>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/loading_button.vue b/app/assets/javascripts/vue_shared/components/loading_button.vue
index 7a53d053eec..216f6c62e69 100644
--- a/app/assets/javascripts/vue_shared/components/loading_button.vue
+++ b/app/assets/javascripts/vue_shared/components/loading_button.vue
@@ -53,7 +53,7 @@ export default {
<template>
<button :class="containerClass" :disabled="loading || disabled" type="button" @click="onClick">
- <transition name="fade">
+ <transition name="fade-in">
<gl-loading-icon
v-if="loading"
:inline="true"
@@ -63,7 +63,7 @@ export default {
class="js-loading-button-icon"
/>
</transition>
- <transition name="fade">
+ <transition name="fade-in">
<slot>
<span v-if="label" class="js-loading-button-label"> {{ label }} </span>
</slot>
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index d1cf2b8f9a0..86189143525 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -2,7 +2,6 @@
* This is a manifest file that'll automatically include all the stylesheets available in this directory
* and any sub-directories. You're free to add application-wide styles to this file and they'll appear at
* the top of the compiled file, but it's generally better to create a new file per style scope.
- *= require jquery.atwho
*= require_self
*= require cropper.css
*/
@@ -15,6 +14,7 @@
* directory.
*/
+@import "../../../node_modules/at.js/dist/css/jquery.atwho";
@import "../../../node_modules/pikaday/scss/pikaday";
@import "../../../node_modules/dropzone/dist/basic";
@import "../../../node_modules/select2/select2";
diff --git a/app/assets/stylesheets/components/dashboard_skeleton.scss b/app/assets/stylesheets/components/dashboard_skeleton.scss
new file mode 100644
index 00000000000..42ede599bc6
--- /dev/null
+++ b/app/assets/stylesheets/components/dashboard_skeleton.scss
@@ -0,0 +1,80 @@
+.dashboard-cards {
+ margin-right: -$gl-padding-8;
+ margin-left: -$gl-padding-8;
+}
+
+.dashboard-card {
+ &-header {
+ &-warning {
+ background-color: $orange-100;
+ }
+
+ &-failed {
+ background-color: $red-100;
+ }
+ }
+
+ &-body {
+ height: 120px;
+
+ &-warning {
+ background-color: $orange-50;
+ }
+
+ &-failed {
+ background-color: $red-50;
+ }
+ }
+
+ &-time-ago {
+ &-icon {
+ color: $gray-500;
+ }
+ }
+
+ &-footer {
+ border-radius: $gl-padding;
+ height: $gl-padding-32;
+
+ &-failed {
+ background-color: $red-100;
+ }
+
+ &-arrow {
+ color: $gray-300;
+ }
+
+ &-downstream {
+ margin-right: -$gl-padding-8;
+ }
+
+ &-extra {
+ background-color: $gray-400;
+ font-size: 10px;
+ line-height: $gl-line-height;
+ width: $gl-padding;
+ }
+ }
+
+ &-skeleton-info {
+ border-radius: $gl-padding;
+ height: $gl-padding;
+ overflow: hidden;
+
+ &::after {
+ content: ' ';
+ display: block;
+ animation: blockTextShine 1s linear infinite forwards;
+ background-repeat: no-repeat;
+ background-size: cover;
+ background-image: linear-gradient(to right,
+ $gray-100 0%,
+ $gray-50 20%,
+ $gray-100 40%,
+ $gray-100 100%);
+ border-radius: $gl-padding;
+ height: $gl-padding;
+ margin-top: -$gl-padding-8;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss
index af79a4d9392..37a729c7a63 100644
--- a/app/assets/stylesheets/framework/avatar.scss
+++ b/app/assets/stylesheets/framework/avatar.scss
@@ -61,6 +61,10 @@
border: 0;
}
+ &.avatar-placeholder {
+ border: 0;
+ }
+
&:not([href]):hover {
border-color: darken($gray-normal, 10%);
}
diff --git a/app/assets/stylesheets/framework/blank.scss b/app/assets/stylesheets/framework/blank.scss
index 91dbb2a6365..cbd390e7145 100644
--- a/app/assets/stylesheets/framework/blank.scss
+++ b/app/assets/stylesheets/framework/blank.scss
@@ -69,6 +69,7 @@
@include media-breakpoint-up(sm) {
display: flex;
+ height: 100%;
align-items: center;
padding: 50px 30px;
}
@@ -99,3 +100,30 @@
}
}
}
+
+@include media-breakpoint-up(lg) {
+ .column-large {
+ flex: 2;
+ }
+
+ .column-small {
+ flex: 1;
+ margin-bottom: 15px;
+
+ .blank-state {
+ max-width: 400px;
+ flex-wrap: wrap;
+ margin-left: 15px;
+ }
+
+ .blank-state-icon {
+ margin-bottom: 30px;
+ }
+ }
+}
+
+@include media-breakpoint-down(xs) {
+ .blank-state-icon svg {
+ width: 315px;
+ }
+}
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index a4af84f8d27..695ce014659 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -443,7 +443,8 @@
border-color: transparent;
}
- &.btn-secondary-hover-link {
+ &.btn-secondary-hover-link,
+ &.btn-default-hover-link {
color: $gl-text-color-secondary;
&:hover,
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index aad5150c0b5..97a9a55c968 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -120,7 +120,7 @@ hr {
text-overflow: ellipsis;
white-space: nowrap;
- > div,
+ > div:not(.block),
.str-truncated {
display: inline;
}
@@ -376,18 +376,23 @@ img.emoji {
.prepend-top-default { margin-top: $gl-padding !important; }
.prepend-top-16 { margin-top: 16px; }
.prepend-top-20 { margin-top: 20px; }
+.prepend-top-32 { margin-top: 32px; }
.prepend-left-4 { margin-left: 4px; }
.prepend-left-5 { margin-left: 5px; }
.prepend-left-8 { margin-left: 8px; }
.prepend-left-10 { margin-left: 10px; }
+.prepend-left-15 { margin-left: 15px; }
.prepend-left-default { margin-left: $gl-padding; }
.prepend-left-20 { margin-left: 20px; }
+.prepend-left-32 { margin-left: 32px; }
.append-right-4 { margin-right: 4px; }
.append-right-5 { margin-right: 5px; }
.append-right-8 { margin-right: 8px; }
.append-right-10 { margin-right: 10px; }
+.append-right-15 { margin-right: 15px; }
.append-right-default { margin-right: $gl-padding; }
.append-right-20 { margin-right: 20px; }
+.prepend-right-32 { margin-right: 32px; }
.append-bottom-0 { margin-bottom: 0; }
.append-bottom-4 { margin-bottom: $gl-padding-4; }
.append-bottom-5 { margin-bottom: 5px; }
@@ -396,8 +401,11 @@ img.emoji {
.append-bottom-15 { margin-bottom: 15px; }
.append-bottom-20 { margin-bottom: 20px; }
.append-bottom-default { margin-bottom: $gl-padding; }
+.prepend-bottom-32 { margin-bottom: 32px; }
.inline { display: inline-block; }
.center { text-align: center; }
+.block { display: block; }
+.flex { display: flex; }
.vertical-align-middle { vertical-align: middle; }
.vertical-align-sub { vertical-align: sub; }
.flex-align-self-center { align-self: center; }
@@ -407,12 +415,6 @@ img.emoji {
.ws-normal { white-space: normal; }
.overflow-auto { overflow: auto; }
-.d-flex-center {
- display: flex;
- align-items: center;
- justify-content: center;
-}
-
/** COMMON SIZING CLASSES **/
.w-0 { width: 0; }
diff --git a/app/assets/stylesheets/framework/filters.scss b/app/assets/stylesheets/framework/filters.scss
index e5b529ae11d..5bcfd5d1322 100644
--- a/app/assets/stylesheets/framework/filters.scss
+++ b/app/assets/stylesheets/framework/filters.scss
@@ -108,6 +108,8 @@
}
.value-container {
+ display: flex;
+ align-items: center;
background-color: $white-normal;
color: $filter-value-text-color;
border-radius: 0 2px 2px 0;
@@ -121,7 +123,7 @@
.remove-token {
display: inline-block;
- padding-left: 4px;
+ padding-left: 8px;
padding-right: 0;
.fa-close {
@@ -412,3 +414,10 @@
padding: 8px 16px;
text-align: center;
}
+
+.search-token-target-branch {
+ .value {
+ font-family: $monospace-font;
+ font-size: 13px;
+ }
+}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index f5ed6621c55..1e025b3a67d 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -304,7 +304,9 @@
}
}
-.caret-down {
+.caret-down,
+.btn .caret-down {
+ top: 0;
height: 11px;
width: 11px;
margin-left: 4px;
diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss
index 49b9b7014ae..3ab61cc5c47 100644
--- a/app/assets/stylesheets/framework/icons.scss
+++ b/app/assets/stylesheets/framework/icons.scss
@@ -31,6 +31,7 @@
}
}
+.ci-status-icon-preparing,
.ci-status-icon-running {
svg {
fill: $blue-400;
diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss
index 1a74e06a75d..298610a0631 100644
--- a/app/assets/stylesheets/framework/lists.scss
+++ b/app/assets/stylesheets/framework/lists.scss
@@ -156,6 +156,12 @@ ul.content-list {
margin-top: 3px;
margin-bottom: 4px;
+ &.btn-ldap-override {
+ @include media-breakpoint-up(sm) {
+ margin-bottom: 0;
+ }
+ }
+
&.has-tooltip,
&:last-child {
margin-right: 0;
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index d6c4e68f68f..de2cd600623 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -61,6 +61,10 @@
padding-top: 0;
line-height: 19px;
+ &.btn.btn-sm {
+ padding: 2px 5px;
+ }
+
&:focus {
margin-top: -10px;
padding-top: 10px;
@@ -150,7 +154,7 @@
}
- table:not(.js-syntax-highlight) {
+ table {
@include markdown-table;
}
}
diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss
index ba3b0906e28..9e192cbe3fc 100644
--- a/app/assets/stylesheets/framework/mixins.scss
+++ b/app/assets/stylesheets/framework/mixins.scss
@@ -29,15 +29,14 @@
display: block;
overflow-x: auto;
border: 0;
- border-color: $gl-gray-100;
tr {
th {
- border-bottom: solid 2px $gl-gray-100;
+ border-bottom: solid 2px $gl-gray-200;
}
td {
- border-color: $gl-gray-100;
+ border-color: $gl-gray-200;
}
}
}
@@ -259,6 +258,7 @@
background: $gray-light;
border: 1px solid $border-color;
color: $gl-text-color;
+ position: -webkit-sticky;
position: sticky;
top: $header-height;
padding: $grid-size;
diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss
index 3a117106cff..cd3d6f8297e 100644
--- a/app/assets/stylesheets/framework/panels.scss
+++ b/app/assets/stylesheets/framework/panels.scss
@@ -7,7 +7,6 @@
margin-bottom: $gl-vert-padding;
}
-
.card-header {
padding: $gl-vert-padding $gl-padding;
line-height: 36px;
diff --git a/app/assets/stylesheets/framework/secondary_navigation_elements.scss b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
index 19640ab5986..31297b9d20c 100644
--- a/app/assets/stylesheets/framework/secondary_navigation_elements.scss
+++ b/app/assets/stylesheets/framework/secondary_navigation_elements.scss
@@ -181,6 +181,33 @@
margin: 0;
width: 100%;
}
+
+ &.inline {
+ display: flex;
+ flex-flow: row wrap;
+ justify-content: space-between;
+
+ > .btn,
+ > .btn-container,
+ > .dropdown,
+ > input,
+ > form {
+ flex: 1 1 auto;
+ margin: 0 0 10px;
+ margin-left: $gl-padding-top;
+ width: auto;
+
+ &:first-child {
+ margin-left: 0;
+ float: none;
+ }
+ }
+
+ .btn-full {
+ flex: 1 1 100%;
+ margin-left: 0;
+ }
+ }
}
}
diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss
index ac673eafdc7..81ccea1e01f 100644
--- a/app/assets/stylesheets/framework/selects.scss
+++ b/app/assets/stylesheets/framework/selects.scss
@@ -264,6 +264,16 @@
}
}
+.project-result {
+ .project-name {
+ font-weight: $gl-font-weight-bold;
+ }
+
+ .project-path {
+ color: $gl-gray-400;
+ }
+}
+
.user-result {
min-height: 24px;
display: flex;
diff --git a/app/assets/stylesheets/framework/system_messages.scss b/app/assets/stylesheets/framework/system_messages.scss
index 3d66136938f..e5edddec71e 100644
--- a/app/assets/stylesheets/framework/system_messages.scss
+++ b/app/assets/stylesheets/framework/system_messages.scss
@@ -12,7 +12,7 @@
p {
@include str-truncated(100%);
- margin-top: 0;
+ margin-top: -1px;
margin-bottom: 0;
}
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 55ce0d7004e..5e5e8bcc3d6 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -136,15 +136,21 @@
margin: 0 0 16px;
}
- table:not(.js-syntax-highlight) {
+ table {
@extend .table;
@extend .table-bordered;
margin: 16px 0;
color: $gl-text-color;
border: 0;
- th {
- background: $label-gray-bg;
+ tr {
+ th {
+ border-bottom: solid 2px $gl-gray-200;
+ }
+
+ td {
+ border-color: $gl-gray-200;
+ }
}
}
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 0333b9445c5..efebbd124d0 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -23,6 +23,7 @@ $darken-border-dashed-factor: 25%;
$white-light: #fff;
$white-normal: #f0f0f0;
$white-dark: #eaeaea;
+$white-transparent: rgba(255, 255, 255, 0.8);
$gray-lightest: #fdfdfd;
$gray-light: #fafafa;
@@ -277,7 +278,7 @@ $general-hover-transition-duration: 100ms;
$general-hover-transition-curve: linear;
$highlight-changes-color: rgb(235, 255, 232);
$performance-bar-height: 35px;
-$system-header-height: 35px;
+$system-header-height: 16px;
$system-footer-height: $system-header-height;
$flash-height: 52px;
$context-header-height: 60px;
@@ -288,6 +289,10 @@ $gl-line-height: 16px;
$gl-line-height-24: 24px;
$gl-line-height-14: 14px;
+$system-header-height: 35px;
+$issue-box-upcoming-bg: #8f8f8f;
+$pages-group-name-color: #4c4e54;
+
/*
* Common component specific colors
*/
@@ -410,7 +415,7 @@ $award-emoji-menu-shadow: rgba(0, 0, 0, 0.175);
$award-emoji-positive-add-bg: #fed159;
$award-emoji-positive-add-lines: #bb9c13;
$award-emoji-width: 376px;
-$award-emoji-width-xs: 300px;
+$award-emoji-width-xs: 90%;
/*
* Search Box
@@ -626,6 +631,18 @@ Animation Functions
$dropdown-animation-timing: cubic-bezier(0.23, 1, 0.32, 1);
/*
+GitLab Plans
+*/
+$gl-gold-plan: #d4af37;
+$gl-silver-plan: #91a1ab;
+$gl-bronze-plan: #cd7f32;
+
+/*
+Cross-project Pipelines
+ */
+$linked-project-column-margin: 60px;
+
+/*
Performance Bar
*/
$perf-bar-production: #222;
@@ -649,6 +666,17 @@ $image-comment-cursor-left-offset: 12;
$image-comment-cursor-top-offset: 12;
/*
+Add GitLab Slack Application
+*/
+$add-to-slack-popup-max-width: 400px;
+$add-to-slack-gif-max-width: 850px;
+$add-to-slack-well-max-width: 750px;
+$add-to-slack-logo-size: 100px;
+$double-headed-arrow-width: 100px;
+$double-headed-arrow-height: 25px;
+$right-arrow-size: 16px;
+
+/*
Popup
*/
$popup-triangle-size: 15px;
diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss
index 79d52932719..efcc437bd7f 100644
--- a/app/assets/stylesheets/framework/variables_overrides.scss
+++ b/app/assets/stylesheets/framework/variables_overrides.scss
@@ -42,6 +42,9 @@ $spacers: (
3: ($spacer * 2),
4: ($spacer * 3),
5: ($spacer * 4),
- 6: ($spacer * 8)
+ 6: ($spacer * 5),
+ 7: ($spacer * 6),
+ 8: ($spacer * 7),
+ 9: ($spacer * 8)
);
$pagination-color: $gl-text-color;
diff --git a/app/assets/stylesheets/framework/vue_transitions.scss b/app/assets/stylesheets/framework/vue_transitions.scss
index e07a177e153..e3bdc0b0199 100644
--- a/app/assets/stylesheets/framework/vue_transitions.scss
+++ b/app/assets/stylesheets/framework/vue_transitions.scss
@@ -1,9 +1,13 @@
.fade-enter-active,
-.fade-leave-active {
+.fade-leave-active,
+.fade-in-enter-active,
+.fade-out-leave-active {
transition: opacity $sidebar-transition-duration $general-hover-transition-curve;
}
.fade-enter,
+.fade-in-enter,
+.fade-out-leave-to,
.fade-leave-to {
opacity: 0;
}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index fa5a182243c..916f6cd3137 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -50,7 +50,6 @@
position: relative;
}
-
.build-trace {
@include build-trace();
}
@@ -392,3 +391,14 @@
right: 0;
margin-top: -17px;
}
+
+@include media-breakpoint-down(sm) {
+ .top-bar {
+ .truncated-info {
+ white-space: nowrap;
+ overflow: hidden;
+ max-width: 220px;
+ text-overflow: ellipsis;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index e50db5310a6..c88922ae5ea 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -9,6 +9,7 @@
@media (min-width: map-get($grid-breakpoints, md)) {
$mr-file-header-top: $mr-version-controls-height + $header-height + $mr-tabs-height;
+ position: -webkit-sticky;
position: sticky;
top: $mr-file-header-top;
z-index: 102;
@@ -725,6 +726,7 @@
}
@include media-breakpoint-up(sm) {
+ position: -webkit-sticky;
position: sticky;
top: $header-height;
background-color: $white-light;
@@ -1015,6 +1017,7 @@
}
.diff-tree-list {
+ position: -webkit-sticky;
position: sticky;
$top-pos: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
top: $header-height + $mr-tabs-height + $mr-version-controls-height + 10px;
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index 8ade995525a..0a07747e0d4 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -15,6 +15,11 @@
word-wrap: nowrap;
}
+.content-list .group-name {
+ font-weight: $gl-font-weight-bold;
+ color: $pages-group-name-color;
+}
+
.group-row {
@include basic-list-stats;
@@ -172,6 +177,50 @@
}
}
+.card {
+ .shared_runners_limit_under_quota {
+ color: $green-500;
+ }
+
+ .shared_runners_limit_over_quota {
+ color: $red-500;
+ }
+}
+
+.pipeline-quota {
+ border-top: 1px solid $table-border-color;
+ border-bottom: 1px solid $table-border-color;
+ margin: 0 0 $gl-padding;
+
+ .row {
+ padding-top: 10px;
+ padding-bottom: 10px;
+ }
+
+ .right {
+ text-align: right;
+ }
+
+ .progress {
+ height: 6px;
+ width: 100%;
+ margin-bottom: 0;
+ margin-top: 4px;
+ }
+}
+
+.user-settings-pipeline-quota {
+ margin-top: $gl-padding;
+
+ .pipeline-quota {
+ border-top: 0;
+ }
+}
+
+table.pipeline-project-metrics tr td {
+ padding: $gl-padding;
+}
+
.mattermost-icon svg {
width: 16px;
height: 16px;
diff --git a/app/assets/stylesheets/pages/import.scss b/app/assets/stylesheets/pages/import.scss
index 7f800367cad..20240835fda 100644
--- a/app/assets/stylesheets/pages/import.scss
+++ b/app/assets/stylesheets/pages/import.scss
@@ -49,3 +49,15 @@
.import-projects-loading-icon {
margin-top: $gl-padding-32;
}
+
+.btn-import {
+ .loading-icon {
+ display: none;
+ }
+
+ &.is-loading {
+ .loading-icon {
+ display: inline-block;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index 75d219320ef..6f98b4f7f13 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -34,7 +34,7 @@
.dropdown-new-label {
.dropdown-content {
- max-height: 136px;
+ max-height: initial;
}
}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index e73d1a1289d..7f8b8ea8100 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -166,6 +166,7 @@
float: left;
.accept-merge-request {
+ &.ci-preparing,
&.ci-pending,
&.ci-running {
@include btn-blue;
@@ -784,6 +785,7 @@
}
@include media-breakpoint-up(md) {
+ position: -webkit-sticky;
position: sticky;
top: $header-height + $mr-tabs-height;
width: 100%;
@@ -805,11 +807,12 @@
.merge-request-tabs-holder {
top: $header-height;
- z-index: 300;
+ z-index: 250;
background-color: $white-light;
border-bottom: 1px solid $border-color;
@include media-breakpoint-up(sm) {
+ position: -webkit-sticky;
position: sticky;
}
@@ -1017,3 +1020,13 @@
padding-left: 50px;
padding-bottom: $gl-padding;
}
+
+.mr-compare {
+ .diff-file .file-title-flex-parent {
+ top: $header-height + 51px;
+
+ .with-performance-bar & {
+ top: $performance-bar-height + $header-height + 51px;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 72f48e98c24..9c72dcbc54c 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -44,6 +44,7 @@ $note-form-margin-left: 72px;
border: 1px solid $border-color;
border-radius: $border-radius-default;
margin: $gl-padding 0;
+ overflow: auto;
&.system-note,
&.note-form {
@@ -224,14 +225,9 @@ $note-form-margin-left: 72px;
overflow-y: hidden;
.note-text {
- @include md-typography;
// Reset ul style types since we're nested inside a ul already
@include bulleted-list;
word-wrap: break-word;
-
- table {
- @include markdown-table;
- }
}
}
@@ -283,8 +279,6 @@ $note-form-margin-left: 72px;
}
.system-note-message {
- display: inline;
-
&::first-letter {
text-transform: lowercase;
}
@@ -607,12 +601,6 @@ $note-form-margin-left: 72px;
}
.note-headline-meta {
- display: inline-block;
-
- .system-note-message {
- white-space: normal;
- }
-
.system-note-separator {
color: $gl-text-color-disabled;
}
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 2b6319ddd4f..bb08440fda8 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -498,7 +498,8 @@
list-style: none;
}
- &:last-child {
+ // when downstream pipelines are present, the last stage isn't the last column
+ &:last-child:not(.has-downstream) {
.build {
// Remove right connecting horizontal line from first build in last stage
&:first-child::after {
@@ -515,7 +516,8 @@
}
}
- &:first-child {
+ // when upstream pipelines are present, the first stage isn't the first column
+ &:first-child:not(.has-upstream) {
.build {
// Remove left curved connectors from all builds in first stage
&:not(:first-child)::before {
@@ -793,6 +795,7 @@
@include mini-pipeline-graph-color($white, $orange-100, $orange-200, $orange-500, $orange-600, $orange-700);
}
+ &.ci-status-icon-preparing,
&.ci-status-icon-running {
@include mini-pipeline-graph-color($white, $blue-100, $blue-200, $blue-500, $blue-600, $blue-700);
}
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index ab26259c007..8e933b62dd9 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -266,21 +266,6 @@
padding-top: 20px;
}
- .cover-controls {
- position: static;
- padding: 0 16px;
- margin-bottom: 20px;
- display: flex;
-
- .btn {
- flex-grow: 1;
-
- &:first-child {
- margin-left: 0;
- }
- }
- }
-
.user-profile-nav {
a {
margin-right: 0;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 1349845f300..bcb306d97d5 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -18,12 +18,9 @@
}
.input-group {
- display: flex;
-
.select2-container {
display: unset;
max-width: unset;
- width: unset !important;
flex-grow: 1;
}
@@ -693,10 +690,6 @@
}
}
-.project-empty-note-panel {
- border-bottom: 1px solid $border-color;
-}
-
.project-stats,
.project-buttons {
.scrolling-tabs-container {
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index 4f9d96da4bd..e4ed685bd1b 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -23,7 +23,10 @@
}
.settings {
- border-bottom: 1px solid $gray-darker;
+ // border-top for each item except the top one
+ + .settings {
+ border-top: 1px solid $border-color;
+ }
&:first-of-type {
margin-top: 10px;
@@ -213,6 +216,31 @@
}
}
+.nested-settings {
+ padding-left: 20px;
+}
+
+.input-btn-group {
+ display: flex;
+
+ .input-large {
+ flex: 1;
+ }
+
+ .btn {
+ margin-left: 10px;
+ }
+}
+
+.settings-flex-row {
+ display: flex;
+ align-items: center;
+
+ .float-right {
+ margin-left: auto;
+ }
+}
+
.prometheus-metrics-monitoring {
.card {
.card-toggle {
@@ -243,6 +271,27 @@
}
}
+ .custom-monitored-metrics {
+ .card-title {
+ display: flex;
+ align-items: center;
+
+ > .btn-success {
+ margin-left: auto;
+ }
+ }
+
+ .custom-metric {
+ display: flex;
+ align-items: center;
+ }
+
+ .custom-metric-link-bold {
+ font-weight: $gl-font-weight-bold;
+ text-decoration: none;
+ }
+ }
+
.loading-metrics,
.empty-metrics {
padding: 30px 10px;
@@ -277,6 +326,12 @@
}
}
+.saml-settings.info-well {
+ .form-control[readonly] {
+ background: $white-light;
+ }
+}
+
.modal-doorkeepr-auth {
.modal-body {
padding: $gl-padding;
diff --git a/app/assets/stylesheets/pages/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss
index d331edaa302..79186480605 100644
--- a/app/assets/stylesheets/pages/stat_graph.scss
+++ b/app/assets/stylesheets/pages/stat_graph.scss
@@ -25,6 +25,8 @@
}
#contributors {
+ flex: 1;
+
.contributors-list {
margin: 0 0 10px;
list-style: none;
diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss
index f4d568d02ac..a59bb31bdcb 100644
--- a/app/assets/stylesheets/pages/status.scss
+++ b/app/assets/stylesheets/pages/status.scss
@@ -44,6 +44,7 @@
}
&.ci-info,
+ &.ci-preparing,
&.ci-running {
@include status-color($blue-100, $blue-500, $blue-600);
}
diff --git a/app/controllers/admin/appearances_controller.rb b/app/controllers/admin/appearances_controller.rb
index 189fee98aa0..383ec2a7d16 100644
--- a/app/controllers/admin/appearances_controller.rb
+++ b/app/controllers/admin/appearances_controller.rb
@@ -14,7 +14,7 @@ class Admin::AppearancesController < Admin::ApplicationController
@appearance = Appearance.new(appearance_params)
if @appearance.save
- redirect_to admin_appearances_path, notice: 'Appearance was successfully created.'
+ redirect_to admin_appearances_path, notice: _('Appearance was successfully created.')
else
render action: 'show'
end
@@ -22,7 +22,7 @@ class Admin::AppearancesController < Admin::ApplicationController
def update
if @appearance.update(appearance_params)
- redirect_to admin_appearances_path, notice: 'Appearance was successfully updated.'
+ redirect_to admin_appearances_path, notice: _('Appearance was successfully updated.')
else
render action: 'show'
end
@@ -33,21 +33,21 @@ class Admin::AppearancesController < Admin::ApplicationController
@appearance.save
- redirect_to admin_appearances_path, notice: 'Logo was successfully removed.'
+ redirect_to admin_appearances_path, notice: _('Logo was successfully removed.')
end
def header_logos
@appearance.remove_header_logo!
@appearance.save
- redirect_to admin_appearances_path, notice: 'Header logo was successfully removed.'
+ redirect_to admin_appearances_path, notice: _('Header logo was successfully removed.')
end
def favicon
@appearance.remove_favicon!
@appearance.save
- redirect_to admin_appearances_path, notice: 'Favicon was successfully removed.'
+ redirect_to admin_appearances_path, notice: _('Favicon was successfully removed.')
end
private
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 8f267eccc8a..ab792cf7403 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -48,7 +48,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
respond_to do |format|
if successful
format.json { head :ok }
- format.html { redirect_to redirect_path, notice: 'Application settings saved successfully' }
+ format.html { redirect_to redirect_path, notice: _('Application settings saved successfully') }
else
format.json { head :bad_request }
format.html { render :show }
@@ -70,13 +70,13 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
def reset_registration_token
@application_setting.reset_runners_registration_token!
- flash[:notice] = 'New runners registration token has been generated!'
+ flash[:notice] = _('New runners registration token has been generated!')
redirect_to admin_runners_path
end
def reset_health_check_token
@application_setting.reset_health_check_access_token!
- flash[:notice] = 'New health check access token has been generated!'
+ flash[:notice] = _('New health check access token has been generated!')
redirect_back_or_default
end
@@ -85,7 +85,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
redirect_to(
admin_application_settings_path,
- notice: 'Started asynchronous removal of all repository check states.'
+ notice: _('Started asynchronous removal of all repository check states.')
)
end
diff --git a/app/controllers/admin/applications_controller.rb b/app/controllers/admin/applications_controller.rb
index 6fc336714b6..3648c8be426 100644
--- a/app/controllers/admin/applications_controller.rb
+++ b/app/controllers/admin/applications_controller.rb
@@ -34,7 +34,7 @@ class Admin::ApplicationsController < Admin::ApplicationController
def update
if @application.update(application_params)
- redirect_to admin_application_path(@application), notice: 'Application was successfully updated.'
+ redirect_to admin_application_path(@application), notice: _('Application was successfully updated.')
else
render :edit
end
@@ -42,7 +42,7 @@ class Admin::ApplicationsController < Admin::ApplicationController
def destroy
@application.destroy
- redirect_to admin_applications_url, status: 302, notice: 'Application was successfully destroyed.'
+ redirect_to admin_applications_url, status: 302, notice: _('Application was successfully destroyed.')
end
private
diff --git a/app/controllers/admin/broadcast_messages_controller.rb b/app/controllers/admin/broadcast_messages_controller.rb
index a91d9a534cd..6e5dd1a1f55 100644
--- a/app/controllers/admin/broadcast_messages_controller.rb
+++ b/app/controllers/admin/broadcast_messages_controller.rb
@@ -19,7 +19,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
@broadcast_message = BroadcastMessage.new(broadcast_message_params)
if @broadcast_message.save
- redirect_to admin_broadcast_messages_path, notice: 'Broadcast Message was successfully created.'
+ redirect_to admin_broadcast_messages_path, notice: _('Broadcast Message was successfully created.')
else
render :index
end
@@ -27,7 +27,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
def update
if @broadcast_message.update(broadcast_message_params)
- redirect_to admin_broadcast_messages_path, notice: 'Broadcast Message was successfully updated.'
+ redirect_to admin_broadcast_messages_path, notice: _('Broadcast Message was successfully updated.')
else
render :edit
end
diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb
index 49ce275ad14..180f7d4c803 100644
--- a/app/controllers/admin/deploy_keys_controller.rb
+++ b/app/controllers/admin/deploy_keys_controller.rb
@@ -25,7 +25,7 @@ class Admin::DeployKeysController < Admin::ApplicationController
def update
if deploy_key.update(update_params)
- flash[:notice] = 'Deploy key was successfully updated.'
+ flash[:notice] = _('Deploy key was successfully updated.')
redirect_to admin_deploy_keys_path
else
render 'edit'
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 46e85e1424f..e0ecdb0c0e9 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -36,7 +36,7 @@ class Admin::GroupsController < Admin::ApplicationController
if @group.save
@group.add_owner(current_user)
- redirect_to [:admin, @group], notice: "Group '#{@group.name}' was successfully created."
+ redirect_to [:admin, @group], notice: _('Group %{group_name} was successfully created.') % { group_name: @group.name }
else
render "new"
end
@@ -44,7 +44,7 @@ class Admin::GroupsController < Admin::ApplicationController
def update
if @group.update(group_params)
- redirect_to [:admin, @group], notice: 'Group was successfully updated.'
+ redirect_to [:admin, @group], notice: _('Group was successfully updated.')
else
render "edit"
end
@@ -55,7 +55,7 @@ class Admin::GroupsController < Admin::ApplicationController
result = Members::CreateService.new(current_user, member_params.merge(limit: -1)).execute(@group)
if result[:status] == :success
- redirect_to [:admin, @group], notice: 'Users were successfully added.'
+ redirect_to [:admin, @group], notice: _('Users were successfully added.')
else
redirect_to [:admin, @group], alert: result[:message]
end
@@ -66,7 +66,7 @@ class Admin::GroupsController < Admin::ApplicationController
redirect_to admin_groups_path,
status: 302,
- alert: "Group '#{@group.name}' was scheduled for deletion."
+ alert: _('Group %{group_name} was scheduled for deletion.') % { group_name: @group.name }
end
private
diff --git a/app/controllers/admin/hooks_controller.rb b/app/controllers/admin/hooks_controller.rb
index d0abdec50ae..51b0f45c5be 100644
--- a/app/controllers/admin/hooks_controller.rb
+++ b/app/controllers/admin/hooks_controller.rb
@@ -14,7 +14,7 @@ class Admin::HooksController < Admin::ApplicationController
@hook = SystemHook.new(hook_params.to_h)
if @hook.save
- redirect_to admin_hooks_path, notice: 'Hook was successfully created.'
+ redirect_to admin_hooks_path, notice: _('Hook was successfully created.')
else
@hooks = SystemHook.all
render :index
@@ -26,7 +26,7 @@ class Admin::HooksController < Admin::ApplicationController
def update
if hook.update(hook_params)
- flash[:notice] = 'System hook was successfully updated.'
+ flash[:notice] = _('System hook was successfully updated.')
redirect_to admin_hooks_path
else
render 'edit'
diff --git a/app/controllers/admin/identities_controller.rb b/app/controllers/admin/identities_controller.rb
index b51c2f678ca..f518f7a657f 100644
--- a/app/controllers/admin/identities_controller.rb
+++ b/app/controllers/admin/identities_controller.rb
@@ -13,7 +13,7 @@ class Admin::IdentitiesController < Admin::ApplicationController
@identity.user_id = user.id
if @identity.save
- redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully created.'
+ redirect_to admin_user_identities_path(@user), notice: _('User identity was successfully created.')
else
render :new
end
@@ -29,7 +29,7 @@ class Admin::IdentitiesController < Admin::ApplicationController
def update
if @identity.update(identity_params)
RepairLdapBlockedUserService.new(@user).execute
- redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully updated.'
+ redirect_to admin_user_identities_path(@user), notice: _('User identity was successfully updated.')
else
render :edit
end
@@ -38,9 +38,9 @@ class Admin::IdentitiesController < Admin::ApplicationController
def destroy
if @identity.destroy
RepairLdapBlockedUserService.new(@user).execute
- redirect_to admin_user_identities_path(@user), status: 302, notice: 'User identity was successfully removed.'
+ redirect_to admin_user_identities_path(@user), status: 302, notice: _('User identity was successfully removed.')
else
- redirect_to admin_user_identities_path(@user), status: 302, alert: 'Failed to remove user identity.'
+ redirect_to admin_user_identities_path(@user), status: 302, alert: _('Failed to remove user identity.')
end
end
diff --git a/app/controllers/admin/impersonation_tokens_controller.rb b/app/controllers/admin/impersonation_tokens_controller.rb
index 706bcc1e549..cfe29d734b7 100644
--- a/app/controllers/admin/impersonation_tokens_controller.rb
+++ b/app/controllers/admin/impersonation_tokens_controller.rb
@@ -12,7 +12,7 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
if @impersonation_token.save
PersonalAccessToken.redis_store!(current_user.id, @impersonation_token.token)
- redirect_to admin_user_impersonation_tokens_path, notice: "A new impersonation token has been created."
+ redirect_to admin_user_impersonation_tokens_path, notice: _("A new impersonation token has been created.")
else
set_index_vars
render :index
@@ -23,9 +23,9 @@ class Admin::ImpersonationTokensController < Admin::ApplicationController
@impersonation_token = finder.find(params[:id])
if @impersonation_token.revoke!
- flash[:notice] = "Revoked impersonation token #{@impersonation_token.name}!"
+ flash[:notice] = _("Revoked impersonation token %{token_name}!") % { token_name: @impersonation_token.name }
else
- flash[:alert] = "Could not revoke impersonation token #{@impersonation_token.name}."
+ flash[:alert] = _("Could not revoke impersonation token %{token_name}.") % { token_name: @impersonation_token.name }
end
redirect_to admin_user_impersonation_tokens_path
diff --git a/app/controllers/admin/keys_controller.rb b/app/controllers/admin/keys_controller.rb
index 4e9262ccc96..340eecd7632 100644
--- a/app/controllers/admin/keys_controller.rb
+++ b/app/controllers/admin/keys_controller.rb
@@ -17,9 +17,9 @@ class Admin::KeysController < Admin::ApplicationController
respond_to do |format|
if key.destroy
- format.html { redirect_to keys_admin_user_path(user), status: 302, notice: 'User key was successfully removed.' }
+ format.html { redirect_to keys_admin_user_path(user), status: 302, notice: _('User key was successfully removed.') }
else
- format.html { redirect_to keys_admin_user_path(user), status: 302, alert: 'Failed to remove user key.' }
+ format.html { redirect_to keys_admin_user_path(user), status: 302, alert: _('Failed to remove user key.') }
end
end
end
diff --git a/app/controllers/admin/labels_controller.rb b/app/controllers/admin/labels_controller.rb
index aa5eae7a474..90c1694fd2e 100644
--- a/app/controllers/admin/labels_controller.rb
+++ b/app/controllers/admin/labels_controller.rb
@@ -21,7 +21,7 @@ class Admin::LabelsController < Admin::ApplicationController
@label = Labels::CreateService.new(label_params).execute(template: true)
if @label.persisted?
- redirect_to admin_labels_url, notice: "Label was created"
+ redirect_to admin_labels_url, notice: _("Label was created")
else
render :new
end
@@ -31,7 +31,7 @@ class Admin::LabelsController < Admin::ApplicationController
@label = Labels::UpdateService.new(label_params).execute(@label)
if @label.valid?
- redirect_to admin_labels_path, notice: 'Label was successfully updated.'
+ redirect_to admin_labels_path, notice: _('Label was successfully updated.')
else
render :edit
end
@@ -43,7 +43,7 @@ class Admin::LabelsController < Admin::ApplicationController
respond_to do |format|
format.html do
- redirect_to admin_labels_path, status: 302, notice: 'Label was removed'
+ redirect_to admin_labels_path, status: 302, notice: _('Label was removed')
end
format.js
end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index 550f29a58d2..fb135d1a32c 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -15,7 +15,7 @@ class Admin::ProjectsController < Admin::ApplicationController
format.html
format.json do
render json: {
- html: view_to_html_string("admin/projects/_projects", locals: { projects: @projects })
+ html: view_to_html_string("admin/projects/_projects", projects: @projects)
}
end
end
@@ -50,7 +50,7 @@ class Admin::ProjectsController < Admin::ApplicationController
redirect_to(
admin_project_path(@project),
- notice: 'Repository check was triggered.'
+ notice: _('Repository check was triggered.')
)
end
diff --git a/app/controllers/admin/runners_controller.rb b/app/controllers/admin/runners_controller.rb
index 8a00408001e..783c59822f1 100644
--- a/app/controllers/admin/runners_controller.rb
+++ b/app/controllers/admin/runners_controller.rb
@@ -34,17 +34,17 @@ class Admin::RunnersController < Admin::ApplicationController
def resume
if Ci::UpdateRunnerService.new(@runner).update(active: true)
- redirect_to admin_runners_path, notice: 'Runner was successfully updated.'
+ redirect_to admin_runners_path, notice: _('Runner was successfully updated.')
else
- redirect_to admin_runners_path, alert: 'Runner was not updated.'
+ redirect_to admin_runners_path, alert: _('Runner was not updated.')
end
end
def pause
if Ci::UpdateRunnerService.new(@runner).update(active: false)
- redirect_to admin_runners_path, notice: 'Runner was successfully updated.'
+ redirect_to admin_runners_path, notice: _('Runner was successfully updated.')
else
- redirect_to admin_runners_path, alert: 'Runner was not updated.'
+ redirect_to admin_runners_path, alert: _('Runner was not updated.')
end
end
diff --git a/app/controllers/admin/spam_logs_controller.rb b/app/controllers/admin/spam_logs_controller.rb
index 18d22c95b61..45cf0d3207e 100644
--- a/app/controllers/admin/spam_logs_controller.rb
+++ b/app/controllers/admin/spam_logs_controller.rb
@@ -14,7 +14,7 @@ class Admin::SpamLogsController < Admin::ApplicationController
spam_log.remove_user(deleted_by: current_user)
redirect_to admin_spam_logs_path,
status: 302,
- notice: "User #{spam_log.user.username} was successfully removed."
+ notice: _('User %{username} was successfully removed.') % { username: spam_log.user.username }
else
spam_log.destroy
head :ok
@@ -25,9 +25,9 @@ class Admin::SpamLogsController < Admin::ApplicationController
spam_log = SpamLog.find(params[:id])
if HamService.new(spam_log).mark_as_ham!
- redirect_to admin_spam_logs_path, notice: 'Spam log successfully submitted as ham.'
+ redirect_to admin_spam_logs_path, notice: _('Spam log successfully submitted as ham.')
else
- redirect_to admin_spam_logs_path, alert: 'Error with Akismet. Please check the logs for more info.'
+ redirect_to admin_spam_logs_path, alert: _('Error with Akismet. Please check the logs for more info.')
end
end
end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index bfa7c7d0109..a02d0843615 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -39,19 +39,19 @@ class Admin::UsersController < Admin::ApplicationController
warden.set_user(user, scope: :user)
- Gitlab::AppLogger.info("User #{current_user.username} has started impersonating #{user.username}")
+ Gitlab::AppLogger.info(_("User %{current_user_username} has started impersonating %{username}") % { current_user_username: current_user.username, username: user.username })
- flash[:alert] = "You are now impersonating #{user.username}"
+ flash[:alert] = _("You are now impersonating %{username}") % { username: user.username }
redirect_to root_path
else
flash[:alert] =
if user.blocked?
- "You cannot impersonate a blocked user"
+ _("You cannot impersonate a blocked user")
elsif user.internal?
- "You cannot impersonate an internal user"
+ _("You cannot impersonate an internal user")
else
- "You cannot impersonate a user who cannot log in"
+ _("You cannot impersonate a user who cannot log in")
end
redirect_to admin_user_path(user)
@@ -60,35 +60,35 @@ class Admin::UsersController < Admin::ApplicationController
def block
if update_user { |user| user.block }
- redirect_back_or_admin_user(notice: "Successfully blocked")
+ redirect_back_or_admin_user(notice: _("Successfully blocked"))
else
- redirect_back_or_admin_user(alert: "Error occurred. User was not blocked")
+ redirect_back_or_admin_user(alert: _("Error occurred. User was not blocked"))
end
end
def unblock
if user.ldap_blocked?
- redirect_back_or_admin_user(alert: "This user cannot be unlocked manually from GitLab")
+ redirect_back_or_admin_user(alert: _("This user cannot be unlocked manually from GitLab"))
elsif update_user { |user| user.activate }
- redirect_back_or_admin_user(notice: "Successfully unblocked")
+ redirect_back_or_admin_user(notice: _("Successfully unblocked"))
else
- redirect_back_or_admin_user(alert: "Error occurred. User was not unblocked")
+ redirect_back_or_admin_user(alert: _("Error occurred. User was not unblocked"))
end
end
def unlock
if update_user { |user| user.unlock_access! }
- redirect_back_or_admin_user(alert: "Successfully unlocked")
+ redirect_back_or_admin_user(alert: _("Successfully unlocked"))
else
- redirect_back_or_admin_user(alert: "Error occurred. User was not unlocked")
+ redirect_back_or_admin_user(alert: _("Error occurred. User was not unlocked"))
end
end
def confirm
if update_user { |user| user.confirm }
- redirect_back_or_admin_user(notice: "Successfully confirmed")
+ redirect_back_or_admin_user(notice: _("Successfully confirmed"))
else
- redirect_back_or_admin_user(alert: "Error occurred. User was not confirmed")
+ redirect_back_or_admin_user(alert: _("Error occurred. User was not confirmed"))
end
end
@@ -96,7 +96,7 @@ class Admin::UsersController < Admin::ApplicationController
update_user { |user| user.disable_two_factor! }
redirect_to admin_user_path(user),
- notice: 'Two-factor Authentication has been disabled for this user'
+ notice: _('Two-factor Authentication has been disabled for this user')
end
def create
@@ -109,7 +109,7 @@ class Admin::UsersController < Admin::ApplicationController
respond_to do |format|
if @user.persisted?
- format.html { redirect_to [:admin, @user], notice: 'User was successfully created.' }
+ format.html { redirect_to [:admin, @user], notice: _('User was successfully created.') }
format.json { render json: @user, status: :created, location: @user }
else
format.html { render "new" }
@@ -138,7 +138,7 @@ class Admin::UsersController < Admin::ApplicationController
end
if result[:status] == :success
- format.html { redirect_to [:admin, user], notice: 'User was successfully updated.' }
+ format.html { redirect_to [:admin, user], notice: _('User was successfully updated.') }
format.json { head :ok }
else
# restore username to keep form action url.
@@ -153,7 +153,7 @@ class Admin::UsersController < Admin::ApplicationController
user.delete_async(deleted_by: current_user, params: params.permit(:hard_delete))
respond_to do |format|
- format.html { redirect_to admin_users_path, status: 302, notice: "The user is being deleted." }
+ format.html { redirect_to admin_users_path, status: 302, notice: _("The user is being deleted.") }
format.json { head :ok }
end
end
@@ -164,11 +164,11 @@ class Admin::UsersController < Admin::ApplicationController
respond_to do |format|
if success
- format.html { redirect_back_or_admin_user(notice: 'Successfully removed email.') }
+ format.html { redirect_back_or_admin_user(notice: _('Successfully removed email.')) }
format.json { head :ok }
else
- format.html { redirect_back_or_admin_user(alert: 'There was an error removing the e-mail.') }
- format.json { render json: 'There was an error removing the e-mail.', status: :bad_request }
+ format.html { redirect_back_or_admin_user(alert: _('There was an error removing the e-mail.')) }
+ format.json { render json: _('There was an error removing the e-mail.'), status: :bad_request }
end
end
end
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 0d5c8657c9e..091327931c2 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
class AutocompleteController < ApplicationController
- skip_before_action :authenticate_user!, only: [:users, :award_emojis]
+ skip_before_action :authenticate_user!, only: [:users, :award_emojis, :merge_request_target_branches]
def users
project = Autocomplete::ProjectFinder
@@ -38,4 +38,11 @@ class AutocompleteController < ApplicationController
def award_emojis
render json: AwardedEmojiFinder.new(current_user).execute
end
+
+ def merge_request_target_branches
+ merge_requests = MergeRequestsFinder.new(current_user, params).execute
+ target_branches = merge_requests.recent_target_branches
+
+ render json: target_branches.map { |target_branch| { title: target_branch } }
+ end
end
diff --git a/app/controllers/concerns/boards_actions.rb b/app/controllers/concerns/boards_actions.rb
new file mode 100644
index 00000000000..ed7ea2f0e04
--- /dev/null
+++ b/app/controllers/concerns/boards_actions.rb
@@ -0,0 +1,38 @@
+# frozen_string_literal: true
+
+module BoardsActions
+ include Gitlab::Utils::StrongMemoize
+ extend ActiveSupport::Concern
+
+ included do
+ include BoardsResponses
+
+ before_action :boards, only: :index
+ before_action :board, only: :show
+ end
+
+ def index
+ respond_with_boards
+ end
+
+ def show
+ # Add / update the board in the recent visits table
+ Boards::Visits::CreateService.new(parent, current_user).execute(board) if request.format.html?
+
+ respond_with_board
+ end
+
+ private
+
+ def boards
+ strong_memoize(:boards) do
+ Boards::ListService.new(parent, current_user).execute
+ end
+ end
+
+ def board
+ strong_memoize(:board) do
+ boards.find(params[:id])
+ end
+ end
+end
diff --git a/app/controllers/concerns/notes_actions.rb b/app/controllers/concerns/notes_actions.rb
index b4fee93713b..f96d1821095 100644
--- a/app/controllers/concerns/notes_actions.rb
+++ b/app/controllers/concerns/notes_actions.rb
@@ -48,7 +48,7 @@ module NotesActions
respond_to do |format|
format.json do
json = {
- commands_changes: @note.commands_changes
+ commands_changes: @note.commands_changes&.slice(:emoji_award, :time_estimate, :spend_time)
}
if @note.persisted? && return_discussion?
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index b044affd4e8..0a47736cad8 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -26,7 +26,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
end
format.json do
render json: {
- html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
+ html: view_to_html_string("dashboard/projects/_projects", projects: @projects)
}
end
end
@@ -43,7 +43,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
format.html
format.json do
render json: {
- html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
+ html: view_to_html_string("dashboard/projects/_projects", projects: @projects)
}
end
end
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index f3d76c5a478..ef86d5f981a 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -15,7 +15,7 @@ class Explore::ProjectsController < Explore::ApplicationController
format.html
format.json do
render json: {
- html: view_to_html_string("explore/projects/_projects", locals: { projects: @projects })
+ html: view_to_html_string("explore/projects/_projects", projects: @projects)
}
end
end
@@ -30,7 +30,7 @@ class Explore::ProjectsController < Explore::ApplicationController
format.html
format.json do
render json: {
- html: view_to_html_string("explore/projects/_projects", locals: { projects: @projects })
+ html: view_to_html_string("explore/projects/_projects", projects: @projects)
}
end
end
@@ -44,7 +44,7 @@ class Explore::ProjectsController < Explore::ApplicationController
format.html
format.json do
render json: {
- html: view_to_html_string("explore/projects/_projects", locals: { projects: @projects })
+ html: view_to_html_string("explore/projects/_projects", projects: @projects)
}
end
end
diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb
index 51fdb6c05fb..40b8d5ed72c 100644
--- a/app/controllers/groups/boards_controller.rb
+++ b/app/controllers/groups/boards_controller.rb
@@ -1,53 +1,16 @@
# frozen_string_literal: true
class Groups::BoardsController < Groups::ApplicationController
- include BoardsResponses
+ include BoardsActions
include RecordUserLastActivity
before_action :assign_endpoint_vars
- before_action :boards, only: :index
- before_action :redirect_to_recent_board, only: :index
-
- def index
- respond_with_boards
- end
-
- def show
- @board = boards.find(params[:id])
-
- # add/update the board in the recent visited table
- Boards::Visits::CreateService.new(@board.group, current_user).execute(@board) if request.format.html?
-
- respond_with_board
- end
private
- def boards
- @boards ||= Boards::ListService.new(group, current_user).execute
- end
-
def assign_endpoint_vars
@boards_endpoint = group_boards_url(group)
@namespace_path = group.to_param
@labels_endpoint = group_labels_url(group)
end
-
- def serialize_as_json(resource)
- resource.as_json(only: [:id])
- end
-
- def includes_board?(board_id)
- boards.any? { |board| board.id == board_id }
- end
-
- def redirect_to_recent_board
- return if request.format.json?
-
- recently_visited = Boards::Visits::LatestService.new(group, current_user).execute
-
- if recently_visited && includes_board?(recently_visited.board_id)
- redirect_to(group_board_path(id: recently_visited.board_id), status: :found)
- end
- end
end
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index 0bc082246a1..f1d6fb00cfc 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -12,6 +12,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
# Authorize
before_action :authorize_admin_group_member!, except: admin_not_required_endpoints
+ skip_before_action :check_two_factor_requirement, only: :leave
skip_cross_project_access_check :index, :create, :update, :destroy, :request_access,
:approve_access_request, :leave, :resend_invite,
:override
diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb
index f476f428fdb..f378f7ac79a 100644
--- a/app/controllers/groups/settings/ci_cd_controller.rb
+++ b/app/controllers/groups/settings/ci_cd_controller.rb
@@ -17,6 +17,16 @@ module Groups
redirect_to group_settings_ci_cd_path
end
+ def update_auto_devops
+ if auto_devops_service.execute
+ flash[:notice] = s_('GroupSettings|Auto DevOps pipeline was updated for the group')
+ else
+ flash[:alert] = s_("GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}." % { error_messages: group.errors.full_messages })
+ end
+
+ redirect_to group_settings_ci_cd_path
+ end
+
private
def define_ci_variables
@@ -29,6 +39,14 @@ module Groups
def authorize_admin_group!
return render_404 unless can?(current_user, :admin_group, group)
end
+
+ def auto_devops_params
+ params.require(:group).permit(:auto_devops_enabled)
+ end
+
+ def auto_devops_service
+ Groups::AutoDevopsService.new(group, current_user, auto_devops_params)
+ end
end
end
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 4e50106398a..0192b1c253e 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -58,11 +58,24 @@ class GroupsController < Groups::ApplicationController
def show
respond_to do |format|
- format.html
+ format.html do
+ render_show_html
+ end
format.atom do
- load_events
- render layout: 'xml.atom'
+ render_details_view_atom
+ end
+ end
+ end
+
+ def details
+ respond_to do |format|
+ format.html do
+ render_details_html
+ end
+
+ format.atom do
+ render_details_view_atom
end
end
end
@@ -119,6 +132,19 @@ class GroupsController < Groups::ApplicationController
protected
+ def render_show_html
+ render 'groups/show'
+ end
+
+ def render_details_html
+ render 'groups/show'
+ end
+
+ def render_details_view_atom
+ load_events
+ render layout: 'xml.atom', template: 'groups/show'
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def authorize_create_group!
allowed = if params[:parent_id].present?
@@ -178,8 +204,8 @@ class GroupsController < Groups::ApplicationController
.includes(:namespace)
@events = EventCollection
- .new(@projects, offset: params[:offset].to_i, filter: event_filter)
- .to_a
+ .new(@projects, offset: params[:offset].to_i, filter: event_filter)
+ .to_a
Events::RenderService
.new(current_user)
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index cc2bb99f55b..e90e8278c13 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -3,6 +3,7 @@
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
include AuthenticatesWithTwoFactor
include Devise::Controllers::Rememberable
+ include AuthHelper
protect_from_forgery except: [:kerberos, :saml, :cas3, :failure], with: :exception, prepend: true
@@ -80,10 +81,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
if current_user
+ return render_403 unless link_provider_allowed?(oauth['provider'])
+
log_audit_event(current_user, with: oauth['provider'])
identity_linker ||= auth_module::IdentityLinker.new(current_user, oauth)
-
identity_linker.link
if identity_linker.changed?
diff --git a/app/controllers/profiles/accounts_controller.rb b/app/controllers/profiles/accounts_controller.rb
index b0d65f284af..0d2a6145d0e 100644
--- a/app/controllers/profiles/accounts_controller.rb
+++ b/app/controllers/profiles/accounts_controller.rb
@@ -14,7 +14,7 @@ class Profiles::AccountsController < Profiles::ApplicationController
return render_404 unless identity
- if unlink_allowed?(provider)
+ if unlink_provider_allowed?(provider)
identity.destroy
else
flash[:alert] = "You are not allowed to unlink your primary login account"
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index ba94196b2f9..83e14275a8b 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -18,21 +18,16 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
two_factor_authentication_reason(
global: lambda do
flash.now[:alert] =
- 'The global settings require you to enable Two-Factor Authentication for your account.'
+ s_('The global settings require you to enable Two-Factor Authentication for your account.')
end,
group: lambda do |groups|
- group_links = groups.map { |group| view_context.link_to group.full_name, group_path(group) }.to_sentence
-
- flash.now[:alert] = %{
- The group settings for #{group_links} require you to enable
- Two-Factor Authentication for your account.
- }.html_safe
+ flash.now[:alert] = groups_notification(groups)
end
)
unless two_factor_grace_period_expired?
grace_period_deadline = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
- flash.now[:alert] = flash.now[:alert] + " You need to do this before #{l(grace_period_deadline)}."
+ flash.now[:alert] = flash.now[:alert] + s_(" You need to do this before %{grace_period_deadline}.") % { grace_period_deadline: l(grace_period_deadline) }
end
end
@@ -49,7 +44,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
render 'create'
else
- @error = 'Invalid pin code'
+ @error = s_('Invalid pin code')
@qr_code = build_qr_code
setup_u2f_registration
render 'show'
@@ -63,7 +58,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
if @u2f_registration.persisted?
session.delete(:challenges)
- redirect_to profile_two_factor_auth_path, notice: "Your U2F device was registered!"
+ redirect_to profile_two_factor_auth_path, notice: s_("Your U2F device was registered!")
else
@qr_code = build_qr_code
setup_u2f_registration
@@ -85,7 +80,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def skip
if two_factor_grace_period_expired?
- redirect_to new_profile_two_factor_auth_path, alert: 'Cannot skip two factor authentication setup'
+ redirect_to new_profile_two_factor_auth_path, alert: s_('Cannot skip two factor authentication setup')
else
session[:skip_two_factor] = current_user.otp_grace_period_started_at + two_factor_grace_period.hours
redirect_to root_path
@@ -126,4 +121,12 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def u2f_registration_params
params.require(:u2f_registration).permit(:device_response, :name)
end
+
+ def groups_notification(groups)
+ group_links = groups.map { |group| view_context.link_to group.full_name, group_path(group) }.to_sentence
+ leave_group_links = groups.map { |group| view_context.link_to (s_("leave %{group_name}") % { group_name: group.full_name }), leave_group_members_path(group), remote: false, method: :delete}.to_sentence
+
+ s_(%{The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}.})
+ .html_safe % { group_links: group_links.html_safe, leave_group_links: leave_group_links.html_safe }
+ end
end
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index 8189b5d182a..95897aaf980 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -1,34 +1,15 @@
# frozen_string_literal: true
class Projects::BoardsController < Projects::ApplicationController
- include BoardsResponses
+ include BoardsActions
include IssuableCollections
before_action :check_issues_available!
before_action :authorize_read_board!, only: [:index, :show]
- before_action :boards, only: :index
before_action :assign_endpoint_vars
- before_action :redirect_to_recent_board, only: :index
-
- def index
- respond_with_boards
- end
-
- def show
- @board = boards.find(params[:id])
-
- # add/update the board in the recent visited table
- Boards::Visits::CreateService.new(@board.project, current_user).execute(@board) if request.format.html?
-
- respond_with_board
- end
private
- def boards
- @boards ||= Boards::ListService.new(project, current_user).execute
- end
-
def assign_endpoint_vars
@boards_endpoint = project_boards_path(project)
@bulk_issues_path = bulk_update_project_issues_path(project)
@@ -39,22 +20,4 @@ class Projects::BoardsController < Projects::ApplicationController
def authorize_read_board!
access_denied! unless can?(current_user, :read_board, project)
end
-
- def serialize_as_json(resource)
- resource.as_json(only: [:id])
- end
-
- def includes_board?(board_id)
- boards.any? { |board| board.id == board_id }
- end
-
- def redirect_to_recent_board
- return if request.format.json?
-
- recently_visited = Boards::Visits::LatestService.new(project, current_user).execute
-
- if recently_visited && includes_board?(recently_visited.board_id)
- redirect_to(namespace_project_board_path(id: recently_visited.board_id), status: :found)
- end
- end
end
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index 0c5328fc941..f28af42d1b7 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -4,6 +4,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
include WorkhorseRequest
before_action :access_check
+ prepend_before_action :deny_head_requests, only: [:info_refs]
rescue_from Gitlab::GitAccess::UnauthorizedError, with: :render_403
rescue_from Gitlab::GitAccess::NotFoundError, with: :render_404
@@ -32,6 +33,10 @@ class Projects::GitHttpController < Projects::GitHttpClientController
private
+ def deny_head_requests
+ head :forbidden if request.head?
+ end
+
def download_request?
upload_pack?
end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 2903f7d705b..2b78abc66df 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -17,7 +17,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
before_action :check_user_can_push_to_source_branch!, only: [:rebase]
before_action only: [:show] do
- push_frontend_feature_flag(:diff_tree_filtering, default_enabled: true)
push_frontend_feature_flag(:expand_diff_full_file)
end
diff --git a/app/controllers/projects/settings/operations_controller.rb b/app/controllers/projects/settings/operations_controller.rb
index 7276964b6e1..1fafc33e917 100644
--- a/app/controllers/projects/settings/operations_controller.rb
+++ b/app/controllers/projects/settings/operations_controller.rb
@@ -3,7 +3,6 @@
module Projects
module Settings
class OperationsController < Projects::ApplicationController
- before_action :check_license
before_action :authorize_update_environment!
helper_method :error_tracking_setting
@@ -65,10 +64,6 @@ module Projects
]
}
end
-
- def check_license
- render_404 unless helpers.settings_operations_available?
- end
end
end
end
diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb
index 1b22907c10f..90d4bc674d9 100644
--- a/app/controllers/search_controller.rb
+++ b/app/controllers/search_controller.rb
@@ -29,6 +29,7 @@ class SearchController < ApplicationController
@search_objects = search_service.search_objects
render_commits if @scope == 'commits'
+ eager_load_user_status if @scope == 'users'
check_single_commit_result
end
@@ -54,6 +55,12 @@ class SearchController < ApplicationController
@search_objects = prepare_commits_for_rendering(@search_objects)
end
+ def eager_load_user_status
+ return if Feature.disabled?(:users_search, default_enabled: true)
+
+ @search_objects = @search_objects.eager_load(:status) # rubocop:disable CodeReuse/ActiveRecord
+ end
+
def check_single_commit_result
if @search_results.single_commit_result?
only_commit = @search_results.objects('commits').first
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 072d07e0ed2..6eab8c5ee51 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -496,7 +496,7 @@ class IssuableFinder
upcoming_ids = Milestone.upcoming_ids(projects, related_groups)
items = items.left_joins_milestones.where(milestone_id: upcoming_ids)
elsif filter_by_started_milestone?
- items = items.left_joins_milestones.where('milestones.start_date <= NOW()')
+ items = items.left_joins_milestones.merge(Milestone.started)
else
items = items.with_milestone(params[:milestone_title])
end
diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb
index 93bee3f1488..84689ff5dc7 100644
--- a/app/finders/merge_requests_finder.rb
+++ b/app/finders/merge_requests_finder.rb
@@ -29,7 +29,7 @@
#
class MergeRequestsFinder < IssuableFinder
def self.scalar_params
- @scalar_params ||= super + [:wip]
+ @scalar_params ||= super + [:wip, :target_branch]
end
def klass
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index 2b1d6f49878..b4ee648361c 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -100,8 +100,12 @@ module AuthHelper
end
# rubocop: enable CodeReuse/ActiveRecord
- def unlink_allowed?(provider)
- %w(saml cas3).exclude?(provider.to_s)
+ def unlink_provider_allowed?(provider)
+ IdentityProviderPolicy.new(current_user, provider).can?(:unlink)
+ end
+
+ def link_provider_allowed?(provider)
+ IdentityProviderPolicy.new(current_user, provider).can?(:link)
end
extend self
diff --git a/app/helpers/auto_devops_helper.rb b/app/helpers/auto_devops_helper.rb
index 67e7e475920..0f0d5350df6 100644
--- a/app/helpers/auto_devops_helper.rb
+++ b/app/helpers/auto_devops_helper.rb
@@ -9,4 +9,17 @@ module AutoDevopsHelper
!project.repository.gitlab_ci_yml &&
!project.ci_service
end
+
+ def badge_for_auto_devops_scope(auto_devops_receiver)
+ return unless auto_devops_receiver.auto_devops_enabled?
+
+ case auto_devops_receiver.first_auto_devops_config[:scope]
+ when :project
+ nil
+ when :group
+ s_('CICD|group enabled')
+ when :instance
+ s_('CICD|instance enabled')
+ end
+ end
end
diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb
index 916dcb1a308..769f75f57c4 100644
--- a/app/helpers/clusters_helper.rb
+++ b/app/helpers/clusters_helper.rb
@@ -14,4 +14,10 @@ module ClustersHelper
render 'clusters/clusters/gcp_signup_offer_banner'
end
end
+
+ def has_rbac_enabled?(cluster)
+ return cluster.platform_kubernetes_rbac? if cluster.platform_kubernetes
+
+ !cluster.provider.legacy_abac?
+ end
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 4a9ed123161..9d028dccad7 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -4,6 +4,7 @@ module GroupsHelper
def group_overview_nav_link_paths
%w[
groups#show
+ groups#details
groups#activity
groups#subgroups
analytics#show
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 2ac8ddc5244..f2abb241753 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -169,7 +169,7 @@ module ProjectsHelper
translation.html_safe
end
- def project_list_cache_key(project)
+ def project_list_cache_key(project, pipeline_status: true)
key = [
project.route.cache_key,
project.cache_key,
@@ -179,10 +179,11 @@ module ProjectsHelper
Gitlab::CurrentSettings.cache_key,
"cross-project:#{can?(current_user, :read_cross_project)}",
max_project_member_access_cache_key(project),
+ pipeline_status,
'v2.6'
]
- key << pipeline_status_cache_key(project.pipeline_status) if project.pipeline_status.has_status?
+ key << pipeline_status_cache_key(project.pipeline_status) if pipeline_status && project.pipeline_status.has_status?
key
end
@@ -364,7 +365,8 @@ module ProjectsHelper
blobs: :download_code,
commits: :download_code,
merge_requests: :read_merge_request,
- notes: [:read_merge_request, :download_code, :read_issue, :read_project_snippet]
+ notes: [:read_merge_request, :download_code, :read_issue, :read_project_snippet],
+ members: :read_project_member
)
end
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 0ee76a51f7d..8110377850b 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -201,4 +201,14 @@ module SearchHelper
def limited_count(count, limit = 1000)
count > limit ? "#{limit}+" : count
end
+
+ def search_tabs?(tab)
+ return false if Feature.disabled?(:users_search, default_enabled: true)
+
+ if @project
+ project_search_tabs?(:members)
+ else
+ can?(current_user, :read_users_list)
+ end
+ end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index c5035797621..9cc7c0a1b97 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -7,20 +7,14 @@ class ApplicationSetting < ActiveRecord::Base
include IgnorableColumn
include ChronicDurationAttribute
- add_authentication_token_field :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption) ? :optional : :required }
+ add_authentication_token_field :runners_registration_token, encrypted: -> { Feature.enabled?(:application_settings_tokens_optional_encryption, default_enabled: true) ? :optional : :required }
add_authentication_token_field :health_check_access_token
- DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
- | # or
- \s # any whitespace character
- | # or
- [\r\n] # any number of newline characters
- }x
-
- # Setting a key restriction to `-1` means that all keys of this type are
- # forbidden.
- FORBIDDEN_KEY_VALUE = KeyRestrictionValidator::FORBIDDEN
- SUPPORTED_KEY_TYPES = %i[rsa dsa ecdsa ed25519].freeze
+ # Include here so it can override methods from
+ # `add_authentication_token_field`
+ # We don't prepend for now because otherwise we'll need to
+ # fix a lot of tests using allow_any_instance_of
+ include ApplicationSettingImplementation
serialize :restricted_visibility_levels # rubocop:disable Cop/ActiveRecordSerialize
serialize :import_sources # rubocop:disable Cop/ActiveRecordSerialize
@@ -42,8 +36,6 @@ class ApplicationSetting < ActiveRecord::Base
cache_markdown_field :shared_runners_text, pipeline: :plain_markdown
cache_markdown_field :after_sign_up_text
- attr_accessor :domain_whitelist_raw, :domain_blacklist_raw
-
default_value_for :id, 1
chronic_duration_attr_writer :archive_builds_in_human_readable, :archive_builds_in_seconds
@@ -69,7 +61,7 @@ class ApplicationSetting < ActiveRecord::Base
url: true
validates :admin_notification_email,
- email: true,
+ devise_email: true,
allow_blank: true
validates :two_factor_grace_period,
@@ -231,266 +223,4 @@ class ApplicationSetting < ActiveRecord::Base
reset_memoized_terms
end
after_commit :expire_performance_bar_allowed_user_ids_cache, if: -> { previous_changes.key?('performance_bar_allowed_group_id') }
-
- def self.defaults
- {
- after_sign_up_text: nil,
- akismet_enabled: false,
- allow_local_requests_from_hooks_and_services: false,
- authorized_keys_enabled: true, # TODO default to false if the instance is configured to use AuthorizedKeysCommand
- container_registry_token_expire_delay: 5,
- default_artifacts_expire_in: '30 days',
- default_branch_protection: Settings.gitlab['default_branch_protection'],
- default_group_visibility: Settings.gitlab.default_projects_features['visibility_level'],
- default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
- default_projects_limit: Settings.gitlab['default_projects_limit'],
- default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
- disabled_oauth_sign_in_sources: [],
- domain_whitelist: Settings.gitlab['domain_whitelist'],
- dsa_key_restriction: 0,
- ecdsa_key_restriction: 0,
- ed25519_key_restriction: 0,
- first_day_of_week: 0,
- gitaly_timeout_default: 55,
- gitaly_timeout_fast: 10,
- gitaly_timeout_medium: 30,
- gravatar_enabled: Settings.gravatar['enabled'],
- help_page_hide_commercial_content: false,
- help_page_text: nil,
- hide_third_party_offers: false,
- housekeeping_bitmaps_enabled: true,
- housekeeping_enabled: true,
- housekeeping_full_repack_period: 50,
- housekeeping_gc_period: 200,
- housekeeping_incremental_repack_period: 10,
- import_sources: Settings.gitlab['import_sources'],
- max_artifacts_size: Settings.artifacts['max_size'],
- max_attachment_size: Settings.gitlab['max_attachment_size'],
- mirror_available: true,
- password_authentication_enabled_for_git: true,
- password_authentication_enabled_for_web: Settings.gitlab['signin_enabled'],
- performance_bar_allowed_group_id: nil,
- rsa_key_restriction: 0,
- plantuml_enabled: false,
- plantuml_url: nil,
- polling_interval_multiplier: 1,
- project_export_enabled: true,
- recaptcha_enabled: false,
- repository_checks_enabled: true,
- repository_storages: ['default'],
- require_two_factor_authentication: false,
- restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
- session_expire_delay: Settings.gitlab['session_expire_delay'],
- send_user_confirmation_email: false,
- shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
- shared_runners_text: nil,
- sign_in_text: nil,
- signup_enabled: Settings.gitlab['signup_enabled'],
- terminal_max_session_time: 0,
- throttle_authenticated_api_enabled: false,
- throttle_authenticated_api_period_in_seconds: 3600,
- throttle_authenticated_api_requests_per_period: 7200,
- throttle_authenticated_web_enabled: false,
- throttle_authenticated_web_period_in_seconds: 3600,
- throttle_authenticated_web_requests_per_period: 7200,
- throttle_unauthenticated_enabled: false,
- throttle_unauthenticated_period_in_seconds: 3600,
- throttle_unauthenticated_requests_per_period: 3600,
- two_factor_grace_period: 48,
- unique_ips_limit_enabled: false,
- unique_ips_limit_per_user: 10,
- unique_ips_limit_time_window: 3600,
- usage_ping_enabled: Settings.gitlab['usage_ping_enabled'],
- instance_statistics_visibility_private: false,
- user_default_external: false,
- user_default_internal_regex: nil,
- user_show_add_ssh_key_message: true,
- usage_stats_set_by_user_id: nil,
- diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
- commit_email_hostname: default_commit_email_hostname,
- protected_ci_variables: false,
- local_markdown_version: 0
- }
- end
-
- def self.default_commit_email_hostname
- "users.noreply.#{Gitlab.config.gitlab.host}"
- end
-
- def self.create_from_defaults
- build_from_defaults.tap(&:save)
- end
-
- def self.human_attribute_name(attr, _options = {})
- if attr == :default_artifacts_expire_in
- 'Default artifacts expiration'
- else
- super
- end
- end
-
- def home_page_url_column_exists?
- ::Gitlab::Database.cached_column_exists?(:application_settings, :home_page_url)
- end
-
- def help_page_support_url_column_exists?
- ::Gitlab::Database.cached_column_exists?(:application_settings, :help_page_support_url)
- end
-
- def disabled_oauth_sign_in_sources=(sources)
- sources = (sources || []).map(&:to_s) & Devise.omniauth_providers.map(&:to_s)
- super(sources)
- end
-
- def domain_whitelist_raw
- self.domain_whitelist&.join("\n")
- end
-
- def domain_blacklist_raw
- self.domain_blacklist&.join("\n")
- end
-
- def domain_whitelist_raw=(values)
- self.domain_whitelist = []
- self.domain_whitelist = values.split(DOMAIN_LIST_SEPARATOR)
- self.domain_whitelist.reject! { |d| d.empty? }
- self.domain_whitelist
- end
-
- def domain_blacklist_raw=(values)
- self.domain_blacklist = []
- self.domain_blacklist = values.split(DOMAIN_LIST_SEPARATOR)
- self.domain_blacklist.reject! { |d| d.empty? }
- self.domain_blacklist
- end
-
- def domain_blacklist_file=(file)
- self.domain_blacklist_raw = file.read
- end
-
- def repository_storages
- Array(read_attribute(:repository_storages))
- end
-
- def commit_email_hostname
- super.presence || self.class.default_commit_email_hostname
- end
-
- def default_project_visibility=(level)
- super(Gitlab::VisibilityLevel.level_value(level))
- end
-
- def default_snippet_visibility=(level)
- super(Gitlab::VisibilityLevel.level_value(level))
- end
-
- def default_group_visibility=(level)
- super(Gitlab::VisibilityLevel.level_value(level))
- end
-
- def restricted_visibility_levels=(levels)
- super(levels&.map { |level| Gitlab::VisibilityLevel.level_value(level) })
- end
-
- def strip_sentry_values
- sentry_dsn.strip! if sentry_dsn.present?
- clientside_sentry_dsn.strip! if clientside_sentry_dsn.present?
- end
-
- def performance_bar_allowed_group
- Group.find_by_id(performance_bar_allowed_group_id)
- end
-
- # Return true if the Performance Bar is enabled for a given group
- def performance_bar_enabled
- performance_bar_allowed_group_id.present?
- end
-
- # Choose one of the available repository storage options. Currently all have
- # equal weighting.
- def pick_repository_storage
- repository_storages.sample
- end
-
- def runners_registration_token
- ensure_runners_registration_token!
- end
-
- def health_check_access_token
- ensure_health_check_access_token!
- end
-
- def usage_ping_can_be_configured?
- Settings.gitlab.usage_ping_enabled
- end
-
- def usage_ping_enabled
- usage_ping_can_be_configured? && super
- end
-
- def allowed_key_types
- SUPPORTED_KEY_TYPES.select do |type|
- key_restriction_for(type) != FORBIDDEN_KEY_VALUE
- end
- end
-
- def key_restriction_for(type)
- attr_name = "#{type}_key_restriction"
-
- has_attribute?(attr_name) ? public_send(attr_name) : FORBIDDEN_KEY_VALUE # rubocop:disable GitlabSecurity/PublicSend
- end
-
- def allow_signup?
- signup_enabled? && password_authentication_enabled_for_web?
- end
-
- def password_authentication_enabled?
- password_authentication_enabled_for_web? || password_authentication_enabled_for_git?
- end
-
- def user_default_internal_regex_enabled?
- user_default_external? && user_default_internal_regex.present?
- end
-
- def user_default_internal_regex_instance
- Regexp.new(user_default_internal_regex, Regexp::IGNORECASE)
- end
-
- delegate :terms, to: :latest_terms, allow_nil: true
- def latest_terms
- @latest_terms ||= Term.latest
- end
-
- def reset_memoized_terms
- @latest_terms = nil
- latest_terms
- end
-
- def archive_builds_older_than
- archive_builds_in_seconds.seconds.ago if archive_builds_in_seconds
- end
-
- private
-
- def ensure_uuid!
- return if uuid?
-
- self.uuid = SecureRandom.uuid
- end
-
- def check_repository_storages
- invalid = repository_storages - Gitlab.config.repositories.storages.keys
- errors.add(:repository_storages, "can't include: #{invalid.join(", ")}") unless
- invalid.empty?
- end
-
- def terms_exist
- return unless enforce_terms?
-
- errors.add(:terms, "You need to set terms to be enforced") unless terms.present?
- end
-
- def expire_performance_bar_allowed_user_ids_cache
- Gitlab::PerformanceBar.expire_allowed_user_ids_cache
- end
end
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
new file mode 100644
index 00000000000..265aa1d4965
--- /dev/null
+++ b/app/models/application_setting_implementation.rb
@@ -0,0 +1,281 @@
+# frozen_string_literal: true
+
+module ApplicationSettingImplementation
+ extend ActiveSupport::Concern
+
+ DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
+ | # or
+ \s # any whitespace character
+ | # or
+ [\r\n] # any number of newline characters
+ }x
+
+ # Setting a key restriction to `-1` means that all keys of this type are
+ # forbidden.
+ FORBIDDEN_KEY_VALUE = KeyRestrictionValidator::FORBIDDEN
+ SUPPORTED_KEY_TYPES = %i[rsa dsa ecdsa ed25519].freeze
+
+ class_methods do
+ def defaults
+ {
+ after_sign_up_text: nil,
+ akismet_enabled: false,
+ allow_local_requests_from_hooks_and_services: false,
+ authorized_keys_enabled: true, # TODO default to false if the instance is configured to use AuthorizedKeysCommand
+ container_registry_token_expire_delay: 5,
+ default_artifacts_expire_in: '30 days',
+ default_branch_protection: Settings.gitlab['default_branch_protection'],
+ default_group_visibility: Settings.gitlab.default_projects_features['visibility_level'],
+ default_project_visibility: Settings.gitlab.default_projects_features['visibility_level'],
+ default_projects_limit: Settings.gitlab['default_projects_limit'],
+ default_snippet_visibility: Settings.gitlab.default_projects_features['visibility_level'],
+ disabled_oauth_sign_in_sources: [],
+ domain_whitelist: Settings.gitlab['domain_whitelist'],
+ dsa_key_restriction: 0,
+ ecdsa_key_restriction: 0,
+ ed25519_key_restriction: 0,
+ first_day_of_week: 0,
+ gitaly_timeout_default: 55,
+ gitaly_timeout_fast: 10,
+ gitaly_timeout_medium: 30,
+ gravatar_enabled: Settings.gravatar['enabled'],
+ help_page_hide_commercial_content: false,
+ help_page_text: nil,
+ hide_third_party_offers: false,
+ housekeeping_bitmaps_enabled: true,
+ housekeeping_enabled: true,
+ housekeeping_full_repack_period: 50,
+ housekeeping_gc_period: 200,
+ housekeeping_incremental_repack_period: 10,
+ import_sources: Settings.gitlab['import_sources'],
+ max_artifacts_size: Settings.artifacts['max_size'],
+ max_attachment_size: Settings.gitlab['max_attachment_size'],
+ mirror_available: true,
+ password_authentication_enabled_for_git: true,
+ password_authentication_enabled_for_web: Settings.gitlab['signin_enabled'],
+ performance_bar_allowed_group_id: nil,
+ rsa_key_restriction: 0,
+ plantuml_enabled: false,
+ plantuml_url: nil,
+ polling_interval_multiplier: 1,
+ project_export_enabled: true,
+ recaptcha_enabled: false,
+ repository_checks_enabled: true,
+ repository_storages: ['default'],
+ require_two_factor_authentication: false,
+ restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'],
+ session_expire_delay: Settings.gitlab['session_expire_delay'],
+ send_user_confirmation_email: false,
+ shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
+ shared_runners_text: nil,
+ sign_in_text: nil,
+ signup_enabled: Settings.gitlab['signup_enabled'],
+ terminal_max_session_time: 0,
+ throttle_authenticated_api_enabled: false,
+ throttle_authenticated_api_period_in_seconds: 3600,
+ throttle_authenticated_api_requests_per_period: 7200,
+ throttle_authenticated_web_enabled: false,
+ throttle_authenticated_web_period_in_seconds: 3600,
+ throttle_authenticated_web_requests_per_period: 7200,
+ throttle_unauthenticated_enabled: false,
+ throttle_unauthenticated_period_in_seconds: 3600,
+ throttle_unauthenticated_requests_per_period: 3600,
+ two_factor_grace_period: 48,
+ unique_ips_limit_enabled: false,
+ unique_ips_limit_per_user: 10,
+ unique_ips_limit_time_window: 3600,
+ usage_ping_enabled: Settings.gitlab['usage_ping_enabled'],
+ instance_statistics_visibility_private: false,
+ user_default_external: false,
+ user_default_internal_regex: nil,
+ user_show_add_ssh_key_message: true,
+ usage_stats_set_by_user_id: nil,
+ diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
+ commit_email_hostname: default_commit_email_hostname,
+ protected_ci_variables: false,
+ local_markdown_version: 0
+ }
+ end
+
+ def default_commit_email_hostname
+ "users.noreply.#{Gitlab.config.gitlab.host}"
+ end
+
+ def create_from_defaults
+ build_from_defaults.tap(&:save)
+ end
+
+ def human_attribute_name(attr, _options = {})
+ if attr == :default_artifacts_expire_in
+ 'Default artifacts expiration'
+ else
+ super
+ end
+ end
+ end
+
+ def home_page_url_column_exists?
+ ::Gitlab::Database.cached_column_exists?(:application_settings, :home_page_url)
+ end
+
+ def help_page_support_url_column_exists?
+ ::Gitlab::Database.cached_column_exists?(:application_settings, :help_page_support_url)
+ end
+
+ def disabled_oauth_sign_in_sources=(sources)
+ sources = (sources || []).map(&:to_s) & Devise.omniauth_providers.map(&:to_s)
+ super(sources)
+ end
+
+ def domain_whitelist_raw
+ self.domain_whitelist&.join("\n")
+ end
+
+ def domain_blacklist_raw
+ self.domain_blacklist&.join("\n")
+ end
+
+ def domain_whitelist_raw=(values)
+ self.domain_whitelist = []
+ self.domain_whitelist = values.split(DOMAIN_LIST_SEPARATOR)
+ self.domain_whitelist.reject! { |d| d.empty? }
+ self.domain_whitelist
+ end
+
+ def domain_blacklist_raw=(values)
+ self.domain_blacklist = []
+ self.domain_blacklist = values.split(DOMAIN_LIST_SEPARATOR)
+ self.domain_blacklist.reject! { |d| d.empty? }
+ self.domain_blacklist
+ end
+
+ def domain_blacklist_file=(file)
+ self.domain_blacklist_raw = file.read
+ end
+
+ def repository_storages
+ Array(read_attribute(:repository_storages))
+ end
+
+ def commit_email_hostname
+ super.presence || self.class.default_commit_email_hostname
+ end
+
+ def default_project_visibility=(level)
+ super(Gitlab::VisibilityLevel.level_value(level))
+ end
+
+ def default_snippet_visibility=(level)
+ super(Gitlab::VisibilityLevel.level_value(level))
+ end
+
+ def default_group_visibility=(level)
+ super(Gitlab::VisibilityLevel.level_value(level))
+ end
+
+ def restricted_visibility_levels=(levels)
+ super(levels&.map { |level| Gitlab::VisibilityLevel.level_value(level) })
+ end
+
+ def strip_sentry_values
+ sentry_dsn.strip! if sentry_dsn.present?
+ clientside_sentry_dsn.strip! if clientside_sentry_dsn.present?
+ end
+
+ def performance_bar_allowed_group
+ Group.find_by_id(performance_bar_allowed_group_id)
+ end
+
+ # Return true if the Performance Bar is enabled for a given group
+ def performance_bar_enabled
+ performance_bar_allowed_group_id.present?
+ end
+
+ # Choose one of the available repository storage options. Currently all have
+ # equal weighting.
+ def pick_repository_storage
+ repository_storages.sample
+ end
+
+ def runners_registration_token
+ ensure_runners_registration_token!
+ end
+
+ def health_check_access_token
+ ensure_health_check_access_token!
+ end
+
+ def usage_ping_can_be_configured?
+ Settings.gitlab.usage_ping_enabled
+ end
+
+ def usage_ping_enabled
+ usage_ping_can_be_configured? && super
+ end
+
+ def allowed_key_types
+ SUPPORTED_KEY_TYPES.select do |type|
+ key_restriction_for(type) != FORBIDDEN_KEY_VALUE
+ end
+ end
+
+ def key_restriction_for(type)
+ attr_name = "#{type}_key_restriction"
+
+ has_attribute?(attr_name) ? public_send(attr_name) : FORBIDDEN_KEY_VALUE # rubocop:disable GitlabSecurity/PublicSend
+ end
+
+ def allow_signup?
+ signup_enabled? && password_authentication_enabled_for_web?
+ end
+
+ def password_authentication_enabled?
+ password_authentication_enabled_for_web? || password_authentication_enabled_for_git?
+ end
+
+ def user_default_internal_regex_enabled?
+ user_default_external? && user_default_internal_regex.present?
+ end
+
+ def user_default_internal_regex_instance
+ Regexp.new(user_default_internal_regex, Regexp::IGNORECASE)
+ end
+
+ delegate :terms, to: :latest_terms, allow_nil: true
+ def latest_terms
+ @latest_terms ||= ApplicationSetting::Term.latest
+ end
+
+ def reset_memoized_terms
+ @latest_terms = nil # rubocop:disable Gitlab/ModuleWithInstanceVariables
+ latest_terms
+ end
+
+ def archive_builds_older_than
+ archive_builds_in_seconds.seconds.ago if archive_builds_in_seconds
+ end
+
+ private
+
+ def ensure_uuid!
+ return if uuid?
+
+ self.uuid = SecureRandom.uuid
+ end
+
+ def check_repository_storages
+ invalid = repository_storages - Gitlab.config.repositories.storages.keys
+ errors.add(:repository_storages, "can't include: #{invalid.join(", ")}") unless
+ invalid.empty?
+ end
+
+ def terms_exist
+ return unless enforce_terms?
+
+ errors.add(:terms, "You need to set terms to be enforced") unless terms.present?
+ end
+
+ def expire_performance_bar_allowed_user_ids_cache
+ Gitlab::PerformanceBar.expire_allowed_user_ids_cache
+ end
+end
diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb
index 2d237383e60..1c95abdd9ee 100644
--- a/app/models/broadcast_message.rb
+++ b/app/models/broadcast_message.rb
@@ -4,7 +4,7 @@ class BroadcastMessage < ActiveRecord::Base
include CacheMarkdownField
include Sortable
- cache_markdown_field :message, pipeline: :broadcast_message
+ cache_markdown_field :message, pipeline: :broadcast_message, whitelisted: true
validates :message, presence: true
validates :starts_at, presence: true
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index a629db82c19..59f47effff7 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -172,6 +172,10 @@ module Ci
end
state_machine :status do
+ event :enqueue do
+ transition [:created, :skipped, :manual, :scheduled] => :preparing, if: :any_unmet_prerequisites?
+ end
+
event :actionize do
transition created: :manual
end
@@ -185,8 +189,12 @@ module Ci
end
event :enqueue_scheduled do
+ transition scheduled: :preparing, if: ->(build) do
+ build.scheduled_at&.past? && build.any_unmet_prerequisites?
+ end
+
transition scheduled: :pending, if: ->(build) do
- build.scheduled_at && build.scheduled_at < Time.now
+ build.scheduled_at&.past? && !build.any_unmet_prerequisites?
end
end
@@ -204,6 +212,12 @@ module Ci
end
end
+ after_transition any => [:preparing] do |build|
+ build.run_after_commit do
+ Ci::BuildPrepareWorker.perform_async(id)
+ end
+ end
+
after_transition any => [:pending] do |build|
build.run_after_commit do
BuildQueueWorker.perform_async(id)
@@ -355,6 +369,16 @@ module Ci
!retried?
end
+ def any_unmet_prerequisites?
+ return false unless Feature.enabled?(:ci_preparing_state, default_enabled: true)
+
+ prerequisites.present?
+ end
+
+ def prerequisites
+ Gitlab::Ci::Build::Prerequisite::Factory.new(self).unmet
+ end
+
def expanded_environment_name
return unless has_environment?
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index ca9725f7a04..826b3f82bbf 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -13,6 +13,7 @@ module Ci
include EnumWithNil
include HasRef
include ShaAttribute
+ include FromUnion
sha_attribute :source_sha
sha_attribute :target_sha
@@ -81,10 +82,14 @@ module Ci
state_machine :status, initial: :created do
event :enqueue do
- transition [:created, :skipped, :scheduled] => :pending
+ transition [:created, :preparing, :skipped, :scheduled] => :pending
transition [:success, :failed, :canceled] => :running
end
+ event :prepare do
+ transition any - [:preparing] => :preparing
+ end
+
event :run do
transition any - [:running] => :running
end
@@ -117,7 +122,7 @@ module Ci
# Do not add any operations to this state_machine
# Create a separate worker for each new operation
- before_transition [:created, :pending] => :running do |pipeline|
+ before_transition [:created, :preparing, :pending] => :running do |pipeline|
pipeline.started_at = Time.now
end
@@ -140,7 +145,7 @@ module Ci
end
end
- after_transition [:created, :pending] => :running do |pipeline|
+ after_transition [:created, :preparing, :pending] => :running do |pipeline|
pipeline.run_after_commit { PipelineMetricsWorker.perform_async(pipeline.id) }
end
@@ -148,7 +153,7 @@ module Ci
pipeline.run_after_commit { PipelineMetricsWorker.perform_async(pipeline.id) }
end
- after_transition [:created, :pending, :running] => :success do |pipeline|
+ after_transition [:created, :preparing, :pending, :running] => :success do |pipeline|
pipeline.run_after_commit { PipelineSuccessWorker.perform_async(pipeline.id) }
end
@@ -185,32 +190,30 @@ module Ci
end
scope :for_user, -> (user) { where(user: user) }
-
- scope :for_merge_request, -> (merge_request, ref, sha) do
- ##
- # We have to filter out unrelated MR pipelines.
- # When merge request is empty, it selects general pipelines, such as push sourced pipelines.
- # When merge request is matched, it selects MR pipelines.
- where(merge_request: [nil, merge_request], ref: ref, sha: sha)
- .sort_by_merge_request_pipelines
- end
+ scope :for_sha, -> (sha) { where(sha: sha) }
+ scope :for_source_sha, -> (source_sha) { where(source_sha: source_sha) }
+ scope :for_sha_or_source_sha, -> (sha) { for_sha(sha).or(for_source_sha(sha)) }
scope :triggered_by_merge_request, -> (merge_request) do
where(source: :merge_request_event, merge_request: merge_request)
end
- scope :detached_merge_request_pipelines, -> (merge_request) do
- triggered_by_merge_request(merge_request).where(target_sha: nil)
+ scope :detached_merge_request_pipelines, -> (merge_request, sha) do
+ triggered_by_merge_request(merge_request).for_sha(sha)
end
- scope :merge_request_pipelines, -> (merge_request) do
- triggered_by_merge_request(merge_request).where.not(target_sha: nil)
+ scope :merge_request_pipelines, -> (merge_request, source_sha) do
+ triggered_by_merge_request(merge_request).for_source_sha(source_sha)
end
scope :mergeable_merge_request_pipelines, -> (merge_request) do
triggered_by_merge_request(merge_request).where(target_sha: merge_request.target_branch_sha)
end
+ scope :triggered_for_branch, -> (ref) do
+ where(source: branch_pipeline_sources).where(ref: ref, tag: false)
+ end
+
# Returns the pipelines in descending order (= newest first), optionally
# limited to a number of references.
#
@@ -298,8 +301,8 @@ module Ci
sources.reject { |source| source == "external" }.values
end
- def self.latest_for_merge_request(merge_request, ref, sha)
- for_merge_request(merge_request, ref, sha).first
+ def self.branch_pipeline_sources
+ @branch_pipeline_sources ||= sources.reject { |source| source == 'merge_request_event' }.values
end
def self.ci_sources_values
@@ -598,6 +601,7 @@ module Ci
retry_optimistic_lock(self) do
case latest_builds_status.to_s
when 'created' then nil
+ when 'preparing' then prepare
when 'pending' then enqueue
when 'running' then run
when 'success' then succeed
@@ -742,6 +746,10 @@ module Ci
triggered_by_merge_request? && target_sha == merge_request.target_branch_sha
end
+ def matches_sha_or_source_sha?(sha)
+ self.sha == sha || self.source_sha == sha
+ end
+
private
def ci_yaml_from_repo
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index ce26ee168ef..43f040a91ae 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -10,7 +10,7 @@ module Ci
include FromUnion
include TokenAuthenticatable
- add_authentication_token_field :token, encrypted: -> { Feature.enabled?(:ci_runners_tokens_optional_encryption) ? :optional : :required }
+ add_authentication_token_field :token, encrypted: -> { Feature.enabled?(:ci_runners_tokens_optional_encryption, default_enabled: true) ? :optional : :required }
enum access_level: {
not_protected: 0,
diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb
index 0389945191e..098f5189517 100644
--- a/app/models/ci/stage.rb
+++ b/app/models/ci/stage.rb
@@ -39,10 +39,14 @@ module Ci
state_machine :status, initial: :created do
event :enqueue do
- transition created: :pending
+ transition [:created, :preparing] => :pending
transition [:success, :failed, :canceled, :skipped] => :running
end
+ event :prepare do
+ transition any - [:preparing] => :preparing
+ end
+
event :run do
transition any - [:running] => :running
end
@@ -76,6 +80,7 @@ module Ci
retry_optimistic_lock(self) do
case statuses.latest.status
when 'created' then nil
+ when 'preparing' then prepare
when 'pending' then enqueue
when 'running' then run
when 'success' then succeed
diff --git a/app/models/clusters/applications/runner.rb b/app/models/clusters/applications/runner.rb
index 941551dadaa..ef9cc4bd6d6 100644
--- a/app/models/clusters/applications/runner.rb
+++ b/app/models/clusters/applications/runner.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Runner < ActiveRecord::Base
- VERSION = '0.2.0'.freeze
+ VERSION = '0.3.0'.freeze
self.table_name = 'clusters_applications_runners'
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index fb2221b601f..7786b48429c 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -110,7 +110,7 @@ module Clusters
# short time later
def terminals(environment)
with_reactive_cache do |data|
- pods = filter_by_label(data[:pods], app: environment.slug)
+ pods = filter_by_project_environment(data[:pods], project.full_path_slug, environment.slug)
terminals = pods.flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) }.compact
terminals.each { |terminal| add_terminal_auth(terminal, terminal_auth) }
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 7f6562b63e5..5f66a661324 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -66,7 +66,10 @@ class CommitStatus < ActiveRecord::Base
end
event :enqueue do
- transition [:created, :skipped, :manual, :scheduled] => :pending
+ # A CommitStatus will never have prerequisites, but this event
+ # is shared by Ci::Build, which cannot progress unless prerequisites
+ # are satisfied.
+ transition [:created, :preparing, :skipped, :manual, :scheduled] => :pending, unless: :any_unmet_prerequisites?
end
event :run do
@@ -74,26 +77,26 @@ class CommitStatus < ActiveRecord::Base
end
event :skip do
- transition [:created, :pending] => :skipped
+ transition [:created, :preparing, :pending] => :skipped
end
event :drop do
- transition [:created, :pending, :running, :scheduled] => :failed
+ transition [:created, :preparing, :pending, :running, :scheduled] => :failed
end
event :success do
- transition [:created, :pending, :running] => :success
+ transition [:created, :preparing, :pending, :running] => :success
end
event :cancel do
- transition [:created, :pending, :running, :manual, :scheduled] => :canceled
+ transition [:created, :preparing, :pending, :running, :manual, :scheduled] => :canceled
end
- before_transition [:created, :skipped, :manual, :scheduled] => :pending do |commit_status|
+ before_transition [:created, :preparing, :skipped, :manual, :scheduled] => :pending do |commit_status|
commit_status.queued_at = Time.now
end
- before_transition [:created, :pending] => :running do |commit_status|
+ before_transition [:created, :preparing, :pending] => :running do |commit_status|
commit_status.started_at = Time.now
end
@@ -180,6 +183,10 @@ class CommitStatus < ActiveRecord::Base
false
end
+ def any_unmet_prerequisites?
+ false
+ end
+
def auto_canceled?
canceled? && auto_canceled_by_id?
end
diff --git a/app/models/commit_status_enums.rb b/app/models/commit_status_enums.rb
index 152105d9429..45e08fa18fe 100644
--- a/app/models/commit_status_enums.rb
+++ b/app/models/commit_status_enums.rb
@@ -14,7 +14,8 @@ module CommitStatusEnums
runner_unsupported: 6,
stale_schedule: 7,
job_execution_timeout: 8,
- archived_failure: 9
+ archived_failure: 9,
+ unmet_prerequisites: 10
}
end
end
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 1a8570b80c3..15d8d58b9b5 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -7,6 +7,7 @@
# cache_markdown_field :foo
# cache_markdown_field :bar
# cache_markdown_field :baz, pipeline: :single_line
+# cache_markdown_field :baz, whitelisted: true
#
# Corresponding foo_html, bar_html and baz_html fields should exist.
module CacheMarkdownField
@@ -37,7 +38,15 @@ module CacheMarkdownField
end
def html_fields
- markdown_fields.map {|field| html_field(field) }
+ markdown_fields.map { |field| html_field(field) }
+ end
+
+ def html_fields_whitelisted
+ markdown_fields.each_with_object([]) do |field, fields|
+ if @data[field].fetch(:whitelisted, false)
+ fields << html_field(field)
+ end
+ end
end
end
@@ -149,13 +158,18 @@ module CacheMarkdownField
alias_method :attributes_before_markdown_cache, :attributes
def attributes
attrs = attributes_before_markdown_cache
+ html_fields = cached_markdown_fields.html_fields
+ whitelisted = cached_markdown_fields.html_fields_whitelisted
+ exclude_fields = html_fields - whitelisted
- attrs.delete('cached_markdown_version')
-
- cached_markdown_fields.html_fields.each do |field|
+ exclude_fields.each do |field|
attrs.delete(field)
end
+ if whitelisted.empty?
+ attrs.delete('cached_markdown_version')
+ end
+
attrs
end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 0d2be4c61ab..8882f48c281 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -5,14 +5,14 @@ module HasStatus
DEFAULT_STATUS = 'created'.freeze
BLOCKED_STATUS = %w[manual scheduled].freeze
- AVAILABLE_STATUSES = %w[created pending running success failed canceled skipped manual scheduled].freeze
+ AVAILABLE_STATUSES = %w[created preparing pending running success failed canceled skipped manual scheduled].freeze
STARTED_STATUSES = %w[running success failed skipped manual scheduled].freeze
- ACTIVE_STATUSES = %w[pending running].freeze
+ ACTIVE_STATUSES = %w[preparing pending running].freeze
COMPLETED_STATUSES = %w[success failed canceled skipped].freeze
- ORDERED_STATUSES = %w[failed pending running manual scheduled canceled success skipped created].freeze
+ ORDERED_STATUSES = %w[failed preparing pending running manual scheduled canceled success skipped created].freeze
STATUSES_ENUM = { created: 0, pending: 1, running: 2, success: 3,
failed: 4, canceled: 5, skipped: 6, manual: 7,
- scheduled: 8 }.freeze
+ scheduled: 8, preparing: 9 }.freeze
UnknownStatusError = Class.new(StandardError)
@@ -26,6 +26,7 @@ module HasStatus
success = scope_relevant.success.select('count(*)').to_sql
manual = scope_relevant.manual.select('count(*)').to_sql
scheduled = scope_relevant.scheduled.select('count(*)').to_sql
+ preparing = scope_relevant.preparing.select('count(*)').to_sql
pending = scope_relevant.pending.select('count(*)').to_sql
running = scope_relevant.running.select('count(*)').to_sql
skipped = scope_relevant.skipped.select('count(*)').to_sql
@@ -37,12 +38,14 @@ module HasStatus
WHEN (#{builds})=(#{skipped}) THEN 'skipped'
WHEN (#{builds})=(#{success}) THEN 'success'
WHEN (#{builds})=(#{created}) THEN 'created'
+ WHEN (#{builds})=(#{preparing}) THEN 'preparing'
WHEN (#{builds})=(#{success})+(#{skipped}) THEN 'success'
WHEN (#{builds})=(#{success})+(#{skipped})+(#{canceled}) THEN 'canceled'
WHEN (#{builds})=(#{created})+(#{skipped})+(#{pending}) THEN 'pending'
WHEN (#{running})+(#{pending})>0 THEN 'running'
WHEN (#{manual})>0 THEN 'manual'
WHEN (#{scheduled})>0 THEN 'scheduled'
+ WHEN (#{preparing})>0 THEN 'preparing'
WHEN (#{created})>0 THEN 'running'
ELSE 'failed'
END)"
@@ -70,6 +73,7 @@ module HasStatus
state_machine :status, initial: :created do
state :created, value: 'created'
+ state :preparing, value: 'preparing'
state :pending, value: 'pending'
state :running, value: 'running'
state :failed, value: 'failed'
@@ -81,6 +85,7 @@ module HasStatus
end
scope :created, -> { where(status: 'created') }
+ scope :preparing, -> { where(status: 'preparing') }
scope :relevant, -> { where(status: AVAILABLE_STATUSES - ['created']) }
scope :running, -> { where(status: 'running') }
scope :pending, -> { where(status: 'pending') }
@@ -90,14 +95,14 @@ module HasStatus
scope :skipped, -> { where(status: 'skipped') }
scope :manual, -> { where(status: 'manual') }
scope :scheduled, -> { where(status: 'scheduled') }
- scope :alive, -> { where(status: [:created, :pending, :running]) }
+ scope :alive, -> { where(status: [:created, :preparing, :pending, :running]) }
scope :created_or_pending, -> { where(status: [:created, :pending]) }
scope :running_or_pending, -> { where(status: [:running, :pending]) }
scope :finished, -> { where(status: [:success, :failed, :canceled]) }
scope :failed_or_canceled, -> { where(status: [:failed, :canceled]) }
scope :cancelable, -> do
- where(status: [:running, :pending, :created, :scheduled])
+ where(status: [:running, :preparing, :pending, :created, :scheduled])
end
end
diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb
index f5bb559ceda..8c769be0489 100644
--- a/app/models/concerns/token_authenticatable.rb
+++ b/app/models/concerns/token_authenticatable.rb
@@ -26,34 +26,41 @@ module TokenAuthenticatable
end
end
- define_method(token_field) do
+ mod = token_authenticatable_module
+
+ mod.define_method(token_field) do
strategy.get_token(self)
end
- define_method("set_#{token_field}") do |token|
+ mod.define_method("set_#{token_field}") do |token|
strategy.set_token(self, token)
end
- define_method("ensure_#{token_field}") do
+ mod.define_method("ensure_#{token_field}") do
strategy.ensure_token(self)
end
# Returns a token, but only saves when the database is in read & write mode
- define_method("ensure_#{token_field}!") do
+ mod.define_method("ensure_#{token_field}!") do
strategy.ensure_token!(self)
end
# Resets the token, but only saves when the database is in read & write mode
- define_method("reset_#{token_field}!") do
+ mod.define_method("reset_#{token_field}!") do
strategy.reset_token!(self)
end
- define_method("#{token_field}_matches?") do |other_token|
+ mod.define_method("#{token_field}_matches?") do |other_token|
token = read_attribute(token_field)
token.present? && ActiveSupport::SecurityUtils.variable_size_secure_compare(other_token, token)
end
end
+ def token_authenticatable_module
+ @token_authenticatable_module ||=
+ const_set(:TokenAuthenticatable, Module.new).tap(&method(:include))
+ end
+
def token_authenticatable_fields
@token_authenticatable_fields ||= []
end
diff --git a/app/models/deployment.rb b/app/models/deployment.rb
index 811e623b7f7..428edfd88de 100644
--- a/app/models/deployment.rb
+++ b/app/models/deployment.rb
@@ -78,6 +78,10 @@ class Deployment < ActiveRecord::Base
Commit.truncate_sha(sha)
end
+ def cluster
+ project.deployment_platform(environment: environment.name)&.cluster
+ end
+
def last?
self == environment.last_deployment
end
diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb
index 805092e527a..87755cf3f3d 100644
--- a/app/models/diff_note.rb
+++ b/app/models/diff_note.rb
@@ -88,7 +88,7 @@ class DiffNote < Note
end
def banzai_render_context(field)
- super.merge(suggestions_filter_enabled: supports_suggestion?)
+ super.merge(project: project, suggestions_filter_enabled: supports_suggestion?)
end
private
diff --git a/app/models/email.rb b/app/models/email.rb
index 3ce6e792fa8..7c33c5c7e64 100644
--- a/app/models/email.rb
+++ b/app/models/email.rb
@@ -7,7 +7,7 @@ class Email < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
- validates :email, presence: true, uniqueness: true, email: true
+ validates :email, presence: true, uniqueness: true, devise_email: true
validate :unique_email, if: ->(email) { email.email_changed? }
scope :confirmed, -> { where.not(confirmed_at: nil) }
diff --git a/app/models/group.rb b/app/models/group.rb
index 495bfe04499..c77586c4cdc 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -56,7 +56,7 @@ class Group < Namespace
validates :two_factor_grace_period, presence: true, numericality: { greater_than_or_equal_to: 0 }
- add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption) ? :optional : :required }
+ add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required }
after_create :post_create_hook
after_destroy :post_destroy_hook
diff --git a/app/models/label.rb b/app/models/label.rb
index 1c3db3eb35d..96bdb7f17c5 100644
--- a/app/models/label.rb
+++ b/app/models/label.rb
@@ -126,6 +126,13 @@ class Label < ActiveRecord::Base
fuzzy_search(query, [:title, :description])
end
+ # Override Gitlab::SQL::Pattern.min_chars_for_partial_matching as
+ # label queries are never global, and so will not use a trigram
+ # index. That means we can have just one character in the LIKE.
+ def self.min_chars_for_partial_matching
+ 1
+ end
+
def open_issues_count(user = nil)
issues_count(user, state: 'opened')
end
diff --git a/app/models/member.rb b/app/models/member.rb
index 8e071a8ff21..5dbc0c2eec9 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -28,7 +28,7 @@ class Member < ActiveRecord::Base
presence: {
if: :invite?
},
- email: {
+ devise_email: {
allow_nil: true
},
uniqueness: {
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index acf80addc6a..5f6d5095bcc 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -203,6 +203,22 @@ class MergeRequest < ActiveRecord::Base
'!'
end
+ # Returns the top 100 target branches
+ #
+ # The returned value is a Array containing branch names
+ # sort by updated_at of merge request:
+ #
+ # ['master', 'develop', 'production']
+ #
+ # limit - The maximum number of target branch to return.
+ def self.recent_target_branches(limit: 100)
+ group(:target_branch)
+ .select(:target_branch)
+ .reorder('MAX(merge_requests.updated_at) DESC')
+ .limit(limit)
+ .pluck(:target_branch)
+ end
+
def rebase_in_progress?
strong_memoize(:rebase_in_progress) do
# The source project can be deleted
@@ -216,7 +232,7 @@ class MergeRequest < ActiveRecord::Base
# branch head commit, for example checking if a merge request can be merged.
# For more information check: https://gitlab.com/gitlab-org/gitlab-ce/issues/40004
def actual_head_pipeline
- head_pipeline&.sha == diff_head_sha ? head_pipeline : nil
+ head_pipeline&.matches_sha_or_source_sha?(diff_head_sha) ? head_pipeline : nil
end
def merge_pipeline
@@ -1133,12 +1149,18 @@ class MergeRequest < ActiveRecord::Base
diverged_commits_count > 0
end
- def all_pipelines(shas: all_commit_shas)
+ def all_pipelines
return Ci::Pipeline.none unless source_project
- @all_pipelines ||=
- source_project.ci_pipelines
- .for_merge_request(self, source_branch, all_commit_shas)
+ shas = all_commit_shas
+
+ strong_memoize(:all_pipelines) do
+ Ci::Pipeline.from_union(
+ [source_project.ci_pipelines.merge_request_pipelines(self, shas),
+ source_project.ci_pipelines.detached_merge_request_pipelines(self, shas),
+ source_project.ci_pipelines.triggered_for_branch(source_branch).for_sha(shas)],
+ remove_duplicates: false).sort_by_merge_request_pipelines
+ end
end
def update_head_pipeline
@@ -1373,8 +1395,7 @@ class MergeRequest < ActiveRecord::Base
private
def find_actual_head_pipeline
- source_project&.ci_pipelines
- &.latest_for_merge_request(self, source_branch, diff_head_sha)
+ all_pipelines.for_sha_or_source_sha(diff_head_sha).first
end
def source_project_variables
diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb
index 351a662ae83..98db1bf7de7 100644
--- a/app/models/merge_request_diff.rb
+++ b/app/models/merge_request_diff.rb
@@ -286,18 +286,21 @@ class MergeRequestDiff < ActiveRecord::Base
return yield(@external_diff_file) if @external_diff_file
external_diff.open do |file|
- begin
- @external_diff_file = file
+ @external_diff_file = file
- yield(@external_diff_file)
- ensure
- @external_diff_file = nil
- end
+ yield(@external_diff_file)
+ ensure
+ @external_diff_file = nil
end
end
private
+ def encode_in_base64?(diff_text)
+ (diff_text.encoding == Encoding::BINARY && !diff_text.ascii_only?) ||
+ diff_text.include?("\0")
+ end
+
def create_merge_request_diff_files(diffs)
rows =
if has_attribute?(:external_diff) && Gitlab.config.external_diffs.enabled
@@ -350,7 +353,7 @@ class MergeRequestDiff < ActiveRecord::Base
diff_hash.tap do |hash|
diff_text = hash[:diff]
- if diff_text.encoding == Encoding::BINARY && !diff_text.ascii_only?
+ if encode_in_base64?(diff_text)
hash[:binary] = true
hash[:diff] = [diff_text].pack('m0')
end
diff --git a/app/models/merge_request_diff_file.rb b/app/models/merge_request_diff_file.rb
index e8d936e265c..16ec4ed470f 100644
--- a/app/models/merge_request_diff_file.rb
+++ b/app/models/merge_request_diff_file.rb
@@ -23,6 +23,6 @@ class MergeRequestDiffFile < ActiveRecord::Base
super
end
- binary? ? content.unpack('m0').first : content
+ binary? ? content.unpack1('m0') : content
end
end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index d6f94cad1fb..a3831ae3fa8 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -37,6 +37,7 @@ class Milestone < ActiveRecord::Base
scope :active, -> { with_state(:active) }
scope :closed, -> { with_state(:closed) }
scope :for_projects, -> { where(group: nil).includes(:project) }
+ scope :started, -> { active.where('milestones.start_date <= CURRENT_DATE') }
scope :for_projects_and_groups, -> (projects, groups) do
projects = projects.compact if projects.is_a? Array
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index a5c479bdc0c..dea34e812ca 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -11,6 +11,7 @@ class Namespace < ApplicationRecord
include IgnorableColumn
include FeatureGate
include FromUnion
+ include Gitlab::Utils::StrongMemoize
ignore_column :deleted_at
@@ -267,6 +268,22 @@ class Namespace < ApplicationRecord
owner.refresh_authorized_projects
end
+ def auto_devops_enabled?
+ first_auto_devops_config[:status]
+ end
+
+ def first_auto_devops_config
+ return { scope: :group, status: auto_devops_enabled } unless auto_devops_enabled.nil?
+
+ strong_memoize(:first_auto_devops_config) do
+ if has_parent?
+ parent.first_auto_devops_config
+ else
+ { scope: :instance, status: Gitlab::CurrentSettings.auto_devops_enabled? }
+ end
+ end
+ end
+
private
def path_or_parent_changed?
diff --git a/app/models/note.rb b/app/models/note.rb
index 1578ae9c4cc..2c9980b1a0d 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -313,6 +313,14 @@ class Note < ActiveRecord::Base
!system?
end
+ # Since we're using `updated_at` as `last_edited_at`, it could be touched by transforming / resolving a note.
+ # This makes sure it is only marked as edited when the note body is updated.
+ def edited?
+ return false if updated_by.blank?
+
+ super
+ end
+
def cross_reference_not_visible_for?(user)
cross_reference? && !all_referenced_mentionables_allowed?(user)
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 4cc13f372c1..611c64c8f49 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -38,7 +38,6 @@ class Project < ActiveRecord::Base
BoardLimitExceeded = Class.new(StandardError)
STATISTICS_ATTRIBUTE = 'repositories_count'.freeze
- NUMBER_OF_PERMITTED_BOARDS = 1
UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze
# Hashed Storage versions handle rolling out new storage to project and dependents models:
# nil: legacy
@@ -85,7 +84,7 @@ class Project < ActiveRecord::Base
default_value_for :snippets_enabled, gitlab_config_features.snippets
default_value_for :only_allow_merge_if_all_discussions_are_resolved, false
- add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption) ? :optional : :required }
+ add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required }
before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? }
@@ -137,7 +136,7 @@ class Project < ActiveRecord::Base
alias_attribute :parent_id, :namespace_id
has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event'
- has_many :boards, before_add: :validate_board_limit
+ has_many :boards
# Project services
has_one :campfire_service
@@ -631,12 +630,21 @@ class Project < ActiveRecord::Base
end
def has_auto_devops_implicitly_enabled?
- auto_devops&.enabled.nil? &&
- (Gitlab::CurrentSettings.auto_devops_enabled? || Feature.enabled?(:force_autodevops_on_by_default, self))
+ auto_devops_config = first_auto_devops_config
+
+ auto_devops_config[:scope] != :project && auto_devops_config[:status]
end
def has_auto_devops_implicitly_disabled?
- auto_devops&.enabled.nil? && !(Gitlab::CurrentSettings.auto_devops_enabled? || Feature.enabled?(:force_autodevops_on_by_default, self))
+ auto_devops_config = first_auto_devops_config
+
+ auto_devops_config[:scope] != :project && !auto_devops_config[:status]
+ end
+
+ def first_auto_devops_config
+ return namespace.first_auto_devops_config if auto_devops&.enabled.nil?
+
+ { scope: :project, status: auto_devops&.enabled || Feature.enabled?(:force_autodevops_on_by_default, self) }
end
def daily_statistics_enabled?
@@ -1200,11 +1208,9 @@ class Project < ActiveRecord::Base
def repo_exists?
strong_memoize(:repo_exists) do
- begin
- repository.exists?
- rescue
- false
- end
+ repository.exists?
+ rescue
+ false
end
end
@@ -1378,6 +1384,7 @@ class Project < ActiveRecord::Base
repository.raw_repository.write_ref('HEAD', "refs/heads/#{branch}")
repository.copy_gitattributes(branch)
repository.after_change_head
+ ProjectCacheWorker.perform_async(self.id, [], [:commit_count])
reload_default_branch
else
errors.add(:base, "Could not change HEAD: branch '#{branch}' does not exist")
@@ -2185,17 +2192,6 @@ class Project < ActiveRecord::Base
"projects/#{id}/pushes_since_gc"
end
- # Similar to the normal callbacks that hook into the life cycle of an
- # Active Record object, you can also define callbacks that get triggered
- # when you add an object to an association collection. If any of these
- # callbacks throw an exception, the object will not be added to the
- # collection. Before you add a new board to the boards collection if you
- # already have 1, 2, or n it will fail, but it if you have 0 that is lower
- # than the number of permitted boards per project it won't fail.
- def validate_board_limit(board)
- raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS
- end
-
def update_project_statistics
stats = statistics || build_statistics
stats.update(namespace_id: namespace_id)
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index f7064d5aaea..81302c516c2 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -205,12 +205,10 @@ class JiraService < IssueTrackerService
# if any transition fails it will log the error message and stop the transition sequence
def transition_issue(issue)
jira_issue_transition_id.scan(Gitlab::Regex.jira_transition_id_regex).each do |transition_id|
- begin
- issue.transitions.build.save!(transition: { id: transition_id })
- rescue => error
- log_error("Issue transition failed", error: error.message, client_url: client_url)
- return false
- end
+ issue.transitions.build.save!(transition: { id: transition_id })
+ rescue => error
+ log_error("Issue transition failed", error: error.message, client_url: client_url)
+ return false
end
end
diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb
index 4cf3a7f3d84..f650dbd3726 100644
--- a/app/models/project_services/kubernetes_service.rb
+++ b/app/models/project_services/kubernetes_service.rb
@@ -131,8 +131,8 @@ class KubernetesService < DeploymentService
# short time later
def terminals(environment)
with_reactive_cache do |data|
- pods = filter_by_label(data[:pods], app: environment.slug)
- terminals = pods.flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) }
+ pods = filter_by_project_environment(data[:pods], project.full_path_slug, environment.slug)
+ terminals = pods.flat_map { |pod| terminals_for_pod(api_url, actual_namespace, pod) }.compact
terminals.each { |terminal| add_terminal_auth(terminal, terminal_auth) }
end
end
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index c43bd45a62f..6ea0716c192 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -183,7 +183,7 @@ class ProjectWiki
end
def commit_details(action, message = nil, title = nil)
- commit_message = message || default_message(action, title)
+ commit_message = message.presence || default_message(action, title)
git_user = Gitlab::Git::User.from_gitlab(@user)
Gitlab::Git::Wiki::CommitDetails.new(@user.id,
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 851175a61b7..ff355295862 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -265,16 +265,14 @@ class Repository
# to avoid unnecessary syncing.
def keep_around(*shas)
shas.each do |sha|
- begin
- next unless sha.present? && commit_by(oid: sha)
+ next unless sha.present? && commit_by(oid: sha)
- next if kept_around?(sha)
+ next if kept_around?(sha)
- # This will still fail if the file is corrupted (e.g. 0 bytes)
- raw_repository.write_ref(keep_around_ref_name(sha), sha)
- rescue Gitlab::Git::CommandError => ex
- Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}"
- end
+ # This will still fail if the file is corrupted (e.g. 0 bytes)
+ raw_repository.write_ref(keep_around_ref_name(sha), sha)
+ rescue Gitlab::Git::CommandError => ex
+ Rails.logger.error "Unable to create keep-around reference for repository #{disk_path}: #{ex}"
end
end
@@ -534,10 +532,9 @@ class Repository
end
def root_ref
- # When the repo does not exist, or there is no root ref, we raise this error so no data is cached.
- raw_repository&.root_ref or raise Gitlab::Git::Repository::NoRepository # rubocop:disable Style/AndOr
+ raw_repository&.root_ref
end
- cache_method :root_ref
+ cache_method_asymmetrically :root_ref
# Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/314
def exists?
diff --git a/app/models/user.rb b/app/models/user.rb
index 778c9e631bd..d2be26370ff 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -162,9 +162,9 @@ class User < ApplicationRecord
validates :name, presence: true
validates :email, confirmation: true
validates :notification_email, presence: true
- validates :notification_email, email: true, if: ->(user) { user.notification_email != user.email }
- validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true
- validates :commit_email, email: true, allow_nil: true, if: ->(user) { user.commit_email != user.email }
+ validates :notification_email, devise_email: true, if: ->(user) { user.notification_email != user.email }
+ validates :public_email, presence: true, uniqueness: true, devise_email: true, allow_blank: true
+ validates :commit_email, devise_email: true, allow_nil: true, if: ->(user) { user.commit_email != user.email }
validates :bio, length: { maximum: 255 }, allow_blank: true
validates :projects_limit,
presence: true,
@@ -917,6 +917,10 @@ class User < ApplicationRecord
DeployKey.unscoped.in_projects(authorized_projects.pluck(:id)).distinct(:id)
end
+ def highest_role
+ members.maximum(:access_level) || Gitlab::Access::NO_ACCESS
+ end
+
def accessible_deploy_keys
@accessible_deploy_keys ||= begin
key_ids = project_deploy_keys.pluck(:id)
diff --git a/app/models/user_interacted_project.rb b/app/models/user_interacted_project.rb
index ae6778e49be..5fc59b274f5 100644
--- a/app/models/user_interacted_project.rb
+++ b/app/models/user_interacted_project.rb
@@ -26,16 +26,14 @@ class UserInteractedProject < ActiveRecord::Base
cached_exists?(attributes) do
transaction(requires_new: true) do
- begin
- where(attributes).select(1).first || create!(attributes)
- true # not caching the whole record here for now
- rescue ActiveRecord::RecordNotUnique
- # Note, above queries are not atomic and prone
- # to race conditions (similar like #find_or_create!).
- # In the case where we hit this, the record we want
- # already exists - shortcut and return.
- true
- end
+ where(attributes).select(1).first || create!(attributes)
+ true # not caching the whole record here for now
+ rescue ActiveRecord::RecordNotUnique
+ # Note, above queries are not atomic and prone
+ # to race conditions (similar like #find_or_create!).
+ # In the case where we hit this, the record we want
+ # already exists - shortcut and return.
+ true
end
end
end
diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb
index e74e5f008d7..db49d3bed9c 100644
--- a/app/policies/group_policy.rb
+++ b/app/policies/group_policy.rb
@@ -26,7 +26,7 @@ class GroupPolicy < BasePolicy
condition(:can_change_parent_share_with_group_lock) { can?(:change_share_with_group_lock, @subject.parent) }
condition(:has_projects) do
- GroupProjectsFinder.new(group: @subject, current_user: @user, options: { include_subgroups: true }).execute.any?
+ GroupProjectsFinder.new(group: @subject, current_user: @user, options: { include_subgroups: true, only_owned: true }).execute.any?
end
condition(:has_clusters, scope: :subject) { clusterable_has_clusters? }
@@ -55,6 +55,7 @@ class GroupPolicy < BasePolicy
rule { has_projects }.policy do
enable :read_list
enable :read_label
+ enable :read_group
end
rule { has_access }.enable :read_namespace
diff --git a/app/policies/identity_provider_policy.rb b/app/policies/identity_provider_policy.rb
new file mode 100644
index 00000000000..d34cdd5bdd4
--- /dev/null
+++ b/app/policies/identity_provider_policy.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+class IdentityProviderPolicy < BasePolicy
+ desc "Provider is SAML or CAS3"
+ condition(:protected_provider, scope: :subject, score: 0) { %w(saml cas3).include?(@subject.to_s) }
+
+ rule { anonymous }.prevent_all
+
+ rule { default }.policy do
+ enable :unlink
+ enable :link
+ end
+
+ rule { protected_provider }.prevent(:unlink)
+end
diff --git a/app/policies/issuable_policy.rb b/app/policies/issuable_policy.rb
index ecb2797d1d9..537319addc2 100644
--- a/app/policies/issuable_policy.rb
+++ b/app/policies/issuable_policy.rb
@@ -17,6 +17,7 @@ class IssuablePolicy < BasePolicy
enable :reopen_issue
enable :read_merge_request
enable :update_merge_request
+ enable :reopen_merge_request
end
rule { locked & ~is_project_member }.policy do
diff --git a/app/policies/merge_request_policy.rb b/app/policies/merge_request_policy.rb
index a2950951d03..a3692857ff4 100644
--- a/app/policies/merge_request_policy.rb
+++ b/app/policies/merge_request_policy.rb
@@ -1,4 +1,7 @@
# frozen_string_literal: true
class MergeRequestPolicy < IssuablePolicy
+ rule { locked }.policy do
+ prevent :reopen_merge_request
+ end
end
diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb
index cf257ed47c8..9f9f5230040 100644
--- a/app/policies/project_policy.rb
+++ b/app/policies/project_policy.rb
@@ -231,6 +231,7 @@ class ProjectPolicy < BasePolicy
enable :admin_merge_request
enable :admin_milestone
enable :update_merge_request
+ enable :reopen_merge_request
enable :create_commit_status
enable :update_commit_status
enable :create_build
diff --git a/app/presenters/ci/pipeline_presenter.rb b/app/presenters/ci/pipeline_presenter.rb
index 57daf04efc6..1c1347c5a57 100644
--- a/app/presenters/ci/pipeline_presenter.rb
+++ b/app/presenters/ci/pipeline_presenter.rb
@@ -3,6 +3,7 @@
module Ci
class PipelinePresenter < Gitlab::View::Presenter::Delegated
include Gitlab::Utils::StrongMemoize
+ include ActionView::Helpers::UrlHelper
# We use a class method here instead of a constant, allowing EE to redefine
# the returned `Hash` more easily.
@@ -32,5 +33,57 @@ module Ci
"Pipeline is redundant and is auto-canceled by Pipeline ##{auto_canceled_by_id}"
end
end
+
+ def ref_text
+ if pipeline.detached_merge_request_pipeline?
+ _("for %{link_to_merge_request} with %{link_to_merge_request_source_branch}").html_safe % { link_to_merge_request: link_to_merge_request, link_to_merge_request_source_branch: link_to_merge_request_source_branch }
+ elsif pipeline.merge_request_pipeline?
+ _("for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}").html_safe % { link_to_merge_request: link_to_merge_request, link_to_merge_request_source_branch: link_to_merge_request_source_branch, link_to_merge_request_target_branch: link_to_merge_request_target_branch }
+ elsif pipeline.ref
+ if pipeline.ref_exists?
+ _("for %{link_to_pipeline_ref}").html_safe % { link_to_pipeline_ref: link_to_pipeline_ref }
+ else
+ _("for %{ref}") % { ref: content_tag(:span, pipeline.ref, class: 'ref-name') }
+ end
+ end
+ end
+
+ def link_to_pipeline_ref
+ link_to(pipeline.ref,
+ project_commits_path(pipeline.project, pipeline.ref),
+ class: "ref-name")
+ end
+
+ def link_to_merge_request
+ return unless merge_request_presenter
+
+ link_to(merge_request_presenter.to_reference,
+ project_merge_request_path(merge_request_presenter.project, merge_request_presenter),
+ class: 'mr-iid')
+ end
+
+ def link_to_merge_request_source_branch
+ return unless merge_request_presenter
+
+ link_to(merge_request_presenter.source_branch,
+ merge_request_presenter.source_branch_commits_path,
+ class: 'ref-name')
+ end
+
+ def link_to_merge_request_target_branch
+ return unless merge_request_presenter
+
+ link_to(merge_request_presenter.target_branch,
+ merge_request_presenter.target_branch_commits_path,
+ class: 'ref-name')
+ end
+
+ private
+
+ def merge_request_presenter
+ return unless pipeline.triggered_by_merge_request?
+
+ @merge_request_presenter ||= pipeline.merge_request.present(current_user: current_user)
+ end
end
end
diff --git a/app/presenters/commit_status_presenter.rb b/app/presenters/commit_status_presenter.rb
index 0cd77da6303..28a25c8b7a3 100644
--- a/app/presenters/commit_status_presenter.rb
+++ b/app/presenters/commit_status_presenter.rb
@@ -11,7 +11,8 @@ class CommitStatusPresenter < Gitlab::View::Presenter::Delegated
runner_unsupported: 'Your runner is outdated, please upgrade your runner',
stale_schedule: 'Delayed job could not be executed by some reason, please try again',
job_execution_timeout: 'The script exceeded the maximum execution time set for the job',
- archived_failure: 'The job is archived and cannot be run'
+ archived_failure: 'The job is archived and cannot be run',
+ unmet_prerequisites: 'The job failed to complete prerequisite tasks'
}.freeze
private_constant :CALLOUT_FAILURE_MESSAGES
diff --git a/app/presenters/merge_request_presenter.rb b/app/presenters/merge_request_presenter.rb
index af164858408..284b1ad9b55 100644
--- a/app/presenters/merge_request_presenter.rb
+++ b/app/presenters/merge_request_presenter.rb
@@ -104,6 +104,12 @@ class MergeRequestPresenter < Gitlab::View::Presenter::Delegated
end
end
+ def source_branch_commits_path
+ if source_branch_exists?
+ project_commits_path(source_project, source_branch)
+ end
+ end
+
def source_branch_path
if source_branch_exists?
project_branch_path(source_project, source_branch)
diff --git a/app/presenters/project_presenter.rb b/app/presenters/project_presenter.rb
index 000b7c433a2..161eebcfb3f 100644
--- a/app/presenters/project_presenter.rb
+++ b/app/presenters/project_presenter.rb
@@ -42,11 +42,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
def empty_repo_statistics_anchors
[
- license_anchor_data,
- commits_anchor_data,
- branches_anchor_data,
- tags_anchor_data,
- files_anchor_data
+ license_anchor_data
].compact.select { |item| item.is_link }
end
@@ -55,9 +51,7 @@ class ProjectPresenter < Gitlab::View::Presenter::Delegated
new_file_anchor_data,
readme_anchor_data,
changelog_anchor_data,
- contribution_guide_anchor_data,
- autodevops_anchor_data,
- kubernetes_cluster_anchor_data
+ contribution_guide_anchor_data
].compact.reject { |item| item.is_link }
end
diff --git a/app/serializers/detailed_status_entity.rb b/app/serializers/detailed_status_entity.rb
index da994d78286..4f23ef0ed82 100644
--- a/app/serializers/detailed_status_entity.rb
+++ b/app/serializers/detailed_status_entity.rb
@@ -9,16 +9,14 @@ class DetailedStatusEntity < Grape::Entity
expose :details_path
expose :illustration do |status|
- begin
- illustration = {
- image: ActionController::Base.helpers.image_path(status.illustration[:image])
- }
- illustration = status.illustration.merge(illustration)
+ illustration = {
+ image: ActionController::Base.helpers.image_path(status.illustration[:image])
+ }
+ illustration = status.illustration.merge(illustration)
- illustration
- rescue NotImplementedError
- # ignored
- end
+ illustration
+ rescue NotImplementedError
+ # ignored
end
expose :favicon do |status|
diff --git a/app/serializers/diff_file_entity.rb b/app/serializers/diff_file_entity.rb
index 13711070a46..066e30cd3bb 100644
--- a/app/serializers/diff_file_entity.rb
+++ b/app/serializers/diff_file_entity.rb
@@ -57,7 +57,7 @@ class DiffFileEntity < DiffFileBaseEntity
diff_file.diff_lines_for_serializer
end
- expose :is_fully_expanded, if: -> (diff_file, _) { Feature.enabled?(:expand_diff_full_file) && diff_file.text? } do |diff_file|
+ expose :is_fully_expanded, if: -> (diff_file, _) { Feature.enabled?(:expand_diff_full_file, default_enabled: true) && diff_file.text? } do |diff_file|
diff_file.fully_expanded?
end
diff --git a/app/serializers/environment_entity.rb b/app/serializers/environment_entity.rb
index 76248e6470e..8258135da4e 100644
--- a/app/serializers/environment_entity.rb
+++ b/app/serializers/environment_entity.rb
@@ -12,7 +12,7 @@ class EnvironmentEntity < Grape::Entity
expose :last_deployment, using: DeploymentEntity
expose :stop_action_available?, as: :has_stop_action
- expose :metrics_path, if: -> (environment, _) { environment.has_metrics? } do |environment|
+ expose :metrics_path, if: -> (*) { environment.has_metrics? } do |environment|
metrics_project_environment_path(environment.project, environment)
end
diff --git a/app/serializers/merge_request_for_pipeline_entity.rb b/app/serializers/merge_request_for_pipeline_entity.rb
index 7779ddfd65a..17a5c4ebbf9 100644
--- a/app/serializers/merge_request_for_pipeline_entity.rb
+++ b/app/serializers/merge_request_for_pipeline_entity.rb
@@ -11,7 +11,7 @@ class MergeRequestForPipelineEntity < Grape::Entity
expose :title
expose :source_branch
- expose :source_branch_path
+ expose :source_branch_commits_path, as: :source_branch_path
expose :target_branch
- expose :target_branch_path
+ expose :target_branch_commits_path, as: :target_branch_path
end
diff --git a/app/serializers/pipeline_entity.rb b/app/serializers/pipeline_entity.rb
index 5ac1e590d39..fba72410217 100644
--- a/app/serializers/pipeline_entity.rb
+++ b/app/serializers/pipeline_entity.rb
@@ -28,7 +28,8 @@ class PipelineEntity < Grape::Entity
expose :can_retry?, as: :retryable
expose :can_cancel?, as: :cancelable
expose :failure_reason?, as: :failure_reason
- expose :detached_merge_request_pipeline?, as: :detached
+ expose :detached_merge_request_pipeline?, as: :detached_merge_request_pipeline
+ expose :merge_request_pipeline?, as: :merge_request_pipeline
end
expose :details do
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index e95ba09c006..707caee482c 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -116,7 +116,7 @@ module Auth
build_can_pull?(requested_project) || user_can_pull?(requested_project) || deploy_token_can_pull?(requested_project)
when 'push'
build_can_push?(requested_project) || user_can_push?(requested_project)
- when '*'
+ when '*', 'delete'
user_can_admin?(requested_project)
else
false
diff --git a/app/services/ci/prepare_build_service.rb b/app/services/ci/prepare_build_service.rb
new file mode 100644
index 00000000000..32f11438b79
--- /dev/null
+++ b/app/services/ci/prepare_build_service.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Ci
+ class PrepareBuildService
+ attr_reader :build
+
+ def initialize(build)
+ @build = build
+ end
+
+ def execute
+ prerequisites.each(&:complete!)
+
+ unless build.enqueue
+ build.drop!(:unmet_prerequisites)
+ end
+ end
+
+ private
+
+ def prerequisites
+ build.prerequisites
+ end
+ end
+end
diff --git a/app/services/groups/auto_devops_service.rb b/app/services/groups/auto_devops_service.rb
new file mode 100644
index 00000000000..1925e0cc0ea
--- /dev/null
+++ b/app/services/groups/auto_devops_service.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Groups
+ class AutoDevopsService < Groups::BaseService
+ def execute
+ raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_group, group)
+
+ group.update(auto_devops_enabled: auto_devops_enabled)
+ end
+
+ private
+
+ def auto_devops_enabled
+ params[:auto_devops_enabled]
+ end
+ end
+end
diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb
index f6cbe769ef4..f87005bcb6c 100644
--- a/app/services/merge_requests/reopen_service.rb
+++ b/app/services/merge_requests/reopen_service.rb
@@ -3,7 +3,7 @@
module MergeRequests
class ReopenService < MergeRequests::BaseService
def execute(merge_request)
- return merge_request unless can?(current_user, :update_merge_request, merge_request)
+ return merge_request unless can?(current_user, :reopen_merge_request, merge_request)
if merge_request.reopen
create_event(merge_request)
diff --git a/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb b/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb
index 7998976b00a..a9570176e81 100644
--- a/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_download_link_list_service.rb
@@ -42,17 +42,15 @@ module Projects
def parse_response_links(objects_response)
objects_response.each_with_object([]) do |entry, link_list|
- begin
- link = entry.dig('actions', DOWNLOAD_ACTION, 'href')
+ link = entry.dig('actions', DOWNLOAD_ACTION, 'href')
- raise DownloadLinkNotFound unless link
+ raise DownloadLinkNotFound unless link
- link_list << LfsDownloadObject.new(oid: entry['oid'],
- size: entry['size'],
- link: add_credentials(link))
- rescue DownloadLinkNotFound, Addressable::URI::InvalidURIError
- log_error("Link for Lfs Object with oid #{entry['oid']} not found or invalid.")
- end
+ link_list << LfsDownloadObject.new(oid: entry['oid'],
+ size: entry['size'],
+ link: add_credentials(link))
+ rescue DownloadLinkNotFound, Addressable::URI::InvalidURIError
+ log_error("Link for Lfs Object with oid #{entry['oid']} not found or invalid.")
end
end
diff --git a/app/services/projects/lfs_pointers/lfs_download_service.rb b/app/services/projects/lfs_pointers/lfs_download_service.rb
index 398f00a598d..a009f479d5d 100644
--- a/app/services/projects/lfs_pointers/lfs_download_service.rb
+++ b/app/services/projects/lfs_pointers/lfs_download_service.rb
@@ -75,17 +75,15 @@ module Projects
create_tmp_storage_dir
File.open(tmp_filename, 'wb') do |file|
- begin
- yield file
- rescue StandardError => e
- # If the lfs file is successfully downloaded it will be removed
- # when it is added to the project's lfs files.
- # Nevertheless if any excetion raises the file would remain
- # in the file system. Here we ensure to remove it
- File.unlink(file) if File.exist?(file)
-
- raise e
- end
+ yield file
+ rescue StandardError => e
+ # If the lfs file is successfully downloaded it will be removed
+ # when it is added to the project's lfs files.
+ # Nevertheless if any excetion raises the file would remain
+ # in the file system. Here we ensure to remove it
+ File.unlink(file) if File.exist?(file)
+
+ raise e
end
end
diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb
index d6af26d949d..f711839e389 100644
--- a/app/services/search/global_service.rb
+++ b/app/services/search/global_service.rb
@@ -23,7 +23,8 @@ module Search
def allowed_scopes
strong_memoize(:allowed_scopes) do
- %w[issues merge_requests milestones]
+ allowed_scopes = %w[issues merge_requests milestones]
+ allowed_scopes << 'users' if Feature.enabled?(:users_search, default_enabled: true)
end
end
diff --git a/app/services/search/group_service.rb b/app/services/search/group_service.rb
index 34803d005e3..6f3b5f00b86 100644
--- a/app/services/search/group_service.rb
+++ b/app/services/search/group_service.rb
@@ -11,6 +11,12 @@ module Search
@group = group
end
+ def execute
+ Gitlab::GroupSearchResults.new(
+ current_user, projects, group, params[:search], default_project_filter: default_project_filter
+ )
+ end
+
def projects
return Project.none unless group
return @projects if defined? @projects
diff --git a/app/services/search/project_service.rb b/app/services/search/project_service.rb
index f223c8be103..32d5cd7ddb2 100644
--- a/app/services/search/project_service.rb
+++ b/app/services/search/project_service.rb
@@ -16,7 +16,12 @@ module Search
end
def scope
- @scope ||= %w[notes issues merge_requests milestones wiki_blobs commits].delete(params[:scope]) { 'blobs' }
+ @scope ||= begin
+ allowed_scopes = %w[notes issues merge_requests milestones wiki_blobs commits]
+ allowed_scopes << 'users' if Feature.enabled?(:users_search, default_enabled: true)
+
+ allowed_scopes.delete(params[:scope]) { 'blobs' }
+ end
end
end
end
diff --git a/app/validators/devise_email_validator.rb b/app/validators/devise_email_validator.rb
new file mode 100644
index 00000000000..6ca921ca7fa
--- /dev/null
+++ b/app/validators/devise_email_validator.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+# DeviseEmailValidator
+#
+# Custom validator for email formats. It asserts that there are no
+# @ symbols or whitespaces in either the localpart or the domain, and that
+# there is a single @ symbol separating the localpart and the domain.
+#
+# The available options are:
+# - regexp: Email regular expression used to validate email formats as instance of Regexp class.
+# If provided value has different type then a new Rexexp class instance is created using the value.
+# Default: +Devise.email_regexp+
+#
+# Example:
+# class User < ActiveRecord::Base
+# validates :personal_email, devise_email: true
+#
+# validates :public_email, devise_email: { regexp: Devise.email_regexp }
+# end
+class DeviseEmailValidator < ActiveModel::EachValidator
+ DEFAULT_OPTIONS = {
+ regexp: Devise.email_regexp
+ }.freeze
+
+ def initialize(options)
+ options.reverse_merge!(DEFAULT_OPTIONS)
+
+ raise ArgumentError, "Expected 'regexp' argument of type class Regexp" unless options[:regexp].is_a?(Regexp)
+
+ super(options)
+ end
+
+ def validate_each(record, attribute, value)
+ record.errors.add(attribute, :invalid) unless value =~ options[:regexp]
+ end
+end
diff --git a/app/validators/email_validator.rb b/app/validators/email_validator.rb
deleted file mode 100644
index 9459edb7515..00000000000
--- a/app/validators/email_validator.rb
+++ /dev/null
@@ -1,7 +0,0 @@
-# frozen_string_literal: true
-
-class EmailValidator < ActiveModel::EachValidator
- def validate_each(record, attribute, value)
- record.errors.add(attribute, :invalid) unless value =~ Devise.email_regexp
- end
-end
diff --git a/app/validators/sha_validator.rb b/app/validators/sha_validator.rb
index 085fca4d65d..77e7cfa4f6b 100644
--- a/app/validators/sha_validator.rb
+++ b/app/validators/sha_validator.rb
@@ -2,7 +2,7 @@
class ShaValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
- return if value.blank? || value.match(/\A\h{40}\z/)
+ return if value.blank? || Commit.valid_hash?(value)
record.errors.add(attribute, 'is not a valid SHA')
end
diff --git a/app/views/admin/application_settings/_account_and_limit.html.haml b/app/views/admin/application_settings/_account_and_limit.html.haml
index 65a24854583..9ed4bc44aae 100644
--- a/app/views/admin/application_settings/_account_and_limit.html.haml
+++ b/app/views/admin/application_settings/_account_and_limit.html.haml
@@ -6,32 +6,35 @@
.form-check
= f.check_box :gravatar_enabled, class: 'form-check-input'
= f.label :gravatar_enabled, class: 'form-check-label' do
- Gravatar enabled
+ = _('Gravatar enabled')
.form-group
= f.label :default_projects_limit, class: 'label-bold'
= f.number_field :default_projects_limit, class: 'form-control'
.form-group
- = f.label :max_attachment_size, 'Maximum attachment size (MB)', class: 'label-bold'
+ = f.label :max_attachment_size, _('Maximum attachment size (MB)'), class: 'label-bold'
= f.number_field :max_attachment_size, class: 'form-control'
+
+ = render_if_exists 'admin/application_settings/repository_size_limit_setting', form: f
+
.form-group
- = f.label :receive_max_input_size, 'Maximum push size (MB)', class: 'label-light'
+ = f.label :receive_max_input_size, _('Maximum push size (MB)'), class: 'label-light'
= f.number_field :receive_max_input_size, class: 'form-control qa-receive-max-input-size-field'
.form-group
- = f.label :session_expire_delay, 'Session duration (minutes)', class: 'label-light'
+ = f.label :session_expire_delay, _('Session duration (minutes)'), class: 'label-light'
= f.number_field :session_expire_delay, class: 'form-control'
- %span.form-text.text-muted#session_expire_delay_help_block GitLab restart is required to apply changes
+ %span.form-text.text-muted#session_expire_delay_help_block= _('GitLab restart is required to apply changes')
.form-group
- = f.label :user_oauth_applications, 'User OAuth applications', class: 'label-bold'
+ = f.label :user_oauth_applications, _('User OAuth applications'), class: 'label-bold'
.form-check
= f.check_box :user_oauth_applications, class: 'form-check-input'
= f.label :user_oauth_applications, class: 'form-check-label' do
- Allow users to register any application to use GitLab as an OAuth provider
+ = _('Allow users to register any application to use GitLab as an OAuth provider')
.form-group
- = f.label :user_default_external, 'New users set to external', class: 'label-bold'
+ = f.label :user_default_external, _('New users set to external'), class: 'label-bold'
.form-check
= f.check_box :user_default_external, class: 'form-check-input'
= f.label :user_default_external, class: 'form-check-label' do
- Newly registered users will by default be external
+ = _('Newly registered users will by default be external')
.prepend-top-10
= _('Internal users')
= f.text_field :user_default_internal_regex, placeholder: _('Regex pattern'), class: 'form-control prepend-top-5'
@@ -40,10 +43,12 @@
= link_to _('More information'), help_page_path('user/permissions', anchor: 'external-users-permissions'),
target: '_blank'
.form-group
- = f.label :user_show_add_ssh_key_message, 'Prompt users to upload SSH keys', class: 'label-bold'
+ = f.label :user_show_add_ssh_key_message, _('Prompt users to upload SSH keys'), class: 'label-bold'
.form-check
= f.check_box :user_show_add_ssh_key_message, class: 'form-check-input'
= f.label :user_show_add_ssh_key_message, class: 'form-check-label' do
- Inform users without uploaded SSH keys that they can't push over SSH until one is added
+ = _("Inform users without uploaded SSH keys that they can't push over SSH until one is added")
+
+ = render_if_exists 'admin/application_settings/availability_on_namespace_setting', form: f
- = f.submit 'Save changes', class: 'btn btn-success qa-save-changes-button'
+ = f.submit _('Save changes'), class: 'btn btn-success qa-save-changes-button'
diff --git a/app/views/admin/application_settings/_ci_cd.html.haml b/app/views/admin/application_settings/_ci_cd.html.haml
index c99d7e9b8e9..b8c481df0d2 100644
--- a/app/views/admin/application_settings/_ci_cd.html.haml
+++ b/app/views/admin/application_settings/_ci_cd.html.haml
@@ -8,7 +8,7 @@
.form-check
= f.check_box :auto_devops_enabled, class: 'form-check-input'
= f.label :auto_devops_enabled, class: 'form-check-label' do
- Default to Auto DevOps pipeline for all projects
+ = s_('CICD|Default to Auto DevOps pipeline for all projects')
.form-text.text-muted
= s_('CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found.')
= link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank'
@@ -21,34 +21,31 @@
.form-check
= f.check_box :shared_runners_enabled, class: 'form-check-input'
= f.label :shared_runners_enabled, class: 'form-check-label' do
- Enable shared runners for new projects
+ = s_("AdminSettings|Enable shared runners for new projects")
+
+ = render_if_exists 'admin/application_settings/shared_runners_minutes_setting', form: f
+
.form-group
= f.label :shared_runners_text, class: 'label-bold'
= f.text_area :shared_runners_text, class: 'form-control', rows: 4
- .form-text.text-muted Markdown enabled
+ .form-text.text-muted= _("Markdown enabled")
.form-group
- = f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'label-bold'
+ = f.label :max_artifacts_size, _('Maximum artifacts size (MB)'), class: 'label-bold'
= f.number_field :max_artifacts_size, class: 'form-control'
.form-text.text-muted
- Set the maximum file size for each job's artifacts
+ = _("Set the maximum file size for each job's artifacts")
= link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'maximum-artifacts-size')
.form-group
- = f.label :default_artifacts_expire_in, 'Default artifacts expiration', class: 'label-bold'
+ = f.label :default_artifacts_expire_in, _('Default artifacts expiration'), class: 'label-bold'
= f.text_field :default_artifacts_expire_in, class: 'form-control'
.form-text.text-muted
- Set the default expiration time for each job's artifacts.
- 0 for unlimited.
- The default unit is in seconds, but you can define an alternative. For example:
- <code>4 mins 2 sec</code>, <code>2h42min</code>.
+ = _("Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>.").html_safe
= link_to icon('question-circle'), help_page_path('user/admin_area/settings/continuous_integration', anchor: 'default-artifacts-expiration')
.form-group
- = f.label :archive_builds_in_human_readable, 'Archive jobs', class: 'label-bold'
+ = f.label :archive_builds_in_human_readable, _('Archive jobs'), class: 'label-bold'
= f.text_field :archive_builds_in_human_readable, class: 'form-control', placeholder: 'never'
.form-text.text-muted
- Set the duration for which the jobs will be considered as old and expired.
- Once that time passes, the jobs will be archived and no longer able to be
- retried. Make it empty to never expire jobs. It has to be no less than 1 day,
- for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>.
+ = _("Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>.").html_safe
.form-group
.form-check
= f.check_box :protected_ci_variables, class: 'form-check-input'
@@ -57,4 +54,4 @@
.form-text.text-muted
= s_('AdminSettings|When creating a new environment variable it will be protected by default.')
- = f.submit 'Save changes', class: "btn btn-success"
+ = f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_email.html.haml b/app/views/admin/application_settings/_email.html.haml
index 60a6be731ea..3f30c75fbb6 100644
--- a/app/views/admin/application_settings/_email.html.haml
+++ b/app/views/admin/application_settings/_email.html.haml
@@ -6,20 +6,16 @@
.form-check
= f.check_box :email_author_in_body, class: 'form-check-input'
= f.label :email_author_in_body, class: 'form-check-label' do
- Include author name in notification email body
+ = _('Include author name in notification email body')
.form-text.text-muted
- Some email servers do not support overriding the email sender name.
- Enable this option to include the name of the author of the issue,
- merge request or comment in the email body instead.
+ = _('Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead.')
.form-group
.form-check
= f.check_box :html_emails_enabled, class: 'form-check-input'
= f.label :html_emails_enabled, class: 'form-check-label' do
- Enable HTML emails
+ = _('Enable HTML emails')
.form-text.text-muted
- By default GitLab sends emails in HTML and plain text formats so mail
- clients can choose what format to use. Disable this option if you only
- want to send emails in plain text format.
+ = _('By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format.')
.form-group
= f.label :commit_email_hostname, _('Custom hostname (for private commit emails)'), class: 'label-bold'
= f.text_field :commit_email_hostname, class: 'form-control'
@@ -27,4 +23,6 @@
- commit_email_hostname_docs_link = link_to _('Learn more'), help_page_path('user/admin_area/settings/email', anchor: 'custom-private-commit-email-hostname'), target: '_blank'
= _("This setting will update the hostname that is used to generate private commit emails. %{learn_more}").html_safe % { learn_more: commit_email_hostname_docs_link }
- = f.submit 'Save changes', class: "btn btn-success"
+ = render_if_exists 'admin/application_settings/email_additional_text_setting', form: f
+
+ = f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_help_page.html.haml b/app/views/admin/application_settings/_help_page.html.haml
index 70c8c74cc5d..aa491c735d1 100644
--- a/app/views/admin/application_settings/_help_page.html.haml
+++ b/app/views/admin/application_settings/_help_page.html.haml
@@ -2,18 +2,20 @@
= form_errors(@application_setting)
%fieldset
+ = render_if_exists 'admin/application_settings/help_text_setting', form: f
+
.form-group
= f.label :help_page_text, class: 'label-bold'
= f.text_area :help_page_text, class: 'form-control', rows: 4
- .form-text.text-muted Markdown enabled
+ .form-text.text-muted= _('Markdown enabled')
.form-group
.form-check
= f.check_box :help_page_hide_commercial_content, class: 'form-check-input'
= f.label :help_page_hide_commercial_content, class: 'form-check-label' do
- Hide marketing-related entries from help
+ = _('Hide marketing-related entries from help')
.form-group
- = f.label :help_page_support_url, 'Support page URL', class: 'label-bold'
+ = f.label :help_page_support_url, _('Support page URL'), class: 'label-bold'
= f.text_field :help_page_support_url, class: 'form-control', placeholder: 'http://company.example.com/getting-help', :'aria-describedby' => 'support_help_block'
- %span.form-text.text-muted#support_help_block Alternate support URL for help page
+ %span.form-text.text-muted#support_help_block= _('Alternate support URL for help page')
- = f.submit 'Save changes', class: "btn btn-success"
+ = f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_repository_mirrors_form.html.haml b/app/views/admin/application_settings/_repository_mirrors_form.html.haml
index 615aa6317b0..f2f2cd1282a 100644
--- a/app/views/admin/application_settings/_repository_mirrors_form.html.haml
+++ b/app/views/admin/application_settings/_repository_mirrors_form.html.haml
@@ -3,13 +3,15 @@
%fieldset
.form-group
- = f.label :mirror_available, 'Enable mirror configuration', class: 'label-bold'
+ = f.label :mirror_available, _('Enable mirror configuration'), class: 'label-bold'
.form-check
= f.check_box :mirror_available, class: 'form-check-input'
= f.label :mirror_available, class: 'form-check-label' do
- Allow mirrors to be set up for projects
+ = _('Allow mirrors to be set up for projects')
%span.form-text.text-muted
- If disabled, only admins will be able to set up mirrors in projects.
+ = _('If disabled, only admins will be able to set up mirrors in projects.')
= link_to icon('question-circle'), help_page_path('workflow/repository_mirroring')
- = f.submit 'Save changes', class: "btn btn-success"
+ = render_if_exists 'admin/application_settings/mirror_settings', form: f
+
+ = f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/application_settings/_visibility_and_access.html.haml b/app/views/admin/application_settings/_visibility_and_access.html.haml
index 0725ffb7f6c..8122d81f578 100644
--- a/app/views/admin/application_settings/_visibility_and_access.html.haml
+++ b/app/views/admin/application_settings/_visibility_and_access.html.haml
@@ -5,6 +5,7 @@
.form-group
= f.label :default_branch_protection, class: 'label-bold'
= f.select :default_branch_protection, options_for_select(Gitlab::Access.protection_options, @application_setting.default_branch_protection), {}, class: 'form-control'
+ = render_if_exists 'admin/application_settings/project_creation_level', form: f, application_setting: @application_setting
.form-group.visibility-level-setting
= f.label :default_project_visibility, class: 'label-bold'
= render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: Project.new)
@@ -22,32 +23,33 @@
.form-check
= level
%span.form-text.text-muted#restricted-visibility-help
- Selected levels cannot be used by non-admin users for groups, projects or snippets.
- If the public level is restricted, user profiles are only visible to logged in users.
+ = _('Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users.')
.form-group
= f.label :import_sources, class: 'label-bold'
= hidden_field_tag 'application_setting[import_sources][]'
- import_sources_checkboxes('import-sources-help', class: 'form-check-input').each do |source|
.form-check= source
%span.form-text.text-muted#import-sources-help
- Enabled sources for code import during project creation. OmniAuth must be configured for GitHub
+ = _('Enabled sources for code import during project creation. OmniAuth must be configured for GitHub')
= link_to "(?)", help_page_path("integration/github")
, Bitbucket
= link_to "(?)", help_page_path("integration/bitbucket")
and GitLab.com
= link_to "(?)", help_page_path("integration/gitlab")
+ = render_if_exists 'admin/application_settings/ldap_access_setting', form: f
+
.form-group
.form-check
= f.check_box :project_export_enabled, class: 'form-check-input'
= f.label :project_export_enabled, class: 'form-check-label' do
- Project export enabled
+ = _('Project export enabled')
.form-group
- %label.label-bold Enabled Git access protocols
+ %label.label-bold= _('Enabled Git access protocols')
= select(:application_setting, :enabled_git_access_protocol, [['Both SSH and HTTP(S)', nil], ['Only SSH', 'ssh'], ['Only HTTP(S)', 'http']], {}, class: 'form-control')
%span.form-text.text-muted#clone-protocol-help
- Allow only the selected protocols to be used for Git access.
+ = _('Allow only the selected protocols to be used for Git access.')
- ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
- field_name = :"#{type}_key_restriction"
@@ -55,4 +57,4 @@
= f.label field_name, "#{type.upcase} SSH keys", class: 'label-bold'
= f.select field_name, key_restriction_options_for_select(type), {}, class: 'form-control'
- = f.submit 'Save changes', class: "btn btn-success"
+ = f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 6756299cf43..581f6ae0714 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -22,9 +22,10 @@
%h3.text-center
Users:
= approximate_count_with_delimiters(@counts, User)
- = render_if_exists 'admin/dashboard/users_statistics'
%hr
- = link_to 'New user', new_admin_user_path, class: "btn btn-success"
+ .btn-group.d-flex{ role: 'group' }
+ = link_to 'New user', new_admin_user_path, class: "btn btn-success"
+ = render_if_exists 'admin/dashboard/users_statistics'
.col-sm-4
.info-well.dark-well
.well-segment.well-centered
@@ -162,7 +163,7 @@
%span.float-right
#{Rails::VERSION::STRING}
%p
- = Gitlab::Database.adapter_name
+ = Gitlab::Database.human_adapter_name
%span.float-right
= Gitlab::Database.version
%p
diff --git a/app/views/admin/deploy_keys/edit.html.haml b/app/views/admin/deploy_keys/edit.html.haml
index 7c04ef03947..99d8af65068 100644
--- a/app/views/admin/deploy_keys/edit.html.haml
+++ b/app/views/admin/deploy_keys/edit.html.haml
@@ -1,10 +1,10 @@
-- page_title 'Edit Deploy Key'
-%h3.page-title Edit public deploy key
+- page_title _('Edit Deploy Key')
+%h3.page-title= _('Edit public deploy key')
%hr
%div
= form_for [:admin, @deploy_key], html: { class: 'deploy-key-form' } do |f|
= render partial: 'shared/deploy_keys/form', locals: { form: f, deploy_key: @deploy_key }
.form-actions
- = f.submit 'Save changes', class: 'btn-success btn'
- = link_to 'Cancel', admin_deploy_keys_path, class: 'btn btn-cancel'
+ = f.submit _('Save changes'), class: 'btn-success btn'
+ = link_to _('Cancel'), admin_deploy_keys_path, class: 'btn btn-cancel'
diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index 01013be06d6..9fffa97f969 100644
--- a/app/views/admin/deploy_keys/index.html.haml
+++ b/app/views/admin/deploy_keys/index.html.haml
@@ -1,19 +1,19 @@
-- page_title "Deploy Keys"
+- page_title _('Deploy Keys')
%h3.page-title.deploy-keys-title
- Public deploy keys (#{@deploy_keys.count})
+ = _('Public deploy keys (%{deploy_keys_count})') % { deploy_keys_count: @deploy_keys.count }
.float-right
- = link_to 'New deploy key', new_admin_deploy_key_path, class: 'btn btn-success btn-sm btn-inverted'
+ = link_to _('New deploy key'), new_admin_deploy_key_path, class: 'btn btn-success btn-sm btn-inverted'
- if @deploy_keys.any?
.table-holder.deploy-keys-list
%table.table
%thead
%tr
- %th.col-sm-2 Title
- %th.col-sm-4 Fingerprint
- %th.col-sm-2 Projects with write access
- %th.col-sm-2 Added at
+ %th.col-sm-2= _('Title')
+ %th.col-sm-4= _('Fingerprint')
+ %th.col-sm-2= _('Projects with write access')
+ %th.col-sm-2= _('Added at')
%th.col-sm-2
%tbody
- @deploy_keys.each do |deploy_key|
@@ -27,8 +27,8 @@
= link_to project.full_name, admin_project_path(project), class: 'label deploy-project-label'
%td
%span.cgray
- added #{time_ago_with_tooltip(deploy_key.created_at)}
+ = _('added %{created_at_timeago}').html_safe % { created_at_timeago: time_ago_with_tooltip(deploy_key.created_at) }
%td
.float-right
- = link_to 'Edit', edit_admin_deploy_key_path(deploy_key), class: 'btn btn-sm'
- = link_to 'Remove', admin_deploy_key_path(deploy_key), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove delete-key'
+ = link_to _('Edit'), edit_admin_deploy_key_path(deploy_key), class: 'btn btn-sm'
+ = link_to _('Remove'), admin_deploy_key_path(deploy_key), data: { confirm: _('Are you sure?') }, method: :delete, class: 'btn btn-sm btn-remove delete-key'
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index a74e052707f..4ffa8d89504 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -117,6 +117,11 @@
%strong
= @user.sign_in_count
+ %li
+ %span.light= _("Highest role:")
+ %strong
+ = Gitlab::Access.human_access_with_none(@user.highest_role)
+
- if @user.ldap_user?
%li
%span.light LDAP uid:
diff --git a/app/views/clusters/clusters/_form.html.haml b/app/views/clusters/clusters/_form.html.haml
index c08b41e2f23..455322b2089 100644
--- a/app/views/clusters/clusters/_form.html.haml
+++ b/app/views/clusters/clusters/_form.html.haml
@@ -33,9 +33,9 @@
- auto_devops_url = help_page_path('topics/autodevops/index')
- auto_devops_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: auto_devops_url }
= s_('ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain.').html_safe % { auto_devops_start: auto_devops_start, auto_devops_end: '</a>'.html_safe }
- - if @cluster.application_ingress_external_ip.present?
+ %span{ :class => ["js-ingress-domain-help-text", ("hide" unless @cluster.application_ingress_external_ip.present?)] }
= s_('ClusterIntegration|Alternatively')
- %code #{@cluster.application_ingress_external_ip}.nip.io
+ %code{ :class => "js-ingress-domain-snippet" } #{@cluster.application_ingress_external_ip}.nip.io
= s_('ClusterIntegration| can be used instead of a custom domain.')
- custom_domain_url = help_page_path('user/project/clusters/index', anchor: 'pointing-your-dns-at-the-external-endpoint')
- custom_domain_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: custom_domain_url }
diff --git a/app/views/clusters/clusters/_sidebar.html.haml b/app/views/clusters/clusters/_sidebar.html.haml
index 6e4415c21a9..60ccad5b943 100644
--- a/app/views/clusters/clusters/_sidebar.html.haml
+++ b/app/views/clusters/clusters/_sidebar.html.haml
@@ -4,3 +4,5 @@
= clusterable.sidebar_text
%p
= clusterable.learn_more_link
+
+= render_if_exists 'clusters/multiple_clusters_message'
diff --git a/app/views/clusters/clusters/gcp/_form.html.haml b/app/views/clusters/clusters/gcp/_form.html.haml
index 8ed4666e79a..00582e19662 100644
--- a/app/views/clusters/clusters/gcp/_form.html.haml
+++ b/app/views/clusters/clusters/gcp/_form.html.haml
@@ -17,9 +17,11 @@
.form-group
= field.label :name, s_('ClusterIntegration|Kubernetes cluster name'), class: 'label-bold'
= field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Kubernetes cluster name')
- .form-group
- = field.label :environment_scope, s_('ClusterIntegration|Environment scope'), class: 'label-bold'
- = field.text_field :environment_scope, class: 'form-control', readonly: !has_multiple_clusters?, placeholder: s_('ClusterIntegration|Environment scope')
+ - if has_multiple_clusters?
+ .form-group
+ = field.label :environment_scope, s_('ClusterIntegration|Environment scope'), class: 'label-bold'
+ = field.text_field :environment_scope, class: 'form-control', placeholder: s_('ClusterIntegration|Environment scope')
+ .form-text.text-muted= s_("ClusterIntegration|Choose which of your environments will use this cluster.")
= field.fields_for :provider_gcp, @gcp_cluster.provider_gcp do |provider_gcp_field|
.form-group
diff --git a/app/views/clusters/clusters/show.html.haml b/app/views/clusters/clusters/show.html.haml
index 884fa323093..61188c6fa0b 100644
--- a/app/views/clusters/clusters/show.html.haml
+++ b/app/views/clusters/clusters/show.html.haml
@@ -17,7 +17,7 @@
install_knative_path: clusterable.install_applications_cluster_path(@cluster, :knative),
update_knative_path: clusterable.update_applications_cluster_path(@cluster, :knative),
toggle_status: @cluster.enabled? ? 'true': 'false',
- has_rbac: @cluster.platform_kubernetes_rbac? ? 'true': 'false',
+ has_rbac: has_rbac_enabled?(@cluster) ? 'true': 'false',
cluster_type: @cluster.cluster_type,
cluster_status: @cluster.status_name,
cluster_status_reason: @cluster.status_reason,
@@ -34,6 +34,8 @@
= render 'banner'
= render 'form'
+ = render_if_exists 'health'
+
.cluster-applications-table#js-cluster-applications
%section.settings#js-cluster-details{ class: ('expanded' if expanded) }
diff --git a/app/views/clusters/clusters/user/_form.html.haml b/app/views/clusters/clusters/user/_form.html.haml
index 9793c77fc2b..136f98d0126 100644
--- a/app/views/clusters/clusters/user/_form.html.haml
+++ b/app/views/clusters/clusters/user/_form.html.haml
@@ -7,6 +7,7 @@
.form-group
= field.label :environment_scope, s_('ClusterIntegration|Environment scope'), class: 'label-bold'
= field.text_field :environment_scope, class: 'form-control', placeholder: s_('ClusterIntegration|Environment scope')
+ .form-text.text-muted= s_("ClusterIntegration|Choose which of your environments will use this cluster.")
= field.fields_for :platform_kubernetes, @user_cluster.platform_kubernetes do |platform_kubernetes_field|
.form-group
diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml
index 4dbda5c754b..b1c192d7bad 100644
--- a/app/views/dashboard/activity.html.haml
+++ b/app/views/dashboard/activity.html.haml
@@ -5,7 +5,7 @@
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
- page_title "Activity"
- header_title "Activity", activity_dashboard_path
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index 19b06ba5cdd..d1d8d970b59 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -2,7 +2,7 @@
- page_title "Groups"
- header_title "Groups", dashboard_groups_path
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
= render 'dashboard/groups_head'
- if params[:filter].blank? && @groups.empty?
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index afd46412fab..352ec986ccb 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -4,7 +4,7 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues")
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
.page-title-holder
%h1.page-title= _('Issues')
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index 3e5f13b92e3..659cc254b10 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -2,7 +2,7 @@
- page_title _("Merge Requests")
- @breadcrumb_link = merge_requests_dashboard_path(assignee_username: current_user.username)
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
.page-title-holder
%h1.page-title= _('Merge Requests')
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 446b4715b2d..dc9468b3368 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -4,7 +4,7 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity")
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
- page_title "Projects"
- header_title "Projects", dashboard_projects_path
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index 3a45f6df017..a0d85446e5f 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -4,7 +4,7 @@
- page_title _("Starred Projects")
- header_title _("Projects"), dashboard_projects_path
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
%div{ class: container_class }
= render "projects/last_push"
diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml
index 47729321961..c569bc682a6 100644
--- a/app/views/dashboard/todos/index.html.haml
+++ b/app/views/dashboard/todos/index.html.haml
@@ -2,7 +2,7 @@
- page_title "Todos"
- header_title "Todos", dashboard_todos_path
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
.page-title-holder
%h1.page-title= _('Todos')
diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml
index 1f5c70a6c6e..5d85d9e431f 100644
--- a/app/views/doorkeeper/applications/index.html.haml
+++ b/app/views/doorkeeper/applications/index.html.haml
@@ -52,7 +52,7 @@
.oauth-authorized-applications.prepend-top-20.append-bottom-default
- if user_oauth_applications?
%h5
- = _("Authorized applications (%{size})") % { size: @authorized_tokens.size }
+ = _("Authorized applications (%{size})") % { size: @authorized_apps.size + @authorized_anonymous_tokens.size }
- if @authorized_tokens.any?
.table-responsive
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index 869be4e8581..fd86d07fc86 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -2,7 +2,7 @@
- page_title _("Groups")
- header_title _("Groups"), dashboard_groups_path
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
- if current_user
= render 'dashboard/groups_head'
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index d18dec7bd8e..dd2bf6a5ef8 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -2,7 +2,7 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
- if current_user
= render 'dashboard/projects_head'
diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml
index d18dec7bd8e..dd2bf6a5ef8 100644
--- a/app/views/explore/projects/starred.html.haml
+++ b/app/views/explore/projects/starred.html.haml
@@ -2,7 +2,7 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
- if current_user
= render 'dashboard/projects_head'
diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml
index d18dec7bd8e..dd2bf6a5ef8 100644
--- a/app/views/explore/projects/trending.html.haml
+++ b/app/views/explore/projects/trending.html.haml
@@ -2,7 +2,7 @@
- page_title _("Projects")
- header_title _("Projects"), dashboard_projects_path
-= render_if_exists "shared/gold_trial_callout"
+= render_dashboard_gold_trial(current_user)
- if current_user
= render 'dashboard/projects_head'
diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml
index 39c0c113793..4daf3683eaf 100644
--- a/app/views/groups/_home_panel.html.haml
+++ b/app/views/groups/_home_panel.html.haml
@@ -13,7 +13,7 @@
= visibility_level_icon(@group.visibility_level, fw: false, options: {class: 'icon'})
.home-panel-metadata.d-flex.align-items-center.text-secondary
%span
- = _("Group")
+ = _("Group ID: %{group_id}") % { group_id: @group.id }
- if current_user
%span.access-request-links.prepend-left-8
= render 'shared/members/access_request_links', source: @group
@@ -47,7 +47,7 @@
%strong= new_subgroup_label
%span= s_("GroupsTree|Create a subgroup in this group.")
- else
- = link_to new_project_label, new_project_path(namespace_id: @group.id), class: "btn btn-success"
+ = link_to new_project_label, new_project_path(namespace_id: @group.id), class: "btn btn-success prepend-top-default"
- if @group.description.present?
.group-home-desc.mt-1
diff --git a/app/views/groups/settings/ci_cd/_auto_devops_form.html.haml b/app/views/groups/settings/ci_cd/_auto_devops_form.html.haml
new file mode 100644
index 00000000000..e7efc0237c8
--- /dev/null
+++ b/app/views/groups/settings/ci_cd/_auto_devops_form.html.haml
@@ -0,0 +1,15 @@
+= form_for group, url: update_auto_devops_group_settings_ci_cd_path(group), method: :patch do |f|
+ = form_errors(group)
+ %fieldset
+ .form-group
+ .card.auto-devops-card
+ .card-body
+ .form-check
+ = f.check_box :auto_devops_enabled, class: 'form-check-input', checked: group.auto_devops_enabled?
+ = f.label :auto_devops_enabled, class: 'form-check-label' do
+ %strong= s_('GroupSettings|Default to Auto DevOps pipeline for all projects within this group')
+ %span.badge.badge-info#auto-devops-badge= badge_for_auto_devops_scope(group)
+ .form-text.text-muted
+ = s_('GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found.')
+ = link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank'
+ = f.submit _('Save changes'), class: 'btn btn-success prepend-top-15'
diff --git a/app/views/groups/settings/ci_cd/show.html.haml b/app/views/groups/settings/ci_cd/show.html.haml
index d9332e36ef5..d0f5cd94002 100644
--- a/app/views/groups/settings/ci_cd/show.html.haml
+++ b/app/views/groups/settings/ci_cd/show.html.haml
@@ -19,3 +19,17 @@
= _('Register and see your runners for this group.')
.settings-content
= render 'groups/runners/index'
+
+%section.settings#auto-devops-settings.no-animate{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('Auto DevOps')
+ %button.btn.btn-default.js-settings-toggle{ type: "button" }
+ = expanded ? _('Collapse') : _('Expand')
+ %p
+ - auto_devops_url = help_page_path('topics/autodevops/index')
+ - auto_devops_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: auto_devops_url }
+ = s_('GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}').html_safe % { auto_devops_start: auto_devops_start, auto_devops_end: '</a>'.html_safe }
+
+ .settings-content
+ = render 'groups/settings/ci_cd/auto_devops_form', group: @group
diff --git a/app/views/layouts/header/_new_dropdown.haml b/app/views/layouts/header/_new_dropdown.haml
index 5a66b02c048..438340464bd 100644
--- a/app/views/layouts/header/_new_dropdown.haml
+++ b/app/views/layouts/header/_new_dropdown.haml
@@ -38,4 +38,4 @@
%li= link_to _('New project'), new_project_path, class: 'qa-global-new-project-link'
- if current_user.can_create_group?
%li= link_to _('New group'), new_group_path
- %li= link_to _('New snippet'), new_snippet_path
+ %li= link_to _('New snippet'), new_snippet_path, class: 'qa-global-new-snippet-link'
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index f659c89dd30..5a27237bf76 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -3,7 +3,7 @@
%ul.list-unstyled.navbar-sub-nav
- if dashboard_nav_link?(:projects)
= nav_link(path: ['root#index', 'projects#trending', 'projects#starred', 'dashboard/projects#index'], html_options: { id: 'nav-projects-dropdown', class: "home dropdown header-projects qa-projects-dropdown", data: { track_label: "projects_dropdown", track_event: "click_dropdown" } }) do
- %button{ type: 'button', data: { toggle: "dropdown" } }
+ %button.btn{ type: 'button', data: { toggle: "dropdown" } }
= _('Projects')
= sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu.frequent-items-dropdown-menu
@@ -11,7 +11,7 @@
- if dashboard_nav_link?(:groups)
= nav_link(controller: ['dashboard/groups', 'explore/groups'], html_options: { id: 'nav-groups-dropdown', class: "home dropdown header-groups qa-groups-dropdown", data: { track_label: "groups_dropdown", track_event: "click_dropdown" } }) do
- %button{ type: 'button', data: { toggle: "dropdown" } }
+ %button.btn{ type: 'button', data: { toggle: "dropdown" } }
= _('Groups')
= sprite_icon('angle-down', css_class: 'caret-down')
.dropdown-menu.frequent-items-dropdown-menu
@@ -29,7 +29,7 @@
- if dashboard_nav_link?(:snippets)
= nav_link(controller: 'dashboard/snippets', html_options: { class: ["d-none d-xl-block", ("d-lg-block" unless has_extra_nav_icons?)] }) do
- = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets', title: _('Snippets') do
+ = link_to dashboard_snippets_path, class: 'dashboard-shortcuts-snippets qa-snippets-link', title: _('Snippets') do
= _('Snippets')
- if any_dashboard_nav_link?([:groups, :milestones, :activity, :snippets])
diff --git a/app/views/layouts/nav/sidebar/_admin.html.haml b/app/views/layouts/nav/sidebar/_admin.html.haml
index 2fdd65f639b..bb2d206ba91 100644
--- a/app/views/layouts/nav/sidebar/_admin.html.haml
+++ b/app/views/layouts/nav/sidebar/_admin.html.haml
@@ -220,7 +220,7 @@
= _('Repository')
- if template_exists?('admin/application_settings/templates')
= nav_link(path: 'application_settings#templates') do
- = link_to templates_admin_application_settings_path, title: _('Templates') do
+ = link_to templates_admin_application_settings_path, title: _('Templates'), class: 'qa-admin-settings-template-item' do
%span
= _('Templates')
= nav_link(path: 'application_settings#ci_cd') do
diff --git a/app/views/layouts/nav/sidebar/_group.html.haml b/app/views/layouts/nav/sidebar/_group.html.haml
index 21ea9f3b2f3..eefe86eb6b4 100644
--- a/app/views/layouts/nav/sidebar/_group.html.haml
+++ b/app/views/layouts/nav/sidebar/_group.html.haml
@@ -20,13 +20,14 @@
= _('Overview')
%ul.sidebar-sub-level-items
- = nav_link(path: ['groups#show', 'groups#activity', 'groups#subgroups'], html_options: { class: "fly-out-top-item" } ) do
+ = nav_link(path: ['groups#show', 'groups#details', 'groups#activity', 'groups#subgroups'], html_options: { class: "fly-out-top-item" } ) do
= link_to group_path(@group) do
%strong.fly-out-top-item-name
= _('Overview')
%li.divider.fly-out-top-item
- = nav_link(path: ['groups#show', 'groups#subgroups'], html_options: { class: 'home' }) do
- = link_to group_path(@group), title: _('Group details') do
+
+ = nav_link(path: ['groups#show', 'groups#details', 'groups#subgroups'], html_options: { class: 'home' }) do
+ = link_to details_group_path(@group), title: _('Group details') do
%span
= _('Details')
@@ -40,9 +41,9 @@
- if group_sidebar_link?(:contribution_analytics)
= nav_link(path: 'analytics#show') do
- = link_to group_analytics_path(@group), title: 'Contribution Analytics', data: {placement: 'right'} do
+ = link_to group_analytics_path(@group), title: _('Contribution Analytics'), data: { placement: 'right' } do
%span
- Contribution Analytics
+ = _('Contribution Analytics')
= render_if_exists "layouts/nav/ee/epic_link", group: @group
diff --git a/app/views/profiles/_email_settings.html.haml b/app/views/profiles/_email_settings.html.haml
new file mode 100644
index 00000000000..fb4da08e129
--- /dev/null
+++ b/app/views/profiles/_email_settings.html.haml
@@ -0,0 +1,16 @@
+- form = local_assigns.fetch(:form)
+- readonly = @user.read_only_attribute?(:email)
+- email_change_disabled = local_assigns.fetch(:email_change_disabled, nil)
+- read_only_help_text = readonly ? s_("Profiles|Your email address was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:email) } : user_email_help_text(@user)
+- help_text = email_change_disabled ? s_("Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO.") % { group_name: @user.managing_group.name } : read_only_help_text
+
+= form.text_field :email, required: true, class: 'input-lg', value: (@user.email unless @user.temp_oauth_email?), help: help_text.html_safe, readonly: readonly || email_change_disabled
+= form.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email),
+ { help: s_("Profiles|This email will be displayed on your public profile"), include_blank: s_("Profiles|Do not show on profile") },
+ control_class: 'select2 input-lg', disabled: email_change_disabled
+- commit_email_link_url = help_page_path('user/profile/index', anchor: 'commit-email', target: '_blank')
+- commit_email_link_start = '<a href="%{url}">'.html_safe % { url: commit_email_link_url }
+- commit_email_docs_link = s_('Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}').html_safe % { commit_email_link_start: commit_email_link_start, commit_email_link_end: '</a>'.html_safe }
+= form.select :commit_email, options_for_select(commit_email_select_options(@user), selected: selected_commit_email(@user)),
+ { help: commit_email_docs_link },
+ control_class: 'select2 input-lg', disabled: email_change_disabled
diff --git a/app/views/profiles/accounts/_providers.html.haml b/app/views/profiles/accounts/_providers.html.haml
new file mode 100644
index 00000000000..068f9cc70f7
--- /dev/null
+++ b/app/views/profiles/accounts/_providers.html.haml
@@ -0,0 +1,21 @@
+%label.label-bold
+ = s_('Profiles|Connected Accounts')
+ %p= s_('Profiles|Click on icon to activate signin with one of the following services')
+ - providers.each do |provider|
+ - unlink_allowed = unlink_provider_allowed?(provider)
+ - link_allowed = link_provider_allowed?(provider)
+ - if unlink_allowed || link_allowed
+ .provider-btn-group
+ .provider-btn-image
+ = provider_image_tag(provider)
+ - if auth_active?(provider)
+ - if unlink_allowed
+ = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'provider-btn' do
+ = s_('Profiles|Disconnect')
+ - else
+ %a.provider-btn
+ = s_('Profiles|Active')
+ - elsif link_allowed
+ = link_to omniauth_authorize_path(:user, provider), method: :post, class: 'provider-btn not-active' do
+ = s_('Profiles|Connect')
+ = render_if_exists 'profiles/accounts/group_saml_unlink_buttons', group_saml_identities: group_saml_identities
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index ee2c5a13b8a..e6380817c8f 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -29,24 +29,7 @@
%p
= s_('Profiles|Activate signin with one of the following services')
.col-lg-8
- %label.label-bold
- = s_('Profiles|Connected Accounts')
- %p= s_('Profiles|Click on icon to activate signin with one of the following services')
- - button_based_providers.each do |provider|
- .provider-btn-group
- .provider-btn-image
- = provider_image_tag(provider)
- - if auth_active?(provider)
- - if unlink_allowed?(provider)
- = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'provider-btn' do
- = s_('Profiles|Disconnect')
- - else
- %a.provider-btn
- = s_('Profiles|Active')
- - else
- = link_to omniauth_authorize_path(:user, provider), method: :post, class: 'provider-btn not-active' do
- = s_('Profiles|Connect')
- = render_if_exists 'profiles/accounts/group_saml_unlink_buttons', group_saml_identities: local_assigns[:group_saml_identities]
+ = render 'providers', providers: button_based_providers, group_saml_identities: local_assigns[:group_saml_identities]
%hr
- if current_user.can_change_username?
.row.prepend-top-default
diff --git a/app/views/profiles/notifications/_email_settings.html.haml b/app/views/profiles/notifications/_email_settings.html.haml
new file mode 100644
index 00000000000..34dcf8f5402
--- /dev/null
+++ b/app/views/profiles/notifications/_email_settings.html.haml
@@ -0,0 +1,6 @@
+- form = local_assigns.fetch(:form)
+.form-group
+ = form.label :notification_email, class: "label-bold"
+ = form.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2", disabled: local_assigns.fetch(:email_change_disabled, nil)
+ .help-block
+ = local_assigns.fetch(:help_text, nil)
diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml
index 712eb2a4573..e616e5546b3 100644
--- a/app/views/profiles/notifications/show.html.haml
+++ b/app/views/profiles/notifications/show.html.haml
@@ -22,9 +22,7 @@
Global notification settings
= form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications prepend-top-default' } do |f|
- .form-group
- = f.label :notification_email, class: "label-bold"
- = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "select2"
+ = render_if_exists 'profiles/notifications/email_settings', form: f
= label_tag :global_notification_level, "Global notification level", class: "label-bold"
%br
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 4d3d92d09c0..1fffea08dae 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -83,18 +83,7 @@
= f.text_field :name, label: 'Full name', required: true, title: s_("Profiles|Using emojis in names seems fun, but please try to set a status message instead"), wrapper: { class: 'col-md-9 qa-full-name' }, help: s_("Profiles|Enter your name, so people you know can recognize you")
= f.text_field :id, readonly: true, label: 'User ID', wrapper: { class: 'col-md-3' }
- - if @user.read_only_attribute?(:email)
- = f.text_field :email, required: true, class: 'input-lg', readonly: true, help: s_("Profiles|Your email address was automatically set based on your %{provider_label} account") % { provider_label: attribute_provider_label(:email) }
- - else
- = f.text_field :email, required: true, class: 'input-lg', value: (@user.email unless @user.temp_oauth_email?),
- help: user_email_help_text(@user)
- = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email),
- { help: s_("Profiles|This email will be displayed on your public profile"), include_blank: s_("Profiles|Do not show on profile") },
- control_class: 'select2 input-lg'
- - commit_email_docs_link = link_to s_('Profiles|Learn more'), help_page_path('user/profile/index', anchor: 'commit-email', target: '_blank')
- = f.select :commit_email, options_for_select(commit_email_select_options(@user), selected: selected_commit_email(@user)),
- { help: s_("Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}").html_safe % { learn_more: commit_email_docs_link } },
- control_class: 'select2 input-lg'
+ = render_if_exists 'profiles/email_settings', form: f
= f.text_field :skype, class: 'input-md', placeholder: s_("Profiles|username")
= f.text_field :linkedin, class: 'input-md', help: s_("Profiles|Your LinkedIn profile name from linkedin.com/in/profilename")
= f.text_field :twitter, class: 'input-md', placeholder: s_("Profiles|@username")
diff --git a/app/views/projects/_flash_messages.html.haml b/app/views/projects/_flash_messages.html.haml
index 7a5fff96676..b72f0e39b23 100644
--- a/app/views/projects/_flash_messages.html.haml
+++ b/app/views/projects/_flash_messages.html.haml
@@ -5,4 +5,5 @@
- if current_user && can?(current_user, :download_code, project)
= render 'shared/no_ssh'
= render 'shared/no_password'
- = render 'shared/auto_devops_implicitly_enabled_banner', project: project
+ - unless project.empty_repo?
+ = render 'shared/auto_devops_implicitly_enabled_banner', project: project
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 1d7287410ea..4ac5a74c85c 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -57,7 +57,10 @@
- if can?(current_user, :download_code, @project)
%nav.project-stats
.nav-links.quick-links
- = render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout)
+ - if @project.empty_repo?
+ = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_anchors
+ - else
+ = render 'stat_anchor_list', anchors: @project.statistics_anchors(show_auto_devops_callout: show_auto_devops_callout)
.home-panel-home-desc.mt-1
- if @project.description.present?
diff --git a/app/views/projects/_new_project_fields.html.haml b/app/views/projects/_new_project_fields.html.haml
index 276363df7da..5129f11875c 100644
--- a/app/views/projects/_new_project_fields.html.haml
+++ b/app/views/projects/_new_project_fields.html.haml
@@ -12,9 +12,9 @@
.form-group.project-path.col-sm-6
= f.label :namespace_id, class: 'label-bold' do
%span= s_("Project URL")
- .input-group
+ .input-group.flex-nowrap
- if current_user.can_select_namespace?
- .input-group-prepend.has-tooltip{ title: root_url }
+ .input-group-prepend.flex-shrink-0.has-tooltip{ title: root_url }
.input-group-text
= root_url
- namespace_id = namespace_id_from(params)
@@ -23,10 +23,10 @@
display_path: true,
extra_group: namespace_id),
{},
- { class: 'select2 js-select-namespace qa-project-namespace-select', tabindex: 1, data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "project_path", track_value: "" }})
+ { class: 'select2 js-select-namespace qa-project-namespace-select block-truncated', tabindex: 1, data: { track_label: "#{track_label}", track_event: "activate_form_input", track_property: "project_path", track_value: "" }})
- else
- .input-group-prepend.static-namespace.has-tooltip{ title: user_url(current_user.username) + '/' }
+ .input-group-prepend.static-namespace.flex-shrink-0.has-tooltip{ title: user_url(current_user.username) + '/' }
.input-group-text.border-0
#{user_url(current_user.username)}/
= f.hidden_field :namespace_id, value: current_user.namespace_id
diff --git a/app/views/projects/blob/_header_content.html.haml b/app/views/projects/blob/_header_content.html.haml
index 4bef45932d0..88fa31a73b0 100644
--- a/app/views/projects/blob/_header_content.html.haml
+++ b/app/views/projects/blob/_header_content.html.haml
@@ -1,7 +1,7 @@
.file-header-content
= blob_icon blob.mode, blob.name
- %strong.file-title-name
+ %strong.file-title-name.qa-file-title-name
= blob.name
= copy_file_path_button(blob.path)
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 0d3c6e7027c..ce55dd78747 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -20,12 +20,9 @@
= link_to commit.title, project_commit_path(project, commit.id, merge_request_iid: merge_request.iid), class: "commit-row-message item-title"
- else
= link_to_markdown_field(commit, :title, link, class: "commit-row-message item-title")
- %span.commit-row-message.d-block.d-sm-none
+ %span.commit-row-message.d-inline.d-sm-none
&middot;
= commit.short_id
- - if commit_status
- .d-block.d-sm-none
- = render_commit_status(commit, ref: ref)
- if commit.description?
%button.text-expander.js-toggle-button
= sprite_icon('ellipsis_h', size: 12)
@@ -40,7 +37,7 @@
%pre.commit-row-description.js-toggle-content.append-bottom-8
= preserve(markdown_field(commit, :description))
- .commit-actions.flex-row.d-none.d-sm-flex
+ .commit-actions.flex-row
- if request.xhr?
= render partial: 'projects/commit/signature', object: commit.signature
- else
@@ -51,7 +48,7 @@
.js-commit-pipeline-status{ data: { endpoint: pipelines_project_commit_path(project, commit.id, ref: ref) } }
- .commit-sha-group
+ .commit-sha-group.d-none.d-sm-flex
.label.label-monospace
= commit.short_id
= clipboard_button(text: commit.id, title: _("Copy commit SHA to clipboard"), class: "btn btn-default", container: "body")
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 081990ac9b7..9fa31c147eb 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -7,89 +7,64 @@
%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
= render "home_panel"
- .project-empty-note-panel
- %h4.append-bottom-20
- = _('The repository for this project is empty')
+ %h4.prepend-top-0.append-bottom-8
+ = _('The repository for this project is empty')
- - if @project.can_current_user_push_code?
- %p
- - link_to_cli = link_to _('command line instructions'), '#repo-command-line-instructions'
- = _('If you already have files you can push them using the %{link_to_cli} below.').html_safe % { link_to_cli: link_to_cli }
- %p
- %em
- - link_to_protected_branches = link_to _('Learn more about protected branches'), help_page_path('user/project/protected_branches')
- = _('Note that the master branch is automatically protected. %{link_to_protected_branches}').html_safe % { link_to_protected_branches: link_to_protected_branches }
-
- %hr
- %p
- - link_to_auto_devops_settings = link_to(s_('AutoDevOps|enable Auto DevOps'), project_settings_ci_cd_path(@project, anchor: 'autodevops-settings'))
- - link_to_add_kubernetes_cluster = link_to(s_('AutoDevOps|add a Kubernetes cluster'), new_project_cluster_path(@project))
- = s_('AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}.').html_safe % { link_to_auto_devops_settings: link_to_auto_devops_settings, link_to_add_kubernetes_cluster: link_to_add_kubernetes_cluster }
+ - if @project.can_current_user_push_code?
+ %p.append-bottom-0
+ = _('You can create files directly in GitLab using one of the following options.')
- %hr
- %p
- = _('Otherwise it is recommended you start with one of the options below.')
- .prepend-top-20
-
- %nav.project-buttons
- .scrolling-tabs-container.inner-page-scroll-tabs.is-smaller.qa-quick-actions
- .fade-left= icon('angle-left')
- .fade-right= icon('angle-right')
- .nav-links.scrolling-tabs.quick-links
- = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons
+ .project-buttons.qa-quick-actions
+ = render 'stat_anchor_list', anchors: @project.empty_repo_statistics_buttons
- if can?(current_user, :push_code, @project)
- %div
- .prepend-top-20
- .empty_wrapper
- %h3#repo-command-line-instructions.page-title-empty
- = _('Command line instructions')
- .git-empty.js-git-empty
- %fieldset
- %h5= _('Git global setup')
- %pre.bg-light
- :preserve
- git config --global user.name "#{h git_user_name}"
- git config --global user.email "#{h git_user_email}"
-
- %fieldset
- %h5= _('Create a new repository')
- %pre.bg-light
- :preserve
- git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
- cd #{h @project.path}
- touch README.md
- git add README.md
- git commit -m "add README"
- - if @project.can_current_user_push_to_default_branch?
- %span><
- git push -u origin master
+ .empty-wrapper.prepend-top-32
+ %h3#repo-command-line-instructions.page-title-empty
+ = _('Command line instructions')
+ %p
+ = _('You can also upload existing files from your computer using the instructions below.')
+ .git-empty.js-git-empty
+ %fieldset
+ %h5= _('Git global setup')
+ %pre.bg-light
+ :preserve
+ git config --global user.name "#{h git_user_name}"
+ git config --global user.email "#{h git_user_email}"
- %fieldset
- %h5= _('Existing folder')
- %pre.bg-light
- :preserve
- cd existing_folder
- git init
- git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
- git add .
- git commit -m "Initial commit"
- - if @project.can_current_user_push_to_default_branch?
- %span><
- git push -u origin master
+ %fieldset
+ %h5= _('Create a new repository')
+ %pre.bg-light
+ :preserve
+ git clone #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
+ cd #{h @project.path}
+ touch README.md
+ git add README.md
+ git commit -m "add README"
+ - if @project.can_current_user_push_to_default_branch?
+ %span><
+ git push -u origin master
- %fieldset
- %h5= _('Existing Git repository')
- %pre.bg-light
- :preserve
- cd existing_repo
- git remote rename origin old-origin
- git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
- - if @project.can_current_user_push_to_default_branch?
- %span><
- git push -u origin --all
- git push -u origin --tags
+ %fieldset
+ %h5= _('Push an existing folder')
+ %pre.bg-light
+ :preserve
+ cd existing_folder
+ git init
+ git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
+ git add .
+ git commit -m "Initial commit"
+ - if @project.can_current_user_push_to_default_branch?
+ %span><
+ git push -u origin master
- - if can? current_user, :remove_project, @project
- .prepend-top-20
- = link_to _('Remove project'), [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-inverted btn-remove float-right"
+ %fieldset
+ %h5= _('Push an existing Git repository')
+ %pre.bg-light
+ :preserve
+ cd existing_repo
+ git remote rename origin old-origin
+ git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'js-clone')}
+ - if @project.can_current_user_push_to_default_branch?
+ %span><
+ git push -u origin --all
+ git push -u origin --tags
diff --git a/app/views/projects/merge_requests/_mr_title.html.haml b/app/views/projects/merge_requests/_mr_title.html.haml
index 3cd83feb842..92e34b3ceda 100644
--- a/app/views/projects/merge_requests/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/_mr_title.html.haml
@@ -1,8 +1,9 @@
- can_update_merge_request = can?(current_user, :update_merge_request, @merge_request)
+- can_reopen_merge_request = can?(current_user, :reopen_merge_request, @merge_request)
- if @merge_request.closed_without_fork?
.alert.alert-danger
- %p The source project of this merge request has been removed.
+ The source project of this merge request has been removed.
.detail-page-header
.detail-page-header-body
@@ -33,10 +34,11 @@
- if can_update_merge_request
%li{ class: [merge_request_button_visibility(@merge_request, true), 'js-close-item'] }
= link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, title: 'Close merge request'
+ - if can_reopen_merge_request
%li{ class: merge_request_button_visibility(@merge_request, false) }
= link_to 'Reopen', merge_request_path(@merge_request, merge_request: { state_event: :reopen }), method: :put, class: 'reopen-mr-link', title: 'Reopen merge request'
- if can_update_merge_request
= link_to 'Edit', edit_project_merge_request_path(@project, @merge_request), class: "d-none d-sm-none d-md-block btn btn-grouped js-issuable-edit qa-edit-button"
- = render 'shared/issuable/close_reopen_button', issuable: @merge_request, can_update: can_update_merge_request, can_reopen: can_update_merge_request
+ = render 'shared/issuable/close_reopen_button', issuable: @merge_request, can_update: can_update_merge_request, can_reopen: can_reopen_merge_request
diff --git a/app/views/projects/mirrors/_authentication_method.html.haml b/app/views/projects/mirrors/_authentication_method.html.haml
index 293a2e3ebfe..ef6db07a1bb 100644
--- a/app/views/projects/mirrors/_authentication_method.html.haml
+++ b/app/views/projects/mirrors/_authentication_method.html.haml
@@ -9,6 +9,7 @@
= f.select :auth_method,
options_for_select(auth_options, mirror.auth_method),
{}, { class: "form-control js-mirror-auth-type qa-authentication-method" }
+ = f.hidden_field :auth_method, value: "password", class: "js-hidden-mirror-auth-type"
.form-group
.collapse.js-well-changing-auth
diff --git a/app/views/projects/pipelines/_info.html.haml b/app/views/projects/pipelines/_info.html.haml
index 55adeb345ab..5d307d6a70d 100644
--- a/app/views/projects/pipelines/_info.html.haml
+++ b/app/views/projects/pipelines/_info.html.haml
@@ -10,13 +10,7 @@
.icon-container
= icon('clock-o')
= pluralize @pipeline.total_size, "job"
- - if @pipeline.ref
- from
- - if @pipeline.ref_exists?
- = link_to @pipeline.ref, project_ref_path(@project, @pipeline.ref), class: "ref-name"
- - else
- %span.ref-name
- = @pipeline.ref
+ = @pipeline.ref_text
- if @pipeline.duration
in
= time_interval_in_words(@pipeline.duration)
@@ -48,9 +42,9 @@
content: "<a class='autodevops-link' href='#{popover_content_url}' target='_blank' rel='noopener noreferrer nofollow'>#{popover_content_text}</a>",
} }
Auto DevOps
- - if @pipeline.merge_request_event?
- %span.js-pipeline-url-mergerequest.badge.badge-info.has-tooltip{ title: "This pipeline is run in a merge request context" }
- merge request
+ - if @pipeline.detached_merge_request_pipeline?
+ %span.js-pipeline-url-mergerequest.badge.badge-info.has-tooltip{ title: "This pipeline is run on the source branch" }
+ detached
- if @pipeline.stuck?
%span.js-pipeline-url-stuck.badge.badge-warning
stuck
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 66e202103a9..c04f076a3ab 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -2,15 +2,15 @@
%ul.pipelines-tabs.nav-links.no-top.no-bottom.mobile-separator.nav.nav-tabs
%li.js-pipeline-tab-link
= link_to project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-pipeline', action: 'pipelines', toggle: 'tab' }, class: 'pipeline-tab' do
- = _("Pipeline")
+ = _('Pipeline')
%li.js-builds-tab-link
= link_to builds_project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-builds', action: 'builds', toggle: 'tab' }, class: 'builds-tab' do
- = _("Jobs")
+ = _('Jobs')
%span.badge.badge-pill.js-builds-counter= pipeline.total_size
- if @pipeline.failed_builds.present?
%li.js-failures-tab-link
= link_to failures_project_pipeline_path(@project, @pipeline), data: { target: '#js-tab-failures', action: 'failures', toggle: 'tab' }, class: 'failures-tab' do
- = _("Failed Jobs")
+ = _('Failed Jobs')
%span.badge.badge-pill.js-failures-counter= @pipeline.failed_builds.count
= render_if_exists "projects/pipelines/tabs_holder", pipeline: @pipeline, project: @project
@@ -24,41 +24,41 @@
%table.table.ci-table.pipeline
%thead
%tr
- %th Status
- %th Job ID
- %th Name
+ %th= _('Status')
+ %th= _('Job ID')
+ %th= _('Name')
%th
- %th Coverage
+ %th= _('Coverage')
%th
= render partial: "projects/stage/stage", collection: pipeline.legacy_stages, as: :stage
- elsif pipeline.project.builds_enabled? && !pipeline.ci_yaml_file
.bs-callout.bs-callout-warning
- \.gitlab-ci.yml not found in this commit
+ = _("%{gitlab_ci_yml} not found in this commit") % { gitlab_ci_yml: ".gitlab-ci.yml" }
- if @pipeline.failed_builds.present?
#js-tab-failures.build-failures.tab-pane.build-page
%table.table.responsive-table.ci-table.responsive-table-sm-rounded
%thead
%th.table-th-transparent
- %th.table-th-transparent= _("Name")
- %th.table-th-transparent= _("Stage")
- %th.table-th-transparent= _("Failure")
+ %th.table-th-transparent= _('Name')
+ %th.table-th-transparent= _('Stage')
+ %th.table-th-transparent= _('Failure')
%tbody
- @pipeline.failed_builds.each_with_index do |build, index|
- job = build.present(current_user: current_user)
%tr.build-state.responsive-table-border-start
- %td.responsive-table-cell.ci-status-icon-failed{ data: { column: "Status"} }
+ %td.responsive-table-cell.ci-status-icon-failed{ data: { column: _('Status')} }
.d-none.d-md-block.build-icon
= custom_icon("icon_status_#{build.status}")
.d-md-none.build-badge
= render "ci/status/badge", link: false, status: job.detailed_status(current_user)
- %td.responsive-table-cell.build-name{ data: { column: _("Name")} }
+ %td.responsive-table-cell.build-name{ data: { column: _('Name')} }
= link_to build.name, pipeline_job_url(pipeline, build)
- %td.responsive-table-cell.build-stage{ data: { column: _("Stage")} }
+ %td.responsive-table-cell.build-stage{ data: { column: _('Stage')} }
= build.stage.titleize
- %td.responsive-table-cell.build-failure{ data: { column: _("Failure")} }
+ %td.responsive-table-cell.build-failure{ data: { column: _('Failure')} }
= build.present.callout_failure_message
%td.responsive-table-cell.build-actions
- if can?(current_user, :update_build, job)
diff --git a/app/views/projects/pipelines/charts.html.haml b/app/views/projects/pipelines/charts.html.haml
index ec17eddba79..9da42fe99ac 100644
--- a/app/views/projects/pipelines/charts.html.haml
+++ b/app/views/projects/pipelines/charts.html.haml
@@ -1,5 +1,5 @@
- @no_container = true
-- page_title _("CI / CD Charts")
+- page_title _('CI / CD Charts')
%div{ class: container_class }
.sub-header-block
diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml
index c0ee81fe28d..4e4638085fd 100644
--- a/app/views/projects/pipelines/index.html.haml
+++ b/app/views/projects/pipelines/index.html.haml
@@ -1,5 +1,7 @@
- @no_container = true
-- page_title "Pipelines"
+- page_title _('Pipelines')
+
+= render_if_exists "shared/shared_runners_minutes_limit_flash_message"
%div{ 'class' => container_class }
#pipelines-list-vue{ data: { endpoint: project_pipelines_path(@project, format: :json),
diff --git a/app/views/projects/pipelines/new.html.haml b/app/views/projects/pipelines/new.html.haml
index f1cdc0a70dd..41fe704601e 100644
--- a/app/views/projects/pipelines/new.html.haml
+++ b/app/views/projects/pipelines/new.html.haml
@@ -1,9 +1,9 @@
-- breadcrumb_title "Pipelines"
-- page_title s_("Pipeline|Run Pipeline")
+- breadcrumb_title _('Pipelines')
+- page_title s_('Pipeline|Run Pipeline')
- settings_link = link_to _('CI/CD settings'), project_settings_ci_cd_path(@project)
%h3.page-title
- = s_("Pipeline|Run Pipeline")
+ = s_('Pipeline|Run Pipeline')
%hr
= form_for @pipeline, as: :pipeline, url: project_pipelines_path(@project), html: { id: "new-pipeline-form", class: "js-new-pipeline-form js-requires-input" } do |f|
@@ -29,7 +29,7 @@
.form-actions
= f.submit s_('Pipeline|Create pipeline'), class: 'btn btn-success js-variables-save-button', tabindex: 3
- = link_to 'Cancel', project_pipelines_path(@project), class: 'btn btn-default float-right'
+ = link_to _('Cancel'), project_pipelines_path(@project), class: 'btn btn-default float-right'
-# haml-lint:disable InlineJavaScript
%script#availableRefs{ type: "application/json" }= @project.repository.ref_names.to_json.html_safe
diff --git a/app/views/projects/pipelines/show.html.haml b/app/views/projects/pipelines/show.html.haml
index 193d437dad1..8a6d7b082e3 100644
--- a/app/views/projects/pipelines/show.html.haml
+++ b/app/views/projects/pipelines/show.html.haml
@@ -1,7 +1,7 @@
- @no_container = true
-- add_to_breadcrumbs "Pipelines", project_pipelines_path(@project)
+- add_to_breadcrumbs _('Pipelines'), project_pipelines_path(@project)
- breadcrumb_title "##{@pipeline.id}"
-- page_title "Pipeline"
+- page_title _('Pipeline')
.js-pipeline-container{ class: container_class, data: { controller_action: "#{controller.action_name}" } }
#js-pipeline-header-vue.pipeline-header-container
@@ -11,11 +11,13 @@
- if @pipeline.builds.empty? && @pipeline.yaml_errors.present?
.bs-callout.bs-callout-danger
- %h4 Found errors in your .gitlab-ci.yml:
+ %h4= _('Found errors in your %{gitlab_ci_yml}:') % { gitlab_ci_yml: '.gitlab-ci.yml' }
%ul
- @pipeline.yaml_errors.split(",").each do |error|
%li= error
- You can test your .gitlab-ci.yml in #{link_to "CI Lint", project_ci_lint_path(@project)}.
+ - lint_link_url = project_ci_lint_path(@project)
+ - lint_link_start = '<a href="%{url}">'.html_safe % { url: lint_link_url }
+ = s_('You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}').html_safe % { gitlab_ci_yml: '.gitlab-ci.yml', lint_link_start: lint_link_start, lint_link_end: '</a>'.html_safe }
- else
= render "projects/pipelines/with_tabs", pipeline: @pipeline
diff --git a/app/views/projects/protected_branches/shared/_index.html.haml b/app/views/projects/protected_branches/shared/_index.html.haml
index 4997770321e..539b184e5c2 100644
--- a/app/views/projects/protected_branches/shared/_index.html.haml
+++ b/app/views/projects/protected_branches/shared/_index.html.haml
@@ -12,7 +12,7 @@
%p
By default, protected branches are designed to:
%ul
- %li prevent their creation, if not already created, from everybody except users who are allowed to merge
+ %li prevent their creation, if not already created, from everybody except Maintainers
%li prevent pushes from everybody except Maintainers
%li prevent <strong>anyone</strong> from force pushing to the branch
%li prevent <strong>anyone</strong> from deleting the branch
diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
index 8c4d1c32ebe..fac68a36e79 100644
--- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
@@ -8,15 +8,15 @@
.card.auto-devops-card
.card-body
.form-check
- = form.check_box :enabled, class: 'form-check-input js-toggle-extra-settings', checked: @project.auto_devops_enabled?
+ = form.check_box :enabled, class: 'form-check-input js-toggle-extra-settings', checked: auto_devops_enabled
= form.label :enabled, class: 'form-check-label' do
%strong= s_('CICD|Default to Auto DevOps pipeline')
- - if @project.has_auto_devops_implicitly_enabled?
- %span.badge.badge-info.js-instance-default-badge= s_('CICD|instance enabled')
+ - if auto_devops_enabled
+ %span.badge.badge-info.js-instance-default-badge= badge_for_auto_devops_scope(@project)
.form-text.text-muted
= s_('CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found.')
= link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank'
- .card-footer.js-extra-settings{ class: @project.auto_devops_enabled? || 'hidden' }
+ .card-footer.js-extra-settings{ class: auto_devops_enabled || 'hidden' }
%p.settings-message.text-center
- kubernetes_cluster_link = help_page_path('user/project/clusters/index')
- kubernetes_cluster_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: kubernetes_cluster_link }
diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml
index 6966bf96724..548b7c06867 100644
--- a/app/views/projects/settings/ci_cd/show.html.haml
+++ b/app/views/projects/settings/ci_cd/show.html.haml
@@ -26,7 +26,7 @@
= s_('CICD|Auto DevOps will automatically build, test, and deploy your application based on a predefined Continuous Integration and Delivery configuration.')
= link_to s_('CICD|Learn more about Auto DevOps'), help_page_path('topics/autodevops/index.md')
.settings-content
- = render 'autodevops_form'
+ = render 'autodevops_form', auto_devops_enabled: @project.auto_devops_enabled?
= render_if_exists 'projects/settings/ci_cd/protected_environments', expanded: expanded
diff --git a/app/views/projects/settings/operations/_error_tracking.html.haml b/app/views/projects/settings/operations/_error_tracking.html.haml
index 6b15331db01..451a79becc3 100644
--- a/app/views/projects/settings/operations/_error_tracking.html.haml
+++ b/app/views/projects/settings/operations/_error_tracking.html.haml
@@ -2,7 +2,7 @@
- setting = error_tracking_setting
-%section.settings.expanded.border-0.no-animate
+%section.settings.expanded.no-animate
.settings-header
%h4
= _('Error Tracking')
diff --git a/app/views/projects/settings/operations/show.html.haml b/app/views/projects/settings/operations/show.html.haml
index 2822debe426..6f777305a54 100644
--- a/app/views/projects/settings/operations/show.html.haml
+++ b/app/views/projects/settings/operations/show.html.haml
@@ -2,5 +2,6 @@
- page_title _('Operations Settings')
- breadcrumb_title _('Operations Settings')
+= render_if_exists 'projects/settings/operations/incidents'
= render 'projects/settings/operations/error_tracking', expanded: true
= render_if_exists 'projects/settings/operations/tracing'
diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml
index aaf9b973cda..df408e5fb60 100644
--- a/app/views/search/_category.html.haml
+++ b/app/views/search/_category.html.haml
@@ -1,3 +1,11 @@
+- users = capture_haml do
+ - if search_tabs?(:members)
+ %li{ class: active_when(@scope == 'users') }
+ = link_to search_filter_path(scope: 'users') do
+ Users
+ %span.badge.badge-pill
+ = limited_count(@search_results.limited_users_count)
+
.scrolling-tabs-container.inner-page-scroll-tabs.is-smaller
.fade-left= icon('angle-left')
.fade-right= icon('angle-right')
@@ -45,6 +53,7 @@
= _("Commits")
%span.badge.badge-pill
= @search_results.commits_count
+ = users
- elsif @show_snippets
%li{ class: active_when(@scope == 'snippet_blobs') }
@@ -78,3 +87,4 @@
= _("Milestones")
%span.badge.badge-pill
= limited_count(@search_results.limited_milestones_count)
+ = users
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index be7a2436d16..2e62039b90a 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -20,7 +20,7 @@
.search-results
- if @scope == 'projects'
.term
- = render 'shared/projects/list', projects: @search_objects
+ = render 'shared/projects/list', projects: @search_objects, pipeline_status: false
- else
= render partial: "search/results/#{@scope.singularize}", collection: @search_objects
diff --git a/app/views/search/results/_user.html.haml b/app/views/search/results/_user.html.haml
new file mode 100644
index 00000000000..8060a1577e4
--- /dev/null
+++ b/app/views/search/results/_user.html.haml
@@ -0,0 +1,10 @@
+%ul.content-list
+ %li
+ .avatar-cell.d-none.d-sm-block
+ = user_avatar(user: user, user_name: user.name, css_class: 'd-none d-sm-inline avatar s40')
+ .user-info
+ = link_to user_path(user), class: 'd-none d-sm-inline' do
+ .item-title
+ = user.name
+ = user_status(user)
+ .cgray= user.to_reference
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index 5073e6ad48f..d7e57fc0d01 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -1,4 +1,4 @@
-.file-content.code.js-syntax-highlight
+.file-content.code.js-syntax-highlight.qa-file-content
.line-numbers
- if blob.data.present?
- link_icon = icon('link')
diff --git a/app/views/shared/deploy_keys/_form.html.haml b/app/views/shared/deploy_keys/_form.html.haml
index eb7808573b9..bc0dc7f9631 100644
--- a/app/views/shared/deploy_keys/_form.html.haml
+++ b/app/views/shared/deploy_keys/_form.html.haml
@@ -13,9 +13,10 @@
= form.label :key, class: 'col-form-label col-sm-2'
.col-sm-10
%p.light
- Paste a machine public key here. Read more about how to generate it
- = link_to 'here', help_page_path('ssh/README')
- = form.text_area :key, class: 'form-control thin-area', rows: 5
+ - link_start = "<a href='#{help_page_path('ssh/README')}' target='_blank' rel='noreferrer noopener'>".html_safe
+ - link_end = '</a>'
+ = _('Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}').html_safe % { link_start: link_start, link_end: link_end.html_safe }
+ = form.text_area :key, class: 'form-control thin_area', rows: 5
- else
= form.label :fingerprint, class: 'col-form-label col-sm-2'
.col-sm-10
@@ -28,6 +29,6 @@
.col-sm-10
= deploy_keys_project_form.label :can_push do
= deploy_keys_project_form.check_box :can_push
- %strong Write access allowed
+ %strong= _('Write access allowed')
%p.light.append-bottom-0
- Allow this key to push to repository as well? (Default only allows pull access.)
+ = _('Allow this key to push to repository as well? (Default only allows pull access.)')
diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml
index f43be304e6b..2bcfcb6fa7c 100644
--- a/app/views/shared/issuable/_search_bar.html.haml
+++ b/app/views/shared/issuable/_search_bar.html.haml
@@ -137,6 +137,11 @@
%li.filter-dropdown-item{ data: { value: 'no', capitalize: true } }
%button.btn.btn-link{ type: 'button' }
= _('No')
+ #js-dropdown-target-branch.filtered-search-input-dropdown-menu.dropdown-menu
+ %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } }
+ %li.filter-dropdown-item
+ %button.btn.btn-link.js-data-value.monospace
+ {{title}}
= render_if_exists 'shared/issuable/filter_weight', type: type
diff --git a/app/views/shared/notes/_note.html.haml b/app/views/shared/notes/_note.html.haml
index 41d6ae79c81..6fec435cc87 100644
--- a/app/views/shared/notes/_note.html.haml
+++ b/app/views/shared/notes/_note.html.haml
@@ -36,14 +36,13 @@
= user_status(note.author)
%span.note-headline-light
= note.author.to_reference
- %span.note-headline-light
- %span.note-headline-meta
- - if note.system
- %span.system-note-message
- = markdown_field(note, :note)
- %span.system-note-separator
- &middot;
- %a.system-note-separator{ href: "##{dom_id(note)}" }= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
+ %span.note-headline-light.note-headline-meta
+ - if note.system
+ %span.system-note-message
+ = markdown_field(note, :note)
+ %span.system-note-separator
+ &middot;
+ %a.system-note-separator{ href: "##{dom_id(note)}" }= time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note-created-ago')
- unless note.system?
.note-actions
- if note.for_personal_snippet?
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index d2b1be29eb9..90fb067e75d 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -10,7 +10,7 @@
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true && can_show_last_commit_in_list?(project)
- css_class = '' unless local_assigns[:css_class]
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
-- cache_key = project_list_cache_key(project)
+- cache_key = project_list_cache_key(project, pipeline_status: pipeline_status)
- updated_tooltip = time_ago_with_tooltip(project.last_activity_date)
- css_controls_class = compact_mode ? "" : "flex-lg-row justify-content-lg-between"
- avatar_container_class = project.creator && use_creator_avatar ? '' : 'rect-avatar'
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 3007da0c189..6f2ddc5bdba 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -9,7 +9,7 @@
.form-group.row
= f.label :title, class: 'col-form-label col-sm-2'
.col-sm-10
- = f.text_field :title, class: 'form-control', required: true, autofocus: true
+ = f.text_field :title, class: 'form-control qa-snippet-title', required: true, autofocus: true
= render 'shared/form_elements/description', model: @snippet, project: @project, form: f
@@ -21,7 +21,7 @@
.col-sm-10
.file-holder.snippet
.js-file-title.file-title
- = f.text_field :file_name, placeholder: "Optionally name this file to add code highlighting, e.g. example.rb for Ruby.", class: 'form-control snippet-file-name'
+ = f.text_field :file_name, placeholder: "Optionally name this file to add code highlighting, e.g. example.rb for Ruby.", class: 'form-control snippet-file-name qa-snippet-file-name'
.file-content.code
%pre#editor= @snippet.content
= f.hidden_field :content, class: 'snippet-file-content'
@@ -31,7 +31,7 @@
.form-actions
- if @snippet.new_record?
- = f.submit 'Create snippet', class: "btn-success btn"
+ = f.submit 'Create snippet', class: "btn-success btn qa-create-snippet-button"
- else
= f.submit 'Save changes', class: "btn-success btn"
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index a43296aa806..0c07eae8643 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -1,6 +1,6 @@
.detail-page-header
.detail-page-header-body
- .snippet-box.has-tooltip.inline.append-right-5{ title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: "body" } }
+ .snippet-box.qa-snippet-box.has-tooltip.inline.append-right-5{ title: snippet_visibility_level_description(@snippet.visibility_level, @snippet), data: { container: "body" } }
%span.sr-only
= visibility_level_label(@snippet.visibility_level)
= visibility_level_icon(@snippet.visibility_level, fw: false)
@@ -17,11 +17,11 @@
= render "snippets/actions"
.snippet-header.limited-header-width
- %h2.snippet-title.prepend-top-0.append-bottom-0
+ %h2.snippet-title.prepend-top-0.append-bottom-0.qa-snippet-title
= markdown_field(@snippet, :title)
- if @snippet.description.present?
- .description
+ .description.qa-snippet-description
.wiki
= markdown_field(@snippet, :description)
%textarea.hidden.js-task-list-field
@@ -34,7 +34,7 @@
.embed-snippet
.input-group
.input-group-prepend
- %button.btn.btn-svg.embed-toggle.input-group-text{ 'data-toggle': 'dropdown', type: 'button' }
+ %button.btn.btn-svg.embed-toggle.input-group-text.qa-embed-type{ 'data-toggle': 'dropdown', type: 'button' }
%span.js-embed-action= _("Embed")
= sprite_icon('angle-down', size: 12, css_class: 'caret-down')
%ul.dropdown-menu.dropdown-menu-selectable.embed-toggle-list
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index b2d88567e0e..6ebd756d3da 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -71,6 +71,7 @@
- pipeline_hooks:build_hooks
- pipeline_hooks:pipeline_hooks
- pipeline_processing:build_finished
+- pipeline_processing:ci_build_prepare
- pipeline_processing:build_queue
- pipeline_processing:build_success
- pipeline_processing:pipeline_process
diff --git a/app/workers/ci/build_prepare_worker.rb b/app/workers/ci/build_prepare_worker.rb
new file mode 100644
index 00000000000..1a35a74ae53
--- /dev/null
+++ b/app/workers/ci/build_prepare_worker.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+module Ci
+ class BuildPrepareWorker
+ include ApplicationWorker
+ include PipelineQueue
+
+ queue_namespace :pipeline_processing
+
+ def perform(build_id)
+ Ci::Build.find_by_id(build_id).try do |build|
+ Ci::PrepareBuildService.new(build).execute
+ end
+ end
+ end
+end
diff --git a/app/workers/cluster_configure_worker.rb b/app/workers/cluster_configure_worker.rb
index 63e6cc147be..b984dee5b21 100644
--- a/app/workers/cluster_configure_worker.rb
+++ b/app/workers/cluster_configure_worker.rb
@@ -5,6 +5,8 @@ class ClusterConfigureWorker
include ClusterQueue
def perform(cluster_id)
+ return if Feature.enabled?(:ci_preparing_state, default_enabled: true)
+
Clusters::Cluster.find_by_id(cluster_id).try do |cluster|
Clusters::RefreshService.create_or_update_namespaces_for_cluster(cluster)
end
diff --git a/app/workers/cluster_project_configure_worker.rb b/app/workers/cluster_project_configure_worker.rb
index 497e57c0d0b..d7bea69a01c 100644
--- a/app/workers/cluster_project_configure_worker.rb
+++ b/app/workers/cluster_project_configure_worker.rb
@@ -5,6 +5,8 @@ class ClusterProjectConfigureWorker
include ClusterQueue
def perform(project_id)
+ return if Feature.enabled?(:ci_preparing_state, default_enabled: true)
+
project = Project.find(project_id)
::Clusters::RefreshService.create_or_update_namespaces_for_project(project)
diff --git a/app/workers/concerns/waitable_worker.rb b/app/workers/concerns/waitable_worker.rb
index 27b94a82444..17946bbc5ca 100644
--- a/app/workers/concerns/waitable_worker.rb
+++ b/app/workers/concerns/waitable_worker.rb
@@ -25,11 +25,9 @@ module WaitableWorker
failed = []
args_list.each do |args|
- begin
- new.perform(*args)
- rescue
- failed << args
- end
+ new.perform(*args)
+ rescue
+ failed << args
end
bulk_perform_async(failed) if failed.present?
diff --git a/app/workers/create_gpg_signature_worker.rb b/app/workers/create_gpg_signature_worker.rb
index 49c7a403838..2827529cc1c 100644
--- a/app/workers/create_gpg_signature_worker.rb
+++ b/app/workers/create_gpg_signature_worker.rb
@@ -20,11 +20,9 @@ class CreateGpgSignatureWorker
# This calculates and caches the signature in the database
commits.each do |commit|
- begin
- Gitlab::Gpg::Commit.new(commit).signature
- rescue => e
- Rails.logger.error("Failed to create signature for commit #{commit.id}. Error: #{e.message}")
- end
+ Gitlab::Gpg::Commit.new(commit).signature
+ rescue => e
+ Rails.logger.error("Failed to create signature for commit #{commit.id}. Error: #{e.message}")
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index 17ad1d5ab88..ed3e354e4c2 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -52,24 +52,22 @@ class EmailsOnPushWorker
end
valid_recipients(recipients).each do |recipient|
- begin
- send_email(
- recipient,
- project_id,
- author_id: author_id,
- ref: ref,
- action: action,
- compare: compare,
- reverse_compare: reverse_compare,
- diff_refs: diff_refs,
- send_from_committer_email: send_from_committer_email,
- disable_diffs: disable_diffs
- )
-
- # These are input errors and won't be corrected even if Sidekiq retries
- rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e
- logger.info("Failed to send e-mail for project '#{project.full_name}' to #{recipient}: #{e}")
- end
+ send_email(
+ recipient,
+ project_id,
+ author_id: author_id,
+ ref: ref,
+ action: action,
+ compare: compare,
+ reverse_compare: reverse_compare,
+ diff_refs: diff_refs,
+ send_from_committer_email: send_from_committer_email,
+ disable_diffs: disable_diffs
+ )
+
+ # These are input errors and won't be corrected even if Sidekiq retries
+ rescue Net::SMTPFatalError, Net::SMTPSyntaxError => e
+ logger.info("Failed to send e-mail for project '#{project.full_name}' to #{recipient}: #{e}")
end
ensure
@email = nil
diff --git a/app/workers/object_storage/migrate_uploads_worker.rb b/app/workers/object_storage/migrate_uploads_worker.rb
index fe5d27b087d..206eb71b898 100644
--- a/app/workers/object_storage/migrate_uploads_worker.rb
+++ b/app/workers/object_storage/migrate_uploads_worker.rb
@@ -126,11 +126,9 @@ module ObjectStorage
def process_uploader(uploader)
MigrationResult.new(uploader.upload).tap do |result|
- begin
- uploader.migrate!(@to_store)
- rescue => e
- result.error = e
- end
+ uploader.migrate!(@to_store)
+ rescue => e
+ result.error = e
end
end
end
diff --git a/app/workers/pipeline_schedule_worker.rb b/app/workers/pipeline_schedule_worker.rb
index ac4e9710f33..02a69ea3b54 100644
--- a/app/workers/pipeline_schedule_worker.rb
+++ b/app/workers/pipeline_schedule_worker.rb
@@ -8,16 +8,15 @@ class PipelineScheduleWorker
def perform
Ci::PipelineSchedule.active.where("next_run_at < ?", Time.now)
.preload(:owner, :project).find_each do |schedule|
- begin
- Ci::CreatePipelineService.new(schedule.project,
- schedule.owner,
- ref: schedule.ref)
- .execute!(:schedule, ignore_skip_ci: true, save_on_errors: true, schedule: schedule)
- rescue => e
- error(schedule, e)
- ensure
- schedule.schedule_next_run!
- end
+
+ Ci::CreatePipelineService.new(schedule.project,
+ schedule.owner,
+ ref: schedule.ref)
+ .execute!(:schedule, ignore_skip_ci: true, save_on_errors: true, schedule: schedule)
+ rescue => e
+ error(schedule, e)
+ ensure
+ schedule.schedule_next_run!
end
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb
index d27b5e62574..b31099bc670 100644
--- a/app/workers/project_cache_worker.rb
+++ b/app/workers/project_cache_worker.rb
@@ -27,6 +27,7 @@ class ProjectCacheWorker
# rubocop: enable CodeReuse/ActiveRecord
def update_statistics(project, statistics = [])
+ return if Gitlab::Database.read_only?
return unless try_obtain_lease_for(project.id, :update_statistics)
Rails.logger.info("Updating statistics for project #{project.id}")
diff --git a/app/workers/remove_expired_members_worker.rb b/app/workers/remove_expired_members_worker.rb
index 41913900571..3497a1f9280 100644
--- a/app/workers/remove_expired_members_worker.rb
+++ b/app/workers/remove_expired_members_worker.rb
@@ -6,11 +6,9 @@ class RemoveExpiredMembersWorker
def perform
Member.expired.find_each do |member|
- begin
- Members::DestroyService.new.execute(member, skip_authorization: true)
- rescue => ex
- logger.error("Expired Member ID=#{member.id} cannot be removed - #{ex}")
- end
+ Members::DestroyService.new.execute(member, skip_authorization: true)
+ rescue => ex
+ logger.error("Expired Member ID=#{member.id} cannot be removed - #{ex}")
end
end
end
diff --git a/changelogs/unreleased/10029-env-item.yml b/changelogs/unreleased/10029-env-item.yml
new file mode 100644
index 00000000000..f4e742d3e17
--- /dev/null
+++ b/changelogs/unreleased/10029-env-item.yml
@@ -0,0 +1,5 @@
+---
+title: Removes EE differences for environment_item.vue
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/10081-env-table.yml b/changelogs/unreleased/10081-env-table.yml
new file mode 100644
index 00000000000..b27a1be8cca
--- /dev/null
+++ b/changelogs/unreleased/10081-env-table.yml
@@ -0,0 +1,5 @@
+---
+title: Removes EE differences for environments_table.vue
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/10095-job-getters.yml b/changelogs/unreleased/10095-job-getters.yml
deleted file mode 100644
index f12fc8b26ec..00000000000
--- a/changelogs/unreleased/10095-job-getters.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Removes EE differences for jobs/getters.js
-merge_request:
-author:
-type: other
diff --git a/changelogs/unreleased/10097-number-utils.yml b/changelogs/unreleased/10097-number-utils.yml
deleted file mode 100644
index 417008f6539..00000000000
--- a/changelogs/unreleased/10097-number-utils.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Moves EE util into the CE file
-merge_request: 25680
-author:
-type: other
diff --git a/changelogs/unreleased/13784-simple-masking-of-protected-variables-in-logs.yml b/changelogs/unreleased/13784-simple-masking-of-protected-variables-in-logs.yml
deleted file mode 100644
index 5c3b6833235..00000000000
--- a/changelogs/unreleased/13784-simple-masking-of-protected-variables-in-logs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add support for masking CI variables.
-merge_request: 25293
-author:
-type: added
diff --git a/changelogs/unreleased/20084-update-the-spinner-component.yml b/changelogs/unreleased/20084-update-the-spinner-component.yml
deleted file mode 100644
index c93648e4f54..00000000000
--- a/changelogs/unreleased/20084-update-the-spinner-component.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add a spinner icon which is rendered using pure css
-merge_request: 25186
-author:
-type: changed
diff --git a/changelogs/unreleased/24642-activity_service_optimization.yml b/changelogs/unreleased/24642-activity_service_optimization.yml
deleted file mode 100644
index bdfa769959e..00000000000
--- a/changelogs/unreleased/24642-activity_service_optimization.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Optimize Redis usage in User::ActivityService
-merge_request: 25005
-author:
-type: performance
diff --git a/changelogs/unreleased/24971-align-emailvalidator-to-validate_email-gem-implementation.yml b/changelogs/unreleased/24971-align-emailvalidator-to-validate_email-gem-implementation.yml
new file mode 100644
index 00000000000..04dbc3a1d5a
--- /dev/null
+++ b/changelogs/unreleased/24971-align-emailvalidator-to-validate_email-gem-implementation.yml
@@ -0,0 +1,5 @@
+---
+title: Align EmailValidator to validate_email gem implementation
+merge_request: 24971
+author: Horatiu Eugen Vlad
+type: fixed
diff --git a/changelogs/unreleased/25942-remove-fake-repository-path-response.yml b/changelogs/unreleased/25942-remove-fake-repository-path-response.yml
new file mode 100644
index 00000000000..e1da28ab03c
--- /dev/null
+++ b/changelogs/unreleased/25942-remove-fake-repository-path-response.yml
@@ -0,0 +1,5 @@
+---
+title: Remove fake repository_path response
+merge_request: 25942
+author: Fabio Papa
+type: other
diff --git a/changelogs/unreleased/27333-re-deploy-rollback-button-should-ask-for-confirmation-before-executing.yml b/changelogs/unreleased/27333-re-deploy-rollback-button-should-ask-for-confirmation-before-executing.yml
deleted file mode 100644
index 8c5f05c3575..00000000000
--- a/changelogs/unreleased/27333-re-deploy-rollback-button-should-ask-for-confirmation-before-executing.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Confirmation Modal to Rollback on Environment
-merge_request: 25110
-author:
-type: added
diff --git a/changelogs/unreleased/2802-security-add-public-internal-groups-as-members-to-your-project-idor.yml b/changelogs/unreleased/2802-security-add-public-internal-groups-as-members-to-your-project-idor.yml
deleted file mode 100644
index 27ad151cd06..00000000000
--- a/changelogs/unreleased/2802-security-add-public-internal-groups-as-members-to-your-project-idor.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Remove the possibility to share a project with a group that a user is not a member
- of
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/32714-copying-comment-with-ordered-list-includes-extraneous-newlines.yml b/changelogs/unreleased/32714-copying-comment-with-ordered-list-includes-extraneous-newlines.yml
deleted file mode 100644
index d0bb4225ce4..00000000000
--- a/changelogs/unreleased/32714-copying-comment-with-ordered-list-includes-extraneous-newlines.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed "Copying comment with ordered list includes extraneous newlines"
-merge_request: 25695
-author:
-type: fixed
diff --git a/changelogs/unreleased/34555-empty-state-for-starred-projects.yml b/changelogs/unreleased/34555-empty-state-for-starred-projects.yml
deleted file mode 100644
index 926d3a2eecf..00000000000
--- a/changelogs/unreleased/34555-empty-state-for-starred-projects.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve empty state for starred projects
-merge_request: 25138
-author:
-type: changed
diff --git a/changelogs/unreleased/35638-move-language-setting-to-preferences.yml b/changelogs/unreleased/35638-move-language-setting-to-preferences.yml
deleted file mode 100644
index d8658218676..00000000000
--- a/changelogs/unreleased/35638-move-language-setting-to-preferences.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Move language setting to preferences
-merge_request: 25427
-author: Fabian Schneider @fabsrc
-type: changed
diff --git a/changelogs/unreleased/37673-minor-issue-with-apostrophe-single-quote-when-clicking-assign-to-me.yml b/changelogs/unreleased/37673-minor-issue-with-apostrophe-single-quote-when-clicking-assign-to-me.yml
deleted file mode 100644
index a470f917d53..00000000000
--- a/changelogs/unreleased/37673-minor-issue-with-apostrophe-single-quote-when-clicking-assign-to-me.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix username escaping when using assign to me for issues
-merge_request: 24673
-author:
-type: fixed
diff --git a/changelogs/unreleased/39010-add-left-margin-to-1st-time-contributor-badge.yml b/changelogs/unreleased/39010-add-left-margin-to-1st-time-contributor-badge.yml
deleted file mode 100644
index 758b97deb3b..00000000000
--- a/changelogs/unreleased/39010-add-left-margin-to-1st-time-contributor-badge.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add left margin to 1st time contributor badge
-merge_request: 25216
-author: Gokhan Apaydin
-type: fixed
diff --git a/changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml b/changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml
deleted file mode 100644
index 1af49fb6a2c..00000000000
--- a/changelogs/unreleased/39676-wiki-api-problems-on-update-parameters-and-500-error.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Require only one parameter when updating a wiki'
-merge_request: 25191
-author: Robert Schilling
-type: fixed
diff --git a/changelogs/unreleased/40396-sidekiq-in-process-group.yml b/changelogs/unreleased/40396-sidekiq-in-process-group.yml
deleted file mode 100644
index e41557e20d0..00000000000
--- a/changelogs/unreleased/40396-sidekiq-in-process-group.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'sidekiq: terminate child processes at shutdown'
-merge_request: 25669
-author:
-type: added
diff --git a/changelogs/unreleased/40396-use-pgroups-for-background-jobs.yml b/changelogs/unreleased/40396-use-pgroups-for-background-jobs.yml
deleted file mode 100644
index 578c780e1a1..00000000000
--- a/changelogs/unreleased/40396-use-pgroups-for-background-jobs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'If chpst is available, make fron-source installations run sidekiq as a process group leader'
-merge_request: 25654
-author:
-type: other
diff --git a/changelogs/unreleased/40795-set-project-name-on-fork-api.yml b/changelogs/unreleased/40795-set-project-name-on-fork-api.yml
deleted file mode 100644
index 742184bbe1e..00000000000
--- a/changelogs/unreleased/40795-set-project-name-on-fork-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add ability to set path and name for project on fork using API
-merge_request: 25363
-author:
-type: added
diff --git a/changelogs/unreleased/41888-access-personal-snippets-by-api.yml b/changelogs/unreleased/41888-access-personal-snippets-by-api.yml
deleted file mode 100644
index 3561a01ec5f..00000000000
--- a/changelogs/unreleased/41888-access-personal-snippets-by-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow all snippets to be accessed by API
-merge_request: 25772
-author:
-type: added
diff --git a/changelogs/unreleased/42086-project-fetch-statistics-api-http-only.yml b/changelogs/unreleased/42086-project-fetch-statistics-api-http-only.yml
deleted file mode 100644
index f2c4f88b746..00000000000
--- a/changelogs/unreleased/42086-project-fetch-statistics-api-http-only.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add project fetch statistics
-merge_request: 23596
-author: Jacopo Beschi @jacopo-beschi
-type: added
diff --git a/changelogs/unreleased/43297-authorized-application-count.yml b/changelogs/unreleased/43297-authorized-application-count.yml
new file mode 100644
index 00000000000..d22e155fb14
--- /dev/null
+++ b/changelogs/unreleased/43297-authorized-application-count.yml
@@ -0,0 +1,5 @@
+---
+title: Fix authorized application count
+merge_request: 25715
+author: moyuru
+type: fixed
diff --git a/changelogs/unreleased/44740-api-to-verify-a-given-user-has-right-to-merge-a-given-mergerequest.yml b/changelogs/unreleased/44740-api-to-verify-a-given-user-has-right-to-merge-a-given-mergerequest.yml
deleted file mode 100644
index 1c739130fcc..00000000000
--- a/changelogs/unreleased/44740-api-to-verify-a-given-user-has-right-to-merge-a-given-mergerequest.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Expose if the current user can merge a MR'
-merge_request: 25207
-author: Robert Schilling
-type: added
diff --git a/changelogs/unreleased/45035-force-push-api.yml b/changelogs/unreleased/45035-force-push-api.yml
deleted file mode 100644
index 05f5a36ac38..00000000000
--- a/changelogs/unreleased/45035-force-push-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Accept force option to overwrite branch on commit via API
-merge_request: 25286
-author:
-type: added
diff --git a/changelogs/unreleased/45305-ci-status-icon-mismatch-on-merge-requests-page-and-the-mr-itself.yml b/changelogs/unreleased/45305-ci-status-icon-mismatch-on-merge-requests-page-and-the-mr-itself.yml
deleted file mode 100644
index 64ab76a2b05..00000000000
--- a/changelogs/unreleased/45305-ci-status-icon-mismatch-on-merge-requests-page-and-the-mr-itself.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix pipeline status icon mismatch
-merge_request: 25407
-author:
-type: fixed
diff --git a/changelogs/unreleased/46464-improve-stop-pipeline-modal.yml b/changelogs/unreleased/46464-improve-stop-pipeline-modal.yml
deleted file mode 100644
index 541acdc56a7..00000000000
--- a/changelogs/unreleased/46464-improve-stop-pipeline-modal.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show pipeline ID, commit, and branch name on modal while stopping pipeline
-merge_request: 25059
-author:
-type: changed
diff --git a/changelogs/unreleased/46750-ci-empty-environment-is-created-even-when-a-job-isn-t-run-when-manual.yml b/changelogs/unreleased/46750-ci-empty-environment-is-created-even-when-a-job-isn-t-run-when-manual.yml
deleted file mode 100644
index d052a28ab51..00000000000
--- a/changelogs/unreleased/46750-ci-empty-environment-is-created-even-when-a-job-isn-t-run-when-manual.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Sort Environments by Last Updated
-merge_request: 25260
-author:
-type: added
diff --git a/changelogs/unreleased/46787-create-project-label-window-is-cut-off-at-the-bottom.yml b/changelogs/unreleased/46787-create-project-label-window-is-cut-off-at-the-bottom.yml
new file mode 100644
index 00000000000..dca1d57d14e
--- /dev/null
+++ b/changelogs/unreleased/46787-create-project-label-window-is-cut-off-at-the-bottom.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed - Create project label window is cut off at the bottom
+merge_request: 26049
+author:
+type: fixed
diff --git a/changelogs/unreleased/47150-update-sshkey.yml b/changelogs/unreleased/47150-update-sshkey.yml
deleted file mode 100644
index 342bdb1e2bc..00000000000
--- a/changelogs/unreleased/47150-update-sshkey.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix validation of certain ed25519 keys
-merge_request: 25115
-author: Merlijn B. W. Wajer
-type: fixed
diff --git a/changelogs/unreleased/47869-jobs-tab-border-top-in-pipeline-s-page-is-1px-off.yml b/changelogs/unreleased/47869-jobs-tab-border-top-in-pipeline-s-page-is-1px-off.yml
deleted file mode 100644
index e08d2a99369..00000000000
--- a/changelogs/unreleased/47869-jobs-tab-border-top-in-pipeline-s-page-is-1px-off.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve Jobs tab border top in pipeline's page is 1px off
-merge_request: 24878
-author:
-type: fixed
diff --git a/changelogs/unreleased/48297-fix-code-selection.yml b/changelogs/unreleased/48297-fix-code-selection.yml
new file mode 100644
index 00000000000..14841b00969
--- /dev/null
+++ b/changelogs/unreleased/48297-fix-code-selection.yml
@@ -0,0 +1,6 @@
+---
+title: Resolve Code in other column of side-by-side diff is highlighted when selecting
+ code on one side
+merge_request: 26423
+author:
+type: fixed
diff --git a/changelogs/unreleased/48798-keybinding-mr-diff.yml b/changelogs/unreleased/48798-keybinding-mr-diff.yml
deleted file mode 100644
index 3ef3f07f27c..00000000000
--- a/changelogs/unreleased/48798-keybinding-mr-diff.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Next/previous navigation between files in MR review
-merge_request: 25355
-author:
-type: added \ No newline at end of file
diff --git a/changelogs/unreleased/49397-move-files-in-ide.yml b/changelogs/unreleased/49397-move-files-in-ide.yml
deleted file mode 100644
index 488091d383c..00000000000
--- a/changelogs/unreleased/49397-move-files-in-ide.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve Move files in the Web IDE
-merge_request: 25431
-author:
-type: added
diff --git a/changelogs/unreleased/49502-gpg-signature-api-endpoint.yml b/changelogs/unreleased/49502-gpg-signature-api-endpoint.yml
deleted file mode 100644
index 8393cb9d282..00000000000
--- a/changelogs/unreleased/49502-gpg-signature-api-endpoint.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add API endpoint to get a commit's GPG signature
-merge_request: 25032
-author:
-type: added
diff --git a/changelogs/unreleased/49663-branch-to-mr-connection.yml b/changelogs/unreleased/49663-branch-to-mr-connection.yml
deleted file mode 100644
index d92ed6fd3bf..00000000000
--- a/changelogs/unreleased/49663-branch-to-mr-connection.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Link to most recent MR from a branch
-merge_request: 25689
-author:
-type: added
diff --git a/changelogs/unreleased/49863-ingress-ip-loading-state.yml b/changelogs/unreleased/49863-ingress-ip-loading-state.yml
new file mode 100644
index 00000000000..51bb27d3153
--- /dev/null
+++ b/changelogs/unreleased/49863-ingress-ip-loading-state.yml
@@ -0,0 +1,5 @@
+---
+title: Show loading spinner while Ingress/Knative IP is being assigned
+merge_request: 25912
+author:
+type: changed
diff --git a/changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml b/changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml
deleted file mode 100644
index 3c8b58f3001..00000000000
--- a/changelogs/unreleased/50006-expose-textcolor-from-public-labels-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Expose text_color for project and group labels'
-merge_request: 25172
-author: Robert Schilling
-type: added
diff --git a/changelogs/unreleased/50313-use-kaniko-to-build-containers-in-autodevops.yml b/changelogs/unreleased/50313-use-kaniko-to-build-containers-in-autodevops.yml
deleted file mode 100644
index 0188df7fce7..00000000000
--- a/changelogs/unreleased/50313-use-kaniko-to-build-containers-in-autodevops.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use auto-build-image for build job in Auto-DevOps.gitlab-ci.yml
-merge_request: 24279
-author:
-type: changed
diff --git a/changelogs/unreleased/50433-make-emoji-picker-bigger.yml b/changelogs/unreleased/50433-make-emoji-picker-bigger.yml
deleted file mode 100644
index 8fcf41df09d..00000000000
--- a/changelogs/unreleased/50433-make-emoji-picker-bigger.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make emoji picker bigger
-merge_request: 25187
-author: Jacopo Beschi @jacopo-beschi
-type: changed
diff --git a/changelogs/unreleased/51819-show-feed-toggle-under-system-notes.yml b/changelogs/unreleased/51819-show-feed-toggle-under-system-notes.yml
deleted file mode 100644
index 76ea4149c56..00000000000
--- a/changelogs/unreleased/51819-show-feed-toggle-under-system-notes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add support for toggling discussion filter from notes section
-merge_request: 25426
-author:
-type: added
diff --git a/changelogs/unreleased/51971-milestones-visibility.yml b/changelogs/unreleased/51971-milestones-visibility.yml
deleted file mode 100644
index 818f0071e6c..00000000000
--- a/changelogs/unreleased/51971-milestones-visibility.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Check if desired milestone for an issue is available
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/52198-timer-is-vertically-misaligned-for-delayed-jobs-in-pipeline-actions.yml b/changelogs/unreleased/52198-timer-is-vertically-misaligned-for-delayed-jobs-in-pipeline-actions.yml
deleted file mode 100644
index 84062c6db91..00000000000
--- a/changelogs/unreleased/52198-timer-is-vertically-misaligned-for-delayed-jobs-in-pipeline-actions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Timer and action name aligned vertically for delayed jobs in pipeline actions'
-merge_request: 25117
-author: Gokhan Apaydin
-type: fixed
diff --git a/changelogs/unreleased/52424-goodbye-hipchat.yml b/changelogs/unreleased/52424-goodbye-hipchat.yml
deleted file mode 100644
index 26dc904af5f..00000000000
--- a/changelogs/unreleased/52424-goodbye-hipchat.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove HipChat integration from GitLab
-merge_request: 22223
-author:
-type: removed
diff --git a/changelogs/unreleased/52447-auto-devops-at-group-level.yml b/changelogs/unreleased/52447-auto-devops-at-group-level.yml
new file mode 100644
index 00000000000..0a21c6a2b7b
--- /dev/null
+++ b/changelogs/unreleased/52447-auto-devops-at-group-level.yml
@@ -0,0 +1,5 @@
+---
+title: Enable/disable Auto DevOps at the Group level
+merge_request: 25533
+author:
+type: added
diff --git a/changelogs/unreleased/52459-display-job-names-consistently-on-pipelines-and-environments-list.yml b/changelogs/unreleased/52459-display-job-names-consistently-on-pipelines-and-environments-list.yml
deleted file mode 100644
index 816fee1ccfc..00000000000
--- a/changelogs/unreleased/52459-display-job-names-consistently-on-pipelines-and-environments-list.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display job names consistently on pipelines and environments list
-merge_request: 24984
-author:
-type: fixed
diff --git a/changelogs/unreleased/52734-styling-of-user-project-and-group-avatars.yml b/changelogs/unreleased/52734-styling-of-user-project-and-group-avatars.yml
deleted file mode 100644
index 9329e81eb83..00000000000
--- a/changelogs/unreleased/52734-styling-of-user-project-and-group-avatars.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add rectangular project and group avatars
-merge_request: 25098
-author:
-type: other
diff --git a/changelogs/unreleased/52778-don-t-display-pipeline-status-if-pipelines-are-disabled.yml b/changelogs/unreleased/52778-don-t-display-pipeline-status-if-pipelines-are-disabled.yml
deleted file mode 100644
index 7fa01e2835a..00000000000
--- a/changelogs/unreleased/52778-don-t-display-pipeline-status-if-pipelines-are-disabled.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Hide pipeline status when pipelines are disabled on project.
-merge_request: 25204
-author:
-type: fixed
diff --git a/changelogs/unreleased/52792-align-mirror-repository-button.yml b/changelogs/unreleased/52792-align-mirror-repository-button.yml
deleted file mode 100644
index e8ce9ee0a4e..00000000000
--- a/changelogs/unreleased/52792-align-mirror-repository-button.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add right padding to the repository mirror action buttons
-merge_request: 25606
-author:
-type: fixed
diff --git a/changelogs/unreleased/52877-ios-publishing-blog-post-and-gitlab-ci-yml-template.yml b/changelogs/unreleased/52877-ios-publishing-blog-post-and-gitlab-ci-yml-template.yml
deleted file mode 100644
index 13529348c60..00000000000
--- a/changelogs/unreleased/52877-ios-publishing-blog-post-and-gitlab-ci-yml-template.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add iOS-fastlane template for .gitlab-ci.yml
-merge_request: 25395
-author:
-type: changed
diff --git a/changelogs/unreleased/53139-hide-tree-single-file.yml b/changelogs/unreleased/53139-hide-tree-single-file.yml
new file mode 100644
index 00000000000..17fe957e42e
--- /dev/null
+++ b/changelogs/unreleased/53139-hide-tree-single-file.yml
@@ -0,0 +1,5 @@
+---
+title: collapse file tree by default if the merge request changes only one file
+merge_request:
+author: Riccardo Padovani <riccardo@rpadovani.com>
+type: changed
diff --git a/changelogs/unreleased/53325-admin-runners-page-fails-with-an-sql-statement-timeout.yml b/changelogs/unreleased/53325-admin-runners-page-fails-with-an-sql-statement-timeout.yml
deleted file mode 100644
index e0ed38fc2fa..00000000000
--- a/changelogs/unreleased/53325-admin-runners-page-fails-with-an-sql-statement-timeout.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use limited counter for runner build count in admin page.
-merge_request: 25220
-author:
-type: fixed
diff --git a/changelogs/unreleased/53336-improve-web-ide-launch-performance.yml b/changelogs/unreleased/53336-improve-web-ide-launch-performance.yml
deleted file mode 100644
index 65439f5a6c2..00000000000
--- a/changelogs/unreleased/53336-improve-web-ide-launch-performance.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve Web IDE launch performance
-merge_request: 25700
-author:
-type: performance
diff --git a/changelogs/unreleased/53361-fresh-protected-branches.yml b/changelogs/unreleased/53361-fresh-protected-branches.yml
deleted file mode 100644
index 55080e719b7..00000000000
--- a/changelogs/unreleased/53361-fresh-protected-branches.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow creation of branches that match a wildcard protection, except directly through git
-merge_request: 24969
-author:
-type: added
diff --git a/changelogs/unreleased/53411-remove_personal_access_tokens_token.yml b/changelogs/unreleased/53411-remove_personal_access_tokens_token.yml
deleted file mode 100644
index 32cca07f58d..00000000000
--- a/changelogs/unreleased/53411-remove_personal_access_tokens_token.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove undigested token column from personal_access_tokens table from the database
-merge_request: 22743
-author:
-type: changed
diff --git a/changelogs/unreleased/53413-externalize-markdown-toolbar-tooltips.yml b/changelogs/unreleased/53413-externalize-markdown-toolbar-tooltips.yml
deleted file mode 100644
index c460760c10b..00000000000
--- a/changelogs/unreleased/53413-externalize-markdown-toolbar-tooltips.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Externalize markdown toolbar buttons tooltips
-merge_request: 25529
-author:
-type: fixed
diff --git a/changelogs/unreleased/53861-api-promote-project-milestone-to-a-group-milestone.yml b/changelogs/unreleased/53861-api-promote-project-milestone-to-a-group-milestone.yml
deleted file mode 100644
index 6c621763e2e..00000000000
--- a/changelogs/unreleased/53861-api-promote-project-milestone-to-a-group-milestone.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Promote project milestone to a group milestone'
-merge_request: 25203
-author: Nermin Vehabovic
-type: added
diff --git a/changelogs/unreleased/53966-make-hashed-storage-migration-safer-and-more-inviting.yml b/changelogs/unreleased/53966-make-hashed-storage-migration-safer-and-more-inviting.yml
deleted file mode 100644
index 556a238ff7d..00000000000
--- a/changelogs/unreleased/53966-make-hashed-storage-migration-safer-and-more-inviting.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Hashed Storage rollback mechanism
-merge_request: 23955
-author:
-type: added
diff --git a/changelogs/unreleased/54643-lower_issuable_finder_complexity.yml b/changelogs/unreleased/54643-lower_issuable_finder_complexity.yml
deleted file mode 100644
index f7f8e4d0e1f..00000000000
--- a/changelogs/unreleased/54643-lower_issuable_finder_complexity.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Speed up group issue search counts
-merge_request: 25411
-author:
-type: performance
diff --git a/changelogs/unreleased/54725-fix-emoji-button-active-state.yml b/changelogs/unreleased/54725-fix-emoji-button-active-state.yml
deleted file mode 100644
index 4f0a436cc87..00000000000
--- a/changelogs/unreleased/54725-fix-emoji-button-active-state.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix hover and active state colors of award emoji button
-merge_request: 25295
-author:
-type: fixed
diff --git a/changelogs/unreleased/54796-api-sort-tie-breaker-for-pagination.yml b/changelogs/unreleased/54796-api-sort-tie-breaker-for-pagination.yml
deleted file mode 100644
index 92b27f63f82..00000000000
--- a/changelogs/unreleased/54796-api-sort-tie-breaker-for-pagination.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Sort tie breaker with id DESC'
-merge_request: 25311
-author: Nermin Vehabovic
-type: changed
diff --git a/changelogs/unreleased/54850-pages-domain-show-view-is-not-protected-by-access-control.yml b/changelogs/unreleased/54850-pages-domain-show-view-is-not-protected-by-access-control.yml
deleted file mode 100644
index 41761213d7b..00000000000
--- a/changelogs/unreleased/54850-pages-domain-show-view-is-not-protected-by-access-control.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Require maintainer access to show pages domain settings
-merge_request: 24926
-author:
-type: fixed
diff --git a/changelogs/unreleased/54924-refactor-notes-actions-params.yml b/changelogs/unreleased/54924-refactor-notes-actions-params.yml
deleted file mode 100644
index b6083820401..00000000000
--- a/changelogs/unreleased/54924-refactor-notes-actions-params.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix commenting on commits having SHA1 starting with a large number
-merge_request: 25278
-author:
-type: fixed
diff --git a/changelogs/unreleased/55057-system-message-to-core.yml b/changelogs/unreleased/55057-system-message-to-core.yml
deleted file mode 100644
index 3381879eb4a..00000000000
--- a/changelogs/unreleased/55057-system-message-to-core.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Port System Header and Footer feature to Core
-merge_request: 25241
-author:
-type: added
diff --git a/changelogs/unreleased/55109-jira-integration-api-doesn-t-respect-available-format.yml b/changelogs/unreleased/55109-jira-integration-api-doesn-t-respect-available-format.yml
deleted file mode 100644
index c58cdc19555..00000000000
--- a/changelogs/unreleased/55109-jira-integration-api-doesn-t-respect-available-format.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Support Jira transition ID as string'
-merge_request: 24400
-author: Robert Schilling
-type: fixed
diff --git a/changelogs/unreleased/55209-tool-tip-hides-menu-item.yml b/changelogs/unreleased/55209-tool-tip-hides-menu-item.yml
deleted file mode 100644
index 44ea4141632..00000000000
--- a/changelogs/unreleased/55209-tool-tip-hides-menu-item.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Close More Actions tooltip when menu opens
-merge_request: 24285
-author:
-type: fixed
diff --git a/changelogs/unreleased/55312-svg.yml b/changelogs/unreleased/55312-svg.yml
deleted file mode 100644
index a6260aeaf2a..00000000000
--- a/changelogs/unreleased/55312-svg.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use export-import svgs from gitlab-svgs
-merge_request: 24954
-author:
-type: other
diff --git a/changelogs/unreleased/55376-related_merge_requests-api-call-returns-merge-requests-that-are-not-related-to-the-issue.yml b/changelogs/unreleased/55376-related_merge_requests-api-call-returns-merge-requests-that-are-not-related-to-the-issue.yml
deleted file mode 100644
index d2f24d6f499..00000000000
--- a/changelogs/unreleased/55376-related_merge_requests-api-call-returns-merge-requests-that-are-not-related-to-the-issue.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Ensure that related merge requests are referenced cross-project'
-merge_request: 25222
-author: Robert Schilling
-type: fixed
diff --git a/changelogs/unreleased/55447-validate-k8s-ca-cert.yml b/changelogs/unreleased/55447-validate-k8s-ca-cert.yml
deleted file mode 100644
index e0448d403da..00000000000
--- a/changelogs/unreleased/55447-validate-k8s-ca-cert.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Validate kubernetes cluster CA certificate
-merge_request: 24990
-author:
-type: changed
diff --git a/changelogs/unreleased/55703-md-image-borders.yml b/changelogs/unreleased/55703-md-image-borders.yml
deleted file mode 100644
index 94297a42f6d..00000000000
--- a/changelogs/unreleased/55703-md-image-borders.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Only show borders for markdown images in notes
-merge_request: 25448
-author:
-type: fixed
diff --git a/changelogs/unreleased/55893-artifacts-download.yml b/changelogs/unreleased/55893-artifacts-download.yml
deleted file mode 100644
index 30c118b7094..00000000000
--- a/changelogs/unreleased/55893-artifacts-download.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes not working dropdowns in pipelines page
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/55925-if-there-is-only-one-changed-page-in-review-app-go-directly-there.yml b/changelogs/unreleased/55925-if-there-is-only-one-changed-page-in-review-app-go-directly-there.yml
deleted file mode 100644
index ef3d9844acb..00000000000
--- a/changelogs/unreleased/55925-if-there-is-only-one-changed-page-in-review-app-go-directly-there.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Review App Link to Changed Page if Only One Change Present
-merge_request: 25048
-author:
-type: changed
diff --git a/changelogs/unreleased/57544-web-ide-new-directory-dialog-shows-file-templates.yml b/changelogs/unreleased/56015-remove-remote-timeout.yml
index 9d9158ca4af..9b40ada5291 100644
--- a/changelogs/unreleased/57544-web-ide-new-directory-dialog-shows-file-templates.yml
+++ b/changelogs/unreleased/56015-remove-remote-timeout.yml
@@ -1,5 +1,5 @@
---
-title: Do not show file templates when creating a new directory in WebIDE
-merge_request: !25119
+title: Fix removing remote mirror failure which leaves unnecessary refs behind
+merge_request: 26213
author:
type: fixed
diff --git a/changelogs/unreleased/56089-merge-gitlab-keys.yml b/changelogs/unreleased/56089-merge-gitlab-keys.yml
new file mode 100644
index 00000000000..5e2cafd3254
--- /dev/null
+++ b/changelogs/unreleased/56089-merge-gitlab-keys.yml
@@ -0,0 +1,5 @@
+---
+title: Merge the gitlab-shell "gitlab-keys" functionality into GitLab CE
+merge_request: 25598
+author:
+type: other
diff --git a/changelogs/unreleased/56237-api-truncated-commit-title.yml b/changelogs/unreleased/56237-api-truncated-commit-title.yml
deleted file mode 100644
index 1a48d0fda1b..00000000000
--- a/changelogs/unreleased/56237-api-truncated-commit-title.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Expose full commit title'
-merge_request: 25189
-author: Robert Schilling
-type: fixed
diff --git a/changelogs/unreleased/56251-fix-issue-board-weekday-shift.yml b/changelogs/unreleased/56251-fix-issue-board-weekday-shift.yml
deleted file mode 100644
index bedc488ebd4..00000000000
--- a/changelogs/unreleased/56251-fix-issue-board-weekday-shift.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Fix weekday shift in issue board cards for UTC+X timezones by removing local timezone to UTC conversion"
-merge_request: 25512
-author: Elias Werberich
-type: fixed
diff --git a/changelogs/unreleased/56477-units-are-appended-to-y-axis-label-on-metrics-dashboard.yml b/changelogs/unreleased/56477-units-are-appended-to-y-axis-label-on-metrics-dashboard.yml
deleted file mode 100644
index 7febe175faf..00000000000
--- a/changelogs/unreleased/56477-units-are-appended-to-y-axis-label-on-metrics-dashboard.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove duplicate units from metrics graph
-merge_request: 25485
-author:
-type: fixed
diff --git a/changelogs/unreleased/56485-implement-graphql-mergerequestsresolver.yml b/changelogs/unreleased/56485-implement-graphql-mergerequestsresolver.yml
deleted file mode 100644
index 5362ac65038..00000000000
--- a/changelogs/unreleased/56485-implement-graphql-mergerequestsresolver.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add field mergeRequests for project in GraphQL
-merge_request: 24805
-author:
-type: added
diff --git a/changelogs/unreleased/56492-implement-new-arguments-state-closed_before-and-closed_after-for-issuesresolver-in-graphql.yml b/changelogs/unreleased/56492-implement-new-arguments-state-closed_before-and-closed_after-for-issuesresolver-in-graphql.yml
deleted file mode 100644
index 9b7aed82d49..00000000000
--- a/changelogs/unreleased/56492-implement-new-arguments-state-closed_before-and-closed_after-for-issuesresolver-in-graphql.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Implement new arguments `state`, `closed_before` and `closed_after` for `IssuesResolver` in GraphQL"
-merge_request: 24910
-author:
-type: changed
diff --git a/changelogs/unreleased/56618-hashed-storage-skip-validation.yml b/changelogs/unreleased/56618-hashed-storage-skip-validation.yml
deleted file mode 100644
index c6b32d0bfec..00000000000
--- a/changelogs/unreleased/56618-hashed-storage-skip-validation.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Skip Project validation during Hashed Storage migration or rollback
-merge_request: 25753
-author:
-type: fixed
diff --git a/changelogs/unreleased/56694-mark-group-level-labels-in-label-api-as-such.yml b/changelogs/unreleased/56694-mark-group-level-labels-in-label-api-as-such.yml
deleted file mode 100644
index ae2d9e18e0b..00000000000
--- a/changelogs/unreleased/56694-mark-group-level-labels-in-label-api-as-such.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Indicate if label is a project label'
-merge_request: 25219
-author: Robert Schilling
-type: added
diff --git a/changelogs/unreleased/56726-fix-n-1-in-issues-and-merge-requests-api.yml b/changelogs/unreleased/56726-fix-n-1-in-issues-and-merge-requests-api.yml
deleted file mode 100644
index 3eb9e484647..00000000000
--- a/changelogs/unreleased/56726-fix-n-1-in-issues-and-merge-requests-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix N+1 query in Issues and MergeRequest API when issuable_metadata is present
-merge_request: 25042
-author: Alex Koval
-type: other
diff --git a/changelogs/unreleased/56787-realtime-validation-for-user-fullname-and-username.yml b/changelogs/unreleased/56787-realtime-validation-for-user-fullname-and-username.yml
deleted file mode 100644
index cc3a60479d3..00000000000
--- a/changelogs/unreleased/56787-realtime-validation-for-user-fullname-and-username.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add realtime validation for user fullname and username on validation
-merge_request: 25017
-author: Ehsan Abdulqader @EhsanZ
-type: added
diff --git a/changelogs/unreleased/56809-graphql-version-api.yml b/changelogs/unreleased/56809-graphql-version-api.yml
deleted file mode 100644
index 72a4b4e5819..00000000000
--- a/changelogs/unreleased/56809-graphql-version-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add metadata about the GitLab server to GraphQL
-merge_request: 24636
-author:
-type: added
diff --git a/changelogs/unreleased/56833-project-improve-empty-repository-state-ui-fe.yml b/changelogs/unreleased/56833-project-improve-empty-repository-state-ui-fe.yml
new file mode 100644
index 00000000000..19cf3d69db1
--- /dev/null
+++ b/changelogs/unreleased/56833-project-improve-empty-repository-state-ui-fe.yml
@@ -0,0 +1,5 @@
+---
+title: 'Project: Improve empty repository state UI'
+merge_request: 26024
+author:
+type: other
diff --git a/changelogs/unreleased/56851-blank-values-in-reactive-cache.yml b/changelogs/unreleased/56851-blank-values-in-reactive-cache.yml
deleted file mode 100644
index 5b9253793be..00000000000
--- a/changelogs/unreleased/56851-blank-values-in-reactive-cache.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow empty values such as [] to be stored in reactive cache
-merge_request: 25283
-author:
-type: fixed
diff --git a/changelogs/unreleased/56851-error-tracking-page-seems-broken.yml b/changelogs/unreleased/56851-error-tracking-page-seems-broken.yml
deleted file mode 100644
index ff4aebb9381..00000000000
--- a/changelogs/unreleased/56851-error-tracking-page-seems-broken.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix error tracking list page
-merge_request: 24806
-author:
-type: fixed
diff --git a/changelogs/unreleased/56863-system-messages-in-email.yml b/changelogs/unreleased/56863-system-messages-in-email.yml
deleted file mode 100644
index 21a90aa95ee..00000000000
--- a/changelogs/unreleased/56863-system-messages-in-email.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show header and footer system messages in email
-merge_request: 25474
-author:
-type: added
diff --git a/changelogs/unreleased/56864-reopen-locked-mr.yml b/changelogs/unreleased/56864-reopen-locked-mr.yml
new file mode 100644
index 00000000000..d1d71531ac8
--- /dev/null
+++ b/changelogs/unreleased/56864-reopen-locked-mr.yml
@@ -0,0 +1,5 @@
+---
+title: Disallow reopening of a locked merge request
+merge_request: 24882
+author: Jan Beckmann
+type: fixed
diff --git a/changelogs/unreleased/56871-list-issues-error.yml b/changelogs/unreleased/56871-list-issues-error.yml
deleted file mode 100644
index af5585c6b5d..00000000000
--- a/changelogs/unreleased/56871-list-issues-error.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display error message when API call to list Sentry issues fails
-merge_request: 24936
-author:
-type: fixed
diff --git a/changelogs/unreleased/56873-only-load-syntax-highlighting-css-when-selected.yml b/changelogs/unreleased/56873-only-load-syntax-highlighting-css-when-selected.yml
deleted file mode 100644
index a7af8994852..00000000000
--- a/changelogs/unreleased/56873-only-load-syntax-highlighting-css-when-selected.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Only load syntax highlight CSS of selected theme
-merge_request: 25232
-author:
-type: performance
diff --git a/changelogs/unreleased/56937-edit-knative-domain-after-it-has-been-deployed.yml b/changelogs/unreleased/56937-edit-knative-domain-after-it-has-been-deployed.yml
deleted file mode 100644
index 11d93b34700..00000000000
--- a/changelogs/unreleased/56937-edit-knative-domain-after-it-has-been-deployed.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes functions finder for upgraded Knative app
-merge_request: 25067
-author:
-type: fixed
diff --git a/changelogs/unreleased/56937-edit-knative-domain.yml b/changelogs/unreleased/56937-edit-knative-domain.yml
deleted file mode 100644
index 7147a4e06b1..00000000000
--- a/changelogs/unreleased/56937-edit-knative-domain.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Edit Knative domain after it has been deployed
-merge_request: 25386
-author:
-type: added
diff --git a/changelogs/unreleased/56954-improve-knative-after-installing-tiller.yml b/changelogs/unreleased/56954-improve-knative-after-installing-tiller.yml
new file mode 100644
index 00000000000..b9fb27c3218
--- /dev/null
+++ b/changelogs/unreleased/56954-improve-knative-after-installing-tiller.yml
@@ -0,0 +1,5 @@
+---
+title: Improve the Knative installation on Clusters
+merge_request: 26339
+author:
+type: added
diff --git a/changelogs/unreleased/57085-introduce-zoom-and-scroll-functionality-on-metrics-charts.yml b/changelogs/unreleased/57085-introduce-zoom-and-scroll-functionality-on-metrics-charts.yml
deleted file mode 100644
index 1d07666dfb1..00000000000
--- a/changelogs/unreleased/57085-introduce-zoom-and-scroll-functionality-on-metrics-charts.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add zoom and scroll to metrics dashboard
-merge_request: 25388
-author:
-type: added
diff --git a/changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml b/changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml
deleted file mode 100644
index 2e0ae9c3732..00000000000
--- a/changelogs/unreleased/57101-api-docs-for-hangouts-chat-service-incorrect.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'API: Fix docs and parameters for hangouts-chat service'
-merge_request: 25180
-author: Robert Schilling
-type: fixed
diff --git a/changelogs/unreleased/57115-just-in-time-k8s-resource-creation.yml b/changelogs/unreleased/57115-just-in-time-k8s-resource-creation.yml
new file mode 100644
index 00000000000..2141c75ec72
--- /dev/null
+++ b/changelogs/unreleased/57115-just-in-time-k8s-resource-creation.yml
@@ -0,0 +1,5 @@
+---
+title: Create Kubernetes resources for projects when their deployment jobs run.
+merge_request: 25586
+author:
+type: changed
diff --git a/changelogs/unreleased/57223-wiki-finder.yml b/changelogs/unreleased/57223-wiki-finder.yml
deleted file mode 100644
index 5ddf197568d..00000000000
--- a/changelogs/unreleased/57223-wiki-finder.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove BATCH_SIZE from WikiFileFinder
-merge_request: 24933
-author:
-type: other
diff --git a/changelogs/unreleased/57353-git-push-fails-on-large-lfs-files-where-the-push-take-a-long-time.yml b/changelogs/unreleased/57353-git-push-fails-on-large-lfs-files-where-the-push-take-a-long-time.yml
deleted file mode 100644
index 46f82afda62..00000000000
--- a/changelogs/unreleased/57353-git-push-fails-on-large-lfs-files-where-the-push-take-a-long-time.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Provide expires_in in LFS authentication payload
-merge_request: 25082
-author:
-type: fixed
diff --git a/changelogs/unreleased/57357-automate-base-domain-help-text.yml b/changelogs/unreleased/57357-automate-base-domain-help-text.yml
new file mode 100644
index 00000000000..fa1831b66ea
--- /dev/null
+++ b/changelogs/unreleased/57357-automate-base-domain-help-text.yml
@@ -0,0 +1,5 @@
+---
+title: Automate base domain help text on Clusters page
+merge_request: 26124
+author:
+type: changed
diff --git a/changelogs/unreleased/57409-loading-button-transition.yml b/changelogs/unreleased/57409-loading-button-transition.yml
new file mode 100644
index 00000000000..3cf169d79de
--- /dev/null
+++ b/changelogs/unreleased/57409-loading-button-transition.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent fade out transition on loading-button component.
+merge_request: 26428
+author:
+type: fixed
diff --git a/changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml b/changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml
deleted file mode 100644
index 6be6a2115b9..00000000000
--- a/changelogs/unreleased/57410-api-create-release-link-with-ftp-address-return-400-bad-request.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add support for FTP assets for releases
-merge_request: 25071
-author: Robert Schilling
-type: added
diff --git a/changelogs/unreleased/57534_filter_impersonated_sessions.yml b/changelogs/unreleased/57534_filter_impersonated_sessions.yml
deleted file mode 100644
index 80aea0ab1bc..00000000000
--- a/changelogs/unreleased/57534_filter_impersonated_sessions.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Do not display impersonated sessions under active sessions and remove ability
- to revoke session
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/57540-filename-trailing-space.yml b/changelogs/unreleased/57540-filename-trailing-space.yml
new file mode 100644
index 00000000000..db85fb350db
--- /dev/null
+++ b/changelogs/unreleased/57540-filename-trailing-space.yml
@@ -0,0 +1,5 @@
+---
+title: Implemented whitespace-trimming for file names in Web IDE
+merge_request: 26270
+author:
+type: fixed
diff --git a/changelogs/unreleased/57564-contributing-button-border.yml b/changelogs/unreleased/57564-contributing-button-border.yml
deleted file mode 100644
index e5875ef1c0f..00000000000
--- a/changelogs/unreleased/57564-contributing-button-border.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix the border style of CONTRIBUTING button when it exists
-merge_request: 25124
-author: Takuya Noguchi
-type: fixed
diff --git a/changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml b/changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml
deleted file mode 100644
index f7d6a6c4863..00000000000
--- a/changelogs/unreleased/57579-gitlab-project-import-fails-sidekiq-undefined-method-import_jid.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix import_jid error on project import
-merge_request: 25239
-author:
-type: fixed
diff --git a/changelogs/unreleased/57582-dropdown-icon-misalignment-on-issues-list-on-mobile-screen.yml b/changelogs/unreleased/57582-dropdown-icon-misalignment-on-issues-list-on-mobile-screen.yml
deleted file mode 100644
index 5681309cb9e..00000000000
--- a/changelogs/unreleased/57582-dropdown-icon-misalignment-on-issues-list-on-mobile-screen.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix alignment of dropdown icon on issuable on mobile
-merge_request: 25205
-author: Takuya Noguchi
-type: fixed
diff --git a/changelogs/unreleased/57612-github-importer-ignores-milestone-due_date.yml b/changelogs/unreleased/57612-github-importer-ignores-milestone-due_date.yml
deleted file mode 100644
index 0d5cd057ade..00000000000
--- a/changelogs/unreleased/57612-github-importer-ignores-milestone-due_date.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Capture due date when importing milestones from Github
-merge_request: 25182
-author: dstanley
-type: changed
diff --git a/changelogs/unreleased/57648-make-emoji-picker-full-width-on-mobile.yml b/changelogs/unreleased/57648-make-emoji-picker-full-width-on-mobile.yml
new file mode 100644
index 00000000000..d92fd2a762e
--- /dev/null
+++ b/changelogs/unreleased/57648-make-emoji-picker-full-width-on-mobile.yml
@@ -0,0 +1,5 @@
+---
+title: Makes emoji picker full width on mobile.
+merge_request: 25883
+author: Jacopo Beschi @jacopo-beschi
+type: fixed
diff --git a/changelogs/unreleased/57655-fix-markdown-tables-border.yml b/changelogs/unreleased/57655-fix-markdown-tables-border.yml
new file mode 100644
index 00000000000..6a8ba8c4353
--- /dev/null
+++ b/changelogs/unreleased/57655-fix-markdown-tables-border.yml
@@ -0,0 +1,5 @@
+---
+title: Fix markdown table header and table content borders
+merge_request: 25666
+author:
+type: fixed
diff --git a/changelogs/unreleased/57671-fix_merge_request_base_pipeline.yml b/changelogs/unreleased/57671-fix_merge_request_base_pipeline.yml
deleted file mode 100644
index d89819eee60..00000000000
--- a/changelogs/unreleased/57671-fix_merge_request_base_pipeline.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Ensure the base pipeline of a Merge Request belongs to its target branch
-merge_request: 25226
-author:
-type: fixed
diff --git a/changelogs/unreleased/57712-project-import-error-user-expected-got-hash.yml b/changelogs/unreleased/57712-project-import-error-user-expected-got-hash.yml
deleted file mode 100644
index 6fb198e1552..00000000000
--- a/changelogs/unreleased/57712-project-import-error-user-expected-got-hash.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix project import error importing releases
-merge_request: 25495
-author:
-type: fixed
diff --git a/changelogs/unreleased/57734-improve-label-dropdown-selection-performance.yml b/changelogs/unreleased/57734-improve-label-dropdown-selection-performance.yml
deleted file mode 100644
index 781446b86d7..00000000000
--- a/changelogs/unreleased/57734-improve-label-dropdown-selection-performance.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve label select rendering
-merge_request: 25281
-author:
-type: performance
diff --git a/changelogs/unreleased/57768-remove-vertical-line.yml b/changelogs/unreleased/57768-remove-vertical-line.yml
deleted file mode 100644
index b73b0fa229e..00000000000
--- a/changelogs/unreleased/57768-remove-vertical-line.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove vertical connecting line placeholder from diff discussion notes
-merge_request: 25292
-author:
-type: fixed
diff --git a/changelogs/unreleased/57784-make-closed-duplicate-and-closed-moved-button-a-link-to-target.yml b/changelogs/unreleased/57784-make-closed-duplicate-and-closed-moved-button-a-link-to-target.yml
deleted file mode 100644
index 2775d9f4e36..00000000000
--- a/changelogs/unreleased/57784-make-closed-duplicate-and-closed-moved-button-a-link-to-target.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Link from Closed (moved) Issues to Moved Issue
-merge_request: 25300
-author:
-type: added
diff --git a/changelogs/unreleased/57785-create-project-template-for-netlify.yml b/changelogs/unreleased/57785-create-project-template-for-netlify.yml
deleted file mode 100644
index 78e9e3dece5..00000000000
--- a/changelogs/unreleased/57785-create-project-template-for-netlify.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve Create Project Template for Netlify
-merge_request: 25453
-author:
-type: changed
diff --git a/changelogs/unreleased/57788-project-labels-tooltip-missing.yml b/changelogs/unreleased/57788-project-labels-tooltip-missing.yml
deleted file mode 100644
index 9146af0e0f3..00000000000
--- a/changelogs/unreleased/57788-project-labels-tooltip-missing.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix bug where project topics truncate
-merge_request: 25398
-author:
-type: fixed
diff --git a/changelogs/unreleased/57794-project-template-for-net.yml b/changelogs/unreleased/57794-project-template-for-net.yml
deleted file mode 100644
index bc05ac10aff..00000000000
--- a/changelogs/unreleased/57794-project-template-for-net.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Project template for .NET Core
-merge_request: 25486
-author:
-type: changed
diff --git a/changelogs/unreleased/57829-issuable-meta-line-ui-broken-on-mobile.yml b/changelogs/unreleased/57829-issuable-meta-line-ui-broken-on-mobile.yml
deleted file mode 100644
index b4b305e76d0..00000000000
--- a/changelogs/unreleased/57829-issuable-meta-line-ui-broken-on-mobile.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix author layouts in issuable meta line UIs on mobile
-merge_request: 25332
-author: Takuya Noguchi
-type: fixed
diff --git a/changelogs/unreleased/57894-buttons-on-group-page-are-misaligned.yml b/changelogs/unreleased/57894-buttons-on-group-page-are-misaligned.yml
new file mode 100644
index 00000000000..ca0f529df6c
--- /dev/null
+++ b/changelogs/unreleased/57894-buttons-on-group-page-are-misaligned.yml
@@ -0,0 +1,5 @@
+---
+title: Fix misalignment of group overview page buttons
+merge_request: 26292
+author:
+type: fixed
diff --git a/changelogs/unreleased/57905-etag-caching-probably-broken-since-11-5-0.yml b/changelogs/unreleased/57905-etag-caching-probably-broken-since-11-5-0.yml
deleted file mode 100644
index 046ef8ee99e..00000000000
--- a/changelogs/unreleased/57905-etag-caching-probably-broken-since-11-5-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix ETag caching not being used for AJAX requests
-merge_request: 25400
-author:
-type: fixed
diff --git a/changelogs/unreleased/57984-store-branch-name.yml b/changelogs/unreleased/57984-store-branch-name.yml
new file mode 100644
index 00000000000..26dfdb7a5d6
--- /dev/null
+++ b/changelogs/unreleased/57984-store-branch-name.yml
@@ -0,0 +1,5 @@
+---
+title: Resolves Branch name is lost if I change commit mode in Web IDE
+merge_request: 26180
+author:
+type: fixed
diff --git a/changelogs/unreleased/57991-frontend-pagination-needs-to-handle-cases-where-the-x-total-pages-header-isn-t-present.yml b/changelogs/unreleased/57991-frontend-pagination-needs-to-handle-cases-where-the-x-total-pages-header-isn-t-present.yml
deleted file mode 100644
index 2e18377a4cf..00000000000
--- a/changelogs/unreleased/57991-frontend-pagination-needs-to-handle-cases-where-the-x-total-pages-header-isn-t-present.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Improve the JS pagination to handle the case when the `X-Total` and `X-Total-Pages` headers aren't present"
-merge_request: 25601
-author:
-type: fixed
diff --git a/changelogs/unreleased/58010-mask-the-existing-variables.yml b/changelogs/unreleased/58010-mask-the-existing-variables.yml
deleted file mode 100644
index cc5fdb29686..00000000000
--- a/changelogs/unreleased/58010-mask-the-existing-variables.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Mask all TOKEN and PASSWORD CI variables.
-merge_request: 25868
-author:
-type: changed
diff --git a/changelogs/unreleased/58020-fix-merge-api-endpoint-param.yml b/changelogs/unreleased/58020-fix-merge-api-endpoint-param.yml
deleted file mode 100644
index 7cfeb4a0cd7..00000000000
--- a/changelogs/unreleased/58020-fix-merge-api-endpoint-param.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Respect the should_remove_source_branch parameter to the merge API
-merge_request: 25525
-author:
-type: fixed
diff --git a/changelogs/unreleased/58023-add-Saturday-to-localization-first-day-of-the-week.yml b/changelogs/unreleased/58023-add-Saturday-to-localization-first-day-of-the-week.yml
deleted file mode 100644
index 69d927dc5e4..00000000000
--- a/changelogs/unreleased/58023-add-Saturday-to-localization-first-day-of-the-week.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Saturday to Localization first day of the week
-merge_request: 25509
-author: Ahmad Haghighi
-type: added
diff --git a/changelogs/unreleased/58082-project-template-for-go-micro.yml b/changelogs/unreleased/58082-project-template-for-go-micro.yml
deleted file mode 100644
index 63a70cda0b8..00000000000
--- a/changelogs/unreleased/58082-project-template-for-go-micro.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add Project template for go-micro
-merge_request: 25553
-author:
-type: changed
diff --git a/changelogs/unreleased/58098-auto-devops-postgres-version-variable.yml b/changelogs/unreleased/58098-auto-devops-postgres-version-variable.yml
deleted file mode 100644
index a7a87f60c28..00000000000
--- a/changelogs/unreleased/58098-auto-devops-postgres-version-variable.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow configuring POSTGRES_VERSION in Auto DevOps
-merge_request: 25500
-author:
-type: added
diff --git a/changelogs/unreleased/58149-fix-read-list-board-policy.yml b/changelogs/unreleased/58149-fix-read-list-board-policy.yml
deleted file mode 100644
index 964813f4c9a..00000000000
--- a/changelogs/unreleased/58149-fix-read-list-board-policy.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Fix error when viewing group issue boards when user doesn't have explicit group
- permissions
-merge_request: 25524
-author:
-type: fixed
diff --git a/changelogs/unreleased/58208-explicitly-set-masterauth.yml b/changelogs/unreleased/58208-explicitly-set-masterauth.yml
new file mode 100644
index 00000000000..e3512d11113
--- /dev/null
+++ b/changelogs/unreleased/58208-explicitly-set-masterauth.yml
@@ -0,0 +1,6 @@
+---
+title: Explicitly set master_auth setting to enable basic auth and client certificate
+ for new GKE clusters
+merge_request: 26018
+author:
+type: other
diff --git a/changelogs/unreleased/58274-folder-icon-in-tags-page.yml b/changelogs/unreleased/58274-folder-icon-in-tags-page.yml
deleted file mode 100644
index db8128b8dfd..00000000000
--- a/changelogs/unreleased/58274-folder-icon-in-tags-page.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use 'folder-open' from sprite icons for Browse Files button in Tag page
-merge_request: 25635
-author:
-type: fixed
diff --git a/changelogs/unreleased/58369-hide-squash-commit.yml b/changelogs/unreleased/58369-hide-squash-commit.yml
deleted file mode 100644
index e44b483b3a1..00000000000
--- a/changelogs/unreleased/58369-hide-squash-commit.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Resolve showing squash commit edit issue when only single commit is present
-merge_request: 25807
-author:
-type: fixed
diff --git a/changelogs/unreleased/58410-change-pixel-size-of-instance-header-footer-message-to-16px.yml b/changelogs/unreleased/58410-change-pixel-size-of-instance-header-footer-message-to-16px.yml
new file mode 100644
index 00000000000..3e494847e75
--- /dev/null
+++ b/changelogs/unreleased/58410-change-pixel-size-of-instance-header-footer-message-to-16px.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce height of instance system header and footer
+merge_request: 25752
+author:
+type: changed
diff --git a/changelogs/unreleased/58482-update-airminc-clair-local-scan-to-2-0-6.yml b/changelogs/unreleased/58482-update-airminc-clair-local-scan-to-2-0-6.yml
new file mode 100644
index 00000000000..be9c38aba1e
--- /dev/null
+++ b/changelogs/unreleased/58482-update-airminc-clair-local-scan-to-2-0-6.yml
@@ -0,0 +1,5 @@
+---
+title: Update clair-local-scan to 2.0.6
+merge_request: 25743
+author: Takuya Noguchi
+type: added
diff --git a/changelogs/unreleased/58570-fix-running-pipline-that-is-imported-via-dotnetcore-template.yml b/changelogs/unreleased/58570-fix-running-pipline-that-is-imported-via-dotnetcore-template.yml
deleted file mode 100644
index b81dc151b44..00000000000
--- a/changelogs/unreleased/58570-fix-running-pipline-that-is-imported-via-dotnetcore-template.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Clean up vendored templates
-merge_request: 25794
-author:
-type: changed
diff --git a/changelogs/unreleased/58648-project-template-for-ios.yml b/changelogs/unreleased/58648-project-template-for-ios.yml
deleted file mode 100644
index 708ecb4d3dc..00000000000
--- a/changelogs/unreleased/58648-project-template-for-ios.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add iOS project template
-merge_request: 25872
-author:
-type: changed
diff --git a/changelogs/unreleased/58739-hashed-storage-prevent-a-migration-and-rollback-running-at-the-same-time.yml b/changelogs/unreleased/58739-hashed-storage-prevent-a-migration-and-rollback-running-at-the-same-time.yml
new file mode 100644
index 00000000000..765a991bb6a
--- /dev/null
+++ b/changelogs/unreleased/58739-hashed-storage-prevent-a-migration-and-rollback-running-at-the-same-time.yml
@@ -0,0 +1,5 @@
+---
+title: 'Hashed Storage: Prevent a migration and rollback running at the same time'
+merge_request: 25976
+author:
+type: changed
diff --git a/changelogs/unreleased/58781-silent-progress-in-auto-devops.yml b/changelogs/unreleased/58781-silent-progress-in-auto-devops.yml
new file mode 100644
index 00000000000..e45db8eafc3
--- /dev/null
+++ b/changelogs/unreleased/58781-silent-progress-in-auto-devops.yml
@@ -0,0 +1,5 @@
+---
+title: Use curl silent/show-error options on Auto DevOps
+merge_request: 25954
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/58789-some-system-notes-on-issuable-are-folded-on-mobile.yml b/changelogs/unreleased/58789-some-system-notes-on-issuable-are-folded-on-mobile.yml
new file mode 100644
index 00000000000..ebfb7aeaa1f
--- /dev/null
+++ b/changelogs/unreleased/58789-some-system-notes-on-issuable-are-folded-on-mobile.yml
@@ -0,0 +1,5 @@
+---
+title: Keep inline as much as possible in system notes on issuable
+merge_request: 25968
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/58797-broken-ui-on-a-closed-merge-request-from-a-deleted-source-project.yml b/changelogs/unreleased/58797-broken-ui-on-a-closed-merge-request-from-a-deleted-source-project.yml
new file mode 100644
index 00000000000..e30f48ed1a8
--- /dev/null
+++ b/changelogs/unreleased/58797-broken-ui-on-a-closed-merge-request-from-a-deleted-source-project.yml
@@ -0,0 +1,5 @@
+---
+title: Fix UI for closed MR when source project is removed
+merge_request: 25967
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/58883-fix-fetching-comments.yml b/changelogs/unreleased/58883-fix-fetching-comments.yml
new file mode 100644
index 00000000000..14c0f1687f2
--- /dev/null
+++ b/changelogs/unreleased/58883-fix-fetching-comments.yml
@@ -0,0 +1,5 @@
+---
+title: Fix error shown when loading links to specific comments
+merge_request: 26092
+author:
+type: fixed
diff --git a/changelogs/unreleased/58889-spinners-are-active-prematurely-in-bitbucket-cloud-import.yml b/changelogs/unreleased/58889-spinners-are-active-prematurely-in-bitbucket-cloud-import.yml
new file mode 100644
index 00000000000..ec357d9a832
--- /dev/null
+++ b/changelogs/unreleased/58889-spinners-are-active-prematurely-in-bitbucket-cloud-import.yml
@@ -0,0 +1,5 @@
+---
+title: Fix continuous bitbucket import loading spinner
+merge_request: 26175
+author:
+type: fixed
diff --git a/changelogs/unreleased/58933-broken-ui-on-commits-on-mobile.yml b/changelogs/unreleased/58933-broken-ui-on-commits-on-mobile.yml
new file mode 100644
index 00000000000..ca9f9dd21c9
--- /dev/null
+++ b/changelogs/unreleased/58933-broken-ui-on-commits-on-mobile.yml
@@ -0,0 +1,5 @@
+---
+title: Fix UI layout on Commits on mobile
+merge_request: 26133
+author: Takuya Noguchi
+type: fixed
diff --git a/changelogs/unreleased/59057-buttons-on-top-from-a-user-profile-page-on-mobile.yml b/changelogs/unreleased/59057-buttons-on-top-from-a-user-profile-page-on-mobile.yml
new file mode 100644
index 00000000000..febbbce2139
--- /dev/null
+++ b/changelogs/unreleased/59057-buttons-on-top-from-a-user-profile-page-on-mobile.yml
@@ -0,0 +1,5 @@
+---
+title: Improve mobile UI on User Profile page
+merge_request: 26240
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/59117-inconsistent-hover-behavior-on-navbar-items.yml b/changelogs/unreleased/59117-inconsistent-hover-behavior-on-navbar-items.yml
new file mode 100644
index 00000000000..eb9dcef4a89
--- /dev/null
+++ b/changelogs/unreleased/59117-inconsistent-hover-behavior-on-navbar-items.yml
@@ -0,0 +1,5 @@
+---
+title: Fix hover animation consistency in top navbar items
+merge_request: 26345
+author:
+type: fixed
diff --git a/changelogs/unreleased/59189-long-names-in-project-path-namespace-dropdown-breaks-past-container.yml b/changelogs/unreleased/59189-long-names-in-project-path-namespace-dropdown-breaks-past-container.yml
new file mode 100644
index 00000000000..bed7fcf2651
--- /dev/null
+++ b/changelogs/unreleased/59189-long-names-in-project-path-namespace-dropdown-breaks-past-container.yml
@@ -0,0 +1,5 @@
+---
+title: Prevent namespace dropdown in new project form from exceeding container
+merge_request: 26343
+author:
+type: fixed
diff --git a/changelogs/unreleased/8711-prep-frontend-single-repo.yml b/changelogs/unreleased/8711-prep-frontend-single-repo.yml
deleted file mode 100644
index 9c16e16a84b..00000000000
--- a/changelogs/unreleased/8711-prep-frontend-single-repo.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Creates a helper function to check if repo is EE
-merge_request: 25647
-author:
-type: other
diff --git a/changelogs/unreleased/9841-geo-unable-to-compare-branches-on-secondary.yml b/changelogs/unreleased/9841-geo-unable-to-compare-branches-on-secondary.yml
deleted file mode 100644
index c014edf9c09..00000000000
--- a/changelogs/unreleased/9841-geo-unable-to-compare-branches-on-secondary.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow users to compare branches on a read-only instance
-merge_request: 25414
-author:
-type: fixed
diff --git a/changelogs/unreleased/add-project-level-config-for-prospective-merge-pipelines-ce.yml b/changelogs/unreleased/add-project-level-config-for-prospective-merge-pipelines-ce.yml
deleted file mode 100644
index 39d7ead9af4..00000000000
--- a/changelogs/unreleased/add-project-level-config-for-prospective-merge-pipelines-ce.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add project level config for merge pipelines
-merge_request: 25385
-author:
-type: added
diff --git a/changelogs/unreleased/add-related-merge-request-count-to-api-response.yml b/changelogs/unreleased/add-related-merge-request-count-to-api-response.yml
deleted file mode 100644
index 7438053a84f..00000000000
--- a/changelogs/unreleased/add-related-merge-request-count-to-api-response.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add related merge request count to api response
-merge_request: 24974
-author:
-type: added
diff --git a/changelogs/unreleased/add-title-attribute-to-file-row.yml b/changelogs/unreleased/add-title-attribute-to-file-row.yml
deleted file mode 100644
index c68d3d544e7..00000000000
--- a/changelogs/unreleased/add-title-attribute-to-file-row.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: add title attribute to display file name
-merge_request: 25154
-author: Satoshi Nakamatsu @satoshicano
-type: added
diff --git a/changelogs/unreleased/add-youtrack-integration.yml b/changelogs/unreleased/add-youtrack-integration.yml
deleted file mode 100644
index f500e625145..00000000000
--- a/changelogs/unreleased/add-youtrack-integration.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add YouTrack integration service
-merge_request: 25361
-author: Yauhen Kotau @bessorion
-type: added
diff --git a/changelogs/unreleased/add_ldap_tls_options.yml b/changelogs/unreleased/add_ldap_tls_options.yml
deleted file mode 100644
index c3678cb8fb6..00000000000
--- a/changelogs/unreleased/add_ldap_tls_options.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow raw `tls_options` to be passed in LDAP configuration
-merge_request: 20678
-author:
-type: changed
diff --git a/changelogs/unreleased/allow-filtering-labels-by-a-single-character.yml b/changelogs/unreleased/allow-filtering-labels-by-a-single-character.yml
new file mode 100644
index 00000000000..31165bbadb7
--- /dev/null
+++ b/changelogs/unreleased/allow-filtering-labels-by-a-single-character.yml
@@ -0,0 +1,5 @@
+---
+title: Allow filtering labels list by one or two characters
+merge_request: 26012
+author:
+type: changed
diff --git a/changelogs/unreleased/allow-maintainers-to-remove-pages.yml b/changelogs/unreleased/allow-maintainers-to-remove-pages.yml
deleted file mode 100644
index 6e344dbe0e9..00000000000
--- a/changelogs/unreleased/allow-maintainers-to-remove-pages.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow maintainers to remove pages
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/allow-to-recursively-include.yml b/changelogs/unreleased/allow-to-recursively-include.yml
deleted file mode 100644
index edfbfcb0146..00000000000
--- a/changelogs/unreleased/allow-to-recursively-include.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow to recursively expand includes
-merge_request: 24356
-author:
-type: added
diff --git a/changelogs/unreleased/an-peek-jaeger.yml b/changelogs/unreleased/an-peek-jaeger.yml
deleted file mode 100644
index 8659ee4f9e0..00000000000
--- a/changelogs/unreleased/an-peek-jaeger.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Provide a performance bar link to the Jaeger UI
-merge_request: 24902
-author:
-type: other
diff --git a/changelogs/unreleased/auto-devops-tags.yml b/changelogs/unreleased/auto-devops-tags.yml
deleted file mode 100644
index 1b96d457776..00000000000
--- a/changelogs/unreleased/auto-devops-tags.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Adds Auto DevOps build job for tags
-merge_request: 25718
-author: walkafwalka
-type: added
diff --git a/changelogs/unreleased/avoid_es_loading_project_ci_status.yml b/changelogs/unreleased/avoid_es_loading_project_ci_status.yml
new file mode 100644
index 00000000000..514909c730d
--- /dev/null
+++ b/changelogs/unreleased/avoid_es_loading_project_ci_status.yml
@@ -0,0 +1,5 @@
+---
+title: Avoid loading pipeline status in project search
+merge_request: 26342
+author:
+type: performance
diff --git a/changelogs/unreleased/bvl-graphql-csrf.yml b/changelogs/unreleased/bvl-graphql-csrf.yml
deleted file mode 100644
index d1e5b56c751..00000000000
--- a/changelogs/unreleased/bvl-graphql-csrf.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow GraphQL requests without CSRF token
-merge_request: 25719
-author:
-type: fixed
diff --git a/changelogs/unreleased/ce-56153-error-tracking-counts.yml b/changelogs/unreleased/ce-56153-error-tracking-counts.yml
new file mode 100644
index 00000000000..fc3d8c01d7f
--- /dev/null
+++ b/changelogs/unreleased/ce-56153-error-tracking-counts.yml
@@ -0,0 +1,5 @@
+---
+title: Add usage counts for error tracking feature
+merge_request: 25472
+author:
+type: added
diff --git a/changelogs/unreleased/change-badges-example-to-pipeline.yml b/changelogs/unreleased/change-badges-example-to-pipeline.yml
deleted file mode 100644
index 8ed4d77fd6c..00000000000
--- a/changelogs/unreleased/change-badges-example-to-pipeline.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Change badges.svg example to pipeline.svg
-merge_request: 25157
-author: Aviad Levy
-type: fixed
diff --git a/changelogs/unreleased/changelogs-readme.yml b/changelogs/unreleased/changelogs-readme.yml
deleted file mode 100644
index 9f391699575..00000000000
--- a/changelogs/unreleased/changelogs-readme.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: add readme to changelogs directory
-merge_request: 25209
-author: "@glensc"
-type: added
diff --git a/changelogs/unreleased/consistent-pagination.yml b/changelogs/unreleased/consistent-pagination.yml
deleted file mode 100644
index 95eefaeb31d..00000000000
--- a/changelogs/unreleased/consistent-pagination.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix inconsistent pagination styles
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/deploy-keys-ext.yml b/changelogs/unreleased/deploy-keys-ext.yml
new file mode 100644
index 00000000000..e1d2fe08425
--- /dev/null
+++ b/changelogs/unreleased/deploy-keys-ext.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize admin deploy keys strings
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/deprecated-migration-inheritance.yml b/changelogs/unreleased/deprecated-migration-inheritance.yml
deleted file mode 100644
index 814c511195b..00000000000
--- a/changelogs/unreleased/deprecated-migration-inheritance.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Directly inheriting from ActiveRecord::Migration is deprecated
-merge_request: 25066
-author: Jasper Maes
-type: other
diff --git a/changelogs/unreleased/diff-tree-resizable.yml b/changelogs/unreleased/diff-tree-resizable.yml
deleted file mode 100644
index 7411640aea5..00000000000
--- a/changelogs/unreleased/diff-tree-resizable.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make file tree in merge requests resizable
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/do-not-force-2fa.yml b/changelogs/unreleased/do-not-force-2fa.yml
new file mode 100644
index 00000000000..f9be40e8f37
--- /dev/null
+++ b/changelogs/unreleased/do-not-force-2fa.yml
@@ -0,0 +1,6 @@
+---
+title: Add link on two-factor authorization settings page to leave group that enforces
+ two-factor authorization
+merge_request: 25731
+author:
+type: changed
diff --git a/changelogs/unreleased/dz-sort-labels-alphabetically.yml b/changelogs/unreleased/dz-sort-labels-alphabetically.yml
deleted file mode 100644
index acfde3de999..00000000000
--- a/changelogs/unreleased/dz-sort-labels-alphabetically.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Sort labels alphabetically on issues and merge requests list
-merge_request: 25470
-author:
-type: changed
diff --git a/changelogs/unreleased/enable-markup-highlighting.yml b/changelogs/unreleased/enable-markup-highlighting.yml
deleted file mode 100644
index 33fcf784f7d..00000000000
--- a/changelogs/unreleased/enable-markup-highlighting.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable syntax highlighting to other supported markups
-merge_request: 25761
-author:
-type: other
diff --git a/changelogs/unreleased/expand-diff-to-full-file.yml b/changelogs/unreleased/expand-diff-to-full-file.yml
deleted file mode 100644
index f41a6be22e8..00000000000
--- a/changelogs/unreleased/expand-diff-to-full-file.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow expanding a diff to display full file
-merge_request: 24406
-author:
-type: added
diff --git a/changelogs/unreleased/expose-additional-merge-request-pipeline-variables.yml b/changelogs/unreleased/expose-additional-merge-request-pipeline-variables.yml
deleted file mode 100644
index 399f60ef219..00000000000
--- a/changelogs/unreleased/expose-additional-merge-request-pipeline-variables.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Expose additional merge request pipeline variables
-merge_request: 24595
-author: Hiroyuki Sato
-type: added
diff --git a/changelogs/unreleased/expose-group-id-on-home-panel.yml b/changelogs/unreleased/expose-group-id-on-home-panel.yml
new file mode 100644
index 00000000000..1efe15a6e1a
--- /dev/null
+++ b/changelogs/unreleased/expose-group-id-on-home-panel.yml
@@ -0,0 +1,5 @@
+---
+title: Expose group id on home panel
+merge_request: 25897
+author: Peter Marko
+type: added
diff --git a/changelogs/unreleased/expose-merge-ref-to-runner.yml b/changelogs/unreleased/expose-merge-ref-to-runner.yml
deleted file mode 100644
index 945f4f6e05a..00000000000
--- a/changelogs/unreleased/expose-merge-ref-to-runner.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Expose refspecs and depth to runner
-merge_request: 25233
-author:
-type: added
diff --git a/changelogs/unreleased/expose-merge-request-entity-for-pipelines.yml b/changelogs/unreleased/expose-merge-request-entity-for-pipelines.yml
deleted file mode 100644
index e5cbc87ba24..00000000000
--- a/changelogs/unreleased/expose-merge-request-entity-for-pipelines.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Expose merge request entity for pipelines
-merge_request: 25679
-author:
-type: added
diff --git a/changelogs/unreleased/fast-destroy-uploads.yml b/changelogs/unreleased/fast-destroy-uploads.yml
deleted file mode 100644
index ee3363a6ae9..00000000000
--- a/changelogs/unreleased/fast-destroy-uploads.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: File uploads are deleted asynchronously when deleting a project or group.
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/feature-api-delete-job-artifacts.yml b/changelogs/unreleased/feature-api-delete-job-artifacts.yml
deleted file mode 100644
index ddbbe3c2650..00000000000
--- a/changelogs/unreleased/feature-api-delete-job-artifacts.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Extend the Gitlab API for deletion of job_artifacts of a single job.
-merge_request: 25522
-author: rroger
-type: added
diff --git a/changelogs/unreleased/feature-gb-add-serverless-cicd-template.yml b/changelogs/unreleased/feature-gb-add-serverless-cicd-template.yml
deleted file mode 100644
index ebb71f00c4b..00000000000
--- a/changelogs/unreleased/feature-gb-add-serverless-cicd-template.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Simplify CI/CD configuration on serverless projects
-merge_request: 25523
-author:
-type: added
diff --git a/changelogs/unreleased/feature-gb-enable-ci-persisted-stages-by-default.yml b/changelogs/unreleased/feature-gb-enable-ci-persisted-stages-by-default.yml
deleted file mode 100644
index ad92135d401..00000000000
--- a/changelogs/unreleased/feature-gb-enable-ci-persisted-stages-by-default.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Enable persisted pipeline stages by default
-merge_request: 25347
-author:
-type: performance
diff --git a/changelogs/unreleased/feature-runner-tag-filter-for-admin-view.yml b/changelogs/unreleased/feature-runner-tag-filter-for-admin-view.yml
deleted file mode 100644
index 86df4595e7b..00000000000
--- a/changelogs/unreleased/feature-runner-tag-filter-for-admin-view.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add a tag filter to the admin runners view
-merge_request: 19740
-author: Alexis Reigel
-type: added
diff --git a/changelogs/unreleased/feature-users-search-results.yml b/changelogs/unreleased/feature-users-search-results.yml
new file mode 100644
index 00000000000..151d08bce12
--- /dev/null
+++ b/changelogs/unreleased/feature-users-search-results.yml
@@ -0,0 +1,5 @@
+---
+title: Add users search results to global search
+merge_request: 21197
+author: Alexis Reigel
+type: added
diff --git a/changelogs/unreleased/filter-confidential-issues.yml b/changelogs/unreleased/filter-confidential-issues.yml
deleted file mode 100644
index 83f19a57aab..00000000000
--- a/changelogs/unreleased/filter-confidential-issues.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Ability to filter confidential issues
-merge_request: 24960
-author: Robert Schilling
-type: added
diff --git a/changelogs/unreleased/filter-merge-requests-by-target-branch.yml b/changelogs/unreleased/filter-merge-requests-by-target-branch.yml
new file mode 100644
index 00000000000..d0aba631c96
--- /dev/null
+++ b/changelogs/unreleased/filter-merge-requests-by-target-branch.yml
@@ -0,0 +1,5 @@
+---
+title: Add target branch filter to merge requests search bar
+merge_request: 24380
+author: Hiroyuki Sato
+type: added
diff --git a/changelogs/unreleased/filter-note-parameters.yml b/changelogs/unreleased/filter-note-parameters.yml
deleted file mode 100644
index fca2a394820..00000000000
--- a/changelogs/unreleased/filter-note-parameters.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Include note in the Rails filter_parameters configuration
-merge_request: 25238
-author:
-type: other
diff --git a/changelogs/unreleased/fix-38010-sidebar-loads-and-collapses.yml b/changelogs/unreleased/fix-38010-sidebar-loads-and-collapses.yml
deleted file mode 100644
index af80a069fde..00000000000
--- a/changelogs/unreleased/fix-38010-sidebar-loads-and-collapses.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed navigation sidebar flashing open on page load
-merge_request: 24555
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-badges-logs.yml b/changelogs/unreleased/fix-badges-logs.yml
deleted file mode 100644
index 6236e7b046d..00000000000
--- a/changelogs/unreleased/fix-badges-logs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Doc - fix the url of pipeline status badge
-merge_request: 25404
-author: Aviad Levy
-type: fixed
diff --git a/changelogs/unreleased/fix-blob-editor-deleting-content.yml b/changelogs/unreleased/fix-blob-editor-deleting-content.yml
deleted file mode 100644
index d5b7bbc7b1c..00000000000
--- a/changelogs/unreleased/fix-blob-editor-deleting-content.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed blob editor deleting file content for certain file paths
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-gb-fix-bridge-jobs-variables-policy.yml b/changelogs/unreleased/fix-gb-fix-bridge-jobs-variables-policy.yml
deleted file mode 100644
index 8a98a39fdc2..00000000000
--- a/changelogs/unreleased/fix-gb-fix-bridge-jobs-variables-policy.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix bridge jobs only/except variables policy
-merge_request: 25710
-author:
-type: fixed
diff --git a/changelogs/unreleased/fix-group-without-owner.yml b/changelogs/unreleased/fix-group-without-owner.yml
deleted file mode 100644
index 884f1b3a08a..00000000000
--- a/changelogs/unreleased/fix-group-without-owner.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: fix group without owner after transfer
-merge_request: 25573
-author: Peter Marko
-type: fixed
diff --git a/changelogs/unreleased/fix-ide-web-worker-relative-url.yml b/changelogs/unreleased/fix-ide-web-worker-relative-url.yml
new file mode 100644
index 00000000000..2accad68c4e
--- /dev/null
+++ b/changelogs/unreleased/fix-ide-web-worker-relative-url.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed Web IDE web workers not working with relative URLs
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-missing-border.yml b/changelogs/unreleased/fix-missing-border.yml
new file mode 100644
index 00000000000..21728223cb8
--- /dev/null
+++ b/changelogs/unreleased/fix-missing-border.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes missing border color in discussion card component
+merge_request: 26242
+author: Farhad Yasir
+type: fixed
diff --git a/changelogs/unreleased/fix-new-merge-request-diff-headers-sticky-position.yml b/changelogs/unreleased/fix-new-merge-request-diff-headers-sticky-position.yml
new file mode 100644
index 00000000000..dadbd5c940f
--- /dev/null
+++ b/changelogs/unreleased/fix-new-merge-request-diff-headers-sticky-position.yml
@@ -0,0 +1,5 @@
+---
+title: Fixed sticky headers in merge request creation diffs
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-pipeline-entity.yml b/changelogs/unreleased/fix-pipeline-entity.yml
new file mode 100644
index 00000000000..b429139402c
--- /dev/null
+++ b/changelogs/unreleased/fix-pipeline-entity.yml
@@ -0,0 +1,5 @@
+---
+title: Add merge request pipeline flag to pipeline entity
+merge_request: 25846
+author:
+type: added
diff --git a/changelogs/unreleased/fix-review-app-env-url.yml b/changelogs/unreleased/fix-review-app-env-url.yml
new file mode 100644
index 00000000000..963cd0c2992
--- /dev/null
+++ b/changelogs/unreleased/fix-review-app-env-url.yml
@@ -0,0 +1,5 @@
+---
+title: Fixes long review app subdomains
+merge_request: 25990
+author: walkafwalka
+type: fixed
diff --git a/changelogs/unreleased/fix_-56347.yml b/changelogs/unreleased/fix_-56347.yml
deleted file mode 100644
index 1d03ed8864c..00000000000
--- a/changelogs/unreleased/fix_-56347.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix overlapping empty-header logo
-merge_request: 24868
-author: Jonas L.
-type: fixed
diff --git a/changelogs/unreleased/gitaly-version-v1.26.0.yml b/changelogs/unreleased/gitaly-version-v1.26.0.yml
deleted file mode 100644
index cc91b13c3c1..00000000000
--- a/changelogs/unreleased/gitaly-version-v1.26.0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Upgrade to Gitaly v1.26.0
-merge_request: 25890
-author:
-type: changed
diff --git a/changelogs/unreleased/gitaly-version-v1.29.0.yml b/changelogs/unreleased/gitaly-version-v1.29.0.yml
new file mode 100644
index 00000000000..b6ce14c33a2
--- /dev/null
+++ b/changelogs/unreleased/gitaly-version-v1.29.0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade to Gitaly v1.29.0
+merge_request: 26406
+author:
+type: changed
diff --git a/changelogs/unreleased/gitlab_kubernetes_helm_bump.yml b/changelogs/unreleased/gitlab_kubernetes_helm_bump.yml
deleted file mode 100644
index b8668d338de..00000000000
--- a/changelogs/unreleased/gitlab_kubernetes_helm_bump.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bump Helm and kubectl used in Kubernetes integration to 2.12.3 and 1.11.7 respectively
-merge_request: 25268
-author:
-type: other
diff --git a/changelogs/unreleased/gokhanap-master-patch-03762.yml b/changelogs/unreleased/gokhanap-master-patch-03762.yml
deleted file mode 100644
index 22ab453e359..00000000000
--- a/changelogs/unreleased/gokhanap-master-patch-03762.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'commit page info-well overflow fix #56436'
-merge_request: 24799
-author: Gokhan Apaydin
-type: fixed
diff --git a/changelogs/unreleased/gt-externalize-app-views-projects-commit.yml b/changelogs/unreleased/gt-externalize-app-views-projects-commit.yml
deleted file mode 100644
index 29dbf2367b7..00000000000
--- a/changelogs/unreleased/gt-externalize-app-views-projects-commit.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Externalize strings from `/app/views/projects/commit`
-merge_request: 24668
-author: George Tsiolis
-type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-projects-pipelines.yml b/changelogs/unreleased/gt-externalize-app-views-projects-pipelines.yml
new file mode 100644
index 00000000000..094cd3ab751
--- /dev/null
+++ b/changelogs/unreleased/gt-externalize-app-views-projects-pipelines.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize strings from `/app/views/projects/pipelines`
+merge_request: 26035
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/gt-update-activity-filter-for-issues.yml b/changelogs/unreleased/gt-update-activity-filter-for-issues.yml
deleted file mode 100644
index f9be54ea8a9..00000000000
--- a/changelogs/unreleased/gt-update-activity-filter-for-issues.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update activity filter for issues
-merge_request: 23423
-author: George Tsiolis
-type: changed
diff --git a/changelogs/unreleased/gt-update-new-password-breadcrumb.yml b/changelogs/unreleased/gt-update-new-password-breadcrumb.yml
deleted file mode 100644
index 43ea2f0d44b..00000000000
--- a/changelogs/unreleased/gt-update-new-password-breadcrumb.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update new password breadcrumb
-merge_request: 25037
-author: George Tsiolis
-type: fixed
diff --git a/changelogs/unreleased/gt-update-operations-settings-breadcrumb-trail.yml b/changelogs/unreleased/gt-update-operations-settings-breadcrumb-trail.yml
deleted file mode 100644
index f7b10ea5c17..00000000000
--- a/changelogs/unreleased/gt-update-operations-settings-breadcrumb-trail.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update operations settings breadcrumb trail
-merge_request: 25539
-author: George Tsiolis
-type: changed
diff --git a/changelogs/unreleased/helm-2-12-3.yml b/changelogs/unreleased/helm-2-12-3.yml
deleted file mode 100644
index 0d0d904a9cb..00000000000
--- a/changelogs/unreleased/helm-2-12-3.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bump Helm and kubectl in Auto DevOps to 2.12.3 and 1.11.7 respectively
-merge_request: 25072
-author:
-type: other
diff --git a/changelogs/unreleased/import-go-to-project-cta.yml b/changelogs/unreleased/import-go-to-project-cta.yml
deleted file mode 100644
index ae719f08790..00000000000
--- a/changelogs/unreleased/import-go-to-project-cta.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve GitHub and Gitea project import table UI
-merge_request: 24606
-author:
-type: other
diff --git a/changelogs/unreleased/improve-performance-for-diverging-commit-counts.yml b/changelogs/unreleased/improve-performance-for-diverging-commit-counts.yml
deleted file mode 100644
index 76ff15cba5b..00000000000
--- a/changelogs/unreleased/improve-performance-for-diverging-commit-counts.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve performance for diverging commit counts
-merge_request: 24287
-author:
-type: performance
diff --git a/changelogs/unreleased/improve-snippets-empty-state.yml b/changelogs/unreleased/improve-snippets-empty-state.yml
deleted file mode 100644
index 9859243a81f..00000000000
--- a/changelogs/unreleased/improve-snippets-empty-state.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Improve snippets empty state
-merge_request: 18348
-author: George Tsiolis
-type: changed
diff --git a/changelogs/unreleased/include-ci-yaml.yml b/changelogs/unreleased/include-ci-yaml.yml
deleted file mode 100644
index 5909950ef0b..00000000000
--- a/changelogs/unreleased/include-ci-yaml.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Validate 'include' keywords in gitlab-ci.yml configuration files.
-merge_request: 24098
-author: Paul Bonaud
-type: fixed
diff --git a/changelogs/unreleased/ingress-hostnames.yml b/changelogs/unreleased/ingress-hostnames.yml
deleted file mode 100644
index 66721113769..00000000000
--- a/changelogs/unreleased/ingress-hostnames.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Added support for ingress hostnames
-merge_request: 25181
-author: walkafwalka
-type: added
diff --git a/changelogs/unreleased/jc-fix-set-project-writable.yml b/changelogs/unreleased/jc-fix-set-project-writable.yml
deleted file mode 100644
index 0bfd90c3967..00000000000
--- a/changelogs/unreleased/jc-fix-set-project-writable.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix method to mark a project repository as writable
-merge_request: 25546
-author:
-type: fixed
diff --git a/changelogs/unreleased/jej-feature-gates-can-be-set-by-group-path.yml b/changelogs/unreleased/jej-feature-gates-can-be-set-by-group-path.yml
deleted file mode 100644
index ba882112f70..00000000000
--- a/changelogs/unreleased/jej-feature-gates-can-be-set-by-group-path.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Allow setting feature flags per GitLab group through the API
-merge_request: 25022
-author:
-type: added
diff --git a/changelogs/unreleased/jira-link-mention-compact.yml b/changelogs/unreleased/jira-link-mention-compact.yml
deleted file mode 100644
index f75f3ce183c..00000000000
--- a/changelogs/unreleased/jira-link-mention-compact.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: "Jira: make issue links title compact"
-merge_request: 25609
-author: Elan Ruusamäe @glensc
-type: changed
diff --git a/changelogs/unreleased/k8s_new_deployment_labels.yml b/changelogs/unreleased/k8s_new_deployment_labels.yml
new file mode 100644
index 00000000000..e9ef3ee0082
--- /dev/null
+++ b/changelogs/unreleased/k8s_new_deployment_labels.yml
@@ -0,0 +1,5 @@
+---
+title: Update deploy boards to additionally select on "app.gitlab.com" annotations
+merge_request: 25623
+author:
+type: changed
diff --git a/changelogs/unreleased/kinolaev-master-patch-87865.yml b/changelogs/unreleased/kinolaev-master-patch-87865.yml
deleted file mode 100644
index b4dbc2c0e1f..00000000000
--- a/changelogs/unreleased/kinolaev-master-patch-87865.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix rollout status for statefulsets and daemonsets
-merge_request: 24972
-author: Sergej Nikolaev <kinolaev@gmail.com>
-type: fixed
diff --git a/changelogs/unreleased/move_chatops_to_core.yml b/changelogs/unreleased/move_chatops_to_core.yml
deleted file mode 100644
index 7a75efedfa8..00000000000
--- a/changelogs/unreleased/move_chatops_to_core.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Move ChatOps to Core
-merge_request: 24780
-author:
-type: changed
diff --git a/changelogs/unreleased/nfriend-update-job-detail-view-sidebar.yml b/changelogs/unreleased/nfriend-update-job-detail-view-sidebar.yml
new file mode 100644
index 00000000000..5364d29710a
--- /dev/null
+++ b/changelogs/unreleased/nfriend-update-job-detail-view-sidebar.yml
@@ -0,0 +1,5 @@
+---
+title: Update job detail sidebar to accommodate post-merge pipeline information
+merge_request: 25777
+author:
+type: added
diff --git a/changelogs/unreleased/nfriend-update-merge-request-widget-pipeline-block.yml b/changelogs/unreleased/nfriend-update-merge-request-widget-pipeline-block.yml
new file mode 100644
index 00000000000..bd4120eb06f
--- /dev/null
+++ b/changelogs/unreleased/nfriend-update-merge-request-widget-pipeline-block.yml
@@ -0,0 +1,6 @@
+---
+title: Update pipeline block on merge request page to accommodate post-merge pipeline
+ information
+merge_request: 25745
+author:
+type: added
diff --git a/changelogs/unreleased/nfriend-update-pipeline-detail-view.yml b/changelogs/unreleased/nfriend-update-pipeline-detail-view.yml
new file mode 100644
index 00000000000..a24325c4eb6
--- /dev/null
+++ b/changelogs/unreleased/nfriend-update-pipeline-detail-view.yml
@@ -0,0 +1,5 @@
+---
+title: Update pipeline detail view to accommodate post-merge pipelines
+merge_request: 25775
+author:
+type: added
diff --git a/changelogs/unreleased/nfriend-update-pipeline-list-view.yml b/changelogs/unreleased/nfriend-update-pipeline-list-view.yml
new file mode 100644
index 00000000000..34e43162b5c
--- /dev/null
+++ b/changelogs/unreleased/nfriend-update-pipeline-list-view.yml
@@ -0,0 +1,5 @@
+---
+title: Update pipeline list view to accommodate post-merge pipeline information
+merge_request: 25690
+author:
+type: added
diff --git a/changelogs/unreleased/only-counted-active-milestones-as-started.yml b/changelogs/unreleased/only-counted-active-milestones-as-started.yml
new file mode 100644
index 00000000000..1a9c4b9023b
--- /dev/null
+++ b/changelogs/unreleased/only-counted-active-milestones-as-started.yml
@@ -0,0 +1,5 @@
+---
+title: Only consider active milestones when using the special Started milestone filter
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/osw-create-and-store-merge-ref-for-mrs.yml b/changelogs/unreleased/osw-create-and-store-merge-ref-for-mrs.yml
deleted file mode 100644
index 012b547a630..00000000000
--- a/changelogs/unreleased/osw-create-and-store-merge-ref-for-mrs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Support merge ref writing (without merging to target branch)
-merge_request: 24692
-author:
-type: added
diff --git a/changelogs/unreleased/osw-fetch-latest-version-when-creating-suggestions.yml b/changelogs/unreleased/osw-fetch-latest-version-when-creating-suggestions.yml
deleted file mode 100644
index 4e01a13d781..00000000000
--- a/changelogs/unreleased/osw-fetch-latest-version-when-creating-suggestions.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Always fetch MR latest version when creating suggestions
-merge_request: 25441
-author:
-type: fixed
diff --git a/changelogs/unreleased/osw-merge-refs-refreshing-api.yml b/changelogs/unreleased/osw-merge-refs-refreshing-api.yml
deleted file mode 100644
index 095600cd088..00000000000
--- a/changelogs/unreleased/osw-merge-refs-refreshing-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: API support for MR merge to temporary merge ref path
-merge_request: 24918
-author:
-type: added
diff --git a/changelogs/unreleased/osw-merge-to-ref-changes-for-ci-team.yml b/changelogs/unreleased/osw-merge-to-ref-changes-for-ci-team.yml
deleted file mode 100644
index dfccd6194d4..00000000000
--- a/changelogs/unreleased/osw-merge-to-ref-changes-for-ci-team.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make merge to refs/merge-requests/:iid/merge not raise when FF-only enabled
-merge_request: 25653
-author:
-type: fixed
diff --git a/changelogs/unreleased/osw-multi-line-suggestions-parsing.yml b/changelogs/unreleased/osw-multi-line-suggestions-parsing.yml
new file mode 100644
index 00000000000..985b01e9254
--- /dev/null
+++ b/changelogs/unreleased/osw-multi-line-suggestions-parsing.yml
@@ -0,0 +1,5 @@
+---
+title: Prepare multi-line suggestions for rendering in Markdown
+merge_request: 26107
+author:
+type: other
diff --git a/changelogs/unreleased/patch-45.yml b/changelogs/unreleased/patch-45.yml
deleted file mode 100644
index 94fa1d29b32..00000000000
--- a/changelogs/unreleased/patch-45.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix incorrect Pages Domains checkbox description.
-merge_request: 25392
-author: Anton Melser
-type: other
diff --git a/changelogs/unreleased/persist-source-sha-and-target-sha-for-pipelines.yml b/changelogs/unreleased/persist-source-sha-and-target-sha-for-pipelines.yml
deleted file mode 100644
index 6957d156161..00000000000
--- a/changelogs/unreleased/persist-source-sha-and-target-sha-for-pipelines.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Persist source sha and target sha for merge pipelines
-merge_request: 25417
-author:
-type: added
diff --git a/changelogs/unreleased/pravi-gitlab-ce-update-recaptcha.yml b/changelogs/unreleased/pravi-gitlab-ce-update-recaptcha.yml
new file mode 100644
index 00000000000..95379fb2ec1
--- /dev/null
+++ b/changelogs/unreleased/pravi-gitlab-ce-update-recaptcha.yml
@@ -0,0 +1,5 @@
+---
+title: Apply recaptcha API change in 4.0
+merge_request: 25921
+author: Praveen Arimbrathodiyil
+type: other
diff --git a/changelogs/unreleased/ravlen-fix-spaces-unicode.yml b/changelogs/unreleased/ravlen-fix-spaces-unicode.yml
deleted file mode 100644
index fbcbdc53cfe..00000000000
--- a/changelogs/unreleased/ravlen-fix-spaces-unicode.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Correct non-standard unicode spaces to regular unicode
-merge_request: 24795
-author: Marcel Amirault
-type: other
diff --git a/changelogs/unreleased/refactor-56367-extract-resolve-with-issue-button-component.yml b/changelogs/unreleased/refactor-56367-extract-resolve-with-issue-button-component.yml
deleted file mode 100644
index 082075506c0..00000000000
--- a/changelogs/unreleased/refactor-56367-extract-resolve-with-issue-button-component.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Extracted ResolveWithIssueButton to its own component
-merge_request: 25093
-author: Martin Hobert
-type: other
diff --git a/changelogs/unreleased/refactor-merge-request-between-pipeline-and-build.yml b/changelogs/unreleased/refactor-merge-request-between-pipeline-and-build.yml
deleted file mode 100644
index bf78f8d84a6..00000000000
--- a/changelogs/unreleased/refactor-merge-request-between-pipeline-and-build.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add suffix (`_event`) to merge request source
-merge_request: 25508
-author:
-type: other
diff --git a/changelogs/unreleased/remove-second-primary-button-on-wiki-edit.yml b/changelogs/unreleased/remove-second-primary-button-on-wiki-edit.yml
deleted file mode 100644
index 045fbbb48b7..00000000000
--- a/changelogs/unreleased/remove-second-primary-button-on-wiki-edit.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove second primary button on wiki edit
-merge_request: 19959
-author: George Tsiolis
-type: changed
diff --git a/changelogs/unreleased/restrict-jupyter-login.yml b/changelogs/unreleased/restrict-jupyter-login.yml
deleted file mode 100644
index 2c20ef3d7ac..00000000000
--- a/changelogs/unreleased/restrict-jupyter-login.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Project level filtering for JupyterHub
-merge_request: 25684
-author: Amit Rathi (amit1rrr)
-type: changed
diff --git a/changelogs/unreleased/rs-admin-user-case-insensitive.yml b/changelogs/unreleased/rs-admin-user-case-insensitive.yml
deleted file mode 100644
index 40398c46a1e..00000000000
--- a/changelogs/unreleased/rs-admin-user-case-insensitive.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Admin section finds users case-insensitively
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/security-2774-milestones-detail.yml b/changelogs/unreleased/security-2774-milestones-detail.yml
deleted file mode 100644
index faf56fee01e..00000000000
--- a/changelogs/unreleased/security-2774-milestones-detail.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display only information visible to current user on the Milestone page
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-2797-milestone-mrs.yml b/changelogs/unreleased/security-2797-milestone-mrs.yml
deleted file mode 100644
index 5bb104ec403..00000000000
--- a/changelogs/unreleased/security-2797-milestone-mrs.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Show only merge requests visible to user on milestone detail page
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-2798-fix-boards-policy.yml b/changelogs/unreleased/security-2798-fix-boards-policy.yml
deleted file mode 100644
index 10e8ac3a787..00000000000
--- a/changelogs/unreleased/security-2798-fix-boards-policy.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Disable issue boards API when issues are disabled
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-2799-emails.yml b/changelogs/unreleased/security-2799-emails.yml
deleted file mode 100644
index dbf1207810e..00000000000
--- a/changelogs/unreleased/security-2799-emails.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Don't show new issue link after move when a user does not have permissions
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-50334.yml b/changelogs/unreleased/security-50334.yml
deleted file mode 100644
index 828ef82b517..00000000000
--- a/changelogs/unreleased/security-50334.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix git clone revealing private repo's presence
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-55468-check-validity-before-querying.yml b/changelogs/unreleased/security-55468-check-validity-before-querying.yml
deleted file mode 100644
index 8bb11a97f52..00000000000
--- a/changelogs/unreleased/security-55468-check-validity-before-querying.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix blind SSRF in Prometheus integration by checking URL before querying
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-56348.yml b/changelogs/unreleased/security-56348.yml
deleted file mode 100644
index a289e4e9077..00000000000
--- a/changelogs/unreleased/security-56348.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Check snippet attached file to be moved is within designated directory
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-commit-private-related-mr.yml b/changelogs/unreleased/security-commit-private-related-mr.yml
deleted file mode 100644
index c4de200b0d8..00000000000
--- a/changelogs/unreleased/security-commit-private-related-mr.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Don't allow non-members to see private related MRs.
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-fj-diff-import-file-read-fix.yml b/changelogs/unreleased/security-fj-diff-import-file-read-fix.yml
deleted file mode 100644
index e98d4e89712..00000000000
--- a/changelogs/unreleased/security-fj-diff-import-file-read-fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix arbitrary file read via diffs during import
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-id-fix-mr-visibility.yml b/changelogs/unreleased/security-id-fix-mr-visibility.yml
deleted file mode 100644
index 8f41d191acc..00000000000
--- a/changelogs/unreleased/security-id-fix-mr-visibility.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Display the correct number of MRs a user has access to
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-id-restricted-access-to-private-repo.yml b/changelogs/unreleased/security-id-restricted-access-to-private-repo.yml
deleted file mode 100644
index 7d7478d297b..00000000000
--- a/changelogs/unreleased/security-id-restricted-access-to-private-repo.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Forbid creating discussions for users with restricted access
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-issue_54789_2.yml b/changelogs/unreleased/security-issue_54789_2.yml
deleted file mode 100644
index 8ecb72a2ae3..00000000000
--- a/changelogs/unreleased/security-issue_54789_2.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Do not disclose milestone titles for unauthorized users
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-kubernetes-google-login-csrf.yml b/changelogs/unreleased/security-kubernetes-google-login-csrf.yml
deleted file mode 100644
index 2f87100a8dd..00000000000
--- a/changelogs/unreleased/security-kubernetes-google-login-csrf.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Validate session key when authorizing with GCP to create a cluster
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-kubernetes-local-ssrf.yml b/changelogs/unreleased/security-kubernetes-local-ssrf.yml
deleted file mode 100644
index 7a2ad092339..00000000000
--- a/changelogs/unreleased/security-kubernetes-local-ssrf.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Block local URLs for Kubernetes integration
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-mermaid.yml b/changelogs/unreleased/security-mermaid.yml
deleted file mode 100644
index ec42b5a1615..00000000000
--- a/changelogs/unreleased/security-mermaid.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Limit mermaid rendering to 5K characters
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-osw-stop-linking-to-packages.yml b/changelogs/unreleased/security-osw-stop-linking-to-packages.yml
deleted file mode 100644
index 078f06140fe..00000000000
--- a/changelogs/unreleased/security-osw-stop-linking-to-packages.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Stop linking to unrecognized package sources
-merge_request: 55518
-author:
-type: security
diff --git a/changelogs/unreleased/security-protect-private-repo-information.yml b/changelogs/unreleased/security-protect-private-repo-information.yml
deleted file mode 100644
index 8b1a528206d..00000000000
--- a/changelogs/unreleased/security-protect-private-repo-information.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix leaking private repository information in API
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-shared-project-private-group.yml b/changelogs/unreleased/security-shared-project-private-group.yml
deleted file mode 100644
index 3b21daa5491..00000000000
--- a/changelogs/unreleased/security-shared-project-private-group.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed ability to see private groups by users not belonging to given group
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/security-tags-oracle.yml b/changelogs/unreleased/security-tags-oracle.yml
deleted file mode 100644
index eb8ad6f646c..00000000000
--- a/changelogs/unreleased/security-tags-oracle.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Prevent releases links API to leak tag existance
-merge_request:
-author:
-type: security
diff --git a/changelogs/unreleased/sh-bump-fog-gem.yml b/changelogs/unreleased/sh-bump-fog-gem.yml
deleted file mode 100644
index 6a26d5c6488..00000000000
--- a/changelogs/unreleased/sh-bump-fog-gem.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixes issue with AWS V4 signatures not working with some S3 providers
-merge_request: 21788
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-cache-root-ref-asymetrically.yml b/changelogs/unreleased/sh-cache-root-ref-asymetrically.yml
new file mode 100644
index 00000000000..106d070cc05
--- /dev/null
+++ b/changelogs/unreleased/sh-cache-root-ref-asymetrically.yml
@@ -0,0 +1,5 @@
+---
+title: Cache Repository#root_ref within a request
+merge_request: 25903
+author:
+type: performance
diff --git a/changelogs/unreleased/sh-fix-cpp-templates-404.yml b/changelogs/unreleased/sh-fix-cpp-templates-404.yml
deleted file mode 100644
index ac958d84099..00000000000
--- a/changelogs/unreleased/sh-fix-cpp-templates-404.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix 404s when C++ .gitignore template selected
-merge_request: 25416
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-double-xhr-pipelines.yml b/changelogs/unreleased/sh-fix-double-xhr-pipelines.yml
deleted file mode 100644
index e6c762f1d47..00000000000
--- a/changelogs/unreleased/sh-fix-double-xhr-pipelines.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove duplicate XHR request when requesting new pipeline page
-merge_request: 25506
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-issue-58103.yml b/changelogs/unreleased/sh-fix-issue-58103.yml
deleted file mode 100644
index 1599af23fed..00000000000
--- a/changelogs/unreleased/sh-fix-issue-58103.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Properly handle multiple X-Forwarded-For addresses in runner IP
-merge_request: 25511
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-issue-59065.yml b/changelogs/unreleased/sh-fix-issue-59065.yml
new file mode 100644
index 00000000000..41cd5ce0960
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-issue-59065.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Error 500 when user commits Wiki page with no commit message
+merge_request: 26247
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-include-project-path-for-internal-api.yml b/changelogs/unreleased/sh-include-project-path-for-internal-api.yml
deleted file mode 100644
index 1973049e9e3..00000000000
--- a/changelogs/unreleased/sh-include-project-path-for-internal-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Include gl_project_path in API /internal/allowed response
-merge_request: 25314
-author:
-type: other
diff --git a/changelogs/unreleased/sh-log-rails-queue-duration.yml b/changelogs/unreleased/sh-log-rails-queue-duration.yml
deleted file mode 100644
index 89390aef108..00000000000
--- a/changelogs/unreleased/sh-log-rails-queue-duration.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Log queue duration in production_json.log
-merge_request: 25075
-author:
-type: other
diff --git a/changelogs/unreleased/sh-optimize-calendar-activities.yml b/changelogs/unreleased/sh-optimize-calendar-activities.yml
deleted file mode 100644
index b8500357e17..00000000000
--- a/changelogs/unreleased/sh-optimize-calendar-activities.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Eliminate most N+1 queries loading UserController#calendar_activities
-merge_request: 25697
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-optimize-todos-api.yml b/changelogs/unreleased/sh-optimize-todos-api.yml
deleted file mode 100644
index 936ac31b853..00000000000
--- a/changelogs/unreleased/sh-optimize-todos-api.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Significantly reduce N+1 queries in /api/v4/todos endpoint
-merge_request: 25711
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-reject-info-refs-head-requests.yml b/changelogs/unreleased/sh-reject-info-refs-head-requests.yml
new file mode 100644
index 00000000000..0dca18e2fd8
--- /dev/null
+++ b/changelogs/unreleased/sh-reject-info-refs-head-requests.yml
@@ -0,0 +1,5 @@
+---
+title: Reject HEAD requests to info/refs endpoint
+merge_request: 26334
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-remove-nplusone-admin-runners-tags.yml b/changelogs/unreleased/sh-remove-nplusone-admin-runners-tags.yml
deleted file mode 100644
index f8ac345bc95..00000000000
--- a/changelogs/unreleased/sh-remove-nplusone-admin-runners-tags.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove N+1 query for tags in /admin/runners page
-merge_request: 25572
-author:
-type: performance
diff --git a/changelogs/unreleased/sh-rugged-commit-is-ancestor.yml b/changelogs/unreleased/sh-rugged-commit-is-ancestor.yml
deleted file mode 100644
index 0f62176b4a5..00000000000
--- a/changelogs/unreleased/sh-rugged-commit-is-ancestor.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bring back Rugged implementation of CommitIsAncestor
-merge_request: 25702
-author:
-type: other
diff --git a/changelogs/unreleased/sh-rugged-find-commit.yml b/changelogs/unreleased/sh-rugged-find-commit.yml
deleted file mode 100644
index 85b5936c9ba..00000000000
--- a/changelogs/unreleased/sh-rugged-find-commit.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bring back Rugged implementation of find_commit
-merge_request: 25477
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-rugged-get-tree-entry.yml b/changelogs/unreleased/sh-rugged-get-tree-entry.yml
deleted file mode 100644
index 4d46b764022..00000000000
--- a/changelogs/unreleased/sh-rugged-get-tree-entry.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bring back Rugged implementation of TreeEntry
-merge_request: 25706
-author:
-type: other
diff --git a/changelogs/unreleased/sh-rugged-tree-entries.yml b/changelogs/unreleased/sh-rugged-tree-entries.yml
deleted file mode 100644
index fca1f204b9b..00000000000
--- a/changelogs/unreleased/sh-rugged-tree-entries.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Bring back Rugged implementation of GetTreeEntries
-merge_request: 25674
-author:
-type: other
diff --git a/changelogs/unreleased/sh-skip-sti-tables-reltuples.yml b/changelogs/unreleased/sh-skip-sti-tables-reltuples.yml
new file mode 100644
index 00000000000..5bf0ccf3e9d
--- /dev/null
+++ b/changelogs/unreleased/sh-skip-sti-tables-reltuples.yml
@@ -0,0 +1,5 @@
+---
+title: Fix counting of groups in admin dashboard
+merge_request: 26009
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-wip-fix-duplicate-env-xhr.yml b/changelogs/unreleased/sh-wip-fix-duplicate-env-xhr.yml
deleted file mode 100644
index e7900e2230d..00000000000
--- a/changelogs/unreleased/sh-wip-fix-duplicate-env-xhr.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix pagination and duplicate requests in environments page
-merge_request: 25582
-author:
-type: fixed
diff --git a/changelogs/unreleased/shell-8-7.yml b/changelogs/unreleased/shell-8-7.yml
deleted file mode 100644
index c2dd0c1f1bb..00000000000
--- a/changelogs/unreleased/shell-8-7.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update GitLab Shell to v8.7.1
-merge_request: 25801
-author:
-type: other
diff --git a/changelogs/unreleased/support-only-changes-on-mr-pipelines.yml b/changelogs/unreleased/support-only-changes-on-mr-pipelines.yml
deleted file mode 100644
index fbab898b799..00000000000
--- a/changelogs/unreleased/support-only-changes-on-mr-pipelines.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: 'Support `only: changes:` on MR pipelines'
-merge_request: 24490
-author: Hiroyuki Sato
-type: added
diff --git a/changelogs/unreleased/table-fix-scroll-and-block.yml b/changelogs/unreleased/table-fix-scroll-and-block.yml
deleted file mode 100644
index e6def0468b8..00000000000
--- a/changelogs/unreleased/table-fix-scroll-and-block.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix large table horizontal scroll and prevent side-by-side tables
-merge_request: 25520
-author: Dany Jupille
-type: fixed
diff --git a/changelogs/unreleased/tpresa-add-highest-role-to-user.yml b/changelogs/unreleased/tpresa-add-highest-role-to-user.yml
new file mode 100644
index 00000000000..9714d8dcc99
--- /dev/null
+++ b/changelogs/unreleased/tpresa-add-highest-role-to-user.yml
@@ -0,0 +1,5 @@
+---
+title: Adding highest role property to admin's user details page
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/tr-error-tracking-project-selection.yml b/changelogs/unreleased/tr-error-tracking-project-selection.yml
deleted file mode 100644
index 36cfe4556bb..00000000000
--- a/changelogs/unreleased/tr-error-tracking-project-selection.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Error tracking configuration - add a Sentry project selection dropdown
-merge_request: 24701
-author:
-type: changed
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-2-0.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-2-0.yml
deleted file mode 100644
index 3bf55630c4d..00000000000
--- a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-2-0.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Update GitLab Runner Helm Chart to 0.2.0
-merge_request: 25493
-author:
-type: other
diff --git a/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-3-0.yml b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-3-0.yml
new file mode 100644
index 00000000000..2e1adb1e1e9
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-runner-helm-chart-to-0-3-0.yml
@@ -0,0 +1,5 @@
+---
+title: Update GitLab Runner Helm Chart to 0.3.0/11.9.0
+merge_request: 26467
+author:
+type: other
diff --git a/changelogs/unreleased/update-rack-oauth2.yml b/changelogs/unreleased/update-rack-oauth2.yml
new file mode 100644
index 00000000000..dc2e7017695
--- /dev/null
+++ b/changelogs/unreleased/update-rack-oauth2.yml
@@ -0,0 +1,5 @@
+---
+title: Update rack-oauth2 1.2.1 -> 1.9.3
+merge_request: 17868
+author:
+type: other
diff --git a/changelogs/unreleased/use-date-for-upcoming-milestone-comparison.yml b/changelogs/unreleased/use-date-for-upcoming-milestone-comparison.yml
deleted file mode 100644
index 06c638a6522..00000000000
--- a/changelogs/unreleased/use-date-for-upcoming-milestone-comparison.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix upcoming milestone when there are milestones with far-future due dates
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/use-encrypted-runner-tokens.yml b/changelogs/unreleased/use-encrypted-runner-tokens.yml
deleted file mode 100644
index e01978557bf..00000000000
--- a/changelogs/unreleased/use-encrypted-runner-tokens.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Use encrypted runner tokens
-merge_request: 25532
-author:
-type: security
diff --git a/changelogs/unreleased/use-only-all-pipelines.yml b/changelogs/unreleased/use-only-all-pipelines.yml
new file mode 100644
index 00000000000..68364d2a923
--- /dev/null
+++ b/changelogs/unreleased/use-only-all-pipelines.yml
@@ -0,0 +1,5 @@
+---
+title: Refactor all_pipelines in Merge request
+merge_request: 25676
+author:
+type: other
diff --git a/changelogs/unreleased/web-ide-commit-header-icon-alignment-fix.yml b/changelogs/unreleased/web-ide-commit-header-icon-alignment-fix.yml
deleted file mode 100644
index 7a6bda1580d..00000000000
--- a/changelogs/unreleased/web-ide-commit-header-icon-alignment-fix.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed alignment of changed icon in Web IDE
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/web-ide-default-editor.yml b/changelogs/unreleased/web-ide-default-editor.yml
deleted file mode 100644
index b98be5c16c2..00000000000
--- a/changelogs/unreleased/web-ide-default-editor.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Make the Web IDE the default editor
-merge_request:
-author:
-type: added
diff --git a/changelogs/unreleased/winh-enable-reply_to_individual_notes.yml b/changelogs/unreleased/winh-enable-reply_to_individual_notes.yml
deleted file mode 100644
index a9c280320dc..00000000000
--- a/changelogs/unreleased/winh-enable-reply_to_individual_notes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add button to start discussion from single comment
-merge_request: 25575
-author:
-type: added
diff --git a/changelogs/unreleased/winh-toggle-comment-draft.yml b/changelogs/unreleased/winh-toggle-comment-draft.yml
new file mode 100644
index 00000000000..6b4aad55a05
--- /dev/null
+++ b/changelogs/unreleased/winh-toggle-comment-draft.yml
@@ -0,0 +1,5 @@
+---
+title: Display draft when toggling replies
+merge_request: 25563
+author:
+type: fixed
diff --git a/changelogs/unreleased/zj-load-languages-from-database.yml b/changelogs/unreleased/zj-load-languages-from-database.yml
deleted file mode 100644
index 1688829b42c..00000000000
--- a/changelogs/unreleased/zj-load-languages-from-database.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Load repository language from the database if detected before
-merge_request: 25518
-author:
-type: performance
diff --git a/config/application.rb b/config/application.rb
index 1c11e347281..6bdf61edfb1 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -94,6 +94,7 @@ module Gitlab
# - Webhook URLs (:hook)
# - Sentry DSN (:sentry_dsn)
# - File content from Web Editor (:content)
+ # - Jira shared secret (:sharedSecret)
#
# NOTE: It is **IMPORTANT** to also update gitlab-workhorse's filter when adding parameters here to not
# introduce another security vulnerability: https://gitlab.com/gitlab-org/gitlab-workhorse/issues/182
@@ -108,6 +109,7 @@ module Gitlab
trace
variables
content
+ sharedSecret
)
# Enable escaping HTML in JSON.
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index 47c76d8bc49..eba7d2b9fb7 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -697,6 +697,7 @@ production: &base
## GitLab Shell settings
gitlab_shell:
path: /home/git/gitlab-shell/
+ authorized_keys_file: /home/git/.ssh/authorized_keys
# File that contains the secret key for verifying access for gitlab-shell.
# Default is '.gitlab_shell_secret' relative to Rails.root (i.e. root of the GitLab app).
@@ -854,6 +855,7 @@ test:
path: tmp/tests/backups
gitlab_shell:
path: tmp/tests/gitlab-shell/
+ authorized_keys_file: tmp/tests/authorized_keys
issues_tracker:
redmine:
title: "Redmine"
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 03800f3d9d2..99bdf5a95c2 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -356,6 +356,7 @@ Settings['sidekiq']['log_format'] ||= 'default'
Settings['gitlab_shell'] ||= Settingslogic.new({})
Settings.gitlab_shell['path'] = Settings.absolute(Settings.gitlab_shell['path'] || Settings.gitlab['user_home'] + '/gitlab-shell/')
Settings.gitlab_shell['hooks_path'] = :deprecated_use_gitlab_shell_path_instead
+Settings.gitlab_shell['authorized_keys_file'] ||= nil
Settings.gitlab_shell['secret_file'] ||= Rails.root.join('.gitlab_shell_secret')
Settings.gitlab_shell['receive_pack'] = true if Settings.gitlab_shell['receive_pack'].nil?
Settings.gitlab_shell['upload_pack'] = true if Settings.gitlab_shell['upload_pack'].nil?
diff --git a/config/initializers/console_message.rb b/config/initializers/console_message.rb
index f7c26732e6d..05eb395028d 100644
--- a/config/initializers/console_message.rb
+++ b/config/initializers/console_message.rb
@@ -5,6 +5,6 @@ if defined?(Rails::Console)
puts "-------------------------------------------------------------------------------------"
puts " GitLab:".ljust(justify) + "#{Gitlab::VERSION} (#{Gitlab.revision})"
puts " GitLab Shell:".ljust(justify) + "#{Gitlab::VersionInfo.parse(Gitlab::Shell.new.version)}"
- puts " #{Gitlab::Database.adapter_name}:".ljust(justify) + Gitlab::Database.version
+ puts " #{Gitlab::Database.human_adapter_name}:".ljust(justify) + Gitlab::Database.version
puts "-------------------------------------------------------------------------------------"
end
diff --git a/config/initializers/rspec_profiling.rb b/config/initializers/rspec_profiling.rb
index 2de310753a9..715e17057e0 100644
--- a/config/initializers/rspec_profiling.rb
+++ b/config/initializers/rspec_profiling.rb
@@ -1,7 +1,28 @@
+# frozen_string_literal: true
+
+return unless Rails.env.test?
+
module RspecProfilingExt
- module PSQL
- def establish_connection
- ::RspecProfiling::Collectors::PSQL::Result.establish_connection(ENV['RSPEC_PROFILING_POSTGRES_URL'])
+ module Collectors
+ class CSVWithTimestamps < ::RspecProfiling::Collectors::CSV
+ TIMESTAMP_FIELDS = %w(created_at updated_at).freeze
+ HEADERS = (::RspecProfiling::Collectors::CSV::HEADERS + TIMESTAMP_FIELDS).freeze
+
+ def insert(attributes)
+ output << HEADERS.map do |field|
+ if TIMESTAMP_FIELDS.include?(field)
+ Time.now
+ else
+ attributes.fetch(field.to_sym)
+ end
+ end
+ end
+
+ private
+
+ def output
+ @output ||= ::CSV.open(path, "w").tap { |csv| csv << HEADERS }
+ end
end
end
@@ -10,9 +31,13 @@ module RspecProfilingExt
if ENV['CI_COMMIT_REF_NAME']
"#{defined?(Gitlab::License) ? 'ee' : 'ce'}:#{ENV['CI_COMMIT_REF_NAME']}"
else
- super
+ super&.chomp
end
end
+
+ def sha
+ super&.chomp
+ end
end
module Run
@@ -30,16 +55,11 @@ module RspecProfilingExt
end
end
-if Rails.env.test?
- RspecProfiling.configure do |config|
- if ENV['RSPEC_PROFILING_POSTGRES_URL'].present?
- RspecProfiling::Collectors::PSQL.prepend(RspecProfilingExt::PSQL)
- config.collector = RspecProfiling::Collectors::PSQL
- end
-
- if ENV.key?('CI')
- RspecProfiling::VCS::Git.prepend(RspecProfilingExt::Git)
- RspecProfiling::Run.prepend(RspecProfilingExt::Run)
- end
+RspecProfiling.configure do |config|
+ if ENV.key?('CI') || ENV.key?('RSPEC_PROFILING')
+ RspecProfiling::VCS::Git.prepend(RspecProfilingExt::Git)
+ RspecProfiling::Run.prepend(RspecProfilingExt::Run)
+ config.collector = RspecProfilingExt::Collectors::CSVWithTimestamps
+ config.csv_path = -> { "rspec_profiling/#{Time.now.to_i}-#{SecureRandom.hex(8)}-rspec-data.csv" }
end
end
diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb
index abc91c3ae51..680cfa6f0ed 100644
--- a/config/initializers/sentry.rb
+++ b/config/initializers/sentry.rb
@@ -20,6 +20,21 @@ def configure_sentry
# Sanitize authentication headers
config.sanitize_http_headers = %w[Authorization Private-Token]
config.tags = { program: Gitlab.process_name }
+ # Debugging for https://gitlab.com/gitlab-org/gitlab-ce/issues/57727
+ config.before_send = lambda do |event, hint|
+ if ActiveModel::MissingAttributeError === hint[:exception]
+ columns_hash = ActiveRecord::Base
+ .connection
+ .schema_cache
+ .instance_variable_get(:@columns_hash)
+ .map { |k, v| [k, v.map(&:first)] }
+ .to_h
+
+ event.extra.merge!(columns_hash)
+ end
+
+ event
+ end
end
end
end
diff --git a/config/initializers/trusted_proxies.rb b/config/initializers/trusted_proxies.rb
index 7af465d8443..13896408806 100644
--- a/config/initializers/trusted_proxies.rb
+++ b/config/initializers/trusted_proxies.rb
@@ -14,10 +14,8 @@ module Rack
end
gitlab_trusted_proxies = Array(Gitlab.config.gitlab.trusted_proxies).map do |proxy|
- begin
- IPAddr.new(proxy)
- rescue IPAddr::InvalidAddressError
- end
+ IPAddr.new(proxy)
+rescue IPAddr::InvalidAddressError
end.compact
Rails.application.config.action_dispatch.trusted_proxies = (
diff --git a/config/karma.config.js b/config/karma.config.js
index e1d7c30b1c2..c30c58edc6f 100644
--- a/config/karma.config.js
+++ b/config/karma.config.js
@@ -6,6 +6,7 @@ const argumentsParser = require('commander');
const webpackConfig = require('./webpack.config.js');
const ROOT_PATH = path.resolve(__dirname, '..');
+const SPECS_PATH = /^(?:\.[\\\/])?(ee[\\\/])?spec[\\\/]javascripts[\\\/]/;
function fatalError(message) {
console.error(chalk.red(`\nError: ${message}\n`));
@@ -26,7 +27,7 @@ webpackConfig.devtool = 'cheap-inline-source-map';
webpackConfig.plugins.push(
new webpack.DefinePlugin({
'process.env.BABEL_ENV': JSON.stringify(process.env.BABEL_ENV || process.env.NODE_ENV || null),
- })
+ }),
);
const specFilters = argumentsParser
@@ -37,13 +38,23 @@ const specFilters = argumentsParser
memo.push(filter, filter.replace(/\/?$/, '/**/*.js'));
return memo;
},
- []
+ [],
)
.parse(process.argv).filterSpec;
-if (specFilters.length) {
- const specsPath = /^(?:\.[\\\/])?spec[\\\/]javascripts[\\\/]/;
+const createContext = (specFiles, regex, suffix) => {
+ const newContext = specFiles.reduce((context, file) => {
+ const relativePath = file.replace(SPECS_PATH, '');
+ context[file] = `./${relativePath}`;
+ return context;
+ }, {});
+
+ webpackConfig.plugins.push(
+ new webpack.ContextReplacementPlugin(regex, path.join(ROOT_PATH, suffix), newContext),
+ );
+};
+if (specFilters.length) {
// resolve filters
let filteredSpecFiles = specFilters.map(filter =>
glob
@@ -51,7 +62,7 @@ if (specFilters.length) {
root: ROOT_PATH,
matchBase: true,
})
- .filter(path => path.endsWith('spec.js'))
+ .filter(path => path.endsWith('spec.js')),
);
// flatten
@@ -64,23 +75,15 @@ if (specFilters.length) {
fatalError('Your filter did not match any test files.');
}
- if (!filteredSpecFiles.every(file => specsPath.test(file))) {
+ if (!filteredSpecFiles.every(file => SPECS_PATH.test(file))) {
fatalError('Test files must be located within /spec/javascripts.');
}
- const newContext = filteredSpecFiles.reduce((context, file) => {
- const relativePath = file.replace(specsPath, '');
- context[file] = `./${relativePath}`;
- return context;
- }, {});
+ const CE_FILES = filteredSpecFiles.filter(file => !file.startsWith('ee'));
+ createContext(CE_FILES, /[^e]{2}[\\\/]spec[\\\/]javascripts$/, 'spec/javascripts');
- webpackConfig.plugins.push(
- new webpack.ContextReplacementPlugin(
- /spec[\\\/]javascripts$/,
- path.join(ROOT_PATH, 'spec/javascripts'),
- newContext
- )
- );
+ const EE_FILES = filteredSpecFiles.filter(file => file.startsWith('ee'));
+ createContext(EE_FILES, /ee[\\\/]spec[\\\/]javascripts$/, 'ee/spec/javascripts');
}
// Karma configuration
@@ -111,18 +114,41 @@ module.exports = function(config) {
],
preprocessors: {
'spec/javascripts/**/*.js': ['webpack', 'sourcemap'],
+ 'ee/spec/javascripts/**/*.js': ['webpack', 'sourcemap'],
},
- reporters: ['progress'],
+ reporters: ['mocha'],
webpack: webpackConfig,
webpackMiddleware: { stats: 'errors-only' },
+ plugins: [
+ 'karma-chrome-launcher',
+ 'karma-coverage-istanbul-reporter',
+ 'karma-jasmine',
+ 'karma-junit-reporter',
+ 'karma-mocha-reporter',
+ 'karma-sourcemap-loader',
+ 'karma-webpack',
+ ],
};
if (process.env.CI) {
- karmaConfig.reporters = ['mocha', 'junit'];
+ karmaConfig.reporters.push('junit');
karmaConfig.junitReporter = {
outputFile: 'junit_karma.xml',
useBrowserName: false,
};
+ } else {
+ // ignore 404s in local environment because we are not fixing them and they bloat the log
+ function ignore404() {
+ return (request, response /* next */) => {
+ response.writeHead(404);
+ return response.end('NOT FOUND');
+ };
+ }
+
+ karmaConfig.middleware = ['ignore-404'];
+ karmaConfig.plugins.push({
+ 'middleware:ignore-404': ['factory', ignore404],
+ });
}
if (process.env.BABEL_ENV === 'coverage' || process.env.NODE_ENV === 'coverage') {
diff --git a/config/routes.rb b/config/routes.rb
index 53c6225eff1..bbf00208545 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -43,6 +43,7 @@ Rails.application.routes.draw do
get '/autocomplete/users/:id' => 'autocomplete#user'
get '/autocomplete/projects' => 'autocomplete#projects'
get '/autocomplete/award_emojis' => 'autocomplete#award_emojis'
+ get '/autocomplete/merge_request_target_branches' => 'autocomplete#merge_request_target_branches'
# Search
get 'search' => 'search#show'
diff --git a/config/routes/group.rb b/config/routes/group.rb
index b3015529c6e..b300fcb757f 100644
--- a/config/routes/group.rb
+++ b/config/routes/group.rb
@@ -14,6 +14,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
get :issues, as: :issues_group
get :merge_requests, as: :merge_requests_group
get :projects, as: :projects_group
+ get :details, as: :details_group
get :activity, as: :activity_group
put :transfer, as: :transfer_group
# TODO: Remove as part of refactor in https://gitlab.com/gitlab-org/gitlab-ce/issues/49693
@@ -31,6 +32,7 @@ constraints(::Constraints::GroupUrlConstrainer.new) do
namespace :settings do
resource :ci_cd, only: [:show], controller: 'ci_cd' do
put :reset_registration_token
+ patch :update_auto_devops
end
end
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 64e6ec49219..20b3f4c0264 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -1,3 +1,4 @@
+const fs = require('fs');
const path = require('path');
const glob = require('glob');
const webpack = require('webpack');
@@ -11,6 +12,10 @@ const ROOT_PATH = path.resolve(__dirname, '..');
const CACHE_PATH = process.env.WEBPACK_CACHE_PATH || path.join(ROOT_PATH, 'tmp/cache');
const IS_PRODUCTION = process.env.NODE_ENV === 'production';
const IS_DEV_SERVER = process.argv.join(' ').indexOf('webpack-dev-server') !== -1;
+const IS_EE =
+ process.env.EE !== undefined
+ ? JSON.parse(process.env.EE)
+ : fs.existsSync(path.join(ROOT_PATH, 'ee'));
const DEV_SERVER_HOST = process.env.DEV_SERVER_HOST || 'localhost';
const DEV_SERVER_PORT = parseInt(process.env.DEV_SERVER_PORT, 10) || 3808;
const DEV_SERVER_LIVERELOAD = IS_DEV_SERVER && process.env.DEV_SERVER_LIVERELOAD !== 'false';
@@ -44,6 +49,14 @@ function generateEntries() {
pageEntries.forEach(path => generateAutoEntries(path));
+ if (IS_EE) {
+ const eePageEntries = glob.sync('pages/**/index.js', {
+ cwd: path.join(ROOT_PATH, 'ee/app/assets/javascripts'),
+ });
+ eePageEntries.forEach(path => generateAutoEntries(path, 'ee'));
+ watchAutoEntries.push(path.join(ROOT_PATH, 'ee/app/assets/javascripts/pages/'));
+ }
+
const autoEntryKeys = Object.keys(autoEntriesMap);
autoEntriesCount = autoEntryKeys.length;
@@ -68,6 +81,31 @@ function generateEntries() {
return Object.assign(manualEntries, autoEntries);
}
+const alias = {
+ '~': path.join(ROOT_PATH, 'app/assets/javascripts'),
+ emojis: path.join(ROOT_PATH, 'fixtures/emojis'),
+ empty_states: path.join(ROOT_PATH, 'app/views/shared/empty_states'),
+ icons: path.join(ROOT_PATH, 'app/views/shared/icons'),
+ images: path.join(ROOT_PATH, 'app/assets/images'),
+ vendor: path.join(ROOT_PATH, 'vendor/assets/javascripts'),
+ vue$: 'vue/dist/vue.esm.js',
+ spec: path.join(ROOT_PATH, 'spec/javascripts'),
+
+ // the following resolves files which are different between CE and EE
+ ee_else_ce: path.join(ROOT_PATH, 'app/assets/javascripts'),
+};
+
+if (IS_EE) {
+ Object.assign(alias, {
+ ee: path.join(ROOT_PATH, 'ee/app/assets/javascripts'),
+ ee_empty_states: path.join(ROOT_PATH, 'ee/app/views/shared/empty_states'),
+ ee_icons: path.join(ROOT_PATH, 'ee/app/views/shared/icons'),
+ ee_images: path.join(ROOT_PATH, 'ee/app/assets/images'),
+ ee_spec: path.join(ROOT_PATH, 'ee/spec/javascripts'),
+ ee_else_ce: path.join(ROOT_PATH, 'ee/app/assets/javascripts'),
+ });
+}
+
module.exports = {
mode: IS_PRODUCTION ? 'production' : 'development',
@@ -85,19 +123,7 @@ module.exports = {
resolve: {
extensions: ['.js', '.gql', '.graphql'],
- alias: {
- '~': path.join(ROOT_PATH, 'app/assets/javascripts'),
- emojis: path.join(ROOT_PATH, 'fixtures/emojis'),
- empty_states: path.join(ROOT_PATH, 'app/views/shared/empty_states'),
- icons: path.join(ROOT_PATH, 'app/views/shared/icons'),
- images: path.join(ROOT_PATH, 'app/assets/images'),
- vendor: path.join(ROOT_PATH, 'vendor/assets/javascripts'),
- vue$: 'vue/dist/vue.esm.js',
- spec: path.join(ROOT_PATH, 'spec/javascripts'),
-
- // the following resolves files which are different between CE and EE
- ee_else_ce: path.join(ROOT_PATH, 'app/assets/javascripts'),
- },
+ alias,
},
module: {
@@ -251,7 +277,7 @@ module.exports = {
} else {
resource.request = path.join(
ROOT_PATH,
- 'app/assets/javascripts/vue_shared/components/empty_component.js'
+ 'app/assets/javascripts/vue_shared/components/empty_component.js',
);
}
}),
@@ -267,7 +293,7 @@ module.exports = {
const missingDeps = Array.from(compilation.missingDependencies);
const nodeModulesPath = path.join(ROOT_PATH, 'node_modules');
const hasMissingNodeModules = missingDeps.some(
- file => file.indexOf(nodeModulesPath) !== -1
+ file => file.indexOf(nodeModulesPath) !== -1,
);
// watch for changes to missing node_modules
@@ -278,7 +304,7 @@ module.exports = {
// report our auto-generated bundle count
console.log(
- `${autoEntriesCount} entries from '/pages' automatically added to webpack output.`
+ `${autoEntriesCount} entries from '/pages' automatically added to webpack output.`,
);
callback();
@@ -298,6 +324,10 @@ module.exports = {
reportFilename: path.join(ROOT_PATH, 'webpack-report/index.html'),
statsFilename: path.join(ROOT_PATH, 'webpack-report/stats.json'),
}),
+
+ new webpack.DefinePlugin({
+ 'process.env.EE': JSON.stringify(IS_EE),
+ }),
].filter(Boolean),
devServer: {
diff --git a/danger/documentation/Dangerfile b/danger/documentation/Dangerfile
index 0cf478b4f89..96c0d9b7478 100644
--- a/danger/documentation/Dangerfile
+++ b/danger/documentation/Dangerfile
@@ -4,33 +4,19 @@ docs_paths_to_review = helper.changes_by_category[:docs]
unless docs_paths_to_review.empty?
message 'This merge request adds or changes files that require a review ' \
- 'from the Technical Writing team whether before or after merging.'
+ 'from the Technical Writing team.'
markdown(<<~MARKDOWN)
## Documentation review
-The following files will require a review from the [Technical Writing](https://about.gitlab.com/handbook/product/technical-writing/) team:
+The following files require a review from a technical writer:
* #{docs_paths_to_review.map { |path| "`#{path}`" }.join("\n* ")}
-Per the [documentation workflows](https://docs.gitlab.com/ee/development/documentation/workflow.html), the review may occur prior to merging this MR **or** after a maintainer has agreed to review and merge this MR without a tech writer review. (Meanwhile, you are welcome to involve a technical writer at any time if you have questions about writing or updating the documentation. GitLabbers are also welcome to use the [#docs](https://gitlab.slack.com/archives/C16HYA2P5) channel on Slack.)
+The review does not need to block merging this merge request. See the:
-The technical writer who, by default, will review this content or collaborate as needed is dependent on the [DevOps stage](https://about.gitlab.com/handbook/product/categories/#devops-stages) to which the content applies:
-
-| Tech writer | Stage(s) |
-| ------------ | ------------------------------------------------------------ |
-| `@marcia` | ~Create ~Release + ~"development guidelines" |
-| `@axil` | ~Distribution ~Gitaly ~Gitter ~Monitor ~Package ~Secure |
-| `@eread` | ~Manage ~Configure ~Geo ~Verify |
-| `@mikelewis` | ~Plan |
-
-**To request a review prior to merging**
-
-- Assign the MR to the applicable technical writer, above. If you are not sure which category the change falls within, or the change is not part of one of these categories, mention one of the usernames above.
-
-**To request a review to commence after merging**
-
-- Create an issue for a doc review [using the Doc Review template](https://gitlab.com/gitlab-org/gitlab-ce/issues/new?issuable_template=Doc%20Review) and assign it to the applicable technicial writer from the table.
+- [DevOps stages](https://about.gitlab.com/handbook/product/categories/#devops-stages) for the appropriate technical writer for this review.
+- [Documentation workflows](https://docs.gitlab.com/ee/development/documentation/workflow.html) for information on when to assign a merge request for review.
MARKDOWN
unless gitlab.mr_labels.include?('Documentation')
diff --git a/danger/roulette/Dangerfile b/danger/roulette/Dangerfile
index 6cf54d0f854..808bc96a0a0 100644
--- a/danger/roulette/Dangerfile
+++ b/danger/roulette/Dangerfile
@@ -58,7 +58,9 @@ changes = helper.changes_by_category
changes.delete(:none)
categories = changes.keys - [:unknown]
-unless changes.empty?
+# Single codebase MRs are reviewed using a slightly different process, so we
+# disable the review roulette for such MRs.
+if changes.any? && !gitlab.mr_labels.include?('single codebase')
team =
begin
helper.project_team
diff --git a/danger/single_codebase/Dangerfile b/danger/single_codebase/Dangerfile
new file mode 100644
index 00000000000..a5938cd6783
--- /dev/null
+++ b/danger/single_codebase/Dangerfile
@@ -0,0 +1,56 @@
+def mention_single_codebase_approvers
+ frontend_maintainers = %w(@filipa @iamphill)
+ backend_maintainers = %w(@rspeicher @rymai @yorickpeterse @godfat)
+
+ rows = []
+ users = []
+
+ if gitlab.mr_labels.include?('frontend')
+ frontend_maintainer = frontend_maintainers.sample
+
+ rows << "| ~frontend | `#{frontend_maintainer}`"
+ users << frontend_maintainer
+ end
+
+ if gitlab.mr_labels.include?('backend')
+ backend_maintainer = backend_maintainers.sample
+
+ rows << "| ~backend | `#{backend_maintainer}`"
+ users << backend_maintainer
+ end
+
+ if rows.empty?
+ backup_maintainer = backend_maintainers.sample
+
+ rows << "| ~frontend / ~backend | `#{backup_maintainer}`"
+ users << backup_maintainer
+ end
+
+ markdown(<<~MARKDOWN.strip)
+ ## Single codebase changes
+
+ This merge request contains changes related to the work of moving towards a
+ [single codebase](https://gitlab.com/groups/gitlab-org/-/epics/802) for
+ Community Edition and Enterprise Edition. These changes will need to be
+ reviewed and approved by the following engineers:
+
+ | Category | Reviewer
+ |----------|---------
+ #{rows.join("\n")}
+
+ To make sure this happens, please follow these steps:
+
+ 1. Add all of the mentioned users to the list of merge request approvals.
+ 2. Assign the merge request to the first person in the above list.
+
+ If you are a reviewer, please follow these steps:
+
+ 1. Review the merge request. If it is good to go, approve it.
+ 2. Once approved, assign to the next person in the above list. If you are
+ the last person in the list, merge the merge request.
+ MARKDOWN
+end
+
+if gitlab.mr_labels.include?('single codebase')
+ mention_single_codebase_approvers
+end
diff --git a/db/fixtures/development/03_settings.rb b/db/fixtures/development/02_settings.rb
index 3a4a5d436bf..3a4a5d436bf 100644
--- a/db/fixtures/development/03_settings.rb
+++ b/db/fixtures/development/02_settings.rb
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/03_project.rb
index 9a5f7cf8175..46018cf68aa 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/03_project.rb
@@ -60,7 +60,7 @@ Sidekiq::Testing.inline! do
path: group_path
)
group.description = FFaker::Lorem.sentence
- group.save
+ group.save!
group.add_owner(User.first)
end
diff --git a/db/fixtures/development/04_labels.rb b/db/fixtures/development/04_labels.rb
new file mode 100644
index 00000000000..b9ae4098d76
--- /dev/null
+++ b/db/fixtures/development/04_labels.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+require 'digest/md5'
+
+class Gitlab::Seeder::GroupLabels
+ def initialize(group, label_per_group: 10)
+ @group = group
+ @label_per_group = label_per_group
+ end
+
+ def seed!
+ @label_per_group.times do
+ label_title = FFaker::Product.brand
+ Labels::CreateService
+ .new(title: label_title, color: "##{Digest::MD5.hexdigest(label_title)[0..5]}")
+ .execute(group: @group)
+ print '.'
+ end
+ end
+end
+
+class Gitlab::Seeder::ProjectLabels
+ def initialize(project, label_per_project: 5)
+ @project = project
+ @label_per_project = label_per_project
+ end
+
+ def seed!
+ @label_per_project.times do
+ label_title = FFaker::Vehicle.model
+ Labels::CreateService
+ .new(title: label_title, color: "##{Digest::MD5.hexdigest(label_title)[0..5]}")
+ .execute(project: @project)
+ print '.'
+ end
+ end
+end
+
+Gitlab::Seeder.quiet do
+ puts "\nGenerating group labels"
+ Group.all.find_each do |group|
+ Gitlab::Seeder::GroupLabels.new(group).seed!
+ end
+
+ puts "\nGenerating project labels"
+ Project.all.find_each do |project|
+ Gitlab::Seeder::ProjectLabels.new(project).seed!
+ end
+end
diff --git a/db/fixtures/development/09_issues.rb b/db/fixtures/development/09_issues.rb
index 16243b72f9a..926401d8b9e 100644
--- a/db/fixtures/development/09_issues.rb
+++ b/db/fixtures/development/09_issues.rb
@@ -3,13 +3,17 @@ require './spec/support/sidekiq'
Gitlab::Seeder.quiet do
Project.all.each do |project|
10.times do
+ label_ids = project.labels.pluck(:id).sample(3)
+ label_ids += project.group.labels.sample(3) if project.group
+
issue_params = {
title: FFaker::Lorem.sentence(6),
description: FFaker::Lorem.sentence,
state: ['opened', 'closed'].sample,
milestone: project.milestones.sample,
assignees: [project.team.users.sample],
- created_at: rand(12).months.ago
+ created_at: rand(12).months.ago,
+ label_ids: label_ids
}
Issues::CreateService.new(project, project.team.users.sample, issue_params).execute
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index 2051bcff8f0..1952f84ed62 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -12,13 +12,17 @@ Gitlab::Seeder.quiet do
source_branch = branches.pop
target_branch = branches.pop
+ label_ids = project.labels.pluck(:id).sample(3)
+ label_ids += project.group.labels.sample(3) if project.group
+
params = {
source_branch: source_branch,
target_branch: target_branch,
title: FFaker::Lorem.sentence(6),
description: FFaker::Lorem.sentences(3).join(" "),
milestone: project.milestones.sample,
- assignee: project.team.users.sample
+ assignee: project.team.users.sample,
+ label_ids: label_ids
}
# Only create MRs with users that are allowed to create MRs
diff --git a/db/fixtures/development/22_labeled_issues_seed.rb b/db/fixtures/development/22_labeled_issues_seed.rb
deleted file mode 100644
index 3730e9c7958..00000000000
--- a/db/fixtures/development/22_labeled_issues_seed.rb
+++ /dev/null
@@ -1,103 +0,0 @@
-# Creates a project with labeled issues for an user.
-# Run this single seed file using: rake db:seed_fu FILTER=labeled USER_ID=74.
-# If an USER_ID is not provided it will use the last created user.
-require './spec/support/sidekiq'
-
-class Gitlab::Seeder::LabeledIssues
- include ::Gitlab::Utils
-
- def initialize(user)
- @user = user
- end
-
- def seed!
- Sidekiq::Testing.inline! do
- group = create_group
-
- create_projects(group)
- create_labels(group)
- create_issues(group)
- end
-
- print '.'
- end
-
- private
-
- def create_group
- group_name = "group_of_#{@user.username}_#{SecureRandom.hex(4)}"
-
- group_params = {
- name: group_name,
- path: group_name,
- description: FFaker::Lorem.sentence
- }
-
- Groups::CreateService.new(@user, group_params).execute
- end
-
- def create_projects(group)
- 5.times do
- project_name = "project_#{SecureRandom.hex(6)}"
-
- params = {
- namespace_id: group.id,
- name: project_name,
- description: FFaker::Lorem.sentence,
- visibility_level: Gitlab::VisibilityLevel.values.sample
- }
-
- Projects::CreateService.new(@user, params).execute
- end
- end
-
- def create_labels(group)
- group.projects.each do |project|
- 5.times do
- label_title = FFaker::Vehicle.model
- Labels::CreateService.new(title: label_title, color: "#69D100").execute(project: project)
- end
- end
-
- 10.times do
- label_title = FFaker::Product.brand
- Labels::CreateService.new(title: label_title).execute(group: group)
- end
- end
-
- def create_issues(group)
- # Get only group labels
- group_labels =
- LabelsFinder.new(@user, group_id: group.id).execute.where.not(group_id: nil)
-
- group.projects.each do |project|
- label_ids = project.labels.pluck(:id).sample(5)
- label_ids.push(*group.labels.sample(4))
-
- 20.times do
- issue_params = {
- title: FFaker::Lorem.sentence(6),
- description: FFaker::Lorem.sentence,
- state: 'opened',
- label_ids: label_ids
-
- }
-
- Issues::CreateService.new(project, @user, issue_params).execute if project.project_feature.present?
- end
- end
- end
-end
-
-Gitlab::Seeder.quiet do
- user_id = ENV['USER_ID']
-
- user =
- if user_id.present?
- User.find(user_id)
- else
- User.last
- end
-
- Gitlab::Seeder::LabeledIssues.new(user).seed!
-end
diff --git a/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb b/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb
index d40c61f24b1..b4658bc4017 100644
--- a/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb
+++ b/db/migrate/20170530130129_project_foreign_keys_with_cascading_deletes.rb
@@ -126,11 +126,10 @@ class ProjectForeignKeysWithCascadingDeletes < ActiveRecord::Migration[4.2]
queues.each do |queue|
# Stealing is racy so it's possible a pop might be called on an
# already-empty queue.
- begin
- remove_orphans(*queue.pop(true))
- stolen = true
- rescue ThreadError
- end
+
+ remove_orphans(*queue.pop(true))
+ stolen = true
+ rescue ThreadError
end
break unless stolen
diff --git a/db/migrate/20190225152525_add_auto_dev_ops_enabled_to_namespaces.rb b/db/migrate/20190225152525_add_auto_dev_ops_enabled_to_namespaces.rb
new file mode 100644
index 00000000000..93e7a84fb02
--- /dev/null
+++ b/db/migrate/20190225152525_add_auto_dev_ops_enabled_to_namespaces.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddAutoDevOpsEnabledToNamespaces < ActiveRecord::Migration[5.0]
+ DOWNTIME = false
+
+ def change
+ add_column :namespaces, :auto_devops_enabled, :boolean
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 59a76e21a5f..dda0445e3f2 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -1377,6 +1377,7 @@ ActiveRecord::Schema.define(version: 20190301182457) do
t.integer "cached_markdown_version"
t.string "runners_token"
t.string "runners_token_encrypted"
+ t.boolean "auto_devops_enabled"
t.index ["created_at"], name: "index_namespaces_on_created_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"}
diff --git a/doc/administration/build_artifacts.md b/doc/administration/build_artifacts.md
index 623a5321f32..85ae2946bc8 100644
--- a/doc/administration/build_artifacts.md
+++ b/doc/administration/build_artifacts.md
@@ -1 +1,5 @@
+---
+redirect_to: 'job_artifacts.md'
+---
+
This document was moved to [job_artifacts](job_artifacts.md).
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index a1ac4a2a57c..b21bfafc096 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -1,6 +1,7 @@
# GitLab Container Registry administration
> **Notes:**
+>
> - [Introduced][ce-4040] in GitLab 8.8.
> - Container Registry manifest `v1` support was added in GitLab 8.9 to support
> Docker versions earlier than 1.10.
diff --git a/doc/administration/git_protocol.md b/doc/administration/git_protocol.md
index 11b2adeeeb8..345e8646f9b 100644
--- a/doc/administration/git_protocol.md
+++ b/doc/administration/git_protocol.md
@@ -5,13 +5,16 @@ description: "Set and configure Git protocol v2"
# Configuring Git Protocol v2
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/46555) in GitLab 11.4.
-> [Temporarily disabled](https://gitlab.com/gitlab-org/gitlab-ce/issues/55769) in GitLab 11.5.8, 11.6.6, 11.7.1, and 11.8+
+> Temporarily disabled (see [confidential issue](../user/project/issues/confidential_issues.md)
+> `https://gitlab.com/gitlab-org/gitlab-ce/issues/55769`) in GitLab 11.5.8, 11.6.6, 11.7.1, and 11.8+.
NOTE: **Note:**
-Git protocol v2 support has been [temporarily disabled](https://gitlab.com/gitlab-org/gitlab-ce/issues/55769),
-as a feature used to hide certain internal references does not function when it
+Git protocol v2 support has been temporarily disabled
+because a feature used to hide certain internal references does not function when it
is enabled, and this has a security impact. Once this problem has been resolved,
-protocol v2 support will be re-enabled.
+protocol v2 support will be re-enabled. For more information, see the
+[confidential issue](../user/project/issues/confidential_issues.md)
+`https://gitlab.com/gitlab-org/gitlab-ce/issues/55769`.
Git protocol v2 improves the v1 wire protocol in several ways and is
enabled by default in GitLab for HTTP requests. In order to enable SSH,
diff --git a/doc/administration/high_availability/redis.md b/doc/administration/high_availability/redis.md
index a52bc5c3b02..3daebc4d84b 100644
--- a/doc/administration/high_availability/redis.md
+++ b/doc/administration/high_availability/redis.md
@@ -14,6 +14,7 @@ a hosted cloud solution or you can use the one that comes bundled with
Omnibus GitLab packages.
> **Notes:**
+>
> - Redis requires authentication for High Availability. See
> [Redis Security](https://redis.io/topics/security) documentation for more
> information. We recommend using a combination of a Redis password and tight
@@ -55,6 +56,7 @@ components below.
### High Availability with Sentinel
> **Notes:**
+>
> - Starting with GitLab `8.11`, you can configure a list of Redis Sentinel
> servers that will monitor a group of Redis servers to provide failover support.
> - Starting with GitLab `8.14`, the Omnibus GitLab Enterprise Edition package
@@ -231,6 +233,7 @@ Pick the one that suits your needs.
This is the section where we install and set up the new Redis instances.
> **Notes:**
+>
> - We assume that you have installed GitLab and all HA components from scratch. If you
> already have it installed and running, read how to
> [switch from a single-machine installation to Redis HA](#switching-from-an-existing-single-machine-installation-to-redis-ha).
diff --git a/doc/administration/index.md b/doc/administration/index.md
index b723edfc78f..5f368ea8d49 100644
--- a/doc/administration/index.md
+++ b/doc/administration/index.md
@@ -41,6 +41,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [System hooks](../system_hooks/system_hooks.md): Notifications when users, projects and keys are changed.
- [Security](../security/README.md): Learn what you can do to further secure your GitLab instance.
- [Usage statistics, version check, and usage ping](../user/admin_area/settings/usage_statistics.md): Enable or disable information about your instance to be sent to GitLab, Inc.
+- [Global user settings](user_settings.md): Configure instance-wide user permissions.
- [Polling](polling.md): Configure how often the GitLab UI polls for updates.
- [GitLab Pages configuration](pages/index.md): Enable and configure GitLab Pages.
- [GitLab Pages configuration for GitLab source installations](pages/source.md): Enable and configure GitLab Pages on
diff --git a/doc/administration/job_artifacts.md b/doc/administration/job_artifacts.md
index 8522d046a92..e7792106f81 100644
--- a/doc/administration/job_artifacts.md
+++ b/doc/administration/job_artifacts.md
@@ -1,6 +1,7 @@
# Jobs artifacts administration
> **Notes:**
+>
> - Introduced in GitLab 8.2 and GitLab Runner 0.7.0.
> - Starting with GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format changed to `ZIP`.
> - Starting with GitLab 8.17, builds are renamed to jobs.
@@ -86,6 +87,7 @@ _The artifacts are stored by default in
### Using object storage
> **Notes:**
+>
> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1762) in
> [GitLab Premium](https://about.gitlab.com/pricing/) 9.4.
> - Since version 9.5, artifacts are [browsable](../user/project/pipelines/job_artifacts.md#browsing-artifacts),
diff --git a/doc/administration/monitoring/index.md b/doc/administration/monitoring/index.md
index d18dddf09c0..fa0459b24ff 100644
--- a/doc/administration/monitoring/index.md
+++ b/doc/administration/monitoring/index.md
@@ -7,4 +7,4 @@ Explore our features to monitor your GitLab instance:
- [GitHub imports](github_imports.md): Monitor the health and progress of GitLab's GitHub importer with various Prometheus metrics.
- [Monitoring uptime](../../user/admin_area/monitoring/health_check.md): Check the server status using the health check endpoint.
- [IP whitelists](ip_whitelist.md): Configure GitLab for monitoring endpoints that provide health check information when probed.
-- [nginx_status](https://docs.gitlab.com/omnibus/settings/nginx.html#enabling-disabling-nginx_status): Monitor your Nginx server status
+- [nginx_status](https://docs.gitlab.com/omnibus/settings/nginx.html#enablingdisabling-nginx_status): Monitor your Nginx server status
diff --git a/doc/administration/monitoring/performance/introduction.md b/doc/administration/monitoring/performance/introduction.md
index 37a5388d2fc..7ace0ec5a93 100644
--- a/doc/administration/monitoring/performance/introduction.md
+++ b/doc/administration/monitoring/performance/introduction.md
@@ -1 +1,5 @@
+---
+redirect_to: 'index.md'
+---
+
This document was moved to [another location](index.md).
diff --git a/doc/administration/monitoring/performance/prometheus.md b/doc/administration/monitoring/performance/prometheus.md
index d73ef5d1789..2c5bab46dd9 100644
--- a/doc/administration/monitoring/performance/prometheus.md
+++ b/doc/administration/monitoring/performance/prometheus.md
@@ -1 +1,5 @@
+---
+redirect_to: '../prometheus/index.md'
+---
+
This document was moved to [monitoring/prometheus](../prometheus/index.md).
diff --git a/doc/administration/monitoring/prometheus/index.md b/doc/administration/monitoring/prometheus/index.md
index 20d7ef9bb74..f2ac155a694 100644
--- a/doc/administration/monitoring/prometheus/index.md
+++ b/doc/administration/monitoring/prometheus/index.md
@@ -1,6 +1,7 @@
# Monitoring GitLab with Prometheus
> **Notes:**
+>
> - Prometheus and the various exporters listed in this page are bundled in the
> Omnibus GitLab package. Check each exporter's documentation for the timeline
> they got added. For installations from source you will have to install them
diff --git a/doc/administration/operations.md b/doc/administration/operations.md
index 4797d2a3206..9cd78105bbb 100644
--- a/doc/administration/operations.md
+++ b/doc/administration/operations.md
@@ -1 +1,5 @@
+---
+redirect_to: 'operations/index.md'
+---
+
This document was moved to [another location](operations/index.md).
diff --git a/doc/administration/operations/speed_up_ssh.md b/doc/administration/operations/speed_up_ssh.md
index 89265b3018b..6dc83c42f53 100644
--- a/doc/administration/operations/speed_up_ssh.md
+++ b/doc/administration/operations/speed_up_ssh.md
@@ -1 +1,5 @@
+---
+redirect_to: 'fast_ssh_key_lookup.md'
+---
+
This document was moved to [another location](fast_ssh_key_lookup.md).
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index 279ad018aed..288ce376687 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -5,6 +5,7 @@ description: 'Learn how to administer GitLab Pages.'
# GitLab Pages administration
> **Notes:**
+>
> - [Introduced][ee-80] in GitLab EE 8.3.
> - Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5.
> - GitLab Pages [were ported][ce-14605] to Community Edition in GitLab 8.17.
diff --git a/doc/administration/raketasks/storage.md b/doc/administration/raketasks/storage.md
index 7ad38abe4f5..c39fef907db 100644
--- a/doc/administration/raketasks/storage.md
+++ b/doc/administration/raketasks/storage.md
@@ -34,17 +34,59 @@ export ID_FROM=20
export ID_TO=50
```
-You can monitor the progress in the _Admin > Monitoring > Background jobs_ screen.
-There is a specific Queue you can watch to see how long it will take to finish: **project_migrate_hashed_storage**
+You can monitor the progress in the **Admin Area > Monitoring > Background Jobs** page.
+There is a specific Queue you can watch to see how long it will take to finish:
+`hashed_storage:hashed_storage_project_migrate`
After it reaches zero, you can confirm every project has been migrated by running the commands bellow.
If you find it necessary, you can run this migration script again to schedule missing projects.
-Any error or warning will be logged in the sidekiq's log file.
+Any error or warning will be logged in Sidekiq's log file.
You only need the `gitlab:storage:migrate_to_hashed` rake task to migrate your repositories, but we have additional
commands below that helps you inspect projects and attachments in both legacy and hashed storage.
+## Rollback from Hashed storage to Legacy storage
+
+If you need to rollback the storage migration for any reason, you can follow the steps described here.
+
+NOTE: **Note:** Hashed Storage will be required in future version of GitLab.
+
+To prevent new projects from being created in the Hashed storage,
+you need to undo the [enable hashed storage][storage-migration] changes.
+
+This task will schedule all your existing projects and associated attachments to be rolled back to the
+Legacy storage type.
+
+For Omnibus installations, run the following:
+
+```bash
+sudo gitlab-rake gitlab:storage:rollback_to_legacy
+```
+
+For source installations, run the following:
+
+```bash
+sudo -u git -H bundle exec rake gitlab:storage:rollback_to_legacy RAILS_ENV=production
+```
+
+Both commands accept a range as environment variable:
+
+```bash
+# to rollback any migrated project from ID 20 to 50.
+export ID_FROM=20
+export ID_TO=50
+```
+
+You can monitor the progress in the **Admin Area > Monitoring > Background Jobs** page.
+On the **Queues** tab, you can watch the `hashed_storage:hashed_storage_project_rollback` queue to see how long the process will take to finish.
+
+
+After it reaches zero, you can confirm every project has been rolled back by running the commands bellow.
+If some projects weren't rolled back, you can run this rollback script again to schedule further rollbacks.
+
+Any error or warning will be logged in Sidekiq's log file.
+
## List projects on Legacy storage
To have a simple summary of projects using **Legacy** storage:
diff --git a/doc/administration/repository_storage_types.md b/doc/administration/repository_storage_types.md
index 4934aaf39f7..25c3d564560 100644
--- a/doc/administration/repository_storage_types.md
+++ b/doc/administration/repository_storage_types.md
@@ -2,6 +2,24 @@
> [Introduced][ce-28283] in GitLab 10.0.
+Two different storage layouts can be used
+to store the repositories on disk and their characteristics.
+
+GitLab can be configured to use one or multiple repository shard locations
+that can be:
+
+- Mounted to the local disk
+- Exposed as an NFS shared volume
+- Acessed via [gitaly] on its own machine.
+
+In GitLab, this is configured in `/etc/gitlab/gitlab.rb` by the `git_data_dirs({})`
+configuration hash. The storage layouts discussed here will apply to any shard
+defined in it.
+
+The `default` repository shard that is available in any installations
+that haven't customized it, points to the local folder: `/var/opt/gitlab/git-data`.
+Anything discussed below is expected to be part of that folder.
+
## Legacy Storage
Legacy Storage is the storage behavior prior to version 10.0. For historical
@@ -66,34 +84,12 @@ by another folder with the next 2 characters. They are both stored in a special
"@hashed/#{hash[0..1]}/#{hash[2..3]}/#{hash}.wiki.git"
```
-### How to migrate to Hashed Storage
-
-In GitLab, go to **Admin > Settings**, find the **Repository Storage** section
-and select "_Use hashed storage paths for newly created and renamed projects_".
-
-To migrate your existing projects to the new storage type, check the specific
-[rake tasks].
-
-[ce-28283]: https://gitlab.com/gitlab-org/gitlab-ce/issues/28283
-[rake tasks]: raketasks/storage.md#migrate-existing-projects-to-hashed-storage
-[storage-paths]: repository_storage_types.md
-
-#### Rollback
-
-There is no automated rollback implemented. Below are the steps required to rollback
-from each storage migration.
-
-The rollback has to be performed in the reverse order. To get into "Legacy" state,
-you need to rollback Attachments first, then Project.
+### Hashed object pools
-Also note that if Geo is enabled, after the migration was triggered, an event is generated
-to replicate the operation on any Secondary node. That means the on disk changes will also
-need to be performed on these nodes as well. Database changes will propagate without issues.
-
-You must make sure the migration event was already processed or otherwise it may migrate
-the files back to Hashed state again.
-
-#### Hashed object pools
+CAUTION: **Beta:**
+Hashed objects pools are considered beta, and are not ready for production use.
+Follow [gitaly#1548](https://gitlab.com/gitlab-org/gitaly/issues/1548) for
+updates.
For deduplication of public forks and their parent repository, objects are pooled
in an object pool. These object pools are a third repository where shared objects
@@ -110,36 +106,60 @@ enabled for individual projects by executing
be on hashed storage, should not be a fork itself, and hashed storage should be
enabled for all new projects.
-##### Attachments
+### How to migrate to Hashed Storage
-To rollback single Attachment migration, rename `aa/bb/abcdef1234567890...` folder back to `namespace/project`.
+To start a migration, enable Hashed Storage for new projects:
+
+1. Go to **Admin > Settings** and expand the **Repository Storage** section.
+2. Select the **Use hashed storage paths for newly created and renamed projects** checkbox.
-Both folder names can be generated by the `FileUploader.absolute_base_dir(project)`, you
-just need to switch the version from the `project` back to the previous one.
+Check if the change breaks any existing integration you may have that
+either runs on the same machine as your repositories are located, or may login to that machine
+to access data (for example, a remote backup solution).
-```ruby
-project.storage_version
-# => 2
+To schedule a complete rollout, see the
+[rake task documentation for storage migration][rake/migrate-to-hashed] for instructions.
-FileUploader.absolute_base_dir(project)
-# => "/opt/gitlab/embedded/service/gitlab-rails/public/uploads/@hashed/d4/73/d4735e3a265e16eee03f59718b9b5d03019c07d8b6c51f90da3a666eec13ab35"
+If you do have any existing integration, you may want to do a small rollout first,
+to validate. You can do so by specifying a range with the operation.
-project.storage_version = 1
+This is an example of how to limit the rollout to Project IDs 50 to 100, running in
+an Omnibus Gitlab installation:
-FileUploader.absolute_base_dir(project)
-# => "/opt/gitlab/embedded/service/gitlab-rails/public/uploads/gitlab/gitlab-shell-renamed"
+```bash
+sudo gitlab-rake gitlab:storage:migrate_to_hashed ID_FROM=50 ID_TO=100
```
-##### Project
+Check the [documentation][rake/migrate-to-hashed] for additional information and instructions for
+source-based installation.
+
+#### Rollback
+
+Similar to the migration, to disable Hashed Storage for new
+projects:
-To rollback single Project migration, move `@hashed/aa/bb/aabbcdef1234567890abcdef.git` and `@hashed/aa/bb/aabbcdef1234567890abcdef.wiki.git`
-back to `namespace/project.git` and `namespace/project.wiki.git` respectively and switch the version from the `project` back to `null`.
+1. Go to **Admin > Settings** and expand the **Repository Storage** section.
+2. Uncheck the **Use hashed storage paths for newly created and renamed projects** checkbox.
+
+To schedule a complete rollback, see the
+[rake task documentation for storage rollback][rake/rollback-to-legacy] for instructions.
+
+The rollback task also supports specifying a range of Project IDs. Here is an example
+of limiting the rollout to Project IDs 50 to 100, in an Omnibus Gitlab installation:
+
+```bash
+sudo gitlab-rake gitlab:storage:rollback_to_legacy ID_FROM=50 ID_TO=100
+```
+
+If you have a Geo setup, please note that the rollback will not be reflected automatically
+on the **secondary** node. You may need to wait for a backfill operation to kick-in and remove
+the remaining repositories from the special `@hashed/` folder manually.
### Hashed Storage coverage
We are incrementally moving every storable object in GitLab to the Hashed
Storage pattern. You can check the current coverage status below (and also see
-the [issue](https://gitlab.com/gitlab-com/infrastructure/issues/2821)).
+the [issue][ce-2821]).
Note that things stored in an S3 compatible endpoint will not have the downsides
mentioned earlier, if they are not prefixed with `#{namespace}/#{project_name}`,
@@ -156,6 +176,7 @@ which is true for CI Cache and LFS Objects.
| CI Artifacts | No | No | Yes | 9.4 / 10.6 |
| CI Cache | No | No | Yes | - |
| LFS Objects | Yes | Similar | Yes | 10.0 / 10.7 |
+| Repository pools| No | Yes | - | 11.6 |
#### Implementation Details
@@ -180,3 +201,10 @@ LFS Objects implements a similar storage pattern using 2 chars, 2 level folders,
```
They are also S3 compatible since **10.0** (GitLab Premium), and available in GitLab Core since **10.7**.
+
+[ce-2821]: https://gitlab.com/gitlab-com/infrastructure/issues/2821
+[ce-28283]: https://gitlab.com/gitlab-org/gitlab-ce/issues/28283
+[rake/migrate-to-hashed]: raketasks/storage.md#migrate-existing-projects-to-hashed-storage
+[rake/rollback-to-legacy]: raketasks/storage.md#rollback
+[storage-paths]: repository_storage_types.md
+[gitaly]: gitaly/index.md
diff --git a/doc/administration/repository_storages.md b/doc/administration/repository_storages.md
index cf6de15743f..af7a385e5a0 100644
--- a/doc/administration/repository_storages.md
+++ b/doc/administration/repository_storages.md
@@ -1 +1,5 @@
+---
+redirect_to: 'repository_storage_paths.md'
+---
+
This document was moved to [another location](repository_storage_paths.md).
diff --git a/doc/administration/uploads.md b/doc/administration/uploads.md
index 8c0c7a36736..708b59a273b 100644
--- a/doc/administration/uploads.md
+++ b/doc/administration/uploads.md
@@ -18,7 +18,7 @@ below.
>**Notes:**
For historical reasons, uploads are stored into a base directory, which by default is `uploads/-/system`. It is strongly discouraged to change this configuration option on an existing GitLab installation.
-_The uploads are stored by default in `/var/opt/gitlab/gitlab-rails/uploads/-/system`._
+_The uploads are stored by default in `/var/opt/gitlab/gitlab-rails/uploads`._
1. To change the storage path for example to `/mnt/storage/uploads`, edit
`/etc/gitlab/gitlab.rb` and add the following line:
diff --git a/doc/administration/user_settings.md b/doc/administration/user_settings.md
new file mode 100644
index 00000000000..f9654655949
--- /dev/null
+++ b/doc/administration/user_settings.md
@@ -0,0 +1,35 @@
+# Modifying global user settings
+
+GitLab administrators can modify user settings for the entire GitLab instance.
+
+## Disallow users creating top-level groups
+
+By default, new users can create top-level groups. To disable this, modify the appropriate configuration file.
+
+For Omnibus installations, add the following to `/etc/gitlab/gitlab.rb`:
+
+```ruby
+gitlab_rails['gitlab_default_can_create_group'] = false
+```
+
+For source installations, uncomment the following line in `config/gitlab.yml`:
+
+```yaml
+# default_can_create_group: false # default: true
+```
+
+## Disallow users changing usernames
+
+By default, new users can change their usernames. To disable this, modify the appropriate configuration file.
+
+For Omnibus installations, add the following to `/etc/gitlab/gitlab.rb`:
+
+```ruby
+gitlab_rails['gitlab_username_changing_enabled'] = false
+```
+
+For source installations, uncomment the following line in `config/gitlab.yml`:
+
+```yaml
+# username_changing_enabled: false # default: true - User can change her username/namespace
+```
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 442178aedff..09546fcac3f 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -475,7 +475,7 @@ GET /projects/:id/repository/commits/:sha/statuses
| `sha` | string | yes | The commit SHA
| `ref` | string | no | The name of a repository branch or tag or, if not given, the default branch
| `stage` | string | no | Filter by [build stage](../ci/yaml/README.md#stages), e.g., `test`
-| `name` | string | no | Filter by [job name](../ci/yaml/README.md#jobs), e.g., `bundler:audit`
+| `name` | string | no | Filter by [job name](../ci/yaml/README.md#introduction), e.g., `bundler:audit`
| `all` | boolean | no | Return all statuses, not only the latest ones
```bash
diff --git a/doc/api/group_milestones.md b/doc/api/group_milestones.md
index eb974267084..1c2f56581eb 100644
--- a/doc/api/group_milestones.md
+++ b/doc/api/group_milestones.md
@@ -1,6 +1,5 @@
# Group milestones API
-> **Notes:**
> [Introduced][ce-12819] in GitLab 9.5.
## List group milestones
diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md
index b8bc4c40124..b66a3198ffb 100644
--- a/doc/api/namespaces.md
+++ b/doc/api/namespaces.md
@@ -64,9 +64,9 @@ Get all namespaces that match a string in their name or path.
GET /namespaces?search=foobar
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `search` | string | no | Returns a list of namespaces the user is authorized to see based on the search criteria |
+| Attribute | Type | Required | Description |
+| --------- | ------ | -------- | ----------- |
+| `search` | string | no | Returns a list of namespaces the user is authorized to see based on the search criteria |
Example request:
@@ -98,9 +98,9 @@ Get a namespace by ID.
GET /namespaces/:id
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | ID or path of the namespace |
+| Attribute | Type | Required | Description |
+| --------- | -------------- | -------- | ----------- |
+| `id` | integer/string | yes | ID or [URL-encoded path of the namespace](README.md#namespaced-path-encoding) |
Example request:
diff --git a/doc/api/pipeline_schedules.md b/doc/api/pipeline_schedules.md
index 137f1fdddec..50d9e007ecc 100644
--- a/doc/api/pipeline_schedules.md
+++ b/doc/api/pipeline_schedules.md
@@ -1,4 +1,4 @@
-# Pipeline schedules
+# Pipeline schedules API
You can read more about [pipeline schedules](../user/project/pipelines/schedules.md).
@@ -278,9 +278,9 @@ curl --request DELETE --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" "https://gi
}
```
-## Pipeline schedule variable
+## Pipeline schedule variables
-> [Introduced][ce-34518] in GitLab 10.0.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/34518) in GitLab 10.0.
## Create a new pipeline schedule variable
@@ -358,5 +358,3 @@ curl --request DELETE --header "PRIVATE-TOKEN: k5ESFgWY2Qf5xEvDcFxZ" "https://gi
"value": "updated value"
}
```
-
-[ce-34518]: https://gitlab.com/gitlab-org/gitlab-ce/issues/34518 \ No newline at end of file
diff --git a/doc/api/project_snippets.md b/doc/api/project_snippets.md
index f02674adfe2..0ccb0517e08 100644
--- a/doc/api/project_snippets.md
+++ b/doc/api/project_snippets.md
@@ -121,7 +121,6 @@ Parameters:
## Get user agent details
-> **Notes:**
> [Introduced][ce-29508] in GitLab 9.4.
Available only for admins.
diff --git a/doc/api/releases/links.md b/doc/api/releases/links.md
index fd7b9d6e6e2..9c91264ed65 100644
--- a/doc/api/releases/links.md
+++ b/doc/api/releases/links.md
@@ -15,7 +15,7 @@ GET /projects/:id/releases/:tag_name/assets/links
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag associated with the Release. |
Example request:
@@ -53,7 +53,7 @@ GET /projects/:id/releases/:tag_name/assets/links/:link_id
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag associated with the Release. |
| `link_id` | integer | yes | The id of the link. |
@@ -84,7 +84,7 @@ POST /projects/:id/releases/:tag_name/assets/links
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag associated with the Release. |
| `name` | string | yes | The name of the link. |
| `url` | string | yes | The URL of the link. |
@@ -120,7 +120,7 @@ PUT /projects/:id/releases/:tag_name/assets/links/:link_id
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag associated with the Release. |
| `link_id` | integer | yes | The id of the link. |
| `name` | string | no | The name of the link. |
@@ -156,7 +156,7 @@ DELETE /projects/:id/releases/:tag_name/assets/links/:link_id
| Attribute | Type | Required | Description |
| ------------- | -------------- | -------- | --------------------------------------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the project](../README.md#namespaced-path-encoding). |
| `tag_name` | string | yes | The tag associated with the Release. |
| `link_id` | integer | yes | The id of the link. |
diff --git a/doc/api/search.md b/doc/api/search.md
index aa601648b2c..6ee3d32d8bc 100644
--- a/doc/api/search.md
+++ b/doc/api/search.md
@@ -17,7 +17,7 @@ GET /search
| `scope` | string | yes | The scope to search in |
| `search` | string | yes | The search query |
-Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, snippet_titles, snippet_blobs.
+Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, snippet_titles, snippet_blobs, users.
The response depends on the requested scope.
@@ -253,7 +253,7 @@ Example response:
### Scope: snippet_blobs
```bash
-curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=snippet_blos&search=test
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=snippet_blobs&search=test
```
Example response:
@@ -281,6 +281,27 @@ Example response:
]
```
+### Scope: users
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/search?scope=users&search=doe
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "name": "John Doe1",
+ "username": "user1",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/c922747a93b40d1ea88262bf1aebee62?s=80&d=identicon",
+ "web_url": "http://localhost/user1"
+ }
+]
+```
+
## Group Search API
Search within the specified group.
@@ -297,7 +318,7 @@ GET /groups/:id/search
| `scope` | string | yes | The scope to search in |
| `search` | string | yes | The search query |
-Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones.
+Search the expression within the specified scope. Currently these scopes are supported: projects, issues, merge_requests, milestones, users.
The response depends on the requested scope.
@@ -499,6 +520,27 @@ Example response:
]
```
+### Scope: users
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/3/search?scope=users&search=doe
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "name": "John Doe1",
+ "username": "user1",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/c922747a93b40d1ea88262bf1aebee62?s=80&d=identicon",
+ "web_url": "http://localhost/user1"
+ }
+]
+```
+
## Project Search API
Search within the specified project.
@@ -515,7 +557,7 @@ GET /projects/:id/search
| `scope` | string | yes | The scope to search in |
| `search` | string | yes | The search query |
-Search the expression within the specified scope. Currently these scopes are supported: issues, merge_requests, milestones, notes, wiki_blobs, commits, blobs.
+Search the expression within the specified scope. Currently these scopes are supported: issues, merge_requests, milestones, notes, wiki_blobs, commits, blobs, users.
The response depends on the requested scope.
@@ -828,4 +870,25 @@ Example response:
]
```
+### Scope: users
+
+```bash
+curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/6/search?scope=users&search=doe
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 1,
+ "name": "John Doe1",
+ "username": "user1",
+ "state": "active",
+ "avatar_url": "http://www.gravatar.com/avatar/c922747a93b40d1ea88262bf1aebee62?s=80&d=identicon",
+ "web_url": "http://localhost/user1"
+ }
+]
+```
+
[ce-41763]: https://gitlab.com/gitlab-org/gitlab-ce/issues/41763
diff --git a/doc/api/services.md b/doc/api/services.md
index c44f5cc5781..03d0a80aa64 100644
--- a/doc/api/services.md
+++ b/doc/api/services.md
@@ -505,10 +505,9 @@ GET /projects/:id/services/jira
Set JIRA service for a project.
-> **Notes:**
-> - Starting with GitLab 8.14, `api_url`, `issues_url`, `new_issue_url` and
-> `project_url` are replaced by `project_key`, `url`. If you are using an
-> older version, [follow this documentation][old-jira-api].
+> Starting with GitLab 8.14, `api_url`, `issues_url`, `new_issue_url` and
+> `project_url` are replaced by `project_key`, `url`. If you are using an
+> older version, [follow this documentation][old-jira-api].
```
PUT /projects/:id/services/jira
diff --git a/doc/api/users.md b/doc/api/users.md
index b0977810120..606003a75e2 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -140,7 +140,8 @@ GET /users
"can_create_project": true,
"two_factor_enabled": true,
"external": false,
- "private_profile": false
+ "private_profile": false,
+ "highest_role":10
}
]
```
diff --git a/doc/ci/README.md b/doc/ci/README.md
index c66a1d4b7a8..47810a8b7b6 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -43,24 +43,28 @@ For complete control, you can manually configure GitLab CI/CD.
With basic knowledge of how GitLab CI/CD works, the following documentation extends your knowledge
into more features:
-| Topic | Description |
-|:-------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------|
-| [Introduction to pipelines and jobs](pipelines.md) | Provides an overview of GitLab CI/CD and jobs. |
-| [CI/CD Variables](variables/README.md) | How environment variables can be configured and made available in pipelines. |
-| [Where variables can be used](variables/where_variables_can_be_used.md) | A deeper look into where and how CI/CD variables can be used. |
-| [User](../user/permissions.md#gitlab-ci) and [job](../user/permissions.md#job-permissions) permissions | Learn about the access levels a user can have for performing certain CI actions. |
-| [Configuring GitLab Runners](runners/README.md) | Documentation for configuring [GitLab Runner](https://docs.gitlab.com/runner/). |
-| [Introduction to environments and deployments](environments.md) | Learn how to separate your jobs into environments and use them for different purposes like testing, building and, deploying. |
-| [Job artifacts](../user/project/pipelines/job_artifacts.md) | Learn about the output of jobs. |
-| [Cache dependencies in GitLab CI/CD](caching/index.md) | Discover how to speed up pipelines using caching. |
-| [Using Git submodules with GitLab CI](git_submodules.md) | How to run your CI jobs when using Git submodules. |
-| [Pipelines for merge requests](merge_request_pipelines/index.md) | Create pipelines specifically for merge requests. |
-| [Using SSH keys with GitLab CI/CD](ssh_keys/README.md) | Use SSH keys in your build environment. |
-| [Triggering pipelines through the API](triggers/README.md) | Use the GitLab API to trigger a pipeline. |
-| [Pipeline schedules](../user/project/pipelines/schedules.md) | Trigger pipelines on a schedule. |
-| [Connecting GitLab with a Kubernetes cluster](../user/project/clusters/index.md) | Integrate one or more Kubernetes clusters to your project. |
-| [ChatOps](chatops/README.md) | Trigger CI jobs from chat, with results sent back to the channel. |
-| [Interactive web terminals](interactive_web_terminal/index.md) | Open an interactive web terminal to debug the running jobs. |
+| Topic | Description |
+|:--------------------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------------------------------------------------------------|
+| [Creating and using CI/CD pipelines](pipelines.md) | Understand, visualize, create, and use CI/CD pipelines. |
+| [CI/CD Variables](variables/README.md) | How environment variables can be configured and made available in pipelines. |
+| [Where variables can be used](variables/where_variables_can_be_used.md) | A deeper look into where and how CI/CD variables can be used. |
+| [User](../user/permissions.md#gitlab-cicd-permissions) and [job](../user/permissions.md#job-permissions) permissions | Learn about the access levels a user can have for performing certain CI actions. |
+| [Configuring GitLab Runners](runners/README.md) | Documentation for configuring [GitLab Runner](https://docs.gitlab.com/runner/). |
+| [Introduction to environments and deployments](environments.md) | Learn how to separate your jobs into environments and use them for different purposes like testing, building and, deploying. |
+| [Job artifacts](../user/project/pipelines/job_artifacts.md) | Learn about the output of jobs. |
+| [Cache dependencies in GitLab CI/CD](caching/index.md) | Discover how to speed up pipelines using caching. |
+| [Using Git submodules with GitLab CI](git_submodules.md) | How to run your CI jobs when using Git submodules. |
+| [Pipelines for merge requests](merge_request_pipelines/index.md) | Create pipelines specifically for merge requests. |
+| [Using SSH keys with GitLab CI/CD](ssh_keys/README.md) | Use SSH keys in your build environment. |
+| [Triggering pipelines through the API](triggers/README.md) | Use the GitLab API to trigger a pipeline. |
+| [Pipeline schedules](../user/project/pipelines/schedules.md) | Trigger pipelines on a schedule. |
+| [Connecting GitLab with a Kubernetes cluster](../user/project/clusters/index.md) | Integrate one or more Kubernetes clusters to your project. |
+| [ChatOps](chatops/README.md) | Trigger CI jobs from chat, with results sent back to the channel. |
+| [Interactive web terminals](interactive_web_terminal/index.md) | Open an interactive web terminal to debug the running jobs. |
+| [Review Apps](review_apps/index.md) | Configure GitLab CI/CD to preview code changes in a per-branch basis. |
+| [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html) **[PREMIUM]** | Check the current health and status of each CI/CD environment running on Kubernetes. |
+| [GitLab CI/CD for external repositories](https://docs.gitlab.com/ee/ci/ci_cd_for_external_repos/index.html) **[PREMIUM]** | Get the benefits of GitLab CI/CD combined with repositories in GitHub and BitBucket Cloud. |
+| [Protected environments](https://docs.gitlab.com/ce/ci/environments/protected_environments.html) **[PREMIUM]** | Ensure that only people with the right privileges can deploy to an environment. |
### GitLab Pages
diff --git a/doc/ci/autodeploy/index.md b/doc/ci/autodeploy/index.md
index 985ec4b972c..5221cbf8609 100644
--- a/doc/ci/autodeploy/index.md
+++ b/doc/ci/autodeploy/index.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../topics/autodevops/index.md#auto-deploy'
+---
+
This document was moved to [another location](../../topics/autodevops/index.md#auto-deploy).
diff --git a/doc/ci/autodeploy/quick_start_guide.md b/doc/ci/autodeploy/quick_start_guide.md
index 985ec4b972c..5221cbf8609 100644
--- a/doc/ci/autodeploy/quick_start_guide.md
+++ b/doc/ci/autodeploy/quick_start_guide.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../topics/autodevops/index.md#auto-deploy'
+---
+
This document was moved to [another location](../../topics/autodevops/index.md#auto-deploy).
diff --git a/doc/ci/build_artifacts/README.md b/doc/ci/build_artifacts/README.md
index 22b3872025f..b63659c1878 100644
--- a/doc/ci/build_artifacts/README.md
+++ b/doc/ci/build_artifacts/README.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../user/project/pipelines/job_artifacts.md'
+---
+
This document was moved to [pipelines/job_artifacts.md](../../user/project/pipelines/job_artifacts.md).
diff --git a/doc/ci/chatops/README.md b/doc/ci/chatops/README.md
index 6ad1df7bb2a..a06fe6961a7 100644
--- a/doc/ci/chatops/README.md
+++ b/doc/ci/chatops/README.md
@@ -2,9 +2,9 @@
> **Notes:**
>
-> * [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4466) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.6. [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24780) to [GitLab Core](https://about.gitlab.com/pricing/) in 11.9.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/4466) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 10.6. [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24780) to [GitLab Core](https://about.gitlab.com/pricing/) in 11.9.
>
-> * ChatOps is currently in alpha, with some important features missing like access control.
+> - ChatOps is currently in alpha, with some important features missing like access control.
GitLab ChatOps provides a method to interact with CI/CD jobs through chat services like Slack. Many organizations' discussion, collaboration, and troubleshooting is taking place in chat services these days, and having a method to run CI/CD jobs with output posted back to the channel can significantly augment a team's workflow.
@@ -12,7 +12,7 @@ GitLab ChatOps provides a method to interact with CI/CD jobs through chat servic
GitLab ChatOps is built upon two existing features, [GitLab CI/CD](../README.md) and [Slack Slash Commmands](../../user/project/integrations/slack_slash_commands.md).
-A new `run` action has been added to the [slash commands](../../integration/slash_commands.md), which takes two arguments: a `<job name>` to execute and the `<job arguments>`. When executed, ChatOps will look up the specified job name and attempt to match it to a corresponding job in [.gitlab-ci.yml](../yaml/README.md). If a matching job is found on `master`, a pipeline containing just that job is scheduled. Two additional [CI/CD variables](../variables/README.html#predefined-variables-environment-variables) are passed to the job: `CHAT_INPUT` contains any additional arguments, and `CHAT_CHANNEL` is set to the name of channel the action was triggered in.
+A new `run` action has been added to the [slash commands](../../integration/slash_commands.md), which takes two arguments: a `<job name>` to execute and the `<job arguments>`. When executed, ChatOps will look up the specified job name and attempt to match it to a corresponding job in [.gitlab-ci.yml](../yaml/README.md). If a matching job is found on `master`, a pipeline containing just that job is scheduled. Two additional [CI/CD variables](../variables/README.md#predefined-environment-variables) are passed to the job: `CHAT_INPUT` contains any additional arguments, and `CHAT_CHANNEL` is set to the name of channel the action was triggered in.
After the job has finished, its output is sent back to Slack provided it has completed within 30 minutes. If a job takes more than 30 minutes to run it must use the Slack API to manually send data back to a channel.
diff --git a/doc/ci/docker/using_docker_build.md b/doc/ci/docker/using_docker_build.md
index 4f9efb57b8d..9266c4511be 100644
--- a/doc/ci/docker/using_docker_build.md
+++ b/doc/ci/docker/using_docker_build.md
@@ -389,6 +389,7 @@ If you're running multiple Runners you will have to modify all configuration fil
## Using the GitLab Container Registry
> **Notes:**
+>
> - This feature requires GitLab 8.8 and GitLab Runner 1.2.
> - Starting from GitLab 8.12, if you have [2FA] enabled in your account, you need
> to pass a [personal access token][pat] instead of your password in order to
diff --git a/doc/ci/environments.md b/doc/ci/environments.md
index fe66f7e3c28..05d392d54cb 100644
--- a/doc/ci/environments.md
+++ b/doc/ci/environments.md
@@ -21,7 +21,7 @@ all within GitLab. All you need to do is define them in your project's
history of your deployments per every environment.
Environments are like tags for your CI jobs, describing where code gets deployed.
-Deployments are created when [jobs] deploy versions of code to environments,
+Deployments are created when [jobs](yaml/README.md#introduction) deploy versions of code to environments,
so every environment can have one or more deployments. GitLab keeps track of
your deployments, so you always know what is currently being deployed on your
servers. If you have a deployment service such as [Kubernetes][kube]
@@ -103,7 +103,7 @@ the Git SHA and environment name.
To sum up, with the above `.gitlab-ci.yml` we have achieved that:
- All branches will run the `test` and `build` jobs.
-- The `deploy_staging` job will run [only](yaml/README.md#only-and-except-simplified) on the `master`
+- The `deploy_staging` job will run [only](yaml/README.md#onlyexcept-basic) on the `master`
branch which means all merge requests that are created from branches don't
get to deploy to the staging server
- When a merge request is merged, all jobs will run and the `deploy_staging`
@@ -298,8 +298,8 @@ here because it is guaranteed to be unique, but if you're using a workflow like
environment names to be more closely based on the branch name - the example
above would give you an URL like `https://100-do-the-thing.example.com`
-Last but not least, we tell the job to run [`only`][only] on branches
-[`except`][only] master.
+Last but not least, we tell the job to run [`only`](yaml/README.md#onlyexcept-basic) on branches
+[`except`](yaml/README.md#onlyexcept-basic) master.
>**Note:**
You are not bound to use the same prefix or only slashes in the dynamic
@@ -613,14 +613,12 @@ Below are some links you may find interesting:
- [Review Apps - Use dynamic environments to deploy your code for every branch](review_apps/index.md)
[Pipelines]: pipelines.md
-[jobs]: yaml/README.md#jobs
[yaml]: yaml/README.md
[environments]: #environments
[deployments]: #deployments
[permissions]: ../user/permissions.md
[variables]: variables/README.md
[env-name]: yaml/README.md#environmentname
-[only]: yaml/README.md#only-and-except-simplified
[onstop]: yaml/README.md#environmenton_stop
[ce-7015]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7015
[gitlab-flow]: ../workflow/gitlab_flow.md
diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md
index a1c997d1de6..7f686781e3c 100644
--- a/doc/ci/examples/README.md
+++ b/doc/ci/examples/README.md
@@ -50,6 +50,10 @@ language users and GitLab by sending a merge request with a guide for that langu
You may want to apply for the [GitLab Community Writers Program](https://about.gitlab.com/community-writers/)
to get paid for writing complete articles for GitLab.
+### Adding templates to your GitLab installation **[PREMIUM ONLY]**
+
+If you want to have customized examples and templates for your own self-managed GitLab instance available to your team, your GitLab administrator can [designate an instance template repository](https://docs.gitlab.com/ee/user/admin_area/settings/instance_template_repository.html) that contains examples and templates specific to your enterprise.
+
## Other resources
This section provides further resources to help you get familiar with different aspects of GitLab CI/CD.
diff --git a/doc/ci/examples/container_scanning.md b/doc/ci/examples/container_scanning.md
index e8e9c73d1b2..36fdc29fe65 100644
--- a/doc/ci/examples/container_scanning.md
+++ b/doc/ci/examples/container_scanning.md
@@ -30,7 +30,7 @@ container_scanning:
- docker:stable-dind
script:
- docker run -d --name db arminc/clair-db:latest
- - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:v2.0.1
+ - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:v2.0.6
- apk add -U wget ca-certificates
- docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
- wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
@@ -95,7 +95,7 @@ container_scanning:
- docker:stable-dind
script:
- docker run -d --name db arminc/clair-db:latest
- - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:v2.0.1
+ - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:v2.0.6
- apk add -U wget ca-certificates
- docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
- wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
diff --git a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
index d9152c45595..cf281605f5e 100644
--- a/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
+++ b/doc/ci/examples/deploy_spring_boot_to_cloud_foundry/index.md
@@ -99,7 +99,7 @@ We've used the `java:8` [docker
image](../../docker/using_docker_images.md) to build
our application as it provides the up-to-date Java 8 JDK on [Docker
Hub](https://hub.docker.com/). We've also added the [`only`
-clause](../../yaml/README.md#only-and-except-simplified)
+clause](../../yaml/README.md#onlyexcept-basic)
to ensure our deployments only happen when we push to the master branch.
Now, since the steps defined in `.gitlab-ci.yml` require credentials to login
diff --git a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
index 04633fa9dc4..908cf85980e 100644
--- a/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
+++ b/doc/ci/examples/devops_and_game_dev_with_gitlab_ci_cd/index.md
@@ -4,6 +4,7 @@ author_gitlab: blitzgren
level: intermediate
article_type: tutorial
date: 2018-03-07
+last_updated: 2019-03-11
---
# DevOps and Game Dev with GitLab CI/CD
@@ -20,7 +21,7 @@ and the basics of game development.
Our [demo game](http://gitlab-game-demo.s3-website-us-east-1.amazonaws.com/) consists of a simple spaceship traveling in space that shoots by clicking the mouse in a given direction.
-Creating a strong CI/CD pipeline at the beginning of developing another game, [Dark Nova](http://darknova.io/about),
+Creating a strong CI/CD pipeline at the beginning of developing another game, [Dark Nova](http://darknova.io/),
was essential for the fast pace the team worked at. This tutorial will build upon my
[previous introductory article](https://ryanhallcs.wordpress.com/2017/03/15/devops-and-game-dev/) and go through the following steps:
@@ -37,8 +38,8 @@ This will also provide
boilerplate code for starting a browser-based game with the following components:
- Written in [Typescript](https://www.typescriptlang.org/) and [PhaserJs](https://phaser.io)
-- Building, running, and testing with [Gulp](http://gulpjs.com/)
-- Unit tests with [Chai](http://chaijs.com/) and [Mocha](https://mochajs.org/)
+- Building, running, and testing with [Gulp](https://gulpjs.com)
+- Unit tests with [Chai](https://www.chaijs.com) and [Mocha](https://mochajs.org/)
- CI/CD with GitLab
- Hosting the codebase on GitLab.com
- Hosting the game on AWS
@@ -317,7 +318,7 @@ allowing us to run our tests by every push.
Our entire `.gitlab-ci.yml` file should now look like this:
```yml
-image: node:6
+image: node:10
build:
stage: build
@@ -412,7 +413,7 @@ use root security credentials. Proper IAM credential management is beyond the sc
article, but AWS will remind you that using root credentials is unadvised and against their
best practices, as they should. Feel free to follow best practices and use a custom IAM user's
credentials, which will be the same two credentials (Key ID and Secret). It's a good idea to
-fully understand [IAM Best Practices in AWS](http://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html). We need to add these credentials to GitLab:
+fully understand [IAM Best Practices in AWS](https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html). We need to add these credentials to GitLab:
1. Log into your AWS account and go to the [Security Credentials page](https://console.aws.amazon.com/iam/home#/security_credential)
1. Click the **Access Keys** section and **Create New Access Key**. Create the key and keep the id and secret around, you'll need them later
@@ -454,7 +455,7 @@ Be sure to update the region and S3 URL in that last script command to fit your
Our final configuration file `.gitlab-ci.yml` looks like:
```yml
-image: node:6
+image: node:10
build:
stage: build
@@ -502,7 +503,7 @@ deploy:
## Conclusion
Within the [demo repository](https://gitlab.com/blitzgren/gitlab-game-demo) you can also find a handful of boilerplate code to get
-[Typescript](https://www.typescriptlang.org/), [Mocha](https://mochajs.org/), [Gulp](http://gulpjs.com/) and [Phaser](https://phaser.io) all playing
+[Typescript](https://www.typescriptlang.org/), [Mocha](https://mochajs.org/), [Gulp](https://gulpjs.com/) and [Phaser](https://phaser.io) all playing
together nicely with GitLab CI/CD, which is the result of lessons learned while making [Dark Nova](http://darknova.io/).
Using a combination of free and open source software, we have a full CI/CD pipeline, a game foundation,
and unit tests, all running and deployed at every push to master - with shockingly little code.
@@ -520,7 +521,7 @@ a lot of breathing room in quickly getting changes to players.
Here are some ideas to further investigate that can speed up or improve your pipeline:
- [Yarn](https://yarnpkg.com) instead of npm
-- Set up a custom [Docker](../../../ci/docker/using_docker_images.md#define-image-and-services-from-gitlab-ci-yml) image that can preload dependencies and tools (like AWS CLI)
-- Forward a [custom domain](http://docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html) to your game's S3 static website
+- Set up a custom [Docker](../../../ci/docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) image that can preload dependencies and tools (like AWS CLI)
+- Forward a [custom domain](https:/docs.aws.amazon.com/AmazonS3/latest/dev/website-hosting-custom-domain-walkthrough.html) to your game's S3 static website
- Combine jobs if you find it unnecessary for a small project
- Avoid the queues and set up your own [custom GitLab CI/CD runner](https://about.gitlab.com/2016/03/01/gitlab-runner-with-docker/)
diff --git a/doc/ci/examples/end_to_end_testing_webdriverio/index.md b/doc/ci/examples/end_to_end_testing_webdriverio/index.md
index d7afe11f41f..bd221b7145e 100644
--- a/doc/ci/examples/end_to_end_testing_webdriverio/index.md
+++ b/doc/ci/examples/end_to_end_testing_webdriverio/index.md
@@ -139,7 +139,7 @@ new browser window interacting with your app as you specified.
Which brings us to the exciting part: how do we run this in GitLab CI/CD? There are two things we
need to do for this:
-1. Set up [CI/CD jobs](../../yaml/README.md#jobs) that actually have a browser available.
+1. Set up [CI/CD jobs](../../yaml/README.md#introduction) that actually have a browser available.
2. Update our WebdriverIO configuration to use those browsers to visit the review apps.
For the scope of this article, we've defined an additional [CI/CD stage](../../yaml/README.md#stages)
@@ -184,7 +184,7 @@ option as an argument to `npm run confidence-check` on the command line.
However, we still need to tell WebdriverIO which browser is available for it to use.
[GitLab CI/CD makes
-a number of variables available](../../variables/README.html#predefined-variables-environment-variables)
+a number of variables available](../../variables/README.html#predefined-environment-variables)
with information about the current CI job. We can use this information to dynamically set
up our WebdriverIO configuration according to the job that is running. More specifically, we can
tell WebdriverIO what browser to execute the test on depending on the name of the currently running
diff --git a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
index 3963a3e511d..0f809ed05ca 100644
--- a/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
+++ b/doc/ci/examples/laravel_with_gitlab_and_envoy/index.md
@@ -5,6 +5,7 @@ author_gitlab: mehranrasulian
level: intermediate
article_type: tutorial
date: 2017-08-31
+last_updated: 2019-03-06
---
# Test and deploy Laravel applications with GitLab CI/CD and Envoy
@@ -268,7 +269,7 @@ The `releases` directory will hold all our deployments:
echo 'Cloning repository'
[ -d {{ $releases_dir }} ] || mkdir {{ $releases_dir }}
git clone --depth 1 {{ $repository }} {{ $new_release_dir }}
- cd {{ $releases_dir }}
+ cd {{ $new_release_dir }}
git reset --hard {{ $commit }}
@endtask
@@ -346,7 +347,7 @@ At the end, our `Envoy.blade.php` file will look like this:
echo 'Cloning repository'
[ -d {{ $releases_dir }} ] || mkdir {{ $releases_dir }}
git clone --depth 1 {{ $repository }} {{ $new_release_dir }}
- cd {{ $releases_dir }}
+ cd {{ $new_release_dir }}
git reset --hard {{ $commit }}
@endtask
@@ -374,7 +375,7 @@ You might want to create another Envoy task to do that for you.
We also create the `.env` file in the same path to set up our production environment variables for Laravel.
These are persistent data and will be shared to every new release.
-Now, we would need to deploy our app by running `envoy run deploy`, but it won't be necessary since GitLab can handle that for us with CI's [environments](../../environments.md), which will be described [later](#setting-up-gitlab-ci-cd) in this tutorial.
+Now, we would need to deploy our app by running `envoy run deploy`, but it won't be necessary since GitLab can handle that for us with CI's [environments](../../environments.md), which will be described [later](#setting-up-gitlab-cicd) in this tutorial.
Now it's time to commit [Envoy.blade.php](https://gitlab.com/mehranrasulian/laravel-sample/blob/master/Envoy.blade.php) and push it to the `master` branch.
To keep things simple, we commit directly to `master`, without using [feature-branches](../../../workflow/gitlab_flow.md#github-flow-as-a-simpler-alternative) since collaboration is beyond the scope of this tutorial.
@@ -557,7 +558,7 @@ So we should adjust the configuration of MySQL instance by defining `MYSQL_DATAB
Find out more about MySQL variables at the [official MySQL Docker Image](https://hub.docker.com/r/_/mysql/).
Also set the variables `DB_HOST` to `mysql` and `DB_USERNAME` to `root`, which are Laravel specific variables.
-We define `DB_HOST` as `mysql` instead of `127.0.0.1`, as we use MySQL Docker image as a service which [is linked to the main Docker image](../../docker/using_docker_images.md#how-services-are-linked-to-the-build).
+We define `DB_HOST` as `mysql` instead of `127.0.0.1`, as we use MySQL Docker image as a service which [is linked to the main Docker image](../../docker/using_docker_images.md#how-services-are-linked-to-the-job).
```yaml
...
diff --git a/doc/ci/examples/sast_docker.md b/doc/ci/examples/sast_docker.md
index 3a657b3a3d5..70b269046e5 100644
--- a/doc/ci/examples/sast_docker.md
+++ b/doc/ci/examples/sast_docker.md
@@ -1 +1,5 @@
-This document was moved to [another location](./container_scanning.md).
+---
+redirect_to: 'container_scanning.md'
+---
+
+This document was moved to [another location](container_scanning.md).
diff --git a/doc/ci/examples/test-scala-application.md b/doc/ci/examples/test-scala-application.md
index 24328bf6c02..fa18cb22aed 100644
--- a/doc/ci/examples/test-scala-application.md
+++ b/doc/ci/examples/test-scala-application.md
@@ -55,8 +55,8 @@ You can use other versions of Scala and SBT by defining them in
Add the `Coverage was \[\d+.\d+\%\]` regular expression in the
**Settings ➔ Pipelines ➔ Coverage report** project setting to
-retrieve the [test coverage] rate from the build trace and have it
-displayed with your jobs.
+retrieve the [test coverage](../../user/project/pipelines/settings.md#test-coverage-report-badge)
+rate from the build trace and have it displayed with your jobs.
**Pipelines** must be enabled for this option to appear.
@@ -69,8 +69,5 @@ in the `.gitlab-ci.yml` file with your application's name.
## Heroku API key
You can look up your Heroku API key in your
-[account](https://dashboard.heroku.com/account). Add a secure [variable] with
+[account](https://dashboard.heroku.com/account). Add a [protected variable](../variables/README.md#protected-variables) with
this value in **Project ➔ Variables** with key `HEROKU_API_KEY`.
-
-[variable]: ../variables/README.md#user-defined-variables-secure-variables
-[test coverage]: ../../user/project/pipelines/settings.md#test-coverage-report-badge
diff --git a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
index f24e79355aa..1a909e8892a 100644
--- a/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
+++ b/doc/ci/examples/test_phoenix_app_with_gitlab_ci_cd/index.md
@@ -4,6 +4,7 @@ author_gitlab: Hostert
level: beginner
article_type: tutorial
date: 2018-02-20
+last_updated: 2019-03-06
---
# Testing a Phoenix application with GitLab CI/CD
@@ -177,7 +178,7 @@ environment it can run. Since we will work with a single environment, we'll edit
configuration file (`test.exs`).
But, why do we need to adjust our configuration? Well, GitLab CI/CD builds and tests our code in one
-isolated virtual machine, called [Runner][runner-site], using Docker technology. In this Runner,
+isolated virtual machine, called [Runner](../../runners/README.md), using Docker technology. In this Runner,
GitLab CI/CD has access to everything our Phoenix application need to run, exactly as we have in our
`localhost`, but we have to tell GitLab CI/CD where to create and find this database using system
variables. This way, GitLab CI/CD will create our test database inside the Runner, just like we do
@@ -417,7 +418,6 @@ other reasons][ci-reasons] to keep using GitLab CI/CD. The benefits to our teams
[ci-docs]: ../../README.md "GitLab CI/CD Documentation"
[skipping-jobs]: ../../yaml/README.md#skipping-jobs "Skipping Jobs"
[gitlab-runners]: ../../runners/README.md "GitLab Runners Documentation"
-[runner-site]: ../../runners/README.md#runners "Runners"
[docker-image]: https://hub.docker.com/r/trenpixster/elixir/ "Elixir Docker Image"
[using-docker]: ../../docker/using_docker_images.md "Using Docker Images"
[hello-gitlab]: https://gitlab.com/Hostert/hello_gitlab_ci "Hello GitLab CI/CD"
diff --git a/doc/ci/img/pipelines-goal.png b/doc/ci/img/pipelines-goal.png
deleted file mode 100644
index f15716d0b8f..00000000000
--- a/doc/ci/img/pipelines-goal.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/img/types-of-pipelines.png b/doc/ci/img/types-of-pipelines.png
deleted file mode 100644
index 829a53d5d52..00000000000
--- a/doc/ci/img/types-of-pipelines.png
+++ /dev/null
Binary files differ
diff --git a/doc/ci/introduction/img/gitlab_workflow_example.png b/doc/ci/introduction/img/gitlab_workflow_example.png
new file mode 100644
index 00000000000..94e7753c3b2
--- /dev/null
+++ b/doc/ci/introduction/img/gitlab_workflow_example.png
Binary files differ
diff --git a/doc/ci/introduction/index.md b/doc/ci/introduction/index.md
index eeb89f80e09..1b423a4696e 100644
--- a/doc/ci/introduction/index.md
+++ b/doc/ci/introduction/index.md
@@ -78,10 +78,10 @@ scripts to be specified in a file called [`.gitlab-ci.yml`](../yaml/README.md),
located in the root path of your repository.
In this file, you can define the scripts you want to run, define include and
-cache dependencies, choose what commands you want to run in sequence
+cache dependencies, choose commands you want to run in sequence
and those you want to run in parallel, define where you want to
-deploy your app, and choose if you want to run the script automatically
-or if you want to trigger it manually. Once you're familiar with
+deploy your app, and specify whether you will want to run the scripts automatically
+or trigger any of them manually. Once you're familiar with
GitLab CI/CD you can add more advanced steps into the configuration file.
To add scripts to that file, you'll need to organize them in a
@@ -144,7 +144,6 @@ so, GitLab CI/CD:
- Runs automated scripts (sequential or parallel) to:
- Build and test your app.
- - Deploy to a staging environment.
- Preview the changes per merge request with Review Apps, as you
would see in your `localhost`.
@@ -155,6 +154,8 @@ Once you're happy with your implementation:
- GitLab CI/CD deploys your changes automatically to a production environment.
- And finally, you and your team can easily roll it back if something goes wrong.
+<img src="img/gitlab_workflow_example.png" alt="GitLab workflow example" class="image-noshadow">
+
GitLab CI/CD is capable of a doing a lot more, but this workflow
exemplifies GitLab's ability to track the entire process,
without the need of any external tool to deliver your software.
@@ -175,7 +176,7 @@ file, so we recommend you read through it to understand GitLab's CI/CD
logic, and learn how to write your own script (or tweak an
existing one) for any application.
-For an deep view of GitLab's CI/CD configuration options, check the
+For a deep view of GitLab's CI/CD configuration options, check the
[`.gitlab-ci.yml` full reference](../yaml/README.md).
### GitLab CI/CD feature set
diff --git a/doc/ci/junit_test_reports.md b/doc/ci/junit_test_reports.md
index cf18c6d9660..d03c0b68daf 100644
--- a/doc/ci/junit_test_reports.md
+++ b/doc/ci/junit_test_reports.md
@@ -113,8 +113,8 @@ There are a few tools that can produce JUnit reports in Java.
In the following example, `gradle` is used to generate the test reports.
If there are multiple test tasks defined, `gradle` will generate multiple
-directories under `build/test-results/`. In that case, you can leverage regex
-matching by defining the following path: `build/test-results/test/TEST-*.xml`:
+directories under `build/test-results/`. In that case, you can leverage glob
+matching by defining the following path: `build/test-results/test/**/TEST-*.xml`:
```yaml
java:
@@ -123,7 +123,7 @@ java:
- gradle test
artifacts:
reports:
- junit: build/test-results/test/TEST-*.xml
+ junit: build/test-results/test/**/TEST-*.xml
```
#### Maven
diff --git a/doc/ci/merge_request_pipelines/index.md b/doc/ci/merge_request_pipelines/index.md
index 2af0a03cbe4..e8953d235a7 100644
--- a/doc/ci/merge_request_pipelines/index.md
+++ b/doc/ci/merge_request_pipelines/index.md
@@ -9,13 +9,18 @@ For example, unit tests, lint checks, and [Review Apps](../review_apps/index.md)
are often used in this cycle.
With pipelines for merge requests, you can design a specific pipeline structure
-for merge requests. All you need to do is just adding `only: [merge_requests]` to
-the jobs that you want it to run for only merge requests.
-Every time, when developers create or update merge requests, a pipeline runs on
-their new commits at every push to GitLab.
+for merge requests.
+
+## Configuring pipelines for merge requests
+
+To configure pipelines for merge requests, add the `only: merge_requests` parameter to
+the jobs that you want to run only for merge requests.
+
+Then, when developers create or update merge requests, a pipeline runs
+every time a commit is pushed to GitLab.
NOTE: **Note**:
-If you use both this feature and [Merge When Pipeline Succeeds](../../user/project/merge_requests/merge_when_pipeline_succeeds.md),
+If you use this feature with [merge when pipeline succeeds](../../user/project/merge_requests/merge_when_pipeline_succeeds.md),
pipelines for merge requests take precedence over the other regular pipelines.
For example, consider the following [`.gitlab-ci.yml`](../yaml/README.md):
@@ -40,15 +45,17 @@ deploy:
script: ./deploy
```
-After the merge request is updated with new commits, GitLab detects that changes
-have occurred and creates a new pipeline for the merge request.
-The pipeline fetches the latest code from the source branch and run tests against it.
+After the merge request is updated with new commits:
+
+- GitLab detects that changes have occurred and creates a new pipeline for the merge request.
+- The pipeline fetches the latest code from the source branch and run tests against it.
+
In the above example, the pipeline contains only `build` and `test` jobs.
-Since the `deploy` job doesn't have the `only: [merge_requests]` rule,
+Since the `deploy` job doesn't have the `only: merge_requests` parameter,
deployment jobs will not happen in the merge request.
-Pipelines tagged as **merge request** indicate that they were triggered
-when a merge request was created or updated.
+Pipelines tagged with the **merge request** badge indicate that they were triggered
+when a merge request was created or updated. For example:
![Merge request page](img/merge_request.png)
@@ -58,13 +65,18 @@ The same tag is shown on the pipeline's details:
## Excluding certain jobs
-The behavior of the `only: merge_requests` rule is such that _only_ jobs with
-that rule are run in the context of a merge request; no other jobs will be run.
+The behavior of the `only: merge_requests` parameter is such that _only_ jobs with
+that parameter are run in the context of a merge request; no other jobs will be run.
-However, you may want to reverse this behaviour, having all of your jobs to run _except_
-for one or two. Consider the following pipeline, with jobs `A`, `B`, and `C`. If you want
-all pipelines to always run `A` and `B`, but only want `C` to run for a merge request,
-you can configure your `.gitlab-ci.yml` file as follows:
+However, you may want to reverse this behavior, having all of your jobs to run _except_
+for one or two.
+
+Consider the following pipeline, with jobs `A`, `B`, and `C`. Imagine you want:
+
+- All pipelines to always run `A` and `B`.
+- `C` to run only for merge requests.
+
+To achieve this, you can configure your `.gitlab-ci.yml` file as follows:
``` yaml
.only-default: &only-default
@@ -90,9 +102,11 @@ C:
- merge_requests
```
-Since `A` and `B` are getting the `only:` rule to execute in all cases, they will
-always run. `C` specifies that it should only run for merge requests, so for any
-pipeline except a merge request pipeline, it will not run.
+Therefore:
+
+- Since `A` and `B` are getting the `only:` rule to execute in all cases, they will always run.
+- Since `C` specifies that it should only run for merge requests, it will not run for any pipeline
+ except a merge request pipeline.
As you can see, this will help you avoid a lot of boilerplate where you'd need
to add that `only:` rule to all of your jobs in order to make them always run. You
diff --git a/doc/ci/permissions/README.md b/doc/ci/permissions/README.md
index 80d8e46f29c..bc1e6ce3e0b 100644
--- a/doc/ci/permissions/README.md
+++ b/doc/ci/permissions/README.md
@@ -1 +1,5 @@
-This document was moved to [user/permissions.md](../../user/permissions.md#gitlab-ci).
+---
+redirect_to: '../../user/permissions.md#gitlab-cicd-permissions'
+---
+
+This document was moved to [user/permissions.md](../../user/permissions.md#gitlab-cicd-permissions).
diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md
index 4f3106c6dc6..c509c341d1e 100644
--- a/doc/ci/pipelines.md
+++ b/doc/ci/pipelines.md
@@ -1,271 +1,331 @@
-# Introduction to pipelines and jobs
+# Creating and using CI/CD pipelines
> Introduced in GitLab 8.8.
+## Introduction
+
+Pipelines are the top-level component of continuous integration, delivery, and deployment.
+
+Pipelines comprise:
+
+- Jobs that define what to run. For example, code compilation or test runs.
+- Stages that define when and how to run. For example, that tests run only after code compilation.
+
+Multiple jobs in the same stage are executed by [Runners](runners/README.md) in parallel, if there are enough concurrent [Runners](runners/README.md).
+
+If all the jobs in a stage:
+
+- Succeed, the pipeline moves on to the next stage.
+- Fail, the next stage is not (usually) executed and the pipeline ends early.
+
NOTE: **Note:**
-If you have a [mirrored repository where GitLab pulls from](https://docs.gitlab.com/ee/workflow/repository_mirroring.html#pulling-from-a-remote-repository-starter),
+If you have a [mirrored repository that GitLab pulls from](https://docs.gitlab.com/ee/workflow/repository_mirroring.html#pulling-from-a-remote-repository-starter),
you may need to enable pipeline triggering in your project's
**Settings > Repository > Pull from a remote repository > Trigger pipelines for mirror updates**.
-## Pipelines
-
-A pipeline is a group of [jobs] that get executed in [stages].
-All of the jobs in a stage are executed in parallel (if there are enough
-concurrent [Runners]), and if they all succeed, the pipeline moves on to the
-next stage. If one of the jobs fails, the next stage is not (usually)
-executed. You can access the pipelines page in your project's **Pipelines** tab.
+### Simple pipeline example
-In the following image you can see that the pipeline consists of four stages
-(`build`, `test`, `staging`, `production`) each one having one or more jobs.
+As an example, imagine a pipeline consisting of four stages, executed in the following order:
->**Note:**
-GitLab capitalizes the stages' names when shown in the [pipeline graphs](#pipeline-graphs).
+- `build`, with a job called `compile`.
+- `test`, with two jobs called `test` and `test2`.
+- `staging`, with a job called `deploy-to-stage`.
+- `production`, with a job called `deploy-to-prod`.
-![Pipelines example](img/pipelines.png)
+## Visualizing pipelines
-## Types of pipelines
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5742) in GitLab 8.11.
-There are three types of pipelines that often use the single shorthand of "pipeline". People often talk about them as if each one is "the" pipeline, but really, they're just pieces of a single, comprehensive pipeline.
+Pipelines can be complex structures with many sequential and parallel jobs.
-![Types of Pipelines](img/types-of-pipelines.png)
+To make it easier to understand the flow of a pipeline, GitLab has pipeline graphs for viewing pipelines
+and their statuses.
-1. **CI Pipeline**: Build and test stages defined in `.gitlab-ci.yml`.
-1. **Deploy Pipeline**: Deploy stage(s) defined in `.gitlab-ci.yml` The flow of deploying code to servers through various stages: e.g. development to staging to production.
-1. **Project Pipeline**: Cross-project CI dependencies [triggered via API][triggers], particularly for micro-services, but also for complicated build dependencies: e.g. api -> front-end, ce/ee -> omnibus.
+Pipeline graphs can be displayed in two different ways, depending on the page you
+access the graph from.
-## Development workflows
+NOTE: **Note:**
+GitLab capitalizes the stages' names when shown in the [pipeline graphs](#pipeline-graphs).
-Pipelines accommodate several development workflows:
+### Regular pipeline graphs
-1. **Branch Flow** (e.g. different branch for dev, qa, staging, production).
-1. **Trunk-based Flow** (e.g. feature branches and single master branch, possibly with tags for releases).
-1. **Fork-based Flow** (e.g. merge requests come from forks).
+Regular pipeline graphs show the names of the jobs of each stage. Regular pipeline graphs can
+be found when you are on a [single pipeline page](#seeing-pipeline-status). For example:
-Example continuous delivery flow:
+![Pipelines example](img/pipelines.png)
-![CD Flow](img/pipelines-goal.png)
+### Pipeline mini graphs
-## Jobs
+Pipeline mini graphs take less space and can tell you at a
+quick glance if all jobs passed or something failed. The pipeline mini graph can
+be found when you navigate to:
-Jobs can be defined in the [`.gitlab-ci.yml`][jobs-yaml] file. Not to be
-confused with a `build` job or `build` stage.
+- The pipelines index page.
+- A single commit page.
+- A merge request page.
-## Defining pipelines
+Pipeline mini graphs allow you to see all related jobs for a single commit and the net result
+of each stage of your pipeline. This allows you to quickly see what failed and
+fix it.
-Pipelines are defined in `.gitlab-ci.yml` by specifying [jobs] that run in
-[stages].
+Stages in pipeline mini graphs are collapsible. Hover your mouse over them and click to expand their jobs.
-See the reference [documentation for jobs](yaml/README.md#jobs).
+| Mini graph | Mini graph expanded |
+|:-------------------------------------------------------------|:---------------------------------------------------------------|
+| ![Pipelines mini graph](img/pipelines_mini_graph_simple.png) | ![Pipelines mini graph extended](img/pipelines_mini_graph.png) |
-## Manually executing pipelines
+### Job ordering in pipeline graphs
-Pipelines can be manually executed, with predefined or manually-specified [variables](variables/README.md).
+Job ordering depends on the type of pipeline graph. For [regular pipeline graphs](#regular-pipeline-graphs), jobs are sorted by name.
-To execute a pipeline manually:
+For [pipeline mini graphs](#pipeline-mini-graphs) ([introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9760)
+in GitLab 9.0), jobs are sorted by severity and then by name.
-1. Navigate to your project's **CI/CD > Pipelines**.
-1. Click on the **Run Pipeline** button.
-1. Select the branch to run the pipeline for and enter any environment variables required for the pipeline run.
+The order of severity is:
-## Seeing pipeline status
+- failed
+- warning
+- pending
+- running
+- manual
+- scheduled
+- canceled
+- success
+- skipped
+- created
-You can find the current and historical pipeline runs under your project's
-**Pipelines** tab. Clicking on a pipeline will show the jobs that were run for
-that pipeline.
+For example:
-![Pipelines index page](img/pipelines_index.png)
+![Pipeline mini graph sorting](img/pipelines_mini_graph_sorting.png)
-## Seeing job status
+### How pipeline duration is calculated
-When you visit a single pipeline you can see the related jobs for that pipeline.
-Clicking on an individual job will show you its job trace, and allow you to
-cancel the job, retry it, or erase the job trace.
+Total running time for a given pipeline excludes retries and pending
+(queued) time.
-![Pipelines example](img/pipelines.png)
+Each job is represented as a `Period`, which consists of:
-## Seeing the failure reason for jobs
+- `Period#first` (when the job started).
+- `Period#last` (when the job finished).
-> [Introduced][ce-17782] in GitLab 10.7.
+A simple example is:
-When a pipeline fails or is allowed to fail, there are several places where you
-can quickly check the reason it failed:
+- A (1, 3)
+- B (2, 4)
+- C (6, 7)
-- **In the pipeline graph** present on the pipeline detail view.
-- **In the pipeline widgets** present in the merge requests and commit pages.
-- **In the job views** present in the global and detailed views of a job.
+In the example:
-In any case, if you hover over the failed job you can see the reason it failed.
+- A begins at 1 and ends at 3.
+- B begins at 2 and ends at 4.
+- C begins at 6 and ends at 7.
-![Pipeline detail](img/job_failure_reason.png)
+Visually, it can be viewed as:
-From [GitLab 10.8][ce-17814] you can also see the reason it failed on the Job detail page.
+```text
+0 1 2 3 4 5 6 7
+ AAAAAAA
+ BBBBBBB
+ CCCC
+```
-## Pipeline graphs
+The union of A, B, and C is (1, 4) and (6, 7). Therefore, the total running time is:
-> [Introduced][ce-5742] in GitLab 8.11.
+```text
+(4 - 1) + (7 - 6) => 4
+```
-Pipelines can be complex structures with many sequential and parallel jobs.
-To make it a little easier to see what is going on, you can view a graph
-of a single pipeline and its status.
+## Configuring pipelines
-A pipeline graph can be shown in two different ways depending on what page you
-are on.
+Pipelines, and their component jobs and stages, are defined in the [`.gitlab-ci.yml`](yaml/README.md) file for each project.
----
+In particular:
-The regular pipeline graph that shows the names of the jobs of each stage can
-be found when you are on a [single pipeline page](#seeing-pipeline-status).
+- Jobs are the [basic configuration](yaml/README.html#introduction) component.
+- Stages are defined using the [`stages`](yaml/README.html#stages) keyword.
-![Pipelines example](img/pipelines.png)
+For all available configuration options, see the [GitLab CI/CD Pipeline Configuration Reference](yaml/README.md).
-Then, there is the pipeline mini graph which takes less space and can give you a
-quick glance if all jobs pass or something failed. The pipeline mini graph can
-be found when you visit:
+### Settings and schedules
-- The pipelines index page.
-- A single commit page.
-- A merge request page.
+In addition to configuring jobs through `.gitlab-ci.yml`, additional configuration options are available
+through the GitLab UI:
-That way, you can see all related jobs for a single commit and the net result
-of each stage of your pipeline. This allows you to quickly see what failed and
-fix it. Stages in pipeline mini graphs are collapsible. Hover your mouse over
-them and click to expand their jobs.
+- Pipeline settings for each project. For more information, see [Pipeline settings](../user/project/pipelines/settings.md).
+- Schedules for pipelines. For more information, see [Pipeline schedules](../user/project/pipelines/schedules.md).
-| **Mini graph** | **Mini graph expanded** |
-| :------------: | :---------------------: |
-| ![Pipelines mini graph](img/pipelines_mini_graph_simple.png) | ![Pipelines mini graph extended](img/pipelines_mini_graph.png) |
+### Grouping jobs
-### Grouping similar jobs in the pipeline graph
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6242) in GitLab 8.12.
-> [Introduced][ce-6242] in GitLab 8.12.
+If you have many similar jobs, your [pipeline graph](#visualizing-pipelines) becomes long and hard
+to read.
-If you have many similar jobs, your pipeline graph becomes very long and hard
-to read. For that reason, similar jobs can automatically be grouped together.
+For that reason, similar jobs can automatically be grouped together.
If the job names are formatted in certain ways, they will be collapsed into
a single group in regular pipeline graphs (not the mini graphs).
+
You'll know when a pipeline has grouped jobs if you don't see the retry or
cancel button inside them. Hovering over them will show the number of grouped
jobs. Click to expand them.
![Grouped pipelines](img/pipelines_grouped.png)
-The basic requirements is that there are two numbers separated with one of
+#### Configuring grouping
+
+In the pipeline [configuration file](yaml/README.md), job names must include two numbers separated with one of
the following (you can even use them interchangeably):
-- A space (` `)
-- A slash (`/`)
-- A colon (`:`)
+- A space.
+- A slash (`/`).
+- A colon (`:`).
->**Note:**
-More specifically, [it uses][regexp] this regular expression: `\d+[\s:\/\\]+\d+\s*`.
+NOTE: **Note:**
+More specifically, it uses [this](https://gitlab.com/gitlab-org/gitlab-ce/blob/2f3dc314f42dbd79813e6251792853bc231e69dd/app/models/commit_status.rb#L99) regular expression: `\d+[\s:\/\\]+\d+\s*`.
+
+#### How grouping works
The jobs will be ordered by comparing those two numbers from left to right. You
usually want the first to be the index and the second the total.
For example, the following jobs will be grouped under a job named `test`:
-- `test 0 3` => `test`
-- `test 1 3` => `test`
-- `test 2 3` => `test`
+- `test 0 3`
+- `test 1 3`
+- `test 2 3`
The following jobs will be grouped under a job named `test ruby`:
-- `test 1:2 ruby` => `test ruby`
-- `test 2:2 ruby` => `test ruby`
+- `test 1:2 ruby`
+- `test 2:2 ruby`
The following jobs will be grouped under a job named `test ruby` as well:
-- `1/3 test ruby` => `test ruby`
-- `2/3 test ruby` => `test ruby`
-- `3/3 test ruby` => `test ruby`
+- `1/3 test ruby`
+- `2/3 test ruby`
+- `3/3 test ruby`
-### Manual actions from the pipeline graph
+### Pipelines for merge requests
-> [Introduced][ce-7931] in GitLab 8.15.
+GitLab supports configuring pipelines that run only for merge requests. For more information, see
+[Pipelines for merge requests](merge_request_pipelines/index.md).
-[Manual actions][manual] allow you to require manual interaction before moving
-forward with a particular job in CI. Your entire pipeline can run automatically,
-but the actual [deploy to production][env-manual] will require a click.
+### Badges
-You can do this straight from the pipeline graph. Just click on the play button
-to execute that particular job. For example, in the image below, the `production`
-stage has a job with a manual action.
+Pipeline status and test coverage report badges are available and configurable for each project.
-![Pipelines example](img/pipelines.png)
+For information on adding pipeline badges to projects, see [Pipeline badges](../user/project/pipelines/settings.md#pipeline-badges).
-### Delay a particular job in the pipeline graph
+## Multi-project pipelines **[PREMIUM]**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21767) in GitLab 11.4.
+Pipelines for different projects can be combined and visualized together.
-When you do not want to run a job immediately, you can [delay the job to run after a certain period](yaml/README.md#whendelayed).
-This is especially useful for timed incremental rollout that new code is rolled out gradually.
-For example, if you start rolling out new code and users do not experience trouble, GitLab automatically completes the deployment from 0% to 100%.
-Alternatively, if you start rolling out and you noticed that a few users experience trouble with the version,
-you can stop the timed incremental rollout by canceling the pipeline, and [rolling](environments.md#rolling-back-changes) it back to the stable version.
+For more information, see [Multi-project pipelines](https://docs.gitlab.com/ee/ci/multi_project_pipelines.html).
-![Pipelines example](img/pipeline_incremental_rollout.png)
+## Working with pipelines
-### Ordering of jobs in pipeline graphs
+In general, pipelines are executed automatically and require no intervention once created.
-**Regular pipeline graph**
+However, there are instances where you'll need to interact with pipelines. These are documented below.
-In the single pipeline page, jobs are sorted by name.
+### Manually executing pipelines
-**Mini pipeline graph**
+Pipelines can be manually executed, with predefined or manually-specified [variables](variables/README.md).
-> [Introduced][ce-9760] in GitLab 9.0.
+You might do this if the results of a pipeline (for example, a code build) is required outside the normal
+operation of the pipeline.
-In the pipeline mini graphs, the jobs are sorted first by severity and then
-by name. The order of severity is:
+To execute a pipeline manually:
-- failed
-- warning
-- pending
-- running
-- manual
-- scheduled
-- canceled
-- success
-- skipped
-- created
+1. Navigate to your project's **CI/CD > Pipelines**.
+1. Click on the **Run Pipeline** button.
+1. On the **Run Pipeline** page:
+ 1. Select the branch to run the pipeline for in the **Create for** field.
+ 1. Enter any [environment variables](variables/README.md) required for the pipeline run.
+ 1. Click the **Create pipeline** button.
-![Pipeline mini graph sorting](img/pipelines_mini_graph_sorting.png)
+The pipeline will execute the jobs as configured.
-## How the pipeline duration is calculated
+### Accessing pipelines
-Total running time for a given pipeline would exclude retries and pending
-(queue) time. We could reduce this problem down to finding the union of
-periods.
+You can find the current and historical pipeline runs under your project's
+**CI/CD > Pipelines** page. Clicking on a pipeline will show the jobs that were run for
+that pipeline.
-So each job would be represented as a `Period`, which consists of
-`Period#first` as when the job started and `Period#last` as when the
-job was finished. A simple example here would be:
+![Pipelines index page](img/pipelines_index.png)
-- A (1, 3)
-- B (2, 4)
-- C (6, 7)
+You can also access pipelines for a merge request by navigating to its **Pipelines** tab.
-Here A begins from 1, and ends to 3. B begins from 2, and ends to 4.
-C begins from 6, and ends to 7. Visually it could be viewed as:
+### Accessing individual jobs
-```
-0 1 2 3 4 5 6 7
- AAAAAAA
- BBBBBBB
- CCCC
-```
+When you access a pipeline, you can see the related jobs for that pipeline.
-The union of A, B, and C would be (1, 4) and (6, 7), therefore the
-total running time should be:
+Clicking on an individual job will show you its job trace, and allow you to:
-```
-(4 - 1) + (7 - 6) => 4
-```
+- Cancel the job.
+- Retry the job.
+- Erase the job trace.
+
+### Seeing the failure reason for jobs
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17782) in GitLab 10.7.
+
+When a pipeline fails or is allowed to fail, there are several places where you
+can quickly check the reason it failed:
+
+- In the pipeline graph, on the pipeline detail view.
+- In the pipeline widgets, in the merge requests and commit pages.
+- In the job views, in the global and detailed views of a job.
+
+In each place, if you hover over the failed job you can see the reason it failed.
-## Badges
+![Pipeline detail](img/job_failure_reason.png)
+
+From [GitLab 10.8](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17814),
+you can also see the reason it failed on the Job detail page.
+
+### Manual actions from pipeline graphs
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7931) in GitLab 8.15.
-Pipeline status and test coverage report badges are available. You can find their
-respective link in the [Pipelines settings] page.
+Manual actions, configured using the [`when:manual`](yaml/README.md#whenmanual) parameter,
+allow you to require manual interaction before moving forward in the pipeline.
+
+You can do this straight from the pipeline graph. Just click on the play button
+to execute that particular job.
+
+For example, your pipeline start automatically, but require manual action to
+[deploy to production](environments.md#manually-deploying-to-environments). In the example below, the `production`
+stage has a job with a manual action.
+
+![Pipelines example](img/pipelines.png)
+
+### Delay a job in a pipeline graph
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21767) in GitLab 11.4.
+
+When you do not want to run a job immediately, you can use the [`when:delayed`](yaml/README.md#whendelayed) parameter to
+delay a job's execution for a certain period.
+
+This is especially useful for timed incremental rollout where new code is rolled out gradually.
+
+For example, if you start rolling out new code and:
+
+- Users do not experience trouble, GitLab can automatically complete the deployment from 0% to 100%.
+- Users experience trouble with the new code, you can stop the timed incremental rollout by canceling the pipeline
+ and [rolling](environments.md#rolling-back-changes) back to the last stable version.
+
+![Pipelines example](img/pipeline_incremental_rollout.png)
+
+### Using the API
+
+GitLab provides API endpoints to:
+
+- Perform basic functions. For more information, see [Pipelines API](../api/pipelines.md).
+- Maintain pipeline schedules. For more information, see [Pipeline schedules API](../api/pipeline_schedules.md).
+- Trigger pipeline runs. For more information, see:
+ - [Triggering pipelines through the API](triggers/README.md).
+ - [Pipeline triggers API](../api/pipeline_triggers.md).
## Security on protected branches
@@ -276,14 +336,14 @@ The following actions are allowed on protected branches only if the user is
[allowed to merge or push](../user/project/protected_branches.md#using-the-allowed-to-merge-and-allowed-to-push-settings)
on that specific branch:
-- Run **manual pipelines** (using [Web UI](#manually-executing-pipelines) or Pipelines API).
-- Run **scheduled pipelines**.
-- Run pipelines using **triggers**.
-- Trigger **manual actions** on existing pipelines.
-- **Retry/cancel** existing jobs (using Web UI or Pipelines API).
+- Run manual pipelines (using the [Web UI](#manually-executing-pipelines) or pipelines API).
+- Run scheduled pipelines.
+- Run pipelines using triggers.
+- Trigger manual actions on existing pipelines.
+- Retry or cancel existing jobs (using the Web UI or pipelines API).
**Variables** marked as **protected** are accessible only to jobs that
-run on protected branches, avoiding untrusted users to get unintended access to
+run on protected branches, preventing untrusted users getting unintended access to
sensitive information like deployment credentials and tokens.
**Runners** marked as **protected** can run jobs only on protected
@@ -291,19 +351,3 @@ branches, avoiding untrusted code to be executed on the protected runner and
preserving deployment keys and other credentials from being unintentionally
accessed. In order to ensure that jobs intended to be executed on protected
runners will not use regular runners, they must be tagged accordingly.
-
-[jobs]: #jobs
-[jobs-yaml]: yaml/README.md#jobs
-[manual]: yaml/README.md#whenmanual
-[env-manual]: environments.md#manually-deploying-to-environments
-[stages]: yaml/README.md#stages
-[runners]: runners/README.html
-[pipelines settings]: ../user/project/pipelines/settings.md
-[triggers]: triggers/README.md
-[ce-5742]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5742
-[ce-6242]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/6242
-[ce-7931]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/7931
-[ce-9760]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9760
-[ce-17782]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17782
-[ce-17814]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17814
-[regexp]: https://gitlab.com/gitlab-org/gitlab-ce/blob/2f3dc314f42dbd79813e6251792853bc231e69dd/app/models/commit_status.rb#L99
diff --git a/doc/ci/review_apps/index.md b/doc/ci/review_apps/index.md
index f4d7b9ad194..9dbcf9d1155 100644
--- a/doc/ci/review_apps/index.md
+++ b/doc/ci/review_apps/index.md
@@ -12,8 +12,8 @@ Review Apps are a collaboration tool that takes the hard work out of providing a
Review Apps:
- Provide an automatic live preview of changes made in a feature branch by spinning up a dynamic environment for your merge requests.
-- Allow designers and product manages to see your changes without needing to check out your branch and run your changes in a sandbox environment.
-- Are fully integrated with the [GitLab DevOps LifeCycle](../../README.md#complete-devops-with-gitlab).
+- Allow designers and product managers to see your changes without needing to check out your branch and run your changes in a sandbox environment.
+- Are fully integrated with the [GitLab DevOps LifeCycle](../../README.md#the-entire-devops-lifecycle).
- Allow you to deploy your changes wherever you want.
![Review Apps Workflow](img/continuous-delivery-review-apps.svg)
diff --git a/doc/ci/services/README.md b/doc/ci/services/README.md
index d94b472b768..2eda5d23976 100644
--- a/doc/ci/services/README.md
+++ b/doc/ci/services/README.md
@@ -10,4 +10,4 @@ be linked with your base image. Below is a list of examples you may use.
- [Using MySQL](mysql.md)
- [Using PostgreSQL](postgres.md)
- [Using Redis](redis.md)
-- [Using Other Services](../docker/using_docker_images.md#how-to-use-other-images-as-services)
+- [Using Other Services](../docker/using_docker_images.md#what-is-a-service)
diff --git a/doc/ci/services/postgres.md b/doc/ci/services/postgres.md
index 3899b555f32..2e6d7ae94d2 100644
--- a/doc/ci/services/postgres.md
+++ b/doc/ci/services/postgres.md
@@ -31,7 +31,7 @@ Database: nice_marmot
```
If you are wondering why we used `postgres` for the `Host`, read more at
-[How is service linked to the job](../docker/using_docker_images.md#how-is-service-linked-to-the-job).
+[How services are linked to the job](../docker/using_docker_images.md#how-services-are-linked-to-the-job).
You can also use any other docker image available on [Docker Hub][hub-pg].
For example, to use PostgreSQL 9.3 the service becomes `postgres:9.3`.
diff --git a/doc/ci/triggers/README.md b/doc/ci/triggers/README.md
index 61037360326..398b017277f 100644
--- a/doc/ci/triggers/README.md
+++ b/doc/ci/triggers/README.md
@@ -4,7 +4,7 @@
>
> - [Introduced](https://about.gitlab.com/2015/08/22/gitlab-7-14-released/) in GitLab 7.14.
> - GitLab 8.12 has a completely redesigned job permissions system. Read all
-> about the [new model and its implications](../../user/project/new_ci_build_permissions_model.md#job-triggers).
+> about the [new model and its implications](../../user/project/new_ci_build_permissions_model.md#pipeline-triggers).
Triggers can be used to force a pipeline rerun of a specific `ref` (branch or
tag) with an API call.
@@ -17,6 +17,12 @@ The following methods of authentication are supported.
A unique trigger token can be obtained when [adding a new trigger](#adding-a-new-trigger).
+DANGER: **Danger:**
+Passing plain text tokens in public projects is a security issue. Potential
+attackers can impersonate the user that exposed their trigger token publicly in
+their `.gitlab-ci.yml` file. Use [variables](../variables/README.md#variables)
+to protect trigger tokens.
+
## Adding a new trigger
You can add a new trigger by going to your project's
@@ -53,9 +59,6 @@ The action is irreversible.
>
> - Valid refs are only the branches and tags. If you pass a commit SHA as a ref,
> it will not trigger a job.
-> - If your project is public, passing the token in plain text is probably not the
-> wisest idea, so you might want to use a
-> [variable](../variables/README.md#variables) for that purpose.
To trigger a job you need to send a `POST` request to GitLab's API endpoint:
@@ -219,7 +222,7 @@ Old triggers, created before GitLab 9.0 will be marked as legacy.
Triggers with the legacy label do not have an associated user and only have
access to the current project. They are considered deprecated and will be
removed with one of the future versions of GitLab. You are advised to
-[take ownership](#taking-ownership) of any legacy triggers.
+[take ownership](#taking-ownership-of-a-trigger) of any legacy triggers.
[ee-2017]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/2017
[ee]: https://about.gitlab.com/pricing/
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 6c9831dacfd..4d15d58cb40 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -16,7 +16,7 @@ Variables of different types can take precedence over other variables, depending
The order of precedence for variables is (from highest to lowest):
-1. [Trigger variables](../triggers/README.md#pass-job-variables-to-a-trigger) or [scheduled pipeline variables](../../user/project/pipelines/schedules.md#making-use-of-scheduled-pipeline-variables).
+1. [Trigger variables](../triggers/README.md#making-use-of-trigger-variables) or [scheduled pipeline variables](../../user/project/pipelines/schedules.md#making-use-of-scheduled-pipeline-variables).
1. Project-level [variables](#variables) or [protected variables](#protected-variables).
1. Group-level [variables](#variables) or [protected variables](#protected-variables).
1. YAML-defined [job-level variables](../yaml/README.md#variables).
@@ -46,7 +46,7 @@ version of Runner required.
NOTE: **Note:**
Starting with GitLab 9.0, we have deprecated some variables. Read the
-[9.0 Renaming](#9-0-renaming) section to find out their replacements. **You are
+[9.0 Renaming](#gitlab-90-renaming) section to find out their replacements. **You are
strongly advised to use the new variables as we will remove the old ones in
future GitLab releases.**
@@ -311,7 +311,7 @@ variables that were set, etc.
Before enabling this, you should ensure jobs are visible to
[team members only](../../user/permissions.md#project-features). You should
-also [erase](../pipelines.md#seeing-build-status) all generated job traces
+also [erase](../pipelines.md#seeing-job-status) all generated job traces
before making them visible again.
To enable debug traces, set the `CI_DEBUG_TRACE` variable to `true`:
@@ -573,7 +573,7 @@ If any of the conditions in `variables` evaluates to truth when using `only`,
a new job is going to be created. If any of the expressions evaluates to truth
when `except` is being used, a job is not going to be created.
-This follows usual rules for [`only` / `except` policies][builds-policies].
+This follows usual rules for [`only` / `except` policies](../yaml/README.md#onlyexcept-advanced).
### Supported syntax
@@ -639,7 +639,6 @@ Below you can find supported syntax reference:
[protected tags]: ../../user/project/protected_tags.md
[shellexecutors]: https://docs.gitlab.com/runner/executors/
[triggered]: ../triggers/README.md
-[builds-policies]: ../yaml/README.md#only-and-except-complex
[gitlab-deploy-token]: ../../user/project/deploy_tokens/index.md#gitlab-deploy-token
[registry]: ../../user/project/container_registry.md
[dependent-repositories]: ../../user/project/new_ci_build_permissions_model.md#dependent-repositories
diff --git a/doc/ci/variables/where_variables_can_be_used.md b/doc/ci/variables/where_variables_can_be_used.md
index 1d98e8426fe..ceca4af1bee 100644
--- a/doc/ci/variables/where_variables_can_be_used.md
+++ b/doc/ci/variables/where_variables_can_be_used.md
@@ -106,9 +106,9 @@ The following variables are known as "persisted":
They are:
-- Supported for definitions where the ["Expansion place"](#gitlab-ci-yml-file) is:
+- Supported for definitions where the ["Expansion place"](#gitlab-ciyml-file) is:
- Runner.
- Script execution shell.
- Not supported:
- - For definitions where the ["Expansion place"](#gitlab-ci-yml-file) is GitLab.
+ - For definitions where the ["Expansion place"](#gitlab-ciyml-file) is GitLab.
- In the `only` and `except` [variables expressions](README.md#variables-expressions).
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 985895acce3..9eb694a2c64 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -270,7 +270,7 @@ There are also two edge cases worth mentioning:
`stage` is defined per-job and relies on [`stages`](#stages) which is defined
globally. It allows to group jobs into different stages, and jobs of the same
-`stage` are executed in `parallel`. For example:
+`stage` are executed in parallel (subject to [certain conditions](#using-your-own-runners)). For example:
```yaml
stages:
@@ -295,6 +295,17 @@ job 4:
script: make deploy
```
+#### Using your own Runners
+
+When using your own Runners, GitLab Runner runs only one job at a time by default (see the
+`concurrent` flag in [Runner global settings](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-global-section)
+for more information).
+
+Jobs will run on your own Runners in parallel only if:
+
+- Run on different Runners.
+- The Runner's `concurrent` setting has been changed.
+
### `only`/`except` (basic)
`only` and `except` are two parameters that set a job policy to limit when
@@ -361,10 +372,11 @@ job:
- branches@gitlab-org/gitlab-ce
except:
- master@gitlab-org/gitlab-ce
+ - release/.*@gitlab-org/gitlab-ce
```
The above example will run `job` for all branches on `gitlab-org/gitlab-ce`,
-except master.
+except `master` and those with names prefixed with `release/`.
If a job does not have an `only` rule, `only: ['branches', 'tags']` is set by
default. If it doesn't have an `except` rule, it is empty.
@@ -411,7 +423,7 @@ If you use multiple keys under `only` or `except`, they act as an AND. The logic
#### `only:refs`/`except:refs`
The `refs` strategy can take the same values as the
-[simplified only/except configuration](#only-and-except-simplified).
+[simplified only/except configuration](#onlyexcept-basic).
In the example below, the `deploy` job is going to be created only when the
pipeline has been [scheduled][schedules] or runs for the `master` branch:
@@ -1228,8 +1240,7 @@ job:
to the paths defined in `artifacts:paths`).
NOTE: **Note:**
-To exclude the folders/files which should not be a part of `untracked` just
-add them to `.gitignore`.
+`artifacts:untracked` ignores configuration in the repository's `.gitignore` file.
Send all Git untracked files:
@@ -1696,6 +1707,11 @@ of using YAML anchors, you can use the [`extends` keyword](#extends).
See [usage examples](#include-examples).
+NOTE: **Note:**
+`.gitlab-ci.yml` configuration included by all methods is evaluated at pipeline creation.
+The configuration is a snapshot in time and persisted in the database. Any changes to
+referenced `.gitlab-ci.yml` configuration will not be reflected in GitLab until the next pipeline is created.
+
#### `include:local`
`include:local` includes a file from the same repository as `.gitlab-ci.yml`.
@@ -1750,7 +1766,7 @@ include:
```
All [nested includes](#nested-includes) will be executed in the scope of the target project,
-so it is possible to used local (relative to target project), project, remote
+so it is possible to use local (relative to target project), project, remote
or template includes.
#### `include:template`
@@ -1788,7 +1804,7 @@ or public project, or template is allowed.
#### Nested includes
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53903) in GitLab 11.7.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/56836) in GitLab 11.9.
Nested includes allow you to compose a set of includes.
A total of 50 includes is allowed.
@@ -2546,4 +2562,4 @@ git push -o ci.skip
[environment]: ../environments.md "CI/CD environments"
[schedules]: ../../user/project/pipelines/schedules.md "Pipelines schedules"
[variables]: ../variables/README.md "CI/CD variables"
-[push-option]: https://git-scm.com/docs/git-push#git-push--oltoptiongt
+[push-option]: https://git-scm.com/docs/git-push#Documentation/git-push.txt--oltoptiongt
diff --git a/doc/container_registry/README.md b/doc/container_registry/README.md
index 5d2f5edcb18..b31870df36d 100644
--- a/doc/container_registry/README.md
+++ b/doc/container_registry/README.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/container_registry.md'
+---
+
This document was moved to [another location](../user/project/container_registry.md).
diff --git a/doc/container_registry/troubleshooting.md b/doc/container_registry/troubleshooting.md
index 2f8cd37b488..c99d7011ac2 100644
--- a/doc/container_registry/troubleshooting.md
+++ b/doc/container_registry/troubleshooting.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/container_registry.md'
+---
+
This document was moved to [user/project/container_registry](../user/project/container_registry.md).
diff --git a/doc/development/README.md b/doc/development/README.md
index 13646cbfe48..5a33c46c620 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -54,6 +54,7 @@ description: 'Learn how to contribute to GitLab.'
- [Prometheus metrics](prometheus_metrics.md)
- [Guidelines for reusing abstractions](reusing_abstractions.md)
- [DeclarativePolicy framework](policies.md)
+- [How Git object deduplication works in GitLab](git_object_deduplication.md)
## Performance guides
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index ae880e3deb6..115c8cfb9ff 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -158,6 +158,18 @@ TODO
TODO
+### Registry
+
+The registry is what users use to store their own Docker images. The bundled
+registry uses nginx as a load balancer and GitLab as an authentication manager.
+Whenever a client requests to pull or push an image from the registry, it will
+return a `401` response along with a header detailing where to get an
+authentication token, in this case the GitLab instance. The client will then
+request a pull or push auth token from GitLab and retry the original request
+to the registry. Learn more about [token authentication](https://docs.docker.com/registry/spec/auth/token/).
+
+An external registry can also be configured to use GitLab as an auth endpoint.
+
## GitLab by Request Type
GitLab provides two "interfaces" for end users to access the service:
diff --git a/doc/development/changelog.md b/doc/development/changelog.md
index 6efed36edf0..273a7fceaf5 100644
--- a/doc/development/changelog.md
+++ b/doc/development/changelog.md
@@ -43,7 +43,7 @@ the `author` field. GitLab team members **should not**.
database records created during Cycle Analytics model spec."
- _Any_ contribution from a community member, no matter how small, **may** have
a changelog entry regardless of these guidelines if the contributor wants one.
- Example: "Fixed a typo on the search results page. (Jane Smith)"
+ Example: "Fixed a typo on the search results page."
- Performance improvements **should** have a changelog entry.
- Any change that introduces a database migration **must** have a
changelog entry.
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index f115045dbb7..b1a32e0ed26 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -20,7 +20,7 @@ importance of involving reviewer(s) in the section on the responsibility of the
If you need some guidance (e.g. it's your first merge request), feel free to ask
one of the [Merge request coaches][team].
-If you need assistance with security scans or comments, feel free to include the
+If you need assistance with security scans or comments, feel free to include the
Security Team (`@gitlab-com/gl-security`) in the review.
The `danger-review` CI job will randomly pick a reviewer and a maintainer for
@@ -58,12 +58,7 @@ from teams other than your own.
#### Security requirements
- 1. If your merge request is processing, storing, or transferring any kind of [RED or ORANGE data](https://docs.google.com/document/d/15eNKGA3zyZazsJMldqTBFbYMnVUSQSpU14lo22JMZQY/edit) (this is a confidential document), it must be
- **approved by a [Security Engineer][team]**.
- 1. If your merge request involves implementing, utilizing, or is otherwise related to any type of authentication, authorization, or session handling mechanism, it must be
- **approved by a [Security Engineer][team]**.
- 1. If your merge request has a goal which requires a cryptographic function such as: confidentiality, integrity, authentication, or non-repudiation, it must be
- **approved by a [Security Engineer][team]**.
+View the updated documentation regarding [internal application security reviews](https://about.gitlab.com/handbook/engineering/security/index.html#internal-application-security-reviews) for **when** and **how** to request a security review.
### The responsibility of the merge request author
@@ -138,10 +133,10 @@ as a domain expert and/or reviewer, it is recommended that they are not also pic
as the maintainer to ultimately approve and merge it.
Try to review in a timely manner; doing so allows everyone involved in the merge
-request to iterate faster as the context is fresh in memory. Further, this
+request to iterate faster as the context is fresh in memory. Further, this
improves contributors' experiences significantly. Reviewers should aim to review
-within two working days from the date they were assigned the merge request. If
-you don't think you'll be able to review a merge request within that time, let
+within two working days from the date they were assigned the merge request. If
+you don't think you'll be able to review a merge request within that time, let
the author know as soon as possible. When the author of the merge request has not
heard anything after two days, a new reviewer should be assigned.
@@ -151,7 +146,7 @@ required approvers.
Maintainers must check before merging if the merge request is introducing new
vulnerabilities, by inspecting the list in the Merge Request [Security
Widget](https://docs.gitlab.com/ee/user/project/merge_requests/#security-reports-ultimate).
-When in doubt, a [Security Engineer][team] can be involved. The list of detected
+When in doubt, a [Security Engineer][team] can be involved. The list of detected
vulnerabilities must be either empty or containing:
- dismissed vulnerabilities in case of false positives
diff --git a/doc/development/contributing/index.md b/doc/development/contributing/index.md
index f7a0fdbeb40..bfe1ef75914 100644
--- a/doc/development/contributing/index.md
+++ b/doc/development/contributing/index.md
@@ -119,6 +119,7 @@ This [documentation](merge_request_workflow.md) outlines the current merge reque
- [Merge request guidelines](merge_request_workflow.md#merge-request-guidelines)
- [Contribution acceptance criteria](merge_request_workflow.md#contribution-acceptance-criteria)
- [Definition of done](merge_request_workflow.md#definition-of-done)
+- [Dependencies](merge_request_workflow.md#dependencies)
## Style guides
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
index 8b14c3b20ea..5e310092a6e 100644
--- a/doc/development/contributing/merge_request_workflow.md
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -1,91 +1,95 @@
# Merge requests
-We welcome merge requests with fixes and improvements to GitLab code, tests,
-and/or documentation. The issues that are specifically suitable for
-community contributions are listed with
-[the `Accepting merge requests` label](issue_workflow.md#label-for-community-contributors),
-but you are free to contribute to any other issue you want.
-
-Please note that if an issue is marked for the current milestone either before
-or while you are working on it, a team member may take over the merge request
+We welcome merge requests from everyone, with fixes and improvements
+to GitLab code, tests, and documentation. The issues that are specifically suitable
+for community contributions are listed with the [`Accepting merge requests`](issue_workflow.md#label-for-community-contributors)
+label, but you are free to contribute to any issue you want.
+
+Please note that if an issue is marked for the current milestone at any time, even
+when you are working on it, a GitLab Inc. team member may take over the merge request
in order to ensure the work is finished before the release date.
-If you want to add a new feature that is not labeled it is best to first create
-a feedback issue (if there isn't one already) and leave a comment asking for it
+If you want to add a new feature that is not labeled, it is best to first create
+an issue (if there isn't one already) and leave a comment asking for it
to be marked as `Accepting Merge Requests`. Please include screenshots or
-wireframes if the feature will also change the UI.
+wireframes of the proposed feature if it will also change the UI.
-Merge requests should be opened at [GitLab.com][gitlab-mr-tracker].
+Merge requests should be submitted to the appropriate project at GitLab.com, for example
+[GitLab CE](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests),
+[GitLab EE](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests),
+[GitLab Runner](https://gitlab.com/gitlab-org/gitlab-runner/merge_requests),
+[GitLab Omnibus](https://gitlab.com/gitlab-org/omnibus-gitlab/merge_requests), etc.
If you are new to GitLab development (or web development in general), see the
-[I want to contribute!](index.md#i-want-to-contribute) section to get you started with
+[I want to contribute!](index.md#i-want-to-contribute) section to get started with
some potentially easy issues.
-To start with GitLab development download the [GitLab Development Kit][gdk] and
-see the [Development section](../../README.md) for some guidelines.
-
-[gitlab-mr-tracker]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests
-[gdk]: https://gitlab.com/gitlab-org/gitlab-development-kit
+To start developing GitLab, download the [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit)
+and see the [Development section](../../README.md) for the required guidelines.
## Merge request guidelines
-If you can, please submit a merge request with the fix or improvements
-including tests. If you don't know how to fix the issue but can write a test
-that exposes the issue we will accept that as well. In general bug fixes that
-include a regression test are merged quickly while new features without proper
-tests are least likely to receive timely feedback. The workflow to make a merge
+If you find an issue, please submit a merge request with a fix or improvement, if
+you can, and include tests. If you don't know how to fix the issue but can write a test
+that exposes the issue, we will accept that as well. In general, bug fixes that
+include a regression test are merged quickly, while new features without proper
+tests might be slower to receive feedback. The workflow to make a merge
request is as follows:
-1. Fork the project into your personal space on GitLab.com
-1. Create a feature branch, branch away from `master`
-1. Write [tests](https://docs.gitlab.com/ee/development/rake_tasks.html#run-tests) and code
-1. [Generate a changelog entry with `bin/changelog`][changelog]
+1. [Fork](../../workflow/forking_workflow.md#creating-a-fork) the project into
+ your personal namespace (or group) on GitLab.com.
+1. Create a feature branch in your fork (don't work off `master`).
+1. Write [tests](../rake_tasks.md#run-tests) and code.
+1. [Generate a changelog entry with `bin/changelog`](../changelog.md)
1. If you are writing documentation, make sure to follow the
- [documentation guidelines][doc-guidelines]
-1. If you have multiple commits please combine them into a few logically
- organized commits by [squashing them][git-squash]
-1. Push the commit(s) to your fork
-1. Submit a merge request (MR) to the `master` branch
- 1. Your merge request needs at least 1 approval but feel free to require more.
- For instance if you're touching backend and frontend code, it's a good idea
+ [documentation guidelines](../documentation/index.md).
+1. Follow the [commit messages guidelines](#commit-messages-guidelines).
+1. If you have multiple commits, combine them into a few logically organized
+ commits by [squashing them](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History#_squashing),
+ but do not change the commit history if you're working on shared branches though.
+1. Push the commit(s) to your working branch in your fork.
+1. Submit a merge request (MR) to the `master` branch in the main GitLab project.
+ 1. Your merge request needs at least 1 approval, but feel free to require more.
+ For instance if you're touching both backend and frontend code, it's a good idea
to require 2 approvals: 1 from a backend maintainer and 1 from a frontend
- maintainer
- 1. You don't have to select any approvers, but you can if you really want
- specific people to approve your merge request
-1. The MR title should describe the change you want to make
-1. The MR description should give a motive for your change and the method you
- used to achieve it.
- 1. If you are contributing code, fill in the template already provided in the
- "Description" field.
+ maintainer.
+ 1. If you're submitting changes to documentation, you'll need approval from a technical
+ writer, based on the appropriate [product category](https://about.gitlab.com/handbook/product/categories/).
+ Only assign the MR to them when it's ready for docs review.
+ 1. You don't have to select any specific approvers, but you can if you really want
+ specific people to approve your merge request.
+1. The MR title should describe the change you want to make.
+1. The MR description should give a reason for your change.
+ 1. If you are contributing code, fill in the description according to the default
+ template already provided in the "Description" field.
1. If you are contributing documentation, choose `Documentation` from the
- "Choose a template" menu and fill in the template.
+ "Choose a template" menu and fill in the description according to the template.
1. Mention the issue(s) your merge request solves, using the `Solves #XXX` or
- `Closes #XXX` syntax to auto-close the issue(s) once the merge request will
- be merged.
-1. If you're allowed to, set a relevant milestone and labels
-1. If the MR changes the UI it should include *Before* and *After* screenshots
-1. If the MR changes CSS classes please include the list of affected pages,
- `grep css-class ./app -R`
-1. Be prepared to answer questions and incorporate feedback even if requests
- for this arrive weeks or months after your MR submission
- 1. If a discussion has been addressed, select the "Resolve discussion" button
- beneath it to mark it resolved.
-1. If your MR touches code that executes shell commands, reads or opens files or
+ `Closes #XXX` syntax to [auto-close](../../user/project/issues/automatic_issue_closing.md)
+ the issue(s) once the merge request is merged.
+1. If you're allowed to (Core team members, for example), set a relevant milestone
+ and [labels](issue_workflow.md).
+1. If the MR changes the UI, it should include *Before* and *After* screenshots.
+1. If the MR changes CSS classes, please include the list of affected pages, which
+ can be found by running `grep css-class ./app -R`.
+1. Be prepared to answer questions and incorporate feedback into your MR with new
+ commits. Once you have fully addressed a suggestion from a reviewer, click the
+ "Resolve discussion" button beneath it to mark it resolved.
+ 1. The merge request author resolves only the discussions they have fully addressed.
+ If there's an open reply or discussion, a suggestion, a question, or anything else,
+ the discussion should be left to be resolved by the reviewer.
+1. If your MR touches code that executes shell commands, reads or opens files, or
handles paths to files on disk, make sure it adheres to the
[shell command guidelines](../shell_commands.md)
1. If your code creates new files on disk please read the
[shared files guidelines](../shared_files.md).
-1. When writing commit messages please follow
- [these](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html)
- [guidelines](http://chris.beams.io/posts/git-commit/).
1. If your merge request adds one or more migrations, make sure to execute all
migrations on a fresh database before the MR is reviewed. If the review leads
- to large changes in the MR, do this again once the review is complete.
-1. For more complex migrations, write tests.
-1. Merge requests **must** adhere to the [merge request performance
- guidelines](../merge_request_performance_guidelines.md).
-1. For tests that use Capybara or PhantomJS, see this [article on how
- to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara).
+ to large changes in the MR, execute the migrations again once the review is complete.
+1. Write tests for more complex migrations.
+1. Merge requests **must** adhere to the [merge request performance guidelines](../merge_request_performance_guidelines.md).
+1. For tests that use Capybara, read
+ [how to write reliable, asynchronous integration tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara).
1. If your merge request introduces changes that require additional steps when
installing GitLab from source, add them to `doc/install/installation.md` in
the same merge request.
@@ -95,109 +99,117 @@ request is as follows:
instructions are specific to a version, add them to the "Version specific
upgrading instructions" section.
-Please keep the change in a single MR **as small as possible**. If you want to
-contribute a large feature think very hard what the minimum viable change is.
-Can you split the functionality? Can you only submit the backend/API code? Can
-you start with a very simple UI? Can you do part of the refactor? The increased
-reviewability of small MRs that leads to higher code quality is more important
-to us than having a minimal commit log. The smaller an MR is the more likely it
-is it will be merged (quickly). After that you can send more MRs to enhance it.
-The ['How to get faster PR reviews' document of Kubernetes](https://github.com/kubernetes/community/blob/master/contributors/devel/faster_reviews.md) also has some great points regarding this.
-
-For examples of feedback on merge requests please look at already
-[closed merge requests][closed-merge-requests]. If you would like quick feedback
-on your merge request feel free to mention someone from the [core team] or one
-of the [Merge request coaches][team].
-Please ensure that your merge request meets the contribution acceptance criteria.
-
-When having your code reviewed and when reviewing merge requests please take the
-[code review guidelines](../code_review.md) into account.
-
-[git-squash]: https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits
-[closed-merge-requests]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed
-[team]: https://about.gitlab.com/team/
+If you would like quick feedback on your merge request feel free to mention someone
+from the [core team](https://about.gitlab.com/community/core-team/) or one of the
+[merge request coaches](https://about.gitlab.com/team/). When having your code reviewed
+and when reviewing merge requests, please keep the [code review guidelines](../code_review.md)
+in mind.
+
+### Keep it simple
+
+*Live by smaller iterations.* Please keep the amount of changes in a single MR **as small as possible**.
+If you want to contribute a large feature, think very carefully about what the
+[minimum viable change](https://about.gitlab.com/handbook/product/#the-minimally-viable-change)
+is. Can you split the functionality into two smaller MRs? Can you submit only the
+backend/API code? Can you start with a very simple UI? Can you do just a part of the
+refactor?
+
+Small MRs which are more easily reviewed, lead to higher code quality which is
+more important to GitLab than having a minimal commit log. The smaller an MR is,
+the more likely it will be merged quickly. After that you can send more MRs to
+enhance and expand the feature. The [How to get faster PR reviews](https://github.com/kubernetes/kubernetes/blob/release-1.5/docs/devel/faster_reviews.md)
+document from the Kubernetes team also has some great points regarding this.
+
+### Commit messages guidelines
+
+When writing commit messages, please follow the guidelines below:
+
+- The commit subject must contain at least 3 words.
+- The commit subject should ideally contain up to 50 characters,
+and must not be longer than 72 characters.
+- The commit subject must start with a capital letter.
+- The commit subject must not end with a period.
+- The commit subject and body must be separated by a blank line.
+- The commit body must not contain more than 72 characters per line.
+- Commits that change 30 or more lines across at least 3 files must
+describe these changes in the commit body.
+- The commit subject or body must not contain Emojis.
+- Use issues and merge requests' full URLs instead of short references,
+as they are displayed as plain text outside of GitLab.
+- The merge request must not contain more than 10 commit messages.
+
+If the guidelines are not met, the MR will not pass the
+[Danger checks](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/danger/commit_messages/Dangerfile).
+For more information see [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/).
## Contribution acceptance criteria
-1. The change is as small as possible
+To make sure that your merge request can be approved, please ensure that it meets
+the contribution acceptance criteria below:
+
+1. The change is as small as possible.
1. Include proper tests and make all tests pass (unless it contains a test
exposing a bug in existing code). Every new class should have corresponding
unit tests, even if the class is exercised at a higher level, such as a feature test.
-1. If you suspect a failing CI build is unrelated to your contribution, you may
- try and restart the failing CI job or ask a developer to fix the
- aforementioned failing test
-1. Your MR initially contains a single commit (please use `git rebase -i` to
- squash commits)
-1. Your changes can merge without problems (if not please rebase if you're the
- only one working on your feature branch, otherwise, merge `master`)
-1. Does not break any existing functionality
-1. Fixes one specific issue or implements one specific feature (do not combine
- things, send separate merge requests if needed)
-1. Migrations should do only one thing (e.g., either create a table, move data
- to a new table or remove an old table) to aid retrying on failure
-1. Keeps the GitLab code base clean and well structured
-1. Contains functionality we think other users will benefit from too
-1. Doesn't add configuration options or settings options since they complicate
- making and testing future changes
-1. Changes do not adversely degrade performance.
- - Avoid repeated polling of endpoints that require a significant amount of overhead
- - Check for N+1 queries via the SQL log or [`QueryRecorder`](https://docs.gitlab.com/ce/development/merge_request_performance_guidelines.html)
- - Avoid repeated access of filesystem
-1. If you need polling to support real-time features, please use
- [polling with ETag caching][polling-etag].
-1. Changes after submitting the merge request should be in separate commits
- (no squashing).
-1. It conforms to the [style guides](style_guides.md) and the following:
- - If your change touches a line that does not follow the style, modify the
- entire line to follow it. This prevents linting tools from generating warnings.
- - Don't touch neighbouring lines. As an exception, automatic mass
- refactoring modifications may leave style non-compliant.
-1. If the merge request adds any new libraries (gems, JavaScript libraries,
- etc.), they should conform to our [Licensing guidelines][license-finder-doc].
- See the instructions in that document for help if your MR fails the
- "license-finder" test with a "Dependencies that need approval" error.
-1. The merge request meets the [definition of done](#definition-of-done).
-
-[license-finder-doc]: ../licensing.md
-[polling-etag]: ../polling.md
+ - If a failing CI build seems to be unrelated to your contribution, you can try
+ restarting the failing CI job, rebasing from master to bring in updates that
+ may resolve the failure, or if it has not been fixed yet, ask a developer to
+ help you fix the test.
+1. The MR initially contains a a few logically organized commits.
+1. The changes can merge without problems. If not, you should rebase if you're the
+ only one working on your feature branch, otherwise merge `master`.
+1. Only one specific issue is fixed or one specific feature is implemented. Do not
+ combine things; send separate merge requests for each issue or feature.
+1. Migrations should do only one thing (e.g., create a table, move data to a new
+ table, or remove an old table) to aid retrying on failure.
+1. Contains functionality that other users will benefit from.
+1. Doesn't add configuration options or settings options since they complicate making
+ and testing future changes.
+1. Changes do not degrade performance:
+ - Avoid repeated polling of endpoints that require a significant amount of overhead.
+ - Check for N+1 queries via the SQL log or [`QueryRecorder`](../merge_request_performance_guidelines.md).
+ - Avoid repeated access of the filesystem.
+ - Use [polling with ETag caching](../polling.md) if needed to support real-time features.
+1. If the merge request adds any new libraries (gems, JavaScript libraries, etc.),
+ they should conform to our [Licensing guidelines](../licensing.md). See those
+ instructions for help if the "license-finder" test fails with a
+ `Dependencies that need approval` error. Also, make the reviewer aware of the new
+ library and explain why you need it.
+1. The merge request meets GitLab's [definition of done](#definition-of-done), below.
## Definition of done
If you contribute to GitLab please know that changes involve more than just
-code. We have the following [definition of done][definition-of-done]. Please ensure you support
-the feature you contribute through all of these steps.
-
-1. Description explaining the relevancy (see following item)
-1. Working and clean code that is commented where needed
-1. [Unit, integration, and system tests][testing] that pass on the CI server
-1. Performance/scalability implications have been considered, addressed, and tested
-1. [Documented][doc-guidelines] in the `/doc` directory
-1. [Changelog entry added][changelog], if necessary
-1. Reviewed by UX/FE/BE and any concerns are addressed
-1. Merged by a project maintainer
-1. Added to the release blog article, if relevant
-1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/), if relevant
-1. Community questions answered
-1. Answers to questions radiated (in docs/wiki/support etc.)
-1. [Black-box tests/end-to-end tests](../testing_guide/testing_levels.md#black-box-tests-at-the-system-level-aka-end-to-end-tests) added if required. Please contact [the quality team](https://about.gitlab.com/handbook/engineering/quality/#teams) with any questions
+code. We use the following [definition of done](https://www.agilealliance.org/glossary/definition-of-done).
+Your contribution is not *done* until you have made sure it meets all of these
+requirements.
+
+1. Clear description explaining the relevancy of the contribution.
+1. Working and clean code that is commented where needed.
+1. [Unit, integration, and system tests](../testing_guide/index.md) that all pass
+ on the CI server.
+1. Performance/scalability implications have been considered, addressed, and tested.
+1. [Documented](../documentation/index.md) in the `/doc` directory.
+1. [Changelog entry added](../changelog.md), if necessary.
+1. Reviewed by relevant (UX/FE/BE/tech writing) reviewers and all concerns are addressed.
+1. Merged by a project maintainer.
+1. Added to the [release post](https://about.gitlab.com/handbook/marketing/blog/release-posts/),
+ if relevant.
+1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/data/features.yml), if relevant.
+1. [Black-box tests/end-to-end tests](../testing_guide/testing_levels.md#black-box-tests-at-the-system-level-aka-end-to-end-tests)
+ added if required. Please contact [the quality team](https://about.gitlab.com/handbook/engineering/quality/#teams)
+ with any questions.
+
+## Dependencies
If you add a dependency in GitLab (such as an operating system package) please
-consider updating the following and note the applicability of each in your
-merge request:
-
-1. Note the addition in the release blog post (create one if it doesn't exist yet) <https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/>
-1. Upgrade guide, for example <https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/7.5-to-7.6.md>
-1. Installation guide <https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies>
-1. GitLab Development Kit <https://gitlab.com/gitlab-org/gitlab-development-kit>
-1. Test suite <https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/prepare_build.sh>
-1. Omnibus package creator <https://gitlab.com/gitlab-org/omnibus-gitlab>
-
-[definition-of-done]: http://guide.agilealliance.org/guide/definition-of-done.html
-[testing]: ../testing_guide/index.md
-
----
-
-[Return to Contributing documentation](index.md)
-
-[changelog]: ../changelog.md "Generate a changelog entry"
-[doc-guidelines]: ../documentation/index.md "Documentation guidelines"
+consider updating the following, and note the applicability of each in your merge
+request:
+
+1. Note the addition in the [release blog post](https://about.gitlab.com/handbook/marketing/blog/release-posts/)
+ (create one if it doesn't exist yet).
+1. [The upgrade guide](../../update/upgrading_from_source.md).
+1. The [GitLab Installation Guide](../../install/installation.md#1-packages-and-dependencies).
+1. The [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
+1. The [CI environment preparation](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/prepare_build.sh).
+1. The [Omnibus package creator](https://gitlab.com/gitlab-org/omnibus-gitlab).
diff --git a/doc/development/documentation/feature-change-workflow.md b/doc/development/documentation/feature-change-workflow.md
index 3f31fe5ca19..1f68b6a6a70 100644
--- a/doc/development/documentation/feature-change-workflow.md
+++ b/doc/development/documentation/feature-change-workflow.md
@@ -78,7 +78,7 @@ For issues requiring any new or updated documentation, the Product Manager (PM)
must:
- Add the `Documentation` label.
-- Confirm or add the [documentation requirements](#documentation-requirements).
+- Confirm or add the [documentation requirements](#documentation-requirements-in-feature-issues).
- Ensure the issue contains any new or updated feature name, overview/description,
and use cases, as required per the [documentation structure and template](structure.md), when applicable.
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index 0c51d3832aa..c0386290785 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -80,11 +80,10 @@ yield a useful result, and ensuring content is helpful and easy to consume.
## Text
-- Split up long lines (wrap text), this makes it much easier to review and edit. Only
- double line breaks are shown as a full line break by creating new paragraphs.
- 80-100 characters is the recommended line length.
+- Splitting long lines (preferably up to 100 characters) can make it easier to provide feedback on small chunks of text.
+- Insert an empty line for new paragraphs.
- Use sentence case for titles, headings, labels, menu items, and buttons.
-- Jump a line between different markups (e.g., after every paragraph, header, list, etc). Example:
+- Insert an empty line between different markups (e.g., after every paragraph, header, list, etc). Example:
```md
## Header
diff --git a/doc/development/new_fe_guide/event_tracking.md b/doc/development/fe_guide/event_tracking.md
index 6ab3fa4acf3..6ab3fa4acf3 100644
--- a/doc/development/new_fe_guide/event_tracking.md
+++ b/doc/development/fe_guide/event_tracking.md
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index 86b8972a69e..d5cc28dc00c 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -1,8 +1,5 @@
# Frontend Development Guidelines
-> **Notice:**
-> We are currently in the process of re-writing our development guide to make it easier to find information. The new guide is still WIP but viewable in [development/new_fe_guide](../new_fe_guide/index.md)
-
This document describes various guidelines to ensure consistency and quality
across GitLab's frontend team.
@@ -12,15 +9,6 @@ GitLab is built on top of [Ruby on Rails][rails] using [Haml][haml] and also a J
Be wary of [the limitations that come with using Hamlit][hamlit-limits]. We also use [SCSS][scss] and plain JavaScript with
modern ECMAScript standards supported through [Babel][babel] and ES module support through [webpack][webpack].
-### Javascript development
-
-[Vue.js][vue] is used for particularly advanced, dynamic elements and based on previous iterations [jQuery][jquery] is used in lot of places through the application's JavaScript.
-
-We also use [Axios][axios] to handle all of our network requests.
-
-We also utilize [webpack][webpack] to handle the bundling, minification, and
-compression of our assets.
-
Working with our frontend assets requires Node (v8.10.0 or greater) and Yarn
(v1.10.0 or greater). You can find information on how to install these on our
[installation guide][install].
@@ -31,6 +19,14 @@ For our currently-supported browsers, see our [requirements][requirements].
---
+## Initiatives
+
+Current high-level frontend goals are listed on [Frontend Epics](https://gitlab.com/groups/gitlab-org/-/epics?label_name%5B%5D=frontend).
+
+## [Principles](principles.md)
+
+High-level guidelines for contributing to GitLab.
+
## [Development Process](development_process.md)
How we plan and execute the work on the frontend.
@@ -73,6 +69,10 @@ How we use SVG for our Icons and Illustrations.
How we use UI components.
+## [Event Tracking](event_tracking.md)
+
+How we use Snowplow to track custom events.
+
---
## Style Guides
@@ -124,14 +124,3 @@ The [externalization part of the guide](../i18n/externalization.md) explains the
[scss-lint]: https://github.com/brigade/scss-lint
[install]: ../../install/installation.md#4-node
[requirements]: ../../install/requirements.md#supported-web-browsers
-
----
-
-## [DropLab](droplab/droplab.md)
-
-Our internal `DropLab` dropdown library.
-
-- [DropLab](droplab/droplab.md)
-- [Ajax plugin](droplab/plugins/ajax.md)
-- [Filter plugin](droplab/plugins/filter.md)
-- [InputSetter plugin](droplab/plugins/input_setter.md)
diff --git a/doc/development/fe_guide/principles.md b/doc/development/fe_guide/principles.md
new file mode 100644
index 00000000000..6fb3456222f
--- /dev/null
+++ b/doc/development/fe_guide/principles.md
@@ -0,0 +1,15 @@
+# Principles
+
+These principles will ensure that your frontend contribution starts off in the right direction.
+
+## Discuss architecture before implementation
+
+Discuss your architecture design in an issue before writing code. This helps decrease the review time and also provides good practice for writing and thinking about system design.
+
+## Be consistent
+
+There are multiple ways of writing code to accomplish the same results. We should be as consistent as possible in how we write code across our codebases. This will make it more easier us to maintain our code across GitLab.
+
+## Improve code [iteratively](https://about.gitlab.com/handbook/values/#iteration)
+
+Whenever you see with existing code that does not follow our current style guide, update it proactively. You don't need to fix everything, but each merge request should iteratively improve our codebase, and reduce technical debt where possible.
diff --git a/doc/development/fe_guide/vue.md b/doc/development/fe_guide/vue.md
index 435fdf39fb4..a34d1fcec89 100644
--- a/doc/development/fe_guide/vue.md
+++ b/doc/development/fe_guide/vue.md
@@ -120,6 +120,13 @@ You can read more about components in Vue.js site, [Component System][component-
#### Vuex
Check this [page](vuex.md) for more details.
+### Mixing Vue and jQuery
+
+- Mixing Vue and jQuery is not recommended.
+- If you need to use a specific jQuery plugin in Vue, [create a wrapper around it][https://vuejs.org/v2/examples/select2.html].
+- It is acceptable for Vue to listen to existing jQuery events using jQuery event listeners.
+- It is not recommended to add new jQuery events for Vue to interact with jQuery.
+
## Style guide
Please refer to the Vue section of our [style guide](style_guide_js.md#vuejs)
diff --git a/doc/development/fe_guide/vuex.md b/doc/development/fe_guide/vuex.md
index 4b60ec80cb8..7b54fa6289c 100644
--- a/doc/development/fe_guide/vuex.md
+++ b/doc/development/fe_guide/vuex.md
@@ -52,7 +52,7 @@ The first thing you should do before writing any code is to design the state.
Often we need to provide data from haml to our Vue application. Let's store it in the state for better access.
```javascript
- export default {
+ export default () => ({
endpoint: null,
isLoading: false,
@@ -62,7 +62,7 @@ Often we need to provide data from haml to our Vue application. Let's store it i
errorAddingUser: false,
users: [],
- };
+ });
```
#### Access `state` properties
diff --git a/doc/development/git_object_deduplication.md b/doc/development/git_object_deduplication.md
new file mode 100644
index 00000000000..81c5f69c7b8
--- /dev/null
+++ b/doc/development/git_object_deduplication.md
@@ -0,0 +1,261 @@
+# How Git object deduplication works in GitLab
+
+When a GitLab user [forks a project](../workflow/forking_workflow.md),
+GitLab creates a new Project with an associated Git repository that is a
+copy of the original project at the time of the fork. If a large project
+gets forked often, this can lead to a quick increase in Git repository
+storage disk use. To counteract this problem, we are adding Git object
+deduplication for forks to GitLab. In this document, we will describe how
+GitLab implements Git object deduplication.
+
+## Enabling Git object deduplication via feature flags
+
+As of GitLab 11.9, Git object deduplication in GitLab is in beta. In this
+document, you can read about the caveats of enabling the feature. Also,
+note that Git object deduplication is limited to forks of public
+projects on hashed repository storage.
+
+You can enable deduplication globally by setting the `object_pools`
+feature flag to `true`:
+
+``` {.ruby}
+Feature.enable(:object_pools)
+```
+
+Or just for forks of a specific project:
+
+``` {.ruby}
+fork_parent = Project.find(MY_PROJECT_ID)
+Feature.enable(:object_pools, fork_parent)
+```
+
+To check if a project uses Git object deduplication, look in a Rails
+console if `project.pool_repository` is present.
+
+## Pool repositories
+
+### Understanding Git alternates
+
+At the Git level, we achieve deduplication by using [Git
+alternates](https://git-scm.com/docs/gitrepository-layout#gitrepository-layout-objects).
+Git alternates is a mechanism that lets a repository borrow objects from
+another repository on the same machine.
+
+If we want repository A to borrow from repository B, we first write a
+path that resolves to `B.git/objects` in the special file
+`A.git/objects/info/alternates`. This establishes the alternates link.
+Next, we must perform a Git repack in A. After the repack, any objects
+that are duplicated between A and B will get deleted from A. Repository
+A is now no longer self-contained, but it still has its own refs and
+configuration. Objects in A that are not in B will remain in A. For this
+to work, it is of course critical that **no objects ever get deleted from
+B** because A might need them.
+
+### Git alternates in GitLab: pool repositories
+
+GitLab organizes this object borrowing by creating special **pool
+repositories** which are hidden from the user. We then use Git
+alternates to let a collection of project repositories borrow from a
+single pool repository. We call such a collection of project
+repositories a pool. Pools form star-shaped networks of repositories
+that borrow from a single pool, which will resemble (but not be
+identical to) the fork networks that get formed when users fork
+projects.
+
+At the Git level, pool repositories are created and managed using Gitaly
+RPC calls. Just like with normal repositories, the authority on which
+pool repositories exist, and which repositories borrow from them, lies
+at the Rails application level in SQL.
+
+In conclusion, we need three things for effective object deduplication
+across a collection of GitLab project repositories at the Git level:
+
+1. A pool repository must exist.
+2. The participating project repositories must be linked to the pool
+ repository via their respective `objects/info/alternates` files.
+3. The pool repository must contain Git object data common to the
+ participating project repositories.
+
+### Deduplication factor
+
+The effectiveness of Git object deduplication in GitLab depends on the
+amount of overlap between the pool repository and each of its
+participants. As of GitLab 11.9, we have a somewhat optimistic system.
+The only data that will be deduplicated is the data in the source
+project repository at the time the pool repository is created. That is,
+the data in the source project at the time of the first fork *after* the
+deduplication feature has been enabled.
+
+When we enable the object deduplication feature for
+gitlab.com/gitlab-org/gitlab-ce, which is about 1GB at the time of
+writing, all new forks of that project would be 1GB smaller than they
+would have been without Git object deduplication. So even in its current
+optimistic form, we expect Git object deduplication in GitLab to make a
+difference.
+
+However, if a lot of Git objects get added to the project repositories
+in a pool after the pool repository was created these new Git objects
+will currently (GitLab 11.9) not get deduplicated. Over time, the
+deduplication factor of the pool will get worse and worse.
+
+As an extreme example, if we create an empty repository A, and fork that
+to repository B, behind the scenes we get an object pool P with no
+objects in it at all. If we then push 1GB of Git data to A, and push the
+same Git data to B, it will not get deduplicated, because that data was
+not in A at the time P was created.
+
+This also matters in less extreme examples. Consider a pool P with
+source project A and 500 active forks B1, B2,...,B500. Suppose,
+optimistically, that the forks are fully deduplicated at the start of
+our scenario. Now some time passes and 200MB of new Git data gets added
+to project A. Because of the forking workflow, this data makes also its way
+into the forks B1, ..., B500. That means we would now have 100GB of Git
+data sitting around (500 \* 200MB) across the forks, that could have
+been deduplicated. But because of the way we do deduplication this new
+data will not be deduplicated.
+
+> TODO Add periodic maintenance of object pools to prevent gradual loss
+> of deduplication over time.
+> https://gitlab.com/groups/gitlab-org/-/epics/524
+
+## SQL model
+
+As of GitLab 11.8, project repositories in GitLab do not have their own
+SQL table. They are indirectly identified by columns on the `projects`
+table. In other words, the only way to look up a project repository is to
+first look up its project, and then call `project.repository`.
+
+With pool repositories we made a fresh start. These live in their own
+`pool_repositories` SQL table. The relations between these two tables
+are as follows:
+
+- a `Project` belongs to at most one `PoolRepository`
+ (`project.pool_repository`)
+- as an automatic consequence of the above, a `PoolRepository` has
+ many `Project`s
+- a `PoolRepository` has exactly one "source `Project`"
+ (`pool.source_project`)
+
+### Assumptions
+
+- All repositories in a pool must use [hashed
+ storage](../administration/repository_storage_types.md). This is so
+ that we don't have to ever worry about updating paths in
+ `object/info/alternates` files.
+- All repositories in a pool must be on the same Gitaly storage shard.
+ The Git alternates mechanism relies on direct disk access across
+ multiple repositories, and we can only assume direct disk access to
+ be possible within a Gitaly storage shard.
+- All project repositories in a pool must have "Public" visibility in
+ GitLab at the time they join. There are gotchas around visibility of
+ Git objects across alternates links. This restriction is a defense
+ against accidentally leaking private Git data.
+- The only two ways to remove a member project from a pool are (1) to
+ delete the project or (2) to move the project to another Gitaly
+ storage shard.
+
+### Creating pools and pool memberships
+
+- When a pool gets created, it must have a source project. The initial
+ contents of the pool repository are a Git clone of the source
+ project repository.
+- The occasion for creating a pool is when an existing eligible
+ (public, hashed storage, non-forked) GitLab project gets forked and
+ this project does not belong to a pool repository yet. The fork
+ parent project becomes the source project of the new pool, and both
+ the fork parent and the fork child project become members of the new
+ pool.
+- Once project A has become the source project of a pool, all future
+ eligible forks of A will become pool members.
+- If the fork source is itself a fork, the resulting repository will
+ neither join the repository nor will a new pool repository be
+ seeded.
+
+ eg:
+
+ Suppose fork A is part of a pool repository, any forks created off
+ of fork A *will not* be a part of the pool repository that fork A is
+ a part of.
+
+ Suppose B is a fork of A, and A does not belong to an object pool.
+ Now C gets created as a fork of B. C will not be part of a pool
+ repository.
+
+> TODO should forks of forks be deduplicated?
+> https://gitlab.com/gitlab-org/gitaly/issues/1532
+
+### Consequences
+
+- If a normal Project participating in a pool gets moved to another
+ Gitaly storage shard, its "belongs to PoolRepository" relation must
+ be broken. Because of the way moving repositories between shard is
+ implemented, we will automatically get a fresh self-contained copy
+ of the project's repository on the new storage shard.
+- If the source project of a pool gets moved to another Gitaly storage
+ shard or is deleted, we may have to break the "PoolRepository has
+ one source Project" relation?
+
+> TODO What happens, or should happen, if a source project changes
+> visibility, is deleted, or moves to another storage shard?
+> https://gitlab.com/gitlab-org/gitaly/issues/1488
+
+## Consistency between the SQL pool relation and Gitaly
+
+As far as Gitaly is concerned, the SQL pool relations make two types of
+claims about the state of affairs on the Gitaly server: pool repository
+existence, and the existence of an alternates connection between a
+repository and a pool.
+
+### Pool existence
+
+If GitLab thinks a pool repository exists (i.e. it exists according to
+SQL), but it does not on the Gitaly server, then certain RPC calls that
+take the object pool as an argument will fail.
+
+> TODO What happens if SQL says the pool repo exists but Gitaly says it
+> does not? https://gitlab.com/gitlab-org/gitaly/issues/1533
+
+If GitLab thinks a pool does not exist, while it does exist on disk,
+that has no direct consequences on its own. However, if other
+repositories on disk borrow objects from this unknown pool repository
+then we risk data loss, see below.
+
+### Pool relation existence
+
+There are three different things that can go wrong here.
+
+#### 1. SQL says repo A belongs to pool P but Gitaly says A has no alternate objects
+
+In this case, we miss out on disk space savings but all RPC's on A itself
+will function fine. As long as Git can find all its objects, it does not
+matter exactly where those objects are.
+
+#### 2. SQL says repo A belongs to pool P1 but Gitaly says A has alternate objects in pool P2
+
+If we are not careful, this situation can lead to data loss. During some
+operations (repository maintenance), GitLab will try to re-link A to its
+pool P1. If this clobbers the existing link to P2, then A will loose Git
+objects and become invalid.
+
+Also, keep in mind that if GitLab's database got messed up, it may not
+even know that P2 exists.
+
+> TODO Ensure that Gitaly will not clobber existing, unexpected
+> alternates links. https://gitlab.com/gitlab-org/gitaly/issues/1534
+
+#### 3. SQL says repo A does not belong to any pool but Gitaly says A belongs to P
+
+This has the same data loss possibility as scenario 2 above.
+
+## Git object deduplication and GitLab Geo
+
+When a pool repository record is created in SQL on a Geo primary, this
+will eventually trigger an event on the Geo secondary. The Geo secondary
+will then create the pool repository in Gitaly. This leads to an
+"eventually consistent" situation because as each pool participant gets
+synchronized, Geo will eventuall trigger garbage collection in Gitaly on
+the secondary, at which stage Git objects will get deduplicated.
+
+> TODO How do we handle the edge case where at the time the Geo
+> secondary tries to create the pool repository, the source project does
+> not exist? https://gitlab.com/gitlab-org/gitaly/issues/1533
diff --git a/doc/development/gitaly.md b/doc/development/gitaly.md
index b1785e6f803..1a4b1743a00 100644
--- a/doc/development/gitaly.md
+++ b/doc/development/gitaly.md
@@ -3,6 +3,12 @@
[Gitaly](https://gitlab.com/gitlab-org/gitaly) is a high-level Git RPC service used by GitLab CE/EE,
Workhorse and GitLab-Shell.
+## Beginner's guide
+
+Start by reading the gitaly repository's
+[Beginner's guide to Gitaly contributions](https://gitlab.com/gitlab-org/gitaly/blob/master/doc/beginners_guide.md).
+It describes how to setup gitaly, the various components of gitaly and what they do, and how to run its test suites.
+
## Developing new Git features
To read or write Git data, a request has to be made to Gitaly. This means that
diff --git a/doc/development/go_guide/index.md b/doc/development/go_guide/index.md
index cdc806a2d31..7fd41c5e01f 100644
--- a/doc/development/go_guide/index.md
+++ b/doc/development/go_guide/index.md
@@ -161,12 +161,39 @@ in the code.
### Logging
-The usage of a logging library is strongly recommended for daemons. Even though
-there is a `log` package in the standard library, we generally use
-[logrus](https://github.com/sirupsen/logrus). Its plugin ("hooks") system
+The usage of a logging library is strongly recommended for daemons. Even
+though there is a `log` package in the standard library, we generally use
+[Logrus](https://github.com/sirupsen/logrus). Its plugin ("hooks") system
makes it a powerful logging library, with the ability to add notifiers and
formatters at the logger level directly.
+#### Structured (JSON) logging
+
+Every binary ideally must have structured (JSON) logging in place as it helps
+with searching and filtering the logs. At GitLab we use structured logging in
+JSON format, as all our infrastructure assumes that. When using
+[Logrus](https://github.com/sirupsen/logrus) you can turn on structured
+logging simply by using the build in [JSON
+formatter](https://github.com/sirupsen/logrus#formatters). This follows the
+same logging type we use in our [Ruby
+applications](../logging.md#use-structured-json-logging).
+
+#### How to use Logrus
+
+There are a few guidelines one should follow when using the
+[Logrus](https://github.com/sirupsen/logrus) package:
+
+- When printing an error use
+ [WithError](https://godoc.org/github.com/sirupsen/logrus#WithError). For
+ exmaple, `logrus.WithError(err).Error("Failed to do something")`.
+- Since we use [structured logging](#structured-json-logging) we can log
+ fields in the context of that code path, such as the URI of the request using
+ [`WithField`](https://godoc.org/github.com/sirupsen/logrus#WithField) or
+ [`WithFields`](https://godoc.org/github.com/sirupsen/logrus#WithFields). For
+ example, `logrus.WithField("file", "/app/go).Info("Opening dir")`. If you
+ have to log multiple keys, always use `WithFields` instead of calling
+ `WithField` more than once.
+
### Tracing and Correlation
[LabKit](https://gitlab.com/gitlab-org/labkit) is a place to keep common
diff --git a/doc/development/new_fe_guide/development/design_patterns.md b/doc/development/new_fe_guide/development/design_patterns.md
deleted file mode 100644
index ee06566ed30..00000000000
--- a/doc/development/new_fe_guide/development/design_patterns.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Design patterns
-
-> TODO: Add content
diff --git a/doc/development/new_fe_guide/development/index.md b/doc/development/new_fe_guide/development/index.md
index cee8e43ebad..5dced3dc466 100644
--- a/doc/development/new_fe_guide/development/index.md
+++ b/doc/development/new_fe_guide/development/index.md
@@ -1,9 +1,5 @@
# Development
-## [Design patterns](design_patterns.md)
-
-Examples of proven design patterns used in our codebase.
-
## [Components](components.md)
Documentation on existing components and how to best create a new component.
@@ -12,14 +8,6 @@ Documentation on existing components and how to best create a new component.
Learn how to implement an accessible frontend.
-## [Network requests](network_requests.md)
-
-Learn how to handle network requests in our codebase.
-
-## [Security](security.md)
-
-Learn how to ensure that our frontend is secure.
-
## [Performance](performance.md)
Learn how to keep our frontend performant.
diff --git a/doc/development/new_fe_guide/development/network_requests.md b/doc/development/new_fe_guide/development/network_requests.md
deleted file mode 100644
index 047c00313bc..00000000000
--- a/doc/development/new_fe_guide/development/network_requests.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Network requests
-
-> TODO: Add content
diff --git a/doc/development/new_fe_guide/development/security.md b/doc/development/new_fe_guide/development/security.md
deleted file mode 100644
index 5bb38f17988..00000000000
--- a/doc/development/new_fe_guide/development/security.md
+++ /dev/null
@@ -1,14 +0,0 @@
-# Security
-
-## Avoid inline scripts and styles
-
-Inline scripts and styles should be avoided in almost all cases. In an effort to protect users from [XSS vulnerabilities](https://en.wikipedia.org/wiki/Cross-site_scripting), we will be disabling inline scripts using Content Security Policy.
-
-## Including external resources
-
-External fonts, CSS, and JavaScript should never be used with the exception of Google Analytics and Piwik - and only when the instance has enabled it. Assets should always be hosted and served locally from the GitLab instance. Embedded resources via `iframes` should never be used except in certain circumstances such as with ReCaptcha, which cannot be used without an `iframe`.
-
-## Resources for security testing
-
-- [Mozilla's HTTP Observatory CLI](https://github.com/mozilla/http-observatory-cli)
-- [Qualys SSL Labs Server Test](https://www.ssllabs.com/ssltest/analyze.html)
diff --git a/doc/development/new_fe_guide/index.md b/doc/development/new_fe_guide/index.md
index 5fd5af252ef..0e8f5486861 100644
--- a/doc/development/new_fe_guide/index.md
+++ b/doc/development/new_fe_guide/index.md
@@ -3,14 +3,6 @@
This guide contains all the information to successfully contribute to GitLab's frontend.
This is a living document, and we welcome contributions, feedback and suggestions.
-## [Principles](principles.md)
-
-Ensure that your frontend contribution starts off in the right direction.
-
-## [Initiatives](initiatives.md)
-
-High level overview of where we are going from a frontend perspective.
-
## [Development](development/index.md)
Guidance on topics related to development.
@@ -27,9 +19,6 @@ Learn about all the internal JavaScript modules that make up our frontend.
Style guides to keep our code consistent.
-## [Event Tracking with Snowplow](event_tracking.md)
-
-How we use Snowplow to track custom events.
## [Tips](tips.md)
diff --git a/doc/development/new_fe_guide/initiatives.md b/doc/development/new_fe_guide/initiatives.md
deleted file mode 100644
index c81ed3579f0..00000000000
--- a/doc/development/new_fe_guide/initiatives.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Initiatives
-
-> TODO: Add Initiatives
diff --git a/doc/development/new_fe_guide/principles.md b/doc/development/new_fe_guide/principles.md
deleted file mode 100644
index 0af5f506a91..00000000000
--- a/doc/development/new_fe_guide/principles.md
+++ /dev/null
@@ -1,35 +0,0 @@
-# Principles
-
-These principles will ensure that your frontend contribution starts off in the right direction.
-
-## Discuss architecture before implementation
-
-Discuss your architecture design in an issue before writing code. This helps decrease the review time and also provides good practice for writing and thinking about system design.
-
-## Be consistent
-
-There are multiple ways of writing code to accomplish the same results. We should be as consistent as possible in how we write code across our codebases. This will make it more easier us to maintain our code across GitLab.
-
-## Enhance progressively
-
-Whenever you see with existing code that does not follow our current style guide, update it proactively. Refrain from changing everything but each merge request should progressively enhance our codebase and reduce technical debt.
-
-## When to use Vue
-
-- Use Vue for feature that make use of heavy DOM manipulation
-- Use Vue for reusable components
-
-## When to use jQuery
-
-- Use jQuery to interact with Bootstrap JavaScript components
-- Avoid jQuery when a better alternative exists. We are slowly moving away from it [#43559][jquery-future]
-
-## Mixing Vue and jQuery
-
-- Mixing Vue and jQuery is not recommended.
-- If you need to use a specific jQuery plugin in Vue, [create a wrapper around it][select2].
-- It is acceptable for Vue to listen to existing jQuery events using jQuery event listeners.
-- It is not recommended to add new jQuery events for Vue to interact with jQuery.
-
-[jquery-future]: https://gitlab.com/gitlab-org/gitlab-ce/issues/43559
-[select2]: https://vuejs.org/v2/examples/select2.html
diff --git a/doc/development/new_fe_guide/style/html.md b/doc/development/new_fe_guide/style/html.md
index 035fcbb28df..e8c9c2ccebf 100644
--- a/doc/development/new_fe_guide/style/html.md
+++ b/doc/development/new_fe_guide/style/html.md
@@ -2,11 +2,11 @@
## Buttons
-<a name="button-type"></a><a name="1.1"></a>
+### Button type
-- [1.1](#button-type) **Use button type** Button tags requires a `type` attribute according to the [W3C HTML specification][button-type-spec].
+Button tags requires a `type` attribute according to the [W3C HTML specification](https://www.w3.org/TR/2011/WD-html5-20110525/the-button-element.html#dom-button-type).
-```
+```html
// bad
<button></button>
@@ -14,11 +14,11 @@
<button type="button"></button>
```
-<a name="button-role"></a><a name="1.2"></a>
+### Button role
-- [1.2](#button-role) **Use button role for non buttons** If an HTML element has an onClick handler but is not a button, it should have `role="button"`. This is more [accessible][button-role-accessible].
+If an HTML element has an `onClick` handler but is not a button, it should have `role="button"`. This is [more accessible](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_button_role).
-```
+```html
// bad
<div onClick="doSomething"></div>
@@ -28,11 +28,11 @@
## Links
-<a name="blank-links"></a><a name="2.1"></a>
+### Blank target
-- [2.1](#blank-links) **Use rel for target blank** Use `rel="noopener noreferrer"` whenever your links open in a new window i.e. `target="_blank"`. This prevents [the following][jitbit-target-blank] security vulnerability documented by JitBit
+Use `rel="noopener noreferrer"` whenever your links open in a new window, i.e. `target="_blank"`. This prevents a security vulnerability [documented by JitBit](https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever/).
-```
+```html
// bad
<a href="url" target="_blank"></a>
@@ -40,18 +40,14 @@
<a href="url" target="_blank" rel="noopener noreferrer"></a>
```
-<a name="fake-links"></a><a name="2.2"></a>
+### Fake links
-- [2.2](#fake-links) **Do not use fake links** Use a button tag if a link only invokes JavaScript click event handlers. This is more semantic.
+**Do not use fake links.** Use a button tag if a link only invokes JavaScript click event handlers, which is more semantic.
-```
+```html
// bad
<a class="js-do-something" href="#"></a>
// good
<button class="js-do-something" type="button"></button>
```
-
-[button-type-spec]: https://www.w3.org/TR/2011/WD-html5-20110525/the-button-element.html#dom-button-type
-[button-role-accessible]: https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_button_role
-[jitbit-target-blank]: https://www.jitbit.com/alexblog/256-targetblank---the-most-underestimated-vulnerability-ever/
diff --git a/doc/development/new_fe_guide/style/javascript.md b/doc/development/new_fe_guide/style/javascript.md
index 7985b893c9e..3019eaa089c 100644
--- a/doc/development/new_fe_guide/style/javascript.md
+++ b/doc/development/new_fe_guide/style/javascript.md
@@ -1,208 +1,196 @@
# JavaScript style guide
-We use [Airbnb's JavaScript Style Guide][airbnb-style-guide] and it's accompanying linter to manage most of our JavaScript style guidelines.
+We use [Airbnb's JavaScript Style Guide](https://github.com/airbnb/javascript) and it's accompanying
+linter to manage most of our JavaScript style guidelines.
-In addition to the style guidelines set by Airbnb, we also have a few specific rules listed below.
+In addition to the style guidelines set by Airbnb, we also have a few specific rules
+listed below.
> **Tip:**
You can run eslint locally by running `yarn eslint`
-## Arrays
+## Avoid forEach
+
+Avoid forEach when mutating data. Use `map`, `reduce` or `filter` instead of `forEach`
+when mutating data. This will minimize mutations in functions,
+which aligns with [Airbnb's style guide](https://github.com/airbnb/javascript#testing--for-real).
+
+```javascript
+// bad
+users.forEach((user, index) => {
+ user.id = index;
+});
+
+// good
+const usersWithId = users.map((user, index) => {
+ return Object.assign({}, user, { id: index });
+});
+```
+
+## Limit number of parameters
+
+If your function or method has more than 3 parameters, use an object as a parameter
+instead.
+
+```javascript
+// bad
+function a(p1, p2, p3) {
+ // ...
+};
+
+// good
+function a(p) {
+ // ...
+};
+```
+
+## Avoid side effects in constructors
+
+Avoid making asynchronous calls, API requests or DOM manipulations in the `constructor`.
+Move them into separate functions instead. This will make tests easier to write and
+code easier to maintain.
-<a name="avoid-foreach"></a><a name="1.1"></a>
+```javascript
+// bad
+class myClass {
+ constructor(config) {
+ this.config = config;
+ axios.get(this.config.endpoint)
+ }
+}
-- [1.1](#avoid-foreach) **Avoid ForEach when mutating data** Use `map`, `reduce` or `filter` instead of `forEach` when mutating data. This will minimize mutations in functions ([which is aligned with Airbnb's style guide][airbnb-minimize-mutations])
+// good
+class myClass {
+ constructor(config) {
+ this.config = config;
+ }
- ```
- // bad
- users.forEach((user, index) => {
- user.id = index;
- });
+ makeRequest() {
+ axios.get(this.config.endpoint)
+ }
+}
+const instance = new myClass();
+instance.makeRequest();
- // good
- const usersWithId = users.map((user, index) => {
- return Object.assign({}, user, { id: index });
- });
- ```
+```
-## Functions
+## Avoid classes to handle DOM events
-<a name="limit-params"></a><a name="2.1"></a>
+If the only purpose of the class is to bind a DOM event and handle the callback, prefer
+using a function.
-- [2.1](#limit-params) **Limit number of parameters** If your function or method has more than 3 parameters, use an object as a parameter instead.
+```javascript
+// bad
+class myClass {
+ constructor(config) {
+ this.config = config;
+ }
- ```
- // bad
- function a(p1, p2, p3) {
- // ...
- };
+ init() {
+ document.addEventListener('click', () => {});
+ }
+}
- // good
- function a(p) {
- // ...
- };
- ```
+// good
-## Classes & constructors
+const myFunction = () => {
+ document.addEventListener('click', () => {
+ // handle callback here
+ });
+}
+```
-<a name="avoid-constructor-side-effects"></a><a name="3.1"></a>
+## Pass element container to constructor
-- [3.1](#avoid-constructor-side-effects) **Avoid side effects in constructors** Avoid making some operations in the `constructor`, such as asynchronous calls, API requests and DOM manipulations. Prefer moving them into separate functions. This will make tests easier to write and code easier to maintain.
+When your class manipulates the DOM, receive the element container as a parameter.
+This is more maintainable and performant.
- ```javascript
- // bad
- class myClass {
- constructor(config) {
- this.config = config;
- axios.get(this.config.endpoint)
- }
- }
+```javascript
+// bad
+class a {
+ constructor() {
+ document.querySelector('.b');
+ }
+}
- // good
- class myClass {
- constructor(config) {
- this.config = config;
- }
+// good
+class a {
+ constructor(options) {
+ options.container.querySelector('.b');
+ }
+}
+```
- makeRequest() {
- axios.get(this.config.endpoint)
- }
- }
- const instance = new myClass();
- instance.makeRequest();
+## Use ParseInt
- ```
+Use `ParseInt` when converting a numeric string into a number.
-<a name="avoid-classes-to-handle-dom-events"></a><a name="3.2"></a>
+```javascript
+// bad
+Number('10')
-- [3.2](#avoid-classes-to-handle-dom-events) **Avoid classes to handle DOM events** If the only purpose of the class is to bind a DOM event and handle the callback, prefer using a function.
+// good
+parseInt('10', 10);
+```
- ```
- // bad
- class myClass {
- constructor(config) {
- this.config = config;
- }
+## CSS Selectors - Use `js-` prefix
- init() {
- document.addEventListener('click', () => {});
- }
- }
+If a CSS class is only being used in JavaScript as a reference to the element, prefix
+the class name with `js-`.
- // good
+```html
+// bad
+<button class="add-user"></button>
- const myFunction = () => {
- document.addEventListener('click', () => {
- // handle callback here
- });
- }
- ```
+// good
+<button class="js-add-user"></button>
+```
-<a name="element-container"></a><a name="3.3"></a>
+## Absolute vs relative paths for modules
-- [3.3](#element-container) **Pass element container to constructor** When your class manipulates the DOM, receive the element container as a parameter.
- This is more maintainable and performant.
+Use relative paths if the module you are importing is less than two levels up.
- ```
- // bad
- class a {
- constructor() {
- document.querySelector('.b');
- }
- }
+```javascript
+// bad
+import GitLabStyleGuide from '~/guides/GitLabStyleGuide';
- // good
- class a {
- constructor(options) {
- options.container.querySelector('.b');
- }
- }
- ```
+// good
+import GitLabStyleGuide from '../GitLabStyleGuide';
+```
-## Type Casting & Coercion
+If the module you are importing is two or more levels up, use an absolute path instead:
-<a name="use-parseint"></a><a name="4.1"></a>
+```javascript
+// bad
+import GitLabStyleGuide from '../../../guides/GitLabStyleGuide';
-- [4.1](#use-parseint) **Use ParseInt** Use `ParseInt` when converting a numeric string into a number.
+// good
+import GitLabStyleGuide from '~/GitLabStyleGuide';
+```
- ```
- // bad
- Number('10')
+Additionally, **do not add to global namespace**.
- // good
- parseInt('10', 10);
- ```
+## Do not use `DOMContentLoaded` in non-page modules
-## CSS Selectors
+Imported modules should act the same each time they are loaded. `DOMContentLoaded`
+events are only allowed on modules loaded in the `/pages/*` directory because those
+are loaded dynamically with webpack.
-<a name="use-js-prefix"></a><a name="5.1"></a>
+## Avoid XSS
-- [5.1](#use-js-prefix) **Use js prefix** If a CSS class is only being used in JavaScript as a reference to the element, prefix the class name with `js-`
+Do not use `innerHTML`, `append()` or `html()` to set content. It opens up too many
+vulnerabilities.
- ```
- // bad
- <button class="add-user"></button>
+## Disabling ESLint in new files
- // good
- <button class="js-add-user"></button>
- ```
+Do not disable ESLint when creating new files. Existing files may have existing rules
+disabled due to legacy compatibility reasons but they are in the process of being refactored.
-## Modules
+Do not disable specific ESLint rules. Due to technical debt, you may disable the following
+rules only if you are invoking/instantiating existing code modules.
-<a name="use-absolute-paths"></a><a name="6.1"></a>
+ - [no-new](http://eslint.org/docs/rules/no-new)
+ - [class-method-use-this](http://eslint.org/docs/rules/class-methods-use-this)
-- [6.1](#use-absolute-paths) **Use absolute paths for nearby modules** Use absolute paths if the module you are importing is less than two levels up.
-
- ```
- // bad
- import GitLabStyleGuide from '~/guides/GitLabStyleGuide';
-
- // good
- import GitLabStyleGuide from '../GitLabStyleGuide';
- ```
-
-<a name="use-relative-paths"></a><a name="6.2"></a>
-
-- [6.2](#use-relative-paths) **Use relative paths for distant modules** If the module you are importing is two or more levels up, use a relative path instead of an absolute path.
-
- ```
- // bad
- import GitLabStyleGuide from '../../../guides/GitLabStyleGuide';
-
- // good
- import GitLabStyleGuide from '~/GitLabStyleGuide';
- ```
-
-<a name="global-namespace"></a><a name="6.3"></a>
-
-- [6.3](#global-namespace) **Do not add to global namespace**
-
-<a name="domcontentloaded"></a><a name="6.4"></a>
-
-- [6.4](#domcontentloaded) **Do not use DOMContentLoaded in non-page modules** Imported modules should act the same each time they are loaded. `DOMContentLoaded` events are only allowed on modules loaded in the `/pages/*` directory because those are loaded dynamically with webpack.
-
-## Security
-
-<a name="avoid-xss"></a><a name="7.1"></a>
-
-- [7.1](#avoid-xss) **Avoid XSS** Do not use `innerHTML`, `append()` or `html()` to set content. It opens up too many vulnerabilities.
-
-## ESLint
-
-<a name="disable-eslint-file"></a><a name="8.1"></a>
-
-- [8.1](#disable-eslint-file) **Disabling ESLint in new files** Do not disable ESLint when creating new files. Existing files may have existing rules disabled due to legacy compatibility reasons but they are in the process of being refactored.
-
-<a name="disable-eslint-rule"></a><a name="8.2"></a>
-
-- [8.2](#disable-eslint-rule) **Disabling ESLint rule** Do not disable specific ESLint rules. Due to technical debt, you may disable the following rules only if you are invoking/instantiating existing code modules
-
- - [no-new][no-new]
- - [class-method-use-this][class-method-use-this]
-
-> Note: Disable these rules on a per line basis. This makes it easier to refactor in the future. E.g. use `eslint-disable-next-line` or `eslint-disable-line`
-
-[airbnb-style-guide]: https://github.com/airbnb/javascript
-[airbnb-minimize-mutations]: https://github.com/airbnb/javascript#testing--for-real
-[no-new]: http://eslint.org/docs/rules/no-new
-[class-method-use-this]: http://eslint.org/docs/rules/class-methods-use-this
+> Note: Disable these rules on a per line basis. This makes it easier to refactor
+ in the future. E.g. use `eslint-disable-next-line` or `eslint-disable-line`.
diff --git a/doc/development/new_fe_guide/tips.md b/doc/development/new_fe_guide/tips.md
index 881ad1662ae..889a5aab2b7 100644
--- a/doc/development/new_fe_guide/tips.md
+++ b/doc/development/new_fe_guide/tips.md
@@ -7,3 +7,19 @@ To clear production compiled assets created with `yarn webpack-prod` you can run
```
yarn clean
```
+
+## Creating feature flags in development
+
+The process for creating a feature flag is the same as [enabling a feature flag in development](https://docs.gitlab.com/ee/development/feature_flags.html#enabling-a-feature-flag-in-development).
+
+Your feature flag can now be:
+
+- [made available to the frontend](https://docs.gitlab.com/ee/development/feature_flags.html#frontend) via the `gon`
+- queried in [tests](https://docs.gitlab.com/ee/development/feature_flags.html#specs)
+- queried in HAML templates and ruby files via the `Feature.enabled?(:my_shiny_new_feature_flag)` method
+
+### More on feature flags
+
+- [Deleting a feature flag](https://docs.gitlab.com/ee/api/features.html#delete-a-feature)
+- [Manage feature flags](https://docs.gitlab.com/ee/development/feature_flags.html)
+- [Feature flags API](https://docs.gitlab.com/ee/api/features.html)
diff --git a/doc/development/rolling_out_changes_using_feature_flags.md b/doc/development/rolling_out_changes_using_feature_flags.md
index 4e0d1b74bc9..8d35a4ecee2 100644
--- a/doc/development/rolling_out_changes_using_feature_flags.md
+++ b/doc/development/rolling_out_changes_using_feature_flags.md
@@ -105,6 +105,20 @@ Flags](feature_flags.md) guide) supports rolling out changes to a percentage of
users. This in turn can be controlled using [GitLab
chatops](https://docs.gitlab.com/ee/ci/chatops/).
+For an up to date list of feature flag commands please see [the source
+code](https://gitlab.com/gitlab-com/chatops/blob/master/lib/chatops/commands/feature.rb).
+Note that all the examples in that file must be preceded by
+`/chatops run`.
+
+If you get an error "Whoops! This action is not allowed. This incident
+will be reported." that means your Slack account is not allowed to
+change feature flags. To test if you are allowed to do anything at all,
+run:
+
+```
+/chatops run feature --help
+```
+
For example, to enable a feature for 25% of all users, run the following in
Slack:
diff --git a/doc/development/testing_guide/end_to_end_tests.md b/doc/development/testing_guide/end_to_end_tests.md
index a239dc84a1c..9837ea515a3 100644
--- a/doc/development/testing_guide/end_to_end_tests.md
+++ b/doc/development/testing_guide/end_to_end_tests.md
@@ -17,18 +17,25 @@ a black-box testing framework for the API and the UI.
We run scheduled pipeline each night to test nightly builds created by Omnibus.
You can find these nightly pipelines at [gitlab-org/quality/nightly/pipelines][quality-nightly-pipelines].
+Results are reported in the `#qa-nightly` Slack channel.
### Testing staging
We run scheduled pipeline each night to test staging.
You can find these nightly pipelines at [gitlab-org/quality/staging/pipelines][quality-staging-pipelines].
+Results are reported in the `#qa-staging` Slack channel.
### Testing code in merge requests
-It is possible to run end-to-end tests (eventually being run within a
-[GitLab QA pipeline][gitlab-qa-pipelines]) for a merge request by triggering
-the `package-and-qa` manual action in the `test` stage, that should be present
-in a merge request widget (unless the merge request is from a fork).
+#### Using the `package-and-qa` job
+
+It is possible to run end-to-end tests for a merge request, eventually being run in
+a pipeline in the [`gitlab-qa`](https://gitlab.com/gitlab-org/gitlab-qa/) project,
+by triggering the `package-and-qa` manual action in the `test` stage (not
+available for forks).
+
+**This runs end-to-end tests against a custom Omnibus package built from your
+merge request's changes.**
Manual action that starts end-to-end tests is also available in merge requests
in [Omnibus GitLab][omnibus-gitlab].
@@ -40,6 +47,29 @@ Below you can read more about how to use it and how does it work.
Currently, we are using _multi-project pipeline_-like approach to run QA
pipelines.
+![QA on merge requests CI/CD architecture](img/qa_on_merge_requests_cicd_architecture.png)
+
+<details>
+<summary>Show mermaid source</summary>
+<pre>
+graph LR
+ A1 -.->|1. Triggers an omnibus-gitlab pipeline and wait for it to be done| A2
+ B2[<b>`Trigger-qa` stage</b><br />`Trigger:qa-test` job] -.->|2. Triggers a gitlab-qa pipeline and wait for it to be done| A3
+
+subgraph gitlab-ce/ee pipeline
+ A1[<b>`test` stage</b><br />`package-and-qa` job]
+ end
+
+subgraph omnibus-gitlab pipeline
+ A2[<b>`Trigger-docker` stage</b></b><br />`Trigger:gitlab-docker` job] -->|once done| B2
+ end
+
+subgraph gitlab-qa pipeline
+ A3>QA jobs run] -.->|3. Reports back the pipeline result to the `package-and-qa` job<br />and post the result on the original commit tested| A1
+ end
+</pre>
+</details>
+
1. Developer triggers a manual action, that can be found in CE / EE merge
requests. This starts a chain of pipelines in multiple projects.
@@ -60,7 +90,25 @@ pipelines.
1. The result of the [GitLab QA pipeline][gitlab-qa-pipelines] is being
propagated upstream, through Omnibus, back to the CE / EE merge request.
-#### How do I write tests?
+#### Using the `review-qa-all` jobs
+
+On every pipeline during the `test` stage, the `review-qa-smoke` job is
+automatically started: it runs the QA smoke suite against the
+[Review App][review-apps].
+
+You can also manually start the `review-qa-all`: it runs the full QA suite
+against the [Review App][review-apps].
+
+**This runs end-to-end tests against a Review App based on [the official GitLab
+Helm chart][helm-chart], itself deployed with custom
+[Cloud Native components][cng] built from your merge request's changes.**
+
+See [Review Apps][review-apps] for more details about Review Apps.
+
+[helm-chart]: https://gitlab.com/charts/gitlab/
+[cng]: https://gitlab.com/gitlab-org/build/CNG
+
+## How do I write tests?
In order to write new tests, you first need to learn more about GitLab QA
architecture. See the [documentation about it][gitlab-qa-architecture].
@@ -80,10 +128,11 @@ you can find an issue you would like to work on in
[omnibus-gitlab]: https://gitlab.com/gitlab-org/omnibus-gitlab
[gitlab-qa]: https://gitlab.com/gitlab-org/gitlab-qa
-[gitlab-qa-readme]: https://gitlab.com/gitlab-org/gitlab-qa/tree/master/README.md
[gitlab-qa-pipelines]: https://gitlab.com/gitlab-org/gitlab-qa/pipelines
+[gitlab-qa-readme]: https://gitlab.com/gitlab-org/gitlab-qa/tree/master/README.md
[quality-nightly-pipelines]: https://gitlab.com/gitlab-org/quality/nightly/pipelines
[quality-staging-pipelines]: https://gitlab.com/gitlab-org/quality/staging/pipelines
+[review-apps]: ./review_apps.md
[gitlab-qa-architecture]: https://gitlab.com/gitlab-org/gitlab-qa/blob/master/docs/architecture.md
[gitlab-qa-issues]: https://gitlab.com/gitlab-org/gitlab-qa/issues?label_name%5B%5D=new+scenario
[gitlab-ce-issues]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name[]=QA&label_name[]=test
diff --git a/doc/development/testing_guide/frontend_testing.md b/doc/development/testing_guide/frontend_testing.md
index 5b66e513a76..71c9637e72c 100644
--- a/doc/development/testing_guide/frontend_testing.md
+++ b/doc/development/testing_guide/frontend_testing.md
@@ -15,7 +15,7 @@ information on general testing practices at GitLab.
## Jest
-GitLab has started to migrate tests to the (Jest)[https://jestjs.io]
+GitLab has started to migrate tests to the [Jest](https://jestjs.io)
testing framework. You can read a [detailed evaluation](https://gitlab.com/gitlab-org/gitlab-ce/issues/49171)
of Jest compared to our use of Karma and Jasmine. In summary, it will allow us
to improve the performance and consistency of our frontend tests.
@@ -35,15 +35,16 @@ If your test exceeds that time, it will fail.
If you cannot improve the performance of the tests, you can increase the timeout
for a specific test using
-[`jest.setTimeout`](https://jestjs.io/docs/en/jest-object#jestsettimeouttimeout).
+[`setTestTimeout`](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/spec/frontend/helpers/timeout.js).
```javascript
-beforeAll(() => {
- jest.setTimeout(500);
-});
+import { setTestTimeout } from 'helpers/timeout';
describe('Component', () => {
- // ...
+ it('does something amazing', () => {
+ setTestTimeout(500);
+ // ...
+ });
});
```
@@ -52,12 +53,9 @@ Remember that the performance of each test depends on the environment.
## Karma test suite
GitLab uses the [Karma][karma] test runner with [Jasmine] as its test
-framework for our JavaScript unit and integration tests. For integration tests,
-we generate HTML files using RSpec (see `spec/javascripts/fixtures/*.rb` for examples).
-Some fixtures are still HAML templates that are translated to HTML files using the same mechanism (see `static_fixtures.rb`).
-Adding these static fixtures should be avoided as they are harder to keep up to date with real views.
-The existing static fixtures will be migrated over time.
-Please see [gitlab-org/gitlab-ce#24753](https://gitlab.com/gitlab-org/gitlab-ce/issues/24753) to track our progress.
+framework for our JavaScript unit and integration tests.
+We generate HTML and JSON fixtures from backend views and controllers
+using RSpec (see `spec/javascripts/fixtures/*.rb` for examples).
Fixtures are served during testing by the [jasmine-jquery][jasmine-jquery] plugin.
JavaScript tests live in `spec/javascripts/`, matching the folder structure
@@ -227,14 +225,12 @@ See this [section][vue-test].
### Running frontend tests
-`rake karma` runs the frontend-only (JavaScript) tests.
-It consists of two subtasks:
+For running the frontend tests, you need the following commands:
-- `rake karma:fixtures` (re-)generates fixtures
-- `rake karma:tests` actually executes the tests
+- `rake karma:fixtures` (re-)generates fixtures.
+- `yarn test` executes the tests.
-As long as the fixtures don't change, `rake karma:tests` (or `yarn karma`)
-is sufficient (and saves you some time).
+As long as the fixtures don't change, `yarn test` is sufficient (and saves you some time).
### Live testing and focused testing
@@ -281,25 +277,6 @@ Information on setting up and running RSpec integration tests with
## Gotchas
-### Errors due to use of unsupported JavaScript features
-
-Similar errors will be thrown if you're using JavaScript features not yet
-supported by the PhantomJS test runner which is used for both Karma and RSpec
-tests. We polyfill some JavaScript objects for older browsers, but some
-features are still unavailable:
-
-- Array.from
-- Array.first
-- Async functions
-- Generators
-- Array destructuring
-- For..Of
-- Symbol/Symbol.iterator
-- Spread
-
-Until these are polyfilled appropriately, they should not be used. Please
-update this list with additional unsupported features.
-
### RSpec errors due to JavaScript
By default RSpec unit tests will not run JavaScript in the headless browser
diff --git a/doc/development/testing_guide/img/qa_on_merge_requests_cicd_architecture.png b/doc/development/testing_guide/img/qa_on_merge_requests_cicd_architecture.png
new file mode 100644
index 00000000000..5b93a05db96
--- /dev/null
+++ b/doc/development/testing_guide/img/qa_on_merge_requests_cicd_architecture.png
Binary files differ
diff --git a/doc/development/testing_guide/review_apps.md b/doc/development/testing_guide/review_apps.md
index fda3ff57316..ffc71051377 100644
--- a/doc/development/testing_guide/review_apps.md
+++ b/doc/development/testing_guide/review_apps.md
@@ -90,8 +90,8 @@ subgraph GCP `gitlab-review-apps` project
## QA runs
On every [pipeline][gitlab-pipeline] during the `test` stage, the
-`review-qa-smoke` job is automatically started: it runs the smoke QA suite.
-You can also manually start the `review-qa-all`: it runs the full QA suite.
+`review-qa-smoke` job is automatically started: it runs the QA smoke suite.
+You can also manually start the `review-qa-all`: it runs the QA full suite.
Note that both jobs first wait for the `review-deploy` job to be finished.
diff --git a/doc/development/testing_guide/smoke.md b/doc/development/testing_guide/smoke.md
index 3360031c220..30d861d7d68 100644
--- a/doc/development/testing_guide/smoke.md
+++ b/doc/development/testing_guide/smoke.md
@@ -7,13 +7,19 @@ functionality is working.
Currently, our suite consists of this basic functionality coverage:
-- User Login (Standard Auth)
-- Project Creation
-- Issue Creation
-- Merge Request Creation
+- User standard authentication
+- SSH Key creation and addition to a user
+- Project simple creation
+- Project creation with Auto-DevOps enabled
+- Issue creation
+- Merge Request creation
+- Snippet creation
Smoke tests have the `:smoke` RSpec metadata.
+See [End-to-end Testing](./end_to_end_tests.md) for more details about
+end-to-end tests.
+
---
[Return to Testing documentation](index.md)
diff --git a/doc/development/testing_guide/testing_levels.md b/doc/development/testing_guide/testing_levels.md
index 5d46833a1e2..352651fe91b 100644
--- a/doc/development/testing_guide/testing_levels.md
+++ b/doc/development/testing_guide/testing_levels.md
@@ -160,7 +160,7 @@ Every new feature should come with a [test plan].
> See [end-to-end tests](end_to_end_tests.md) for more information.
Note that `qa/spec` contains unit tests of the QA framework itself, not to be
-confused with the application's [unit tests](#unit-tests) or
+confused with the application's [unit tests](#unit-tests) or
[end-to-end tests](#black-box-tests-at-the-system-level-aka-end-to-end-tests).
[multiple pieces]: ../architecture.md#components
@@ -234,6 +234,8 @@ you should write an integration test using Jasmine.
[big]: https://twitter.com/timbray/status/822470746773409794
[picture]: https://twitter.com/withzombies/status/829716565834752000
[tests-cost]: https://medium.com/table-xi/high-cost-tests-and-high-value-tests-a86e27a54df#.2ulyh3a4e
+[RSpec]: https://github.com/rspec/rspec-rails#feature-specs
+[Capybara]: https://github.com/teamcapybara/capybara
---
diff --git a/doc/development/ux_guide/users.md b/doc/development/ux_guide/users.md
index 11bac11257c..3aecbbffd73 100644
--- a/doc/development/ux_guide/users.md
+++ b/doc/development/ux_guide/users.md
@@ -1,5 +1,5 @@
---
-redirect_to: 'https://design.gitlab.com/getting-started/personas/'
+redirect_to: 'https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/'
---
-The content of this document was moved into the [GitLab Design System](https://design.gitlab.com/).
+This document was moved to [another location](https://about.gitlab.com/handbook/marketing/product-marketing/roles-personas/).
diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md
index da7853d7d34..8fecdc6948e 100644
--- a/doc/gitlab-basics/create-your-ssh-keys.md
+++ b/doc/gitlab-basics/create-your-ssh-keys.md
@@ -1,35 +1,22 @@
-# How to create your SSH Keys
+# How to create your SSH keys
-1. Go to your [command line](start-using-git.md) and follow the [instructions](../ssh/README.md) to generate your SSH key pair.
-
-1. Log in to GitLab with your credentials.
-1. In the upper-right corner, click your avatar and then click **Settings**.
-
- ![Profile settings dropdown](img/profile_settings.png)
-
-1. Navigate to the **SSH keys** tab.
-
- ![SSH Keys](img/profile_settings_ssh_keys.png)
-
-1. Paste your **public** key that you generated in the first step in the 'Key'
- box.
+This topic describes how to create SSH keys. You do this to use Git over SSH instead of Git over HTTP.
- ![Paste SSH public key](img/profile_settings_ssh_keys_paste_pub.png)
+## Creating your SSH keys
+1. Go to your [command line](start-using-git.md) and follow the [instructions](../ssh/README.md) to generate your SSH key pair.
+1. Log in to GitLab.
+1. In the upper-right corner, click your avatar and select **Settings**.
+1. On the **User Settings** menu, select **SSH keys**.
+1. Paste the **public** key generated in the first step in the **Key**
+ text field.
1. Optionally, give it a descriptive title so that you can recognize it in the
event you add multiple keys.
-
- ![SSH key title](img/profile_settings_ssh_keys_title.png)
-
-1. Finally, click **Add key** to add it to GitLab. You will be able to see
+1. Finally, click the **Add key** button to add it to GitLab. You will be able to see
its fingerprint, title, and creation date.
![SSH key single page](img/profile_settings_ssh_keys_single_key.png)
->**Note:**
-Once you add a key, you cannot edit it, only remove it. In case the paste
-didn't work, you will have to remove the offending key and re-add it.
-
----
-
-Congratulations! You are now ready to use Git over SSH, instead of Git over HTTP!
+NOTE: **Note:**
+Once you add a key, you cannot edit it. If the paste
+didn't work, you need to remove the offending key and re-add it.
diff --git a/doc/gitlab-basics/img/profile_settings.png b/doc/gitlab-basics/img/profile_settings.png
deleted file mode 100644
index b91b698fb18..00000000000
--- a/doc/gitlab-basics/img/profile_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys.png b/doc/gitlab-basics/img/profile_settings_ssh_keys.png
deleted file mode 100644
index 8ac603a2af9..00000000000
--- a/doc/gitlab-basics/img/profile_settings_ssh_keys.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png
deleted file mode 100644
index 0b1c64a72f3..00000000000
--- a/doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png
+++ /dev/null
Binary files differ
diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_title.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_title.png
deleted file mode 100644
index 02ca0bf7478..00000000000
--- a/doc/gitlab-basics/img/profile_settings_ssh_keys_title.png
+++ /dev/null
Binary files differ
diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md
index 1d5e5dd6e15..fb939ff8aac 100644
--- a/doc/hooks/custom_hooks.md
+++ b/doc/hooks/custom_hooks.md
@@ -1,3 +1,7 @@
+---
+redirect_to: '../administration/custom_hooks.md'
+---
+
# Custom Git Hooks
This document was moved to [administration/custom_hooks.md](../administration/custom_hooks.md).
diff --git a/doc/incoming_email/README.md b/doc/incoming_email/README.md
index db0f03f2c98..9544983974f 100644
--- a/doc/incoming_email/README.md
+++ b/doc/incoming_email/README.md
@@ -1 +1,5 @@
+---
+redirect_to: '../administration/reply_by_email.md'
+---
+
This document was moved to [administration/reply_by_email](../administration/reply_by_email.md).
diff --git a/doc/incoming_email/postfix.md b/doc/incoming_email/postfix.md
index 90833238ac5..a7192325229 100644
--- a/doc/incoming_email/postfix.md
+++ b/doc/incoming_email/postfix.md
@@ -1 +1,5 @@
+---
+redirect_to: '../administration/reply_by_email_postfix_setup.md'
+---
+
This document was moved to [administration/reply_by_email_postfix_setup](../administration/reply_by_email_postfix_setup.md).
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 8ab7189c2a6..9a6c2ce1976 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -207,6 +207,9 @@ We support the current and the previous major release of:
- Microsoft Edge
- Internet Explorer 11
+The browser vendors release regular minor version updates with important bug fixes and security updates.
+Support is only provided for the current minor version of the major version you are running.
+
Each time a new browser version is released, we begin supporting that version and stop supporting the third most recent version.
Note: We do not support running GitLab with JavaScript disabled in the browser and have no plans of supporting that
diff --git a/doc/integration/chat_commands.md b/doc/integration/chat_commands.md
index 2856992ee25..1a4fb46046d 100644
--- a/doc/integration/chat_commands.md
+++ b/doc/integration/chat_commands.md
@@ -1 +1,5 @@
+---
+redirect_to: 'slash_commands.md'
+---
+
This document was moved to [integration/slash_commands.md](slash_commands.md).
diff --git a/doc/integration/crowd.md b/doc/integration/crowd.md
index 2bc526dc3db..30e3e888b29 100644
--- a/doc/integration/crowd.md
+++ b/doc/integration/crowd.md
@@ -1 +1,5 @@
+---
+redirect_to: '../administration/auth/crowd.md'
+---
+
This document was moved to [`administration/auth/crowd`](../administration/auth/crowd.md).
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
index edd1af423ca..c3328e01081 100644
--- a/doc/integration/external-issue-tracker.md
+++ b/doc/integration/external-issue-tracker.md
@@ -1,14 +1,17 @@
# External issue tracker
-GitLab has a great issue tracker but you can also use an external one such as
-Jira, Redmine, YouTrack, or Bugzilla. Issue trackers are configurable per GitLab project
-and allow you to do the following:
+GitLab has a great [issue tracker](../user/project/issues/index.md) but you can also use an external one
+such as Jira, Redmine, YouTrack, or Bugzilla. External issue trackers are configurable per GitLab project.
-- you can reference these external issues inside GitLab interface
- (merge requests, commits, comments) and they will be automatically converted
- into links
+Once configured, you can reference external issues using the format `CODE-123`, where:
-You can have enabled both external and internal GitLab issue trackers in parallel. The **Issues** link always opens the internal issue tracker and in case the internal issue tracker is disabled the link is not visible in the menu.
+- `CODE` is a unique code for the tracker.
+- `123` is the issue number in the tracker.
+
+These references in GitLab merge requests, commits, or comments are automatically converted to links to the issues.
+
+You can keep GitLab's issue tracker enabled in parallel or disable it. When enabled, the **Issues** link in the
+GitLab menu always opens the internal issue tracker. When disabled, the link is not visible in the menu.
## Configuration
diff --git a/doc/integration/jira.md b/doc/integration/jira.md
index b6923f74e28..c09fde08326 100644
--- a/doc/integration/jira.md
+++ b/doc/integration/jira.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/integrations/jira.md'
+---
+
This document was moved to [integrations/jira](../user/project/integrations/jira.md).
diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md
index 242890af981..76c124d2ce9 100644
--- a/doc/integration/ldap.md
+++ b/doc/integration/ldap.md
@@ -1 +1,5 @@
+---
+redirect_to: '../administration/auth/ldap.md'
+---
+
This document was moved to [`administration/auth/ldap`](../administration/auth/ldap.md).
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 69bbd05c367..2932c884d04 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -256,7 +256,7 @@ gitlab_rails['omniauth_enabled'] = false
You can enable profile syncing from selected OmniAuth providers and for all or for specific user information.
-When authenticating using LDAP, the user's email is always synced.
+When authenticating using LDAP, the user's name and email are always synced.
```ruby
gitlab_rails['sync_profile_from_provider'] = ['twitter', 'google_oauth2']
diff --git a/doc/integration/slack.md b/doc/integration/slack.md
index 8cd151fbf95..f84ab769218 100644
--- a/doc/integration/slack.md
+++ b/doc/integration/slack.md
@@ -1 +1,5 @@
+---
+redirect_to: '../project_services/slack.md'
+---
+
This document was moved to [project_services/slack.md](../project_services/slack.md).
diff --git a/doc/logs/logs.md b/doc/logs/logs.md
index a2eca62d691..0cb092c85fd 100644
--- a/doc/logs/logs.md
+++ b/doc/logs/logs.md
@@ -1 +1,5 @@
+---
+redirect_to: '../administration/logs.md'
+---
+
This document was moved to [administration/logs.md](../administration/logs.md).
diff --git a/doc/monitoring/health_check.md b/doc/monitoring/health_check.md
index 6cf93c33ec2..b611fa388b4 100644
--- a/doc/monitoring/health_check.md
+++ b/doc/monitoring/health_check.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/admin_area/monitoring/health_check.md'
+---
+
This document was moved to [user/admin_area/monitoring/health_check](../user/admin_area/monitoring/health_check.md).
diff --git a/doc/monitoring/performance/gitlab_configuration.md b/doc/monitoring/performance/gitlab_configuration.md
index 19d46135930..233a12ebd6f 100644
--- a/doc/monitoring/performance/gitlab_configuration.md
+++ b/doc/monitoring/performance/gitlab_configuration.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../administration/monitoring/performance/gitlab_configuration.md'
+---
+
This document was moved to [administration/monitoring/performance/gitlab_configuration](../../administration/monitoring/performance/gitlab_configuration.md).
diff --git a/doc/monitoring/performance/grafana_configuration.md b/doc/monitoring/performance/grafana_configuration.md
index 0d4be02ff5f..f4e3561a19f 100644
--- a/doc/monitoring/performance/grafana_configuration.md
+++ b/doc/monitoring/performance/grafana_configuration.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../administration/monitoring/performance/grafana_configuration.md'
+---
+
This document was moved to [administration/monitoring/performance/grafana_configuration](../../administration/monitoring/performance/grafana_configuration.md).
diff --git a/doc/monitoring/performance/influxdb_configuration.md b/doc/monitoring/performance/influxdb_configuration.md
index 15fd275e916..ae5f4c7e9df 100644
--- a/doc/monitoring/performance/influxdb_configuration.md
+++ b/doc/monitoring/performance/influxdb_configuration.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../administration/monitoring/performance/influxdb_configuration.md'
+---
+
This document was moved to [administration/monitoring/performance/influxdb_configuration](../../administration/monitoring/performance/influxdb_configuration.md).
diff --git a/doc/monitoring/performance/influxdb_schema.md b/doc/monitoring/performance/influxdb_schema.md
index e53f9701dc3..57fb74cb6cd 100644
--- a/doc/monitoring/performance/influxdb_schema.md
+++ b/doc/monitoring/performance/influxdb_schema.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../administration/monitoring/performance/influxdb_schema.md'
+---
+
This document was moved to [administration/monitoring/performance/influxdb_schema](../../administration/monitoring/performance/influxdb_schema.md).
diff --git a/doc/monitoring/performance/introduction.md b/doc/monitoring/performance/introduction.md
index 4d6f02b6547..e23eabd5f40 100644
--- a/doc/monitoring/performance/introduction.md
+++ b/doc/monitoring/performance/introduction.md
@@ -1 +1,5 @@
+---
+redirect_to: '../../administration/monitoring/performance/index.md'
+---
+
This document was moved to [administration/monitoring/performance/introduction](../../administration/monitoring/performance/index.md).
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index 78d67aeec78..85923a40b2c 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -1,3 +1,7 @@
+---
+redirect_to: '../user/permissions.md'
+---
+
# Permissions
This document was moved to [user/permissions.md](../user/permissions.md).
diff --git a/doc/profile/README.md b/doc/profile/README.md
index fda6d85a84c..4932cf33b87 100644
--- a/doc/profile/README.md
+++ b/doc/profile/README.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/profile/index.md'
+---
+
This document was moved to [user/profile/account](../user/profile/index.md).
diff --git a/doc/profile/preferences.md b/doc/profile/preferences.md
index cc16f3afe41..cf99bd61f5d 100644
--- a/doc/profile/preferences.md
+++ b/doc/profile/preferences.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/profile/preferences.md'
+---
+
This document was moved to [another location](../user/profile/preferences.md).
diff --git a/doc/profile/two_factor_authentication.md b/doc/profile/two_factor_authentication.md
index 60918a0339c..453ac833f59 100644
--- a/doc/profile/two_factor_authentication.md
+++ b/doc/profile/two_factor_authentication.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/profile/account/two_factor_authentication.md'
+---
+
This document was moved to [user/profile/account](../user/profile/account/two_factor_authentication.md).
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index dcd5e6e2245..574ba961cb0 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -100,6 +100,13 @@ the gitlab task runner pod via `kubectl`. Refer to [backing up a GitLab installa
kubectl exec -it <gitlab task-runner pod> backup-utility
```
+Similarly to the Kubernetes case, if you have scaled out your GitLab
+cluster to use multiple application servers, you should pick a
+designated node (that won't be auto-scaled away) for running the
+backup rake task. Because the backup rake task is tightly coupled to
+the main Rails application, this is typically a node on which you're
+also running Unicorn/Puma and/or Sidekiq.
+
Example output:
```
@@ -402,6 +409,8 @@ an access key from the Google console first:
1. Select "Interoperability" and create an access key
1. Make note of the "Access Key" and "Secret" and replace them in the
configurations below
+1. In the buckets advanced settings ensure the Access Control option "Set object-level
+ and bucket-level permissions" is selected
1. Make sure you already have a bucket created
For Omnibus GitLab packages:
diff --git a/doc/topics/authentication/index.md b/doc/topics/authentication/index.md
index eef26612d5b..0fc7f57feab 100644
--- a/doc/topics/authentication/index.md
+++ b/doc/topics/authentication/index.md
@@ -21,7 +21,7 @@ This page gathers all the resources for the topic **Authentication** within GitL
- [Enforce Two-factor Authentication (2FA)](../../security/two_factor_authentication.md#enforce-two-factor-authentication-2fa)
- **Articles:**
- [How to Configure LDAP with GitLab CE](../../administration/auth/how_to_configure_ldap_gitlab_ce/index.md)
- - [How to Configure LDAP with GitLab EE](https://docs.gitlab.com/ee/administration/auth/how_to_configure_ldap_gitlab_ee/index.md)
+ - [How to Configure LDAP with GitLab EE](https://docs.gitlab.com/ee/administration/auth/how_to_configure_ldap_gitlab_ee/index.html)
- [Feature Highlight: LDAP Integration](https://about.gitlab.com/2014/07/10/feature-highlight-ldap-sync/)
- [Debugging LDAP](https://about.gitlab.com/handbook/support/workflows/support-engineering/ldap/debugging_ldap.html)
- **Integrations:**
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 195dd3e8846..fb3f9711711 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -230,8 +230,25 @@ can enable/disable Auto DevOps at either the project-level or instance-level.
1. Click **Save changes** for the changes to take effect.
NOTE: **Note:**
-Even when disabled at the instance level, project maintainers are still able to enable
-Auto DevOps at the project level.
+Even when disabled at the instance level, group owners and project maintainers are still able to enable
+Auto DevOps at group-level and project-level, respectively.
+
+### Enabling/disabling Auto DevOps at the group-level
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/52447) in GitLab 11.10.
+
+To enable or disable Auto DevOps at the group-level:
+
+1. Go to group's **Settings > CI/CD > Auto DevOps** page.
+1. Toggle the **Default to Auto DevOps pipeline** checkbox (checked to enable, unchecked to disable).
+1. Click **Save changes** button for the changes to take effect.
+
+When enabling or disabling Auto DevOps at group-level, group configuration will be implicitly used for
+the subgroups and projects inside that group, unless Auto DevOps is specifically enabled or disabled on
+the subgroup or project.
+
+NOTE: **Note**
+Only administrators and group owners are allowed to enable or disable Auto DevOps at group-level.
### Enabling/disabling Auto DevOps at the project-level
@@ -442,10 +459,10 @@ process. Auto Review Apps create a Review App for each branch.
Auto Review Apps will deploy your app to your Kubernetes cluster only. When no cluster
is available, no deployment will occur.
-The Review App will have a unique URL based on the project name, the branch
+The Review App will have a unique URL based on the project ID, the branch or tag
name, and a unique number, combined with the Auto DevOps base domain. For
-example, `user-project-branch-1234.example.com`. A link to the Review App shows
-up in the merge request widget for easy discovery. When the branch is deleted,
+example, `13083-review-project-branch-123456.example.com`. A link to the Review App shows
+up in the merge request widget for easy discovery. When the branch or tag is deleted,
for example after the merge request is merged, the Review App will automatically
be deleted.
@@ -506,17 +523,22 @@ enable them.
You can make use of [environment variables](#environment-variables) to automatically
scale your pod replicas.
-It's important to note that when a project is deployed to a Kubernetes cluster,
-it relies on a Docker image that has been pushed to the
-[GitLab Container Registry](../../user/project/container_registry.md). Kubernetes
-fetches this image and uses it to run the application. If the project is public,
-the image can be accessed by Kubernetes without any authentication, allowing us
-to have deployments more usable. If the project is private/internal, the
-Registry requires credentials to pull the image. Currently, this is addressed
-by providing `CI_JOB_TOKEN` as the password that can be used, but this token will
-no longer be valid as soon as the deployment job finishes. This means that
-Kubernetes can run the application, but in case it should be restarted or
-executed somewhere else, it cannot be accessed again.
+> [Introduced][ce-19507] in GitLab 11.0.
+
+For internal and private projects a [GitLab Deploy Token](../../user/project/deploy_tokens/index.md#gitlab-deploy-token)
+will be automatically created, when Auto DevOps is enabled and the Auto DevOps settings are saved. This Deploy Token
+can be used for permanent access to the registry.
+
+If the GitLab Deploy Token cannot be found, `CI_REGISTRY_PASSWORD` is
+used. Note that `CI_REGISTRY_PASSWORD` is only valid during deployment.
+This means that Kubernetes will be able to successfully pull the
+container image during deployment but in cases where the image needs to
+be pulled again, e.g. after pod eviction, Kubernetes will fail to do so
+as it will be attempting to fetch the image using
+`CI_REGISTRY_PASSWORD`.
+
+NOTE: **Note:**
+When the GitLab Deploy Token has been manually revoked, it won't be automatically created.
#### Migrations
@@ -551,15 +573,6 @@ The `/app` path is the directory of your project inside the docker image
as [configured by
Herokuish](https://github.com/gliderlabs/herokuish#paths)
-> [Introduced][ce-19507] in GitLab 11.0.
-
-For internal and private projects a [GitLab Deploy Token](../../user/project/deploy_tokens/index.md#gitlab-deploy-token)
-will be automatically created, when Auto DevOps is enabled and the Auto DevOps settings are saved. This Deploy Token
-can be used for permanent access to the registry.
-
-Note: **Note**
-When the GitLab Deploy Token has been manually revoked, it won't be automatically created.
-
### Auto Monitoring
NOTE: **Note:**
@@ -708,6 +721,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| `INCREMENTAL_ROLLOUT_MODE`| From GitLab 11.4, this variable, if present, can be used to enable an [incremental rollout](#incremental-rollout-to-production-premium) of your application for the production environment.<br/>Set to: <ul><li>`manual`, for manual deployment jobs.</li><li>`timed`, for automatic rollout deployments with a 5 minute delay each one.</li></ul> |
| `TEST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `test` job. If the variable is present, the job will not be created. |
| `CODE_QUALITY_DISABLED` | From GitLab 11.0, this variable can be used to disable the `codequality` job. If the variable is present, the job will not be created. |
+| `LICENSE_MANAGEMENT_DISABLED` | From GitLab 11.0, this variable can be used to disable the `license_management` job. If the variable is present, the job will not be created. |
| `SAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast` job. If the variable is present, the job will not be created. |
| `DEPENDENCY_SCANNING_DISABLED` | From GitLab 11.0, this variable can be used to disable the `dependency_scanning` job. If the variable is present, the job will not be created. |
| `CONTAINER_SCANNING_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast:container` job. If the variable is present, the job will not be created. |
@@ -1008,6 +1022,10 @@ planned for a subsequent release.
buildpack](#custom-buildpacks).
- Auto Test may fail because of a mismatch between testing frameworks. In this
case, you may need to customize your `.gitlab-ci.yml` with your test commands.
+- Auto Deploy may fail if it is unable to create a Kubernetes namespace and
+ service account for your project. See the
+ [troubleshooting failed deployments](../../user/project/clusters/index.md#troubleshooting-failed-deployment-jobs)
+ section to debug why these resources were not created.
### Disable the banner instance wide
diff --git a/doc/university/README.md b/doc/university/README.md
index 3e7d02770e4..cf13246067f 100644
--- a/doc/university/README.md
+++ b/doc/university/README.md
@@ -4,12 +4,9 @@ comments: false
# GitLab University
-GitLab University is the best place to learn about **Version Control with Git and GitLab**.
+GitLab University is a great place to start when learning about version control with Git and GitLab, as well as other GitLab features.
-It doesn't replace, but accompanies our great [Documentation](https://docs.gitlab.com)
-and [Blog Articles](https://about.gitlab.com/blog/).
-
-Would you like to contribute to GitLab University? Then please take a look at our contribution [process](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/PROCESS.md) for more information.
+If you're looking for a GitLab subscription for _your university_, see our [Education](https://about.gitlab.com/solutions/education/) page.
## GitLab University Curriculum
diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md
index 01979f12a01..22011cc6967 100644
--- a/doc/user/admin_area/settings/continuous_integration.md
+++ b/doc/user/admin_area/settings/continuous_integration.md
@@ -20,13 +20,13 @@ From now on, every existing project and newly created ones that don't have a
`.gitlab-ci.yml`, will use the Auto DevOps pipelines.
If you want to disable it for a specific project, you can do so in
-[its settings](../../../topics/autodevops/index.md##enablingdisabling-auto-devops).
+[its settings](../../../topics/autodevops/index.md#enablingdisabling-auto-devops).
## Maximum artifacts size **[CORE ONLY]**
The maximum size of the [job artifacts][art-yml] can be set in the Admin area
of your GitLab instance. The value is in *MB* and the default is 100MB per job;
-on GitLab.com it's [set to 1G](../../gitlab_com/index.md#gitlab-ci-cd).
+on GitLab.com it's [set to 1G](../../gitlab_com/index.md#gitlab-cicd).
To change it:
@@ -40,7 +40,7 @@ The default expiration time of the [job artifacts](../../../administration/job_a
can be set in the Admin area of your GitLab instance. The syntax of duration is
described in [`artifacts:expire_in`](../../../ci/yaml/README.md#artifactsexpire_in)
and the default value is `30 days`. On GitLab.com they
-[never expire](../../gitlab_com/index.md#gitlab-ci-cd).
+[never expire](../../gitlab_com/index.md#gitlab-cicd).
1. Go to **Admin area > Settings > Continuous Integration and Deployment**.
1. Change the value of default expiration time.
diff --git a/doc/user/discussions/index.md b/doc/user/discussions/index.md
index 90936034fea..23b9604a456 100644
--- a/doc/user/discussions/index.md
+++ b/doc/user/discussions/index.md
@@ -11,7 +11,7 @@ You can leave a comment in the following places:
- commit diffs
There are standard comments, and you also have the option to create a comment
-in the form of a threaded discussion. A comment can also be [turned into a discussion](#start-a-discussion-by-replying-to-a-non-discussion-comment)
+in the form of a threaded discussion. A comment can also be [turned into a discussion](#start-a-discussion-by-replying-to-a-standard-comment)
when it receives a reply.
The comment area supports [Markdown] and [quick actions]. You can edit your own
@@ -19,13 +19,14 @@ comment at any time, and anyone with [Maintainer access level][permissions] or
higher can also edit a comment made by someone else.
You can also reply to a comment notification email to reply to the comment if
-[Reply by email] is configured for your GitLab instance. Replying to a standard comment
+[Reply by email] is configured for your GitLab instance. Replying to a standard comment
creates another standard comment. Replying to a discussion comment creates a reply in the
discussion thread. Email replies support [Markdown] and [quick actions], just as if you replied from the web.
## Resolvable comments and discussions
> **Notes:**
+>
> - The main feature was [introduced][ce-5022] in GitLab 8.11.
> - Resolvable discussions can be added only to merge request diffs.
@@ -276,7 +277,7 @@ edit existing comments. Non-team members are restricted from adding or editing c
| :-----------: | :----------: |
| ![Comment form member](img/lock_form_member.png) | ![Comment form non-member](img/lock_form_non_member.png) |
-Additionally locked issues can not be reopened.
+Additionally, locked issues and merge requests can not be reopened.
## Filtering notes
@@ -357,7 +358,7 @@ Clicking on the **Reply to comment** button will bring the reply area into focus
![Reply to comment feature](img/reply_to_comment.gif)
-Relying to a non-discussion comment will convert the non-discussion comment to a
+Relying to a non-discussion comment will convert the non-discussion comment to a
threaded discussion once the reply is submitted. This conversion is considered an edit
to the original comment, so a note about when it was last edited will appear underneath it.
diff --git a/doc/user/gitlab_com/index.md b/doc/user/gitlab_com/index.md
index 762cf911fcf..5dc798fd8d3 100644
--- a/doc/user/gitlab_com/index.md
+++ b/doc/user/gitlab_com/index.md
@@ -51,7 +51,7 @@ Below are the settings for [GitLab Pages].
| TLS certificates support| yes | no |
The maximum size of your Pages site is regulated by the artifacts maximum size
-which is part of [GitLab CI/CD](#gitlab-ci-cd).
+which is part of [GitLab CI/CD](#gitlab-cicd).
## GitLab CI/CD
diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md
index 8cdfb13a97b..e67795b9bae 100644
--- a/doc/user/group/clusters/index.md
+++ b/doc/user/group/clusters/index.md
@@ -25,8 +25,22 @@ deployments.
| Application | GitLab version | Description | Helm Chart |
| ----------- | -------------- | ----------- | ---------- |
-| [Helm Tiller](https://docs.helm.sh) | 10.2+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. | n/a |
-| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps](../../../topics/autodevops/index.md) or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) |
+| [Helm Tiller](https://docs.helm.sh) | 11.6+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. | n/a |
+| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress) | 11.6+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps](../../../topics/autodevops/index.md) or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) |
+| [Cert-Manager](https://docs.cert-manager.io/en/latest/) | 11.6+ | Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by [Let's Encrypt](https://letsencrypt.org/) and ensure that certificates are valid and up-to-date. | [stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager) |
+
+NOTE: **Note:**
+Some [cluster
+applications](../../project/clusters/index.md#installing-applications)
+are installable only for a project-level cluster. Support for installing these
+applications in a group-level cluster is planned for future releases. For updates, see:
+
+- Support installing [Runner in group-level
+ clusters](https://gitlab.com/gitlab-org/gitlab-ce/issues/51988)
+- Support installing [JupyterHub in group-level
+ clusters](https://gitlab.com/gitlab-org/gitlab-ce/issues/51989)
+- Support installing [Prometheus in group-level
+ clusters](https://gitlab.com/gitlab-org/gitlab-ce/issues/51963)
## RBAC compatibility
@@ -72,11 +86,10 @@ The domain should have a wildcard DNS configured to the Ingress IP address.
## Environment scopes **[PREMIUM]**
-When adding more than one Kubernetes cluster to your project, you need
-to differentiate them with an environment scope. The environment scope
-associates clusters with [environments](../../../ci/environments.md)
-similar to how the [environment-specific
-variables](../../../ci/variables/README.md#limiting-environment-scopes-of-variables)
+When adding more than one Kubernetes cluster to your project, you need to differentiate
+them with an environment scope. The environment scope associates clusters with
+[environments](../../../ci/environments.md) similar to how the
+[environment-specific variables](https://docs.gitlab.com/ee/ci/variables/README.html#limiting-environment-scopes-of-variables-premium)
work.
While evaluating which environment matches the environment scope of a
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index c1f50bcc593..3bcfd30079d 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -63,9 +63,8 @@ together in a single list view.
## Create a new group
-> **Notes:**
-> - For a list of words that are not allowed to be used as group names see the
-> [reserved names](../reserved_names.md).
+> For a list of words that are not allowed to be used as group names see the
+> [reserved names](../reserved_names.md).
You can create a group in GitLab from:
@@ -168,20 +167,21 @@ Alternatively, you can [lock the sharing with group feature](#share-with-group-l
In GitLab Enterprise Edition it is possible to manage GitLab group memberships using LDAP groups.
See [the GitLab Enterprise Edition documentation](../../integration/ldap.md) for more information.
-## Transfer groups to another group
+## Transferring groups
-From 10.5 there are two different ways to transfer a group:
+From GitLab 10.5, groups can be transferred in the following ways:
-- Either by transferring a group into another group (making it a subgroup of that group).
-- Or by converting a subgroup into a root group (a group with no parent).
+- Top-level groups can be transferred to a group, converting them into subgroups.
+- Subgroups can be transferred to a new parent group.
+- Subgroups can be transferred out from a parent group, converting them into top-level groups.
-Please make sure to understand that:
+When transferring groups, note:
-- Changing a group's parent can have unintended side effects. See [Redirects when changing repository paths](https://docs.gitlab.com/ce/user/project/index.html#redirects-when-changing-repository-paths)
-- You can only transfer the group to a group you manage.
+- Changing a group's parent can have unintended side effects. See [Redirects when changing repository paths](../project/index.md#redirects-when-changing-repository-paths).
+- You can only transfer groups to groups you manage.
- You will need to update your local repositories to point to the new location.
-- If the parent group's visibility is lower than the group current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility.
-- Only explicit group membership is transferred, not the inherited membership. If this would leave the group without an owner, the transferring user is added as owner instead.
+- If the parent group's visibility is lower than the group's current visibility, visibility levels for subgroups and projects will be changed to match the new parent group's visibility.
+- Only explicit group membership is transferred, not inherited membership. If the group's owners have only inherited membership, this would leave the group without an owner. In this case, the user transferring the group becomes the group's owner.
## Group settings
diff --git a/doc/user/index.md b/doc/user/index.md
index b84879601ff..d408504249e 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -36,34 +36,34 @@ To get familiar with the concepts needed to develop code on GitLab, read the fol
GitLab is a Git-based platform that integrates a great number of essential tools for software development and deployment, and project management:
-- Hosting code in repositories with version control
+- Hosting code in repositories with version control.
- Tracking proposals for new implementations, bug reports, and feedback with a
- fully featured [Issue Tracker](project/issues/index.md#issue-tracker)
-- Organizing and prioritizing with [Issue Boards](project/issues/index.md#issue-boards)
+ fully featured [Issue Tracker](project/issues/index.md#issue-tracker).
+- Organizing and prioritizing with [Issue Boards](project/issues/index.md#issue-board).
- Reviewing code in [Merge Requests](project/merge_requests/index.md) with live-preview changes per
- branch with [Review Apps](../ci/review_apps/index.md)
-- Building, testing and deploying with built-in [Continuous Integration](../ci/README.md)
-- Deploying personal and professional static websites with [GitLab Pages](project/pages/index.md)
-- Integrating with Docker by using [GitLab Container Registry](project/container_registry.md)
-- Tracking the development lifecycle by usingn [GitLab Cycle Analytics](project/cycle_analytics.md)
+ branch with [Review Apps](../ci/review_apps/index.md).
+- Building, testing, and deploying with built-in [Continuous Integration](../ci/README.md).
+- Deploying personal and professional static websites with [GitLab Pages](project/pages/index.md).
+- Integrating with Docker by using [GitLab Container Registry](project/container_registry.md).
+- Tracking the development lifecycle by using [GitLab Cycle Analytics](project/cycle_analytics.md).
With GitLab Enterprise Edition, you can also:
-- Provide support with [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html)
+- Provide support with [Service Desk](https://docs.gitlab.com/ee/user/project/service_desk.html).
- Improve collaboration with
[Merge Request Approvals](https://docs.gitlab.com/ee/user/project/merge_requests/index.html#merge-request-approvals),
[Multiple Assignees for Issues](https://docs.gitlab.com/ee/user/project/issues/multiple_assignees_for_issues.html),
- and [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards)
-- Create formal relationships between issues with [Related Issues](https://docs.gitlab.com/ee/user/project/issues/related_issues.html)
+ and [Multiple Issue Boards](project/issue_board.md#multiple-issue-boards-starter).
+- Create formal relationships between issues with [Related Issues](https://docs.gitlab.com/ee/user/project/issues/related_issues.html).
- Use [Burndown Charts](https://docs.gitlab.com/ee/user/project/milestones/burndown_charts.html) to track progress during a sprint or while working on a new version of their software.
-- Leverage [Elasticsearch](https://docs.gitlab.com/ee/integration/elasticsearch.html) with [Advanced Global Search](https://docs.gitlab.com/ee/user/search/advanced_global_search.html) and [Advanced Syntax Search](https://docs.gitlab.com/ee/user/search/advanced_search_syntax.html) for faster, more advanced code search across your entire GitLab instance
-- [Authenticate users with Kerberos](https://docs.gitlab.com/ee/integration/kerberos.html)
+- Leverage [Elasticsearch](https://docs.gitlab.com/ee/integration/elasticsearch.html) with [Advanced Global Search](https://docs.gitlab.com/ee/user/search/advanced_global_search.html) and [Advanced Syntax Search](https://docs.gitlab.com/ee/user/search/advanced_search_syntax.html) for faster, more advanced code search across your entire GitLab instance.
+- [Authenticate users with Kerberos](https://docs.gitlab.com/ee/integration/kerberos.html).
- [Mirror a repository](https://docs.gitlab.com/ee/workflow/repository_mirroring.html) from elsewhere on your local server.
-- [Export issues as CSV](https://docs.gitlab.com/ee/user/project/issues/csv_export.html)
-- View your entire CI/CD pipeline involving more than one project with [Multiple-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html)
-- [Lock files](https://docs.gitlab.com/ee/user/project/file_lock.html) to prevent conflicts
-- View the current health and status of each CI environment running on Kubernetes with [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html)
-- Leverage continuous delivery method with [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html)
+- [Export issues as CSV](https://docs.gitlab.com/ee/user/project/issues/csv_export.html).
+- View your entire CI/CD pipeline involving more than one project with [Multiple-Project Pipelines](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html).
+- [Lock files](https://docs.gitlab.com/ee/user/project/file_lock.html) to prevent conflicts.
+- View the current health and status of each CI environment running on Kubernetes with [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html).
+- Leverage continuous delivery method with [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html).
You can also [integrate](project/integrations/project_services.md) GitLab with
numerous third-party applications, such as Mattermost, Microsoft Teams, Trello,
@@ -125,7 +125,7 @@ merge requests, code snippets, and commits.
When performing inline reviews to implementations
to your codebase through merge requests you can
-gather feedback through [resolvable discussions](discussions/index.md#resolvable-discussions).
+gather feedback through [resolvable discussions](discussions/index.md#resolvable-comments-and-discussions).
### GitLab Flavored Markdown (GFM)
diff --git a/doc/user/instance_statistics/convdev.md b/doc/user/instance_statistics/convdev.md
index 247be1fb392..2c9e0ecbf65 100644
--- a/doc/user/instance_statistics/convdev.md
+++ b/doc/user/instance_statistics/convdev.md
@@ -2,7 +2,7 @@
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30469) in GitLab 9.3.
-NOTE: **NOTE**
+NOTE: **Note:**
Your GitLab instance's [usage ping](../admin_area/settings/usage_statistics.md#usage-ping-core-only) must be activated in order to use this feature.
The Conversational Development Index (ConvDev Index) gives you an overview of your entire
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index a7a87773eec..d8bc3a9187e 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -288,15 +288,15 @@ On Linux, you can download [Noto Color Emoji](https://www.google.com/get/noto/he
Ubuntu 18.04 (like many modern Linux distros) has this font installed by default.
```
-Sometimes you want to <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/monkey.png" width="20px" height="20px"> around a bit and add some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/star2.png" width="20px" height="20px"> to your <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/speech_balloon.png" width="20px" height="20px">. Well we have a gift for you:
+Sometimes you want to <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/monkey.png" width="20px" height="20px"> around a bit and add some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/star2.png" width="20px" height="20px"> to your <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/speech_balloon.png" width="20px" height="20px">. Well we have a gift for you:
-<img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/zap.png" width="20px" height="20px">You can use emoji anywhere GFM is supported. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/v.png" width="20px" height="20px">
+<img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/zap.png" width="20px" height="20px">You can use emoji anywhere GFM is supported. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/v.png" width="20px" height="20px">
-You can use it to point out a <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/bug.png" width="20px" height="20px"> or warn about <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/speak_no_evil.png" width="20px" height="20px"> patches. And if someone improves your really <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/snail.png" width="20px" height="20px"> code, send them some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/birthday.png" width="20px" height="20px">. People will <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/heart.png" width="20px" height="20px"> you for that.
+You can use it to point out a <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/bug.png" width="20px" height="20px"> or warn about <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/speak_no_evil.png" width="20px" height="20px"> patches. And if someone improves your really <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/snail.png" width="20px" height="20px"> code, send them some <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/birthday.png" width="20px" height="20px">. People will <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/heart.png" width="20px" height="20px"> you for that.
-If you are new to this, don't be <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/fearful.png" width="20px" height="20px">. You can easily join the emoji <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/family.png" width="20px" height="20px">. All you need to do is to look up one of the supported codes.
+If you are new to this, don't be <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/fearful.png" width="20px" height="20px">. You can easily join the emoji <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/family.png" width="20px" height="20px">. All you need to do is to look up one of the supported codes.
-Consult the [Emoji Cheat Sheet](https://www.webfx.com/tools/emoji-cheat-sheet/) for a list of all supported emoji codes. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/app/assets/images/emoji/thumbsup.png" width="20px" height="20px">
+Consult the [Emoji Cheat Sheet](https://www.webfx.com/tools/emoji-cheat-sheet/) for a list of all supported emoji codes. <img src="https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/-/emojis/1/thumbsup.png" width="20px" height="20px">
Most emoji are natively supported on macOS, Windows, iOS, Android and will fallback to image-based emoji where there is lack of support.
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 833b9b66102..adc0f4d568b 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -23,6 +23,12 @@ To add or import a user, you can follow the
See our [product handbook on permissions](https://about.gitlab.com/handbook/product#permissions-in-gitlab)
+## Instance-wide user permissions
+
+By default, users can create top-level groups and change their
+usernames. A GitLab administrator can configure the GitLab instance to
+[modify this behavior](../administration/user_settings.md).
+
## Project members permissions
NOTE: **Note:**
@@ -42,6 +48,8 @@ The following table depicts the various user permission levels in a project.
| See a job log | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
| Download and browse job artifacts | ✓ [^3] | ✓ | ✓ | ✓ | ✓ |
| View wiki pages | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
+| Create and edit wiki pages | | | ✓ | ✓ | ✓ |
+| Delete wiki pages | | | | ✓ | ✓ |
| View license management reports **[ULTIMATE]** | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| View Security reports **[ULTIMATE]** | ✓ [^1] | ✓ | ✓ | ✓ | ✓ |
| View project code | [^1] | ✓ | ✓ | ✓ | ✓ |
@@ -74,7 +82,6 @@ The following table depicts the various user permission levels in a project.
| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
| Remove non-protected branches | | | ✓ | ✓ | ✓ |
| Add tags | | | ✓ | ✓ | ✓ |
-| Write a wiki | | | ✓ | ✓ | ✓ |
| Cancel and retry jobs | | | ✓ | ✓ | ✓ |
| Create or update commit status | | | ✓ | ✓ | ✓ |
| Update a container registry | | | ✓ | ✓ | ✓ |
@@ -237,7 +244,7 @@ The regex pattern format is Ruby, but it needs to be convertible to JavaScript,
Here are some examples:
-- Use `\.internal@domain\.com` to mark email addresses containing ".internal@domain.com" internal.
+- Use `\.internal@domain\.com$` to mark email addresses ending with ".internal@domain.com" internal.
- Use `^(?:(?!\.ext@domain\.com).)*$\r?` to mark users with email addresses NOT including .ext@domain.com internal.
Please be aware that this regex could lead to a DOS attack, [see](https://en.wikipedia.org/wiki/ReDoS?) ReDos on Wikipedia.
diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md
index 8e5fe4b0fb9..b74bd81d467 100644
--- a/doc/user/profile/account/two_factor_authentication.md
+++ b/doc/user/profile/account/two_factor_authentication.md
@@ -59,6 +59,7 @@ of recovery codes.
### Enable 2FA via U2F device
> **Notes:**
+>
> - GitLab officially only supports [Yubikey] U2F devices.
> - Support for U2F devices was added in GitLab 8.8.
diff --git a/doc/user/profile/img/personal_access_tokens.png b/doc/user/profile/img/personal_access_tokens.png
deleted file mode 100644
index d29f4cb0a20..00000000000
--- a/doc/user/profile/img/personal_access_tokens.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/profile/personal_access_tokens.md b/doc/user/profile/personal_access_tokens.md
index 7d55048c994..3a4d09c35d9 100644
--- a/doc/user/profile/personal_access_tokens.md
+++ b/doc/user/profile/personal_access_tokens.md
@@ -20,21 +20,19 @@ at midnight UTC.
You can create as many personal access tokens as you like from your GitLab
profile.
-1. Log in to your GitLab account.
-1. Go to your **Profile settings**.
-1. Go to **Access tokens**.
-1. Choose a name and optionally an expiry date for the token.
+1. Log in to GitLab.
+1. In the upper-right corner, click your avatar and select **Settings**.
+1. On the **User Settings** menu, select **Access Tokens**.
+1. Choose a name and optional expiry date for the token.
1. Choose the [desired scopes](#limiting-scopes-of-a-personal-access-token).
-1. Click on **Create personal access token**.
+1. Click the **Create personal access token** button.
1. Save the personal access token somewhere safe. Once you leave or refresh
the page, you won't be able to access it again.
-![Personal access tokens page](img/personal_access_tokens.png)
+### Revoking a personal access token
-## Revoking a personal access token
-
-At any time, you can revoke any personal access token by just clicking the
-respective **Revoke** button under the 'Active personal access tokens' area.
+At any time, you can revoke any personal access token by clicking the
+respective **Revoke** button under the **Active Personal Access Token** area.
## Limiting scopes of a personal access token
diff --git a/doc/user/project/badges.md b/doc/user/project/badges.md
index 19eb95099ce..8849dd2d684 100644
--- a/doc/user/project/badges.md
+++ b/doc/user/project/badges.md
@@ -63,6 +63,12 @@ are available:
- `%{commit_sha}`: ID of the most recent commit to the default branch of a
project's repository
+NOTE: **Note:**
+Placeholders allow badges to expose otherwise-private information, such as the
+default branch or commit SHA when the project is configured to have a private
+repository. This is by design, as badges are intended to be used publicly. Avoid
+using these placeholders if the information is sensitive.
+
## API
You can also configure badges via the GitLab API. As in the settings, there is
diff --git a/doc/user/project/bulk_editing.md b/doc/user/project/bulk_editing.md
index fead99c5e88..d0c7daf4692 100644
--- a/doc/user/project/bulk_editing.md
+++ b/doc/user/project/bulk_editing.md
@@ -1,6 +1,7 @@
# Bulk editing issues and merge requests
> **Notes:**
+>
> - A permission level of `Reporter` or higher is required in order to manage
> issues.
> - A permission level of `Developer` or higher is required in order to manage
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index ef85b2f6837..5a74ac96e83 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -69,12 +69,20 @@ new Kubernetes cluster to your project:
- **Number of nodes** - Enter the number of nodes you wish the cluster to have.
- **Machine type** - The [machine type](https://cloud.google.com/compute/docs/machine-types)
of the Virtual Machine instance that the cluster will be based on.
- - **RBAC-enabled cluster** - Leave this checked if using default GKE creation options, see the [RBAC section](#role-based-access-control-rbac-core-only) for more information.
+ - **RBAC-enabled cluster** - Leave this checked if using default GKE creation options, see the [RBAC section](#role-based-access-control-rbac) for more information.
1. Finally, click the **Create Kubernetes cluster** button.
After a couple of minutes, your cluster will be ready to go. You can now proceed
to install some [pre-defined applications](#installing-applications).
+NOTE: **Note:**
+GitLab requires basic authentication enabled and a client certificate issued for
+the cluster in order to setup an [initial service
+account](#access-controls). Starting from [GitLab
+11.10](https://gitlab.com/gitlab-org/gitlab-ce/issues/58208), the cluster
+creation process will explicitly request that basic authentication and
+client certificate is enabled.
+
## Adding an existing Kubernetes cluster
To add an existing Kubernetes cluster to your project:
@@ -350,7 +358,7 @@ by GitLab before installing any of the applications.
| ----------- | :------------: | ----------- | --------------- |
| [Helm Tiller](https://docs.helm.sh/) | 10.2+ | Helm is a package manager for Kubernetes and is required to install all the other applications. It is installed in its own pod inside the cluster which can run the `helm` CLI in a safe environment. | n/a |
| [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps] or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) |
-| [Cert Manager](http://docs.cert-manager.io/en/latest/) | 11.6+ | Cert Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert Manager on your cluster will issue a certificate by [Let's Encrypt](https://letsencrypt.org/) and ensure that certificates are valid and up-to-date. | [stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager) |
+| [Cert-Manager](https://docs.cert-manager.io/en/latest/) | 11.6+ | Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by [Let's Encrypt](https://letsencrypt.org/) and ensure that certificates are valid and up-to-date. | [stable/cert-manager](https://github.com/helm/charts/tree/master/stable/cert-manager) |
| [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications. | [stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus) |
| [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) |
| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use a [custom Jupyter image](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) that installs additional useful packages on top of the base Jupyter. Authentication will be enabled only for [project members](../members/index.md) with [Developer or higher](../../permissions.md) access to the project. You will also see ready-to-use DevOps Runbooks built with Nurtch's [Rubix library](https://github.com/amit1rrr/rubix). More information on creating executable runbooks can be found in [our Nurtch documentation](runbooks/index.md#nurtch-executable-runbooks). | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) |
@@ -477,12 +485,9 @@ differentiate the new cluster with the rest.
## Setting the environment scope **[PREMIUM]**
-When adding more than one Kubernetes clusters to your project, you need
-to differentiate them with an environment scope. The environment scope
-associates clusters with [environments](../../../ci/environments.md)
-similar to how the [environment-specific
-variables](../../../ci/variables/README.md#limiting-environment-scopes-of-variables)
-work.
+When adding more than one Kubernetes cluster to your project, you need to differentiate
+them with an environment scope. The environment scope associates clusters with [environments](../../../ci/environments.md) similar to how the
+[environment-specific variables](https://docs.gitlab.com/ee/ci/variables/README.html#limiting-environment-scopes-of-variables-premium) work.
The default environment scope is `*`, which means all jobs, regardless of their
environment, will use that cluster. Each scope can only be used by a single
@@ -545,32 +550,33 @@ GitLab CI/CD build environment.
| `KUBE_CA_PEM_FILE` | Path to a file containing PEM data. Only present if a custom CA bundle was specified. |
| `KUBE_CA_PEM` | (**deprecated**) Raw PEM data. Only if a custom CA bundle was specified. |
| `KUBECONFIG` | Path to a file containing `kubeconfig` for this deployment. CA bundle would be embedded if specified. This config also embeds the same token defined in `KUBE_TOKEN` so you likely will only need this variable. This variable name is also automatically picked up by `kubectl` so you won't actually need to reference it explicitly if using `kubectl`. |
-| `KUBE_INGRESS_BASE_DOMAIN` | From GitLab 11.8, this variable can be used to set a domain per cluster. See [cluster domains](#base-domain) for more information. | 
+| `KUBE_INGRESS_BASE_DOMAIN` | From GitLab 11.8, this variable can be used to set a domain per cluster. See [cluster domains](#base-domain) for more information. |
NOTE: **NOTE:**
Prior to GitLab 11.5, `KUBE_TOKEN` was the Kubernetes token of the main
service account of the cluster integration.
-### Troubleshooting missing `KUBECONFIG` or `KUBE_TOKEN`
+### Troubleshooting failed deployment jobs
+
+GitLab will create a namespace and service account specifically for your
+deployment jobs. These resources are created just before the deployment
+job starts. Sometimes there may be errors that cause their creation to fail.
+
+In such instances, your job will fail with the message:
-GitLab will create a new service account specifically for your CI builds. The
-new service account is created when the cluster is added to the project.
-Sometimes there may be errors that cause the service account creation to fail.
+```The job failed to complete prerequisite tasks```
-In such instances, your build will not be passed the `KUBECONFIG` or
-`KUBE_TOKEN` variables and, if you are using Auto DevOps, your Auto DevOps
-pipelines will no longer trigger a `production` deploy build. You will need to
-check the [logs](../../../administration/logs.md) to debug why the service
-account creation failed.
+You will need to check the [logs](../../../administration/logs.md) to debug
+why the namespace and service account creation failed.
A common reason for failure is that the token you gave GitLab did not have
[`cluster-admin`](https://kubernetes.io/docs/reference/access-authn-authz/rbac/#user-facing-roles)
privileges as GitLab expects.
-Another common problem for why these variables are not being passed to your
-builds is that they must have a matching
+Another common problem is caused by a missing `KUBECONFIG` or `KUBE_TOKEN`.
+To be passed to your job, it must have a matching
[`environment:name`](../../../ci/environments.md#defining-environments). If
-your build has no `environment:name` set, it will not be passed the Kubernetes
+your job has no `environment:name` set, it will not be passed the Kubernetes
credentials.
## Monitoring your Kubernetes cluster **[ULTIMATE]**
diff --git a/doc/user/project/container_registry.md b/doc/user/project/container_registry.md
index dec6eac2508..83b268db967 100644
--- a/doc/user/project/container_registry.md
+++ b/doc/user/project/container_registry.md
@@ -1,7 +1,8 @@
# GitLab Container Registry
> **Notes:**
-> [Introduced][ce-4040] in GitLab 8.8.
+>
+> - [Introduced][ce-4040] in GitLab 8.8.
> - Docker Registry manifest `v1` support was added in GitLab 8.9 to support Docker
> versions earlier than 1.10.
> - This document is about the user guide. To learn how to enable GitLab Container
@@ -10,7 +11,7 @@
> - Starting from GitLab 8.12, if you have 2FA enabled in your account, you need
> to pass a [personal access token][pat] instead of your password in order to
> login to GitLab's Container Registry.
-> - Multiple level image names support was added in GitLab 9.1
+> - Multiple level image names support was added in GitLab 9.1.
With the Docker Container Registry integrated into GitLab, every project can
have its own space to store its Docker images.
@@ -41,6 +42,7 @@ to enable it.
## Build and push images
> **Notes:**
+>
> - Moving or renaming existing container registry repositories is not supported
> once you have pushed images because the images are signed, and the
> signature includes the repository name.
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 4148310dc98..4d19464cb7a 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -17,7 +17,7 @@ When you create a project in GitLab, you'll have access to a large number of
- [Issue tracker](issues/index.md): Discuss implementations with your team within issues
- [Issue Boards](issue_board.md): Organize and prioritize your workflow
- - [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards): Allow your teams to create their own workflows (Issue Boards) for the same project **[STARTER]**
+ - [Multiple Issue Boards](issue_board.md#multiple-issue-boards-starter): Allow your teams to create their own workflows (Issue Boards) for the same project **[STARTER]**
- [Repositories](repository/index.md): Host your code in a fully
integrated platform
- [Branches](repository/branches/index.md): use Git branching strategies to
diff --git a/doc/user/project/integrations/img/issue_configuration.png b/doc/user/project/integrations/img/issue_configuration.png
deleted file mode 100644
index 5dfd85974d8..00000000000
--- a/doc/user/project/integrations/img/issue_configuration.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md
index 754711f5919..a90167b9767 100644
--- a/doc/user/project/integrations/jira.md
+++ b/doc/user/project/integrations/jira.md
@@ -56,6 +56,7 @@ When connecting to **JIRA Cloud**, which supports authentication via API token,
### Configuring GitLab
> **Notes:**
+>
> - The currently supported Jira versions are `v6.x` and `v7.x.`. GitLab 7.8 or
> higher is required.
> - GitLab 8.14 introduced a new way to integrate with Jira which greatly simplified
@@ -142,6 +143,7 @@ the same goal:
where `PROJECT-1` is the issue ID of the Jira project.
> **Notes:**
+>
> - Only commits and merges into the project's default branch (usually **master**) will
> close an issue in Jira. You can change your projects default branch under
> [project settings](img/jira_project_settings.png).
diff --git a/doc/user/project/integrations/prometheus.md b/doc/user/project/integrations/prometheus.md
index 26989e2a8a4..43a9e24526d 100644
--- a/doc/user/project/integrations/prometheus.md
+++ b/doc/user/project/integrations/prometheus.md
@@ -13,7 +13,7 @@ There are two ways to set up Prometheus integration, depending on where your app
- For deployments on Kubernetes, GitLab can automatically [deploy and manage Prometheus](#managed-prometheus-on-kubernetes).
- For other deployment targets, simply [specify the Prometheus server](#manual-configuration-of-prometheus).
-Once enabled, GitLab will automatically detect metrics from known services in the [metric library](#monitoring-ci-cd-environments).
+Once enabled, GitLab will automatically detect metrics from known services in the [metric library](#monitoring-cicd-environments).
## Enabling Prometheus Integration
diff --git a/doc/user/project/integrations/redmine.md b/doc/user/project/integrations/redmine.md
index 8112aa21859..bac7eecfce4 100644
--- a/doc/user/project/integrations/redmine.md
+++ b/doc/user/project/integrations/redmine.md
@@ -18,9 +18,7 @@
![Redmine configuration](img/redmine_configuration.png)
-1. To disable the internal issue tracking system in a project, navigate to the General page, expand [Permissions](../settings/index.md#sharing-and-permissions), and slide the Issues switch invalid.
-
- ![Issue configuration](img/issue_configuration.png)
+1. To disable the internal issue tracking system in a project, navigate to the General page, expand the [permissions](../settings/index.md#sharing-and-permissions) section and switch the **Issues** toggle to disabled.
## Referencing issues in Redmine
diff --git a/doc/user/project/integrations/webhooks.md b/doc/user/project/integrations/webhooks.md
index c3fc6d4b859..d324e0de0cc 100644
--- a/doc/user/project/integrations/webhooks.md
+++ b/doc/user/project/integrations/webhooks.md
@@ -1041,7 +1041,12 @@ X-Gitlab-Event: Pipeline Hook
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
- "runner": null,
+ "runner": {
+ "id":380987,
+ "description":"shared-runners-manager-6.gitlab.com",
+ "active":true,
+ "is_shared":true
+ },
"artifacts_file":{
"filename": null,
"size": null
@@ -1062,7 +1067,12 @@ X-Gitlab-Event: Pipeline Hook
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
- "runner": null,
+ "runner": {
+ "id":380987,
+ "description":"shared-runners-manager-6.gitlab.com",
+ "active":true,
+ "is_shared":true
+ },
"artifacts_file":{
"filename": null,
"size": null
@@ -1083,7 +1093,12 @@ X-Gitlab-Event: Pipeline Hook
"username": "root",
"avatar_url": "http://www.gravatar.com/avatar/e32bd13e2add097461cb96824b7a829c?s=80\u0026d=identicon"
},
- "runner": null,
+ "runner": {
+ "id":380987,
+ "description":"shared-runners-manager-6.gitlab.com",
+ "active":true,
+ "is_shared":true
+ },
"artifacts_file":{
"filename": null,
"size": null
diff --git a/doc/user/project/integrations/youtrack.md b/doc/user/project/integrations/youtrack.md
index 2ab14a8db2c..a2a468b6fe4 100644
--- a/doc/user/project/integrations/youtrack.md
+++ b/doc/user/project/integrations/youtrack.md
@@ -1,31 +1,38 @@
# YouTrack Service
-JetBrains YouTrack is a web-based issue tracking and project management platform.
-Please refer official [documentation](https://www.jetbrains.com/help/youtrack/standalone/YouTrack-Documentation.html) for details about YouTrack itself.
+JetBrains [YouTrack](https://www.jetbrains.com/help/youtrack/standalone/YouTrack-Documentation.html) is a web-based issue tracking and project management platform.
+You can configure YouTrack as an [External Issue Tracker](../../../integration/external-issue-tracker.md) in GitLab.
-1. To enable the YouTrack integration in a project, navigate to the
-[Integrations page](project_services.md#accessing-the-project-services), click
-the **YouTrack** service, and fill in the required details on the page as described
-in the table below.
+## Enable the YouTrack integration
- | Field | Description |
- | ----- | ----------- |
- | `description` | A name for the issue tracker (to differentiate between instances, for example) |
- | `project_url` | The URL to the project in YouTrack which is being linked to this GitLab project |
- | `issues_url` | The URL to the issue in YouTrack project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. |
+To enable YouTrack integration in a project:
- Once you have configured and enabled YouTrack you'll see the YouTrack link on the GitLab project pages that takes you to the appropriate YouTrack project.
+1. Navigate to the project's **Settings > [Integrations](project_services.md#accessing-the-project-services)** page.
+1. Click the **YouTrack** service, ensure it's active, and enter the required details on the page as described in the table below.
-1. To disable the internal issue tracking system in a project, navigate to the General page, expand [Permissions](../settings/index.md#sharing-and-permissions), and slide the Issues switch invalid.
+ | Field | Description |
+ |:----------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+ | **Description** | Name for the issue tracker (to differentiate between instances, for example). |
+ | **Project url** | URL to the project in YouTrack which is being linked to this GitLab project. |
+ | **Issues url** | URL to the issue in YouTrack project that is linked to this GitLab project. Note that the **Issues url** requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. |
- ![Issue configuration](img/issue_configuration.png)
+1. Click the **Save changes** button.
-## Referencing issues in YouTrack
+Once you have configured and enabled YouTrack, you'll see the YouTrack link on the GitLab project pages that takes you to the appropriate YouTrack project.
-Issues in YouTrack can be referenced as `<PROJECT>-<ID>` where `<PROJECT>`
-starts with a capital letter which is then followed by capital or lower case
-letters, numbers or underscores, and `<ID>` is a number (example `Api_32-143`).
+## Disable the internal issue tracker
-`<PROJECT>` part is included into issue_id and links can point any YouTrack
-project (`issues_url` + issue_id)
+To disable the internal issue tracker in a project:
+
+1. Navigate to the project's **Settings > General** page.
+1. Expand the [permissions section](../settings/index.md#sharing-and-permissions) and switch the **Issues** toggle to disabled.
+
+## Referencing YouTrack issues in GitLab
+
+Issues in YouTrack can be referenced as `<PROJECT>-<ID>`. `<PROJECT>`
+must start with a capital letter and can then be followed by capital or lower case
+letters, numbers or underscores. `<ID>` is a number. An example reference is `YT-101` or `Api_32-143`.
+
+References to `<PROJECT>-<ID>` in merge requests, commits, or comments are automatically linked to the YouTrack issue URL.
+For more information, see the [External Issue Tracker](../../../integration/external-issue-tracker.md) documentation.
diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md
index 66168080087..ca19ce4d328 100644
--- a/doc/user/project/issue_board.md
+++ b/doc/user/project/issue_board.md
@@ -28,11 +28,11 @@ Issue Boards** (version introduced in GitLab 8.11 - August 2016).
### Advanced features of Issue Boards
With [GitLab Starter](https://about.gitlab.com/pricing/), you can create
-[multiple issue boards](#multiple-issue-boards) for a given project. **[STARTER]**
+[multiple issue boards](#multiple-issue-boards-starter) for a given project. **[STARTER]**
With [GitLab Premium](https://about.gitlab.com/pricing/), you can also create multiple
-issue boards for your groups, and add lists for [assignees](#assignee-lists) and
-[milestones](#milestone-lists). **[PREMIUM]**
+issue boards for your groups, and add lists for [assignees](#assignee-lists-premium) and
+[milestones](#milestone-lists-premium). **[PREMIUM]**
Check all the [advanced features of Issue Boards](#gitlab-enterprise-features-for-issue-boards)
below.
@@ -97,7 +97,7 @@ If we have the labels "**backend**", "**frontend**", "**staging**", and
### Use cases for Multiple Issue Boards
-With [Multiple Issue Boards](#multiple-issue-boards), available only in
+With [Multiple Issue Boards](#multiple-issue-boards-starter), available only in
[GitLab Enterprise Edition](https://about.gitlab.com/pricing/),
each team can have their own board to organize their workflow individually.
@@ -220,7 +220,7 @@ Click the button at the top right to toggle focus mode on and off. In focus mode
The top of each list indicates the sum of issue weights for the issues that
belong to that list. This is useful when using boards for capacity allocation,
-especially in combination with [assignee lists](#assignee-lists).
+especially in combination with [assignee lists](#assignee-lists-premium).
![Issue Board summed weights](img/issue_board_summed_weights.png)
diff --git a/doc/user/project/issues/confidential_issues.md b/doc/user/project/issues/confidential_issues.md
index 8eada25234f..2c755e0fb4d 100644
--- a/doc/user/project/issues/confidential_issues.md
+++ b/doc/user/project/issues/confidential_issues.md
@@ -1,6 +1,6 @@
# Confidential issues
-> [Introduced][ce-3282] in GitLab 8.6.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3282) in GitLab 8.6.
Confidential issues are issues visible only to members of a project with
[sufficient permissions](#permissions-and-access-to-confidential-issues).
@@ -67,7 +67,7 @@ There is also an indicator on the sidebar denoting confidentiality.
There are two kinds of level access for confidential issues. The general rule
is that confidential issues are visible only to members of a project with at
-least [Reporter access][permissions]. However, a guest user can also create
+least [Reporter access](../../permissions.md#project-members-permissions). However, a guest user can also create
confidential issues, but can only view the ones that they created themselves.
Confidential issues are also hidden in search results for unprivileged users.
@@ -77,6 +77,3 @@ project's search results respectively.
| Maintainer access | Guest access |
| :-----------: | :----------: |
| ![Confidential issues search master](img/confidential_issues_search_master.png) | ![Confidential issues search guest](img/confidential_issues_search_guest.png) |
-
-[permissions]: ../../permissions.md#project
-[ce-3282]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3282
diff --git a/doc/user/project/issues/crosslinking_issues.md b/doc/user/project/issues/crosslinking_issues.md
index 786d1c81b1b..ff5b1f2ce50 100644
--- a/doc/user/project/issues/crosslinking_issues.md
+++ b/doc/user/project/issues/crosslinking_issues.md
@@ -48,13 +48,12 @@ issues in merge requests.
## From Merge Requests
-Mentioning issues in merge request comments work exactly the same way
+Mentioning issues in merge request comments works exactly the same way as
they do for [related issues](#from-related-issues).
-When you mention an issue in a merge request description, you can either
-[close the issue as soon as the merge request is merged](closing_issues.md#via-merge-request),
-or simply link both issue and merge request as described in the
-[closing issues documentation](closing_issues.md#from-related-issues).
+When you mention an issue in a merge request description, it will simply
+[link the issue and merge request together](#from-related-issues). Additionally,
+you can also [set an issue to close as soon as the merge request is merged](closing_issues.md#via-merge-request).
![issue mentioned in MR](img/mention_in_merge_request.png)
diff --git a/doc/user/project/issues/due_dates.md b/doc/user/project/issues/due_dates.md
index 7972c14c1c4..987c16dfab6 100644
--- a/doc/user/project/issues/due_dates.md
+++ b/doc/user/project/issues/due_dates.md
@@ -1,11 +1,11 @@
# Due dates
-> [Introduced][ce-3614] in GitLab 8.7.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3614) in GitLab 8.7.
Please read through the [GitLab Issue Documentation](index.md) for an overview on GitLab Issues.
Due dates can be used in issues to keep track of deadlines and make sure
-features are shipped on time. Due dates require at least [Reporter permissions][permissions]
+features are shipped on time. Due dates require at least [Reporter permissions](../../permissions.md#project-members-permissions)
to be able to edit them. On the contrary, they can be seen by everybody.
## Setting a due date
@@ -47,6 +47,3 @@ on the _Subscribe to calendar_ button on the following pages:
GitLab header
- on the **Project Issues** page
- on the **Group Issues** page
-
-[ce-3614]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3614
-[permissions]: ../../permissions.md#project
diff --git a/doc/user/project/issues/index.md b/doc/user/project/issues/index.md
index 907a305fe23..675c280a12a 100644
--- a/doc/user/project/issues/index.md
+++ b/doc/user/project/issues/index.md
@@ -109,7 +109,7 @@ issue within your team only, you can make that
[issue confidential](confidential_issues.md). Even if your project
is public, that issue will be preserved. The browser will
respond with a 404 error whenever someone who is not a project
-member with at least [Reporter level](../../permissions.md#project) tries to
+member with at least [Reporter level](../../permissions.md#project-members-permissions) tries to
access that issue's URL.
Learn more about them on the [confidential issues documentation](confidential_issues.md).
@@ -140,7 +140,7 @@ Read through the documentation for [Issue Boards](../issue_board.md)
to find out more about this feature.
With [GitLab Starter](https://about.gitlab.com/pricing/), you can also
-create various boards per project with [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.html#multiple-issue-boards).
+create various boards per project with [Multiple Issue Boards](../issue_board.html#multiple-issue-boards-starter).
### Import Issues from CSV
diff --git a/doc/user/project/merge_requests/img/filter_wip_merge_requests.png b/doc/user/project/merge_requests/img/filter_wip_merge_requests.png
index 81878709487..8df6a3c9a29 100644
--- a/doc/user/project/merge_requests/img/filter_wip_merge_requests.png
+++ b/doc/user/project/merge_requests/img/filter_wip_merge_requests.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/wip_blocked_accept_button.png b/doc/user/project/merge_requests/img/wip_blocked_accept_button.png
index 31f23be4d3d..b6d38d85165 100644
--- a/doc/user/project/merge_requests/img/wip_blocked_accept_button.png
+++ b/doc/user/project/merge_requests/img/wip_blocked_accept_button.png
Binary files differ
diff --git a/doc/user/project/merge_requests/img/wip_mark_as_wip.png b/doc/user/project/merge_requests/img/wip_mark_as_wip.png
deleted file mode 100644
index 2c2a263b316..00000000000
--- a/doc/user/project/merge_requests/img/wip_mark_as_wip.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/img/wip_unmark_as_wip.png b/doc/user/project/merge_requests/img/wip_unmark_as_wip.png
deleted file mode 100644
index 327ad9a8448..00000000000
--- a/doc/user/project/merge_requests/img/wip_unmark_as_wip.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
index bdd7d0022e6..f8af71ab46b 100644
--- a/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
+++ b/doc/user/project/merge_requests/merge_when_pipeline_succeeds.md
@@ -1,4 +1,4 @@
-# Merge When Pipeline Succeeds
+# Merge when pipeline succeeds
When reviewing a merge request that looks ready to merge but still has one or
more CI jobs running, you can set it to be merged automatically when the
diff --git a/doc/user/project/merge_requests/versions.md b/doc/user/project/merge_requests/versions.md
index 90500fd9c21..70bd1e60594 100644
--- a/doc/user/project/merge_requests/versions.md
+++ b/doc/user/project/merge_requests/versions.md
@@ -1,6 +1,7 @@
# Merge requests versions
> **Notes:**
+>
> - [Introduced][ce-5467] in GitLab 8.12.
> - Comments are disabled while viewing outdated merge versions or comparing to
> versions other than base.
diff --git a/doc/user/project/merge_requests/work_in_progress_merge_requests.md b/doc/user/project/merge_requests/work_in_progress_merge_requests.md
index 66ac7740157..6f33eb9a482 100644
--- a/doc/user/project/merge_requests/work_in_progress_merge_requests.md
+++ b/doc/user/project/merge_requests/work_in_progress_merge_requests.md
@@ -1,25 +1,46 @@
# "Work In Progress" Merge Requests
-To prevent merge requests from accidentally being accepted before they're
-completely ready, GitLab blocks the "Accept" button for merge requests that
-have been marked a **Work In Progress**.
+If a merge request is not yet ready to be merged, perhaps due to continued development
+or open discussions, you can prevent it from being accepted before it's ready by flagging
+it as a **Work In Progress**. This will disable the "Merge" button, preventing it from
+being merged, and it will stay disabled until the "WIP" flag has been removed.
![Blocked Accept Button](img/wip_blocked_accept_button.png)
-To mark a merge request a Work In Progress, simply start its title with `[WIP]`
-or `WIP:`. As an alternative, you're also able to do it by sending a commit
-with its title starting with `wip` or `WIP` to the merge request's source branch.
-
-![Mark as WIP](img/wip_mark_as_wip.png)
-
-To allow a Work In Progress merge request to be accepted again when it's ready,
-simply remove the `WIP` prefix.
-
-![Unmark as WIP](img/wip_unmark_as_wip.png)
-
-## Filtering merge requests with WIP Status
-
-To filter merge requests with the `WIP` status, you can type `wip`
-and select the value for your filter from the merge request search input.
+## Adding the "Work In Progress" flag to a Merge Request
+
+There are several ways to flag a merge request as a Work In Progress:
+
+- Add "[WIP]" or "WIP:" to the start of the merge request's title. Clicking on
+ **Start the title with WIP:**, under the title box, when editing the merge request's
+ description will have the same effect.
+- Add the `/wip` [quick action](../quick_actions.md#quick-actions-for-issues-and-merge-requests)
+ in a discussion comment in the merge request. This is a toggle, and can be repeated
+ to change the status back. Note that any other text in the comment will be discarded.
+- Add "wip" or "WIP" to the start of a commit message targeting the merge request's
+ source branch. This is not a toggle, and doing it again in another commit will have
+ no effect.
+
+## Removing the "Work In Progress" flag from a Merge Request
+
+Similar to above, when a Merge Request is ready to be merged, you can remove the
+"Work in Progress" flag in several ways:
+
+- Remove "[WIP]" or "WIP:" from the start of the merge request's title. Clicking on
+ **Remove the WIP: prefix from the title**, under the title box, when editing the merge
+ request's description, will have the same effect.
+- Add the `/wip` [quick action](../quick_actions.md#quick-actions-for-issues-and-merge-requests)
+ in a discussion comment in the merge request. This is a toggle, and can be repeated
+ to change the status back. Note that any other text in the comment will be discarded.
+- Click on the **Resolve WIP status** button near the bottom of the merge request description,
+ next to the "Merge" button (see [image above](#work-in-progress-merge-requests)).
+ Must have at least Developer level permissions on the project for the button to
+ be visible.
+
+## Including/Excluding WIP Merge Requests when searching
+
+When viewing/searching the merge requests list, you can choose to include or exclude
+WIP merge requests by adding a "WIP" filter in the search box, and choosing "Yes"
+(to include) or "No" (to exclude).
![Filter WIP MRs](img/filter_wip_merge_requests.png)
diff --git a/doc/user/project/milestones/index.md b/doc/user/project/milestones/index.md
index e6033ca8655..a7d6144e3ec 100644
--- a/doc/user/project/milestones/index.md
+++ b/doc/user/project/milestones/index.md
@@ -92,7 +92,7 @@ When filtering by milestone, in addition to choosing a specific project mileston
- **None**: Show issues or merge requests with no assigned milestone.
- **Any**: Show issues or merge requests that have an assigned milestone.
- **Upcoming**: Show issues or merge requests that have been assigned the open milestone that has the next upcoming due date (i.e. nearest due date in the future).
-- **Started**: Show issues or merge requests that have an assigned milestone with a start date that is before today.
+- **Started**: Show issues or merge requests that have an open assigned milestone with a start date that is before today.
## Milestone view
diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md
index d7a1a69f29d..6c3fa5eb463 100644
--- a/doc/user/project/new_ci_build_permissions_model.md
+++ b/doc/user/project/new_ci_build_permissions_model.md
@@ -49,7 +49,7 @@ It is important to note that we have a few types of users:
Administrator will have to be a member of it in order to have access to it
via another project's job.
-- **External users**: CI jobs created by [external users][ext] will have
+- **External users**: CI jobs created by [external users](../permissions.md#external-users-permissions) will have
access only to projects to which user has at least reporter access. This
rules out accessing all internal projects by default,
@@ -60,7 +60,7 @@ Let's consider the following scenario:
hosted in private repositories and you have multiple CI jobs that make use
of these repositories.
-1. You invite a new [external user][ext]. CI jobs created by that user do not
+1. You invite a new [external user](../permissions.md#external-users-permissions). CI jobs created by that user do not
have access to internal repositories, because the user also doesn't have the
access from within GitLab. You as an employee have to grant explicit access
for this user. This allows us to prevent from accidental data leakage.
@@ -205,6 +205,7 @@ With the update permission model we also extended the support for accessing
Container Registries for private projects.
> **Notes:**
+>
> - GitLab Runner versions prior to 1.8 don't incorporate the introduced changes
> for permissions. This makes the `image:` directive to not work with private
> projects automatically and it needs to be configured manually on Runner's host
@@ -232,7 +233,6 @@ test:
[job permissions]: ../permissions.md#job-permissions
[comment]: https://gitlab.com/gitlab-org/gitlab-ce/issues/22484#note_16648302
-[ext]: ../permissions.md#external-users
[gitsub]: ../../ci/git_submodules.md
[https]: ../admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols
[triggers]: ../../ci/triggers/README.md
diff --git a/doc/user/project/pages/getting_started_part_four.md b/doc/user/project/pages/getting_started_part_four.md
index 021139d486d..f552f60a07e 100644
--- a/doc/user/project/pages/getting_started_part_four.md
+++ b/doc/user/project/pages/getting_started_part_four.md
@@ -24,7 +24,7 @@ one for the first time.**
[GitLab CI/CD](../../../ci/README.md) serves
numerous purposes, to build, test, and deploy your app
from GitLab through
-[Continuous Integration, Continuous Delivery, and Continuous Deployment](../../../ci/introduction/index.md#introduction-to-continuous-methods)
+[Continuous Integration, Continuous Delivery, and Continuous Deployment](../../../ci/introduction/index.md#introduction-to-cicd-methodologies)
methods. You will need it to build your website with GitLab Pages,
and deploy it to the Pages server.
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index 6bb58689f38..39f14a1126f 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -1,6 +1,7 @@
# Exploring GitLab Pages
> **Notes:**
+>
> - This feature was [introduced][ee-80] in GitLab EE 8.3.
> - Custom CNAMEs with TLS support were [introduced][ee-173] in GitLab EE 8.5.
> - GitLab Pages [was ported][ce-14605] to Community Edition in GitLab 8.17.
@@ -115,7 +116,7 @@ gives you absolute control over the build process. You can actually watch your
website being built live by following the CI job traces.
For a simplified user guide on setting up GitLab CI/CD for Pages, read through
-the article [GitLab Pages from A to Z: Part 4 - Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_four.md#creating-and-tweaking-gitlab-ci-yml-for-gitlab-pages)
+the article [GitLab Pages from A to Z: Part 4 - Creating and Tweaking `.gitlab-ci.yml` for GitLab Pages](getting_started_part_four.md)
> **Note:**
> Before reading this section, make sure you familiarize yourself with GitLab CI
@@ -151,7 +152,7 @@ Depending on how you plan to publish your website, the steps defined in the
Be aware that Pages are by default branch/tag agnostic and their deployment
relies solely on what you specify in `.gitlab-ci.yml`. If you don't limit the
-`pages` job with the [`only` parameter](../../../ci/yaml/README.md#only-and-except-simplified),
+`pages` job with the [`only` parameter](../../../ci/yaml/README.md#onlyexcept-basic),
whenever a new commit is pushed to whatever branch or tag, the Pages will be
overwritten. In the example below, we limit the Pages to be deployed whenever
a commit is pushed only on the `master` branch:
@@ -252,7 +253,7 @@ get you started.
Remember that GitLab Pages are by default branch/tag agnostic and their
deployment relies solely on what you specify in `.gitlab-ci.yml`. You can limit
-the `pages` job with the [`only` parameter](../../../ci/yaml/README.md#only-and-except-simplified),
+the `pages` job with the [`only` parameter](../../../ci/yaml/README.md#onlyexcept-basic),
whenever a new commit is pushed to a branch that will be used specifically for
your pages.
diff --git a/doc/user/project/pipelines/job_artifacts.md b/doc/user/project/pipelines/job_artifacts.md
index 8b57129c9e1..a3f40c20192 100644
--- a/doc/user/project/pipelines/job_artifacts.md
+++ b/doc/user/project/pipelines/job_artifacts.md
@@ -1,6 +1,7 @@
# Introduction to job artifacts
> **Notes:**
+>
> - Since GitLab 8.2 and GitLab Runner 0.7.0, job artifacts that are created by
> GitLab Runner are uploaded to GitLab and are downloadable as a single archive
> (`tar.gz`) using the GitLab UI.
@@ -152,7 +153,7 @@ For example:
https://gitlab.com/gitlab-org/gitlab-ce/-/jobs/artifacts/master/browse?job=coverage
```
-There is also a URL to specific files, including html files that
+There is also a URL to specific files, including html files that
are shown in [GitLab Pages](../../../administration/pages/index.md):
```
@@ -183,7 +184,7 @@ information in the UI.
DANGER: **Warning:**
This is a destructive action that leads to data loss. Use with caution.
-If you have at least Developer [permissions](../../permissions.md#gitlab-ci-cd-permissions)
+If you have at least Developer [permissions](../../permissions.md#gitlab-cicd-permissions)
on the project, you can erase a single job via the UI which will also remove the
artifacts and the job's trace.
@@ -191,9 +192,9 @@ artifacts and the job's trace.
1. Click the trash icon at the top right of the job's trace.
1. Confirm the deletion.
-## Retrieve artifacts of private projects when using GitLab CI
+## Retrieve artifacts of private projects when using GitLab CI
In order to retrieve a job artifact of a different project, you might need to use a private token in order to [authenticate and download](../../../api/jobs.md#get-job-artifacts) the artifacts.
[expiry date]: ../../../ci/yaml/README.md#artifactsexpire_in
-[ce-14399]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14399 \ No newline at end of file
+[ce-14399]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/14399
diff --git a/doc/user/project/pipelines/schedules.md b/doc/user/project/pipelines/schedules.md
index 58a0fbc97cd..2911a56cf67 100644
--- a/doc/user/project/pipelines/schedules.md
+++ b/doc/user/project/pipelines/schedules.md
@@ -1,6 +1,7 @@
# Pipeline schedules
> **Notes**:
+>
> - This feature was introduced in 9.1 as [Trigger Schedule][ce-10533].
> - In 9.2, the feature was [renamed to Pipeline Schedule][ce-10853].
> - Cron notation is parsed by [Fugit](https://github.com/floraison/fugit).
@@ -58,7 +59,7 @@ GitLab CI so that they can be used in your `.gitlab-ci.yml` file.
To configure that a job can be executed only when the pipeline has been
scheduled (or the opposite), you can use
-[only and except](../../../ci/yaml/README.md#only-and-except-simplified) configuration keywords.
+[only and except](../../../ci/yaml/README.md#onlyexcept-basic) configuration keywords.
```
job:on-schedule:
diff --git a/doc/user/project/protected_branches.md b/doc/user/project/protected_branches.md
index 3eb8123144f..2060b5dd4a2 100644
--- a/doc/user/project/protected_branches.md
+++ b/doc/user/project/protected_branches.md
@@ -10,7 +10,7 @@ created protected branches.
By default, a protected branch does four simple things:
- it prevents its creation, if not already created, from everybody except users
- who are allowed to merge
+ with Maintainer permission
- it prevents pushes from everybody except users with Maintainer permission
- it prevents **anyone** from force pushing to the branch
- it prevents **anyone** from deleting the branch
@@ -96,7 +96,7 @@ all matching branches:
## Creating a protected branch
-> [Introduced][https://gitlab.com/gitlab-org/gitlab-ce/issues/53361] in GitLab 11.9.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53361) in GitLab 11.9.
When a protected branch or wildcard protected branches are set to
[**No one** is **Allowed to push**](#using-the-allowed-to-merge-and-allowed-to-push-settings),
diff --git a/doc/user/project/wiki/index.md b/doc/user/project/wiki/index.md
index 127a30d6669..532247a98cd 100644
--- a/doc/user/project/wiki/index.md
+++ b/doc/user/project/wiki/index.md
@@ -12,10 +12,6 @@ You can create Wiki pages in the web interface or
[locally using Git](#adding-and-editing-wiki-pages-locally) since every Wiki is
a separate Git repository.
->**Note:**
-A [permission level][permissions] of **Guest** is needed to view a Wiki and
-**Developer** is needed to create and edit Wiki pages.
-
## First time creating the Home page
The first time you visit a Wiki, you will be directed to create the Home page.
@@ -28,6 +24,9 @@ message.
## Creating a new wiki page
+NOTE: **Note:**
+A [permission level][permissions] of **Developer** is needed to create Wiki pages.
+
Create a new page by clicking the **New page** button that can be found
in all wiki pages. You will be asked to fill in the page name from which GitLab
will create the path to the page. You can specify a full path for the new file
@@ -58,12 +57,18 @@ repository, you will have to upload them again.
## Editing a wiki page
+NOTE: **Note:**
+A [permission level][permissions] of **Developer** is needed to edit Wiki pages.
+
To edit a page, simply click on the **Edit** button. From there on, you can
change its content. When done, click **Save changes** for the changes to take
effect.
## Deleting a wiki page
+NOTE: **Note:**
+A [permission level][permissions] of **Maintainer** is needed to delete Wiki pages.
+
You can find the **Delete** button only when editing a page. Click on it and
confirm you want the page to be deleted.
diff --git a/doc/user/snippets.md b/doc/user/snippets.md
index 569bdc9e2d5..7b580a057f2 100644
--- a/doc/user/snippets.md
+++ b/doc/user/snippets.md
@@ -20,7 +20,7 @@ and private. See [Public access](../public_access/public_access.md) for more inf
## Project snippets
Project snippets are always related to a specific project.
-See [Project's features](project/index.md#projects-features) for more information.
+See [Project features](project/index.md#project-features) for more information.
## Discover snippets
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index 0ebe5eea173..fffb6a5d86d 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -1 +1,5 @@
+---
+redirect_to: '../user/project/integrations/webhooks.md'
+---
+
This document was moved to [project/integrations/webhooks](../user/project/integrations/webhooks.md).
diff --git a/doc/workflow/repository_mirroring.md b/doc/workflow/repository_mirroring.md
index 8a2f4e1b40e..ae1624b7dc0 100644
--- a/doc/workflow/repository_mirroring.md
+++ b/doc/workflow/repository_mirroring.md
@@ -408,3 +408,11 @@ settings are recommended:
Perforce Helix.
Read about [Git Fusion settings on Perforce.com](https://www.perforce.com/perforce/doc.current/manuals/git-fusion/Content/Git-Fusion/section_vss_bdw_w3.html#section_zdp_zz1_3l).
+
+## Troubleshooting
+
+Should an error occur during a push, GitLab will display an "Error" highlight for that repository. Details on the error can then be seen by hovering over the highlight text.
+
+### 13:Received RST_STREAM with error code 2 with GitHub
+
+If you receive an "13:Received RST_STREAM with error code 2" while mirroring to a GitHub repository, your GitHub settings might be set to block pushes that expose your email address used in commits. Either set your email address on GitHub to be public, or disable the [Block command line pushes that expose my email](http://github.com/settings/emails) setting.
diff --git a/jest.config.js b/jest.config.js
index efbf2e602c1..1f6e04390ae 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -36,4 +36,5 @@ module.exports = {
'^.+\\.vue$': 'vue-jest',
},
transformIgnorePatterns: ['node_modules/(?!(@gitlab/ui)/)'],
+ timers: 'fake',
};
diff --git a/lib/api/discussions.rb b/lib/api/discussions.rb
index 91eb6a23701..8afe6dda414 100644
--- a/lib/api/discussions.rb
+++ b/lib/api/discussions.rb
@@ -7,9 +7,7 @@ module API
before { authenticate! }
- NOTEABLE_TYPES = [Issue, Snippet, MergeRequest, Commit].freeze
-
- NOTEABLE_TYPES.each do |noteable_type|
+ Helpers::DiscussionsHelpers.noteable_types.each do |noteable_type|
parent_type = noteable_type.parent_class.to_s.underscore
noteables_str = noteable_type.to_s.underscore.pluralize
noteables_path = noteable_type == Commit ? "repository/#{noteables_str}" : noteables_str
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 2cd0d93b205..611523a2444 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -86,6 +86,10 @@ module API
expose :admin?, as: :is_admin
end
+ class UserDetailsWithAdmin < UserWithAdmin
+ expose :highest_role
+ end
+
class UserStatus < Grape::Entity
expose :emoji
expose :message
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 64958ff982a..cb0d6d96f29 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -58,6 +58,22 @@ module API
end
# rubocop: enable CodeReuse/ActiveRecord
+ def create_group
+ # This is a separate method so that EE can extend its behaviour, without
+ # having to modify this code directly.
+ ::Groups::CreateService
+ .new(current_user, declared_params(include_missing: false))
+ .execute
+ end
+
+ def update_group(group)
+ # This is a separate method so that EE can extend its behaviour, without
+ # having to modify this code directly.
+ ::Groups::UpdateService
+ .new(group, current_user, declared_params(include_missing: false))
+ .execute
+ end
+
def find_group_projects(params)
group = find_group!(params[:id])
options = {
@@ -127,7 +143,7 @@ module API
authorize! :create_group
end
- group = ::Groups::CreateService.new(current_user, declared_params(include_missing: false)).execute
+ group = create_group
if group.persisted?
present group, with: Entities::GroupDetail, current_user: current_user
@@ -153,7 +169,7 @@ module API
group = find_group!(params[:id])
authorize! :admin_group, group
- if ::Groups::UpdateService.new(group, current_user, declared_params(include_missing: false)).execute
+ if update_group(group)
present group, with: Entities::GroupDetail, current_user: current_user
else
render_validation_error!(group)
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 825fab62034..b8bd180bdc1 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -6,6 +6,7 @@ module API
include Helpers::Pagination
SUDO_HEADER = "HTTP_SUDO".freeze
+ GITLAB_SHARED_SECRET_HEADER = "Gitlab-Shared-Secret".freeze
SUDO_PARAM = :sudo
API_USER_ENV = 'gitlab.api.user'.freeze
@@ -212,10 +213,12 @@ module API
end
def authenticate_by_gitlab_shell_token!
- input = params['secret_token'].try(:chomp)
- unless Devise.secure_compare(secret_token, input)
- unauthorized!
- end
+ input = params['secret_token']
+ input ||= Base64.decode64(headers[GITLAB_SHARED_SECRET_HEADER]) if headers.key?(GITLAB_SHARED_SECRET_HEADER)
+
+ input&.chomp!
+
+ unauthorized! unless Devise.secure_compare(secret_token, input)
end
def authenticated_with_full_private_access!
diff --git a/lib/api/helpers/discussions_helpers.rb b/lib/api/helpers/discussions_helpers.rb
new file mode 100644
index 00000000000..94a5bf75c39
--- /dev/null
+++ b/lib/api/helpers/discussions_helpers.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module DiscussionsHelpers
+ def self.noteable_types
+ # This is a method instead of a constant, allowing EE to more easily
+ # extend it.
+ [Issue, Snippet, MergeRequest, Commit]
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/notes_helpers.rb b/lib/api/helpers/notes_helpers.rb
index 795dca5cf03..a068de4361c 100644
--- a/lib/api/helpers/notes_helpers.rb
+++ b/lib/api/helpers/notes_helpers.rb
@@ -3,6 +3,12 @@
module API
module Helpers
module NotesHelpers
+ def self.noteable_types
+ # This is a method instead of a constant, allowing EE to more easily
+ # extend it.
+ [Issue, MergeRequest, Snippet]
+ end
+
def update_note(noteable, note_id)
note = noteable.notes.find(params[:note_id])
diff --git a/lib/api/helpers/resource_label_events_helpers.rb b/lib/api/helpers/resource_label_events_helpers.rb
new file mode 100644
index 00000000000..23574deb59b
--- /dev/null
+++ b/lib/api/helpers/resource_label_events_helpers.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module ResourceLabelEventsHelpers
+ def self.eventable_types
+ # This is a method instead of a constant, allowing EE to more easily
+ # extend it.
+ [Issue, MergeRequest]
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/search_helpers.rb b/lib/api/helpers/search_helpers.rb
new file mode 100644
index 00000000000..0e052e0e273
--- /dev/null
+++ b/lib/api/helpers/search_helpers.rb
@@ -0,0 +1,22 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module SearchHelpers
+ def self.global_search_scopes
+ # This is a separate method so that EE can redefine it.
+ %w(projects issues merge_requests milestones snippet_titles snippet_blobs users)
+ end
+
+ def self.group_search_scopes
+ # This is a separate method so that EE can redefine it.
+ %w(projects issues merge_requests milestones users)
+ end
+
+ def self.project_search_scopes
+ # This is a separate method so that EE can redefine it.
+ %w(issues merge_requests milestones notes wiki_blobs commits blobs users)
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers/services_helpers.rb b/lib/api/helpers/services_helpers.rb
new file mode 100644
index 00000000000..8582c45798f
--- /dev/null
+++ b/lib/api/helpers/services_helpers.rb
@@ -0,0 +1,721 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ # Helpers module for API::Services
+ #
+ # The data structures inside this model are returned using class methods,
+ # allowing EE to extend them where necessary.
+ module ServicesHelpers
+ def self.chat_notification_settings
+ [
+ {
+ required: true,
+ name: :webhook,
+ type: String,
+ desc: 'The chat webhook'
+ },
+ {
+ required: false,
+ name: :username,
+ type: String,
+ desc: 'The chat username'
+ },
+ {
+ required: false,
+ name: :channel,
+ type: String,
+ desc: 'The default chat channel'
+ }
+ ].freeze
+ end
+
+ def self.chat_notification_flags
+ [
+ {
+ required: false,
+ name: :notify_only_broken_pipelines,
+ type: Boolean,
+ desc: 'Send notifications for broken pipelines'
+ },
+ {
+ required: false,
+ name: :notify_only_default_branch,
+ type: Boolean,
+ desc: 'Send notifications only for the default branch'
+ }
+ ].freeze
+ end
+
+ def self.chat_notification_channels
+ [
+ {
+ required: false,
+ name: :push_channel,
+ type: String,
+ desc: 'The name of the channel to receive push_events notifications'
+ },
+ {
+ required: false,
+ name: :issue_channel,
+ type: String,
+ desc: 'The name of the channel to receive issues_events notifications'
+ },
+ {
+ required: false,
+ name: :confidential_issue_channel,
+ type: String,
+ desc: 'The name of the channel to receive confidential_issues_events notifications'
+ },
+ {
+ required: false,
+ name: :merge_request_channel,
+ type: String,
+ desc: 'The name of the channel to receive merge_requests_events notifications'
+ },
+ {
+ required: false,
+ name: :note_channel,
+ type: String,
+ desc: 'The name of the channel to receive note_events notifications'
+ },
+ {
+ required: false,
+ name: :tag_push_channel,
+ type: String,
+ desc: 'The name of the channel to receive tag_push_events notifications'
+ },
+ {
+ required: false,
+ name: :pipeline_channel,
+ type: String,
+ desc: 'The name of the channel to receive pipeline_events notifications'
+ },
+ {
+ required: false,
+ name: :wiki_page_channel,
+ type: String,
+ desc: 'The name of the channel to receive wiki_page_events notifications'
+ }
+ ].freeze
+ end
+
+ def self.chat_notification_events
+ [
+ {
+ required: false,
+ name: :push_events,
+ type: Boolean,
+ desc: 'Enable notifications for push_events'
+ },
+ {
+ required: false,
+ name: :issues_events,
+ type: Boolean,
+ desc: 'Enable notifications for issues_events'
+ },
+ {
+ required: false,
+ name: :confidential_issues_events,
+ type: Boolean,
+ desc: 'Enable notifications for confidential_issues_events'
+ },
+ {
+ required: false,
+ name: :merge_requests_events,
+ type: Boolean,
+ desc: 'Enable notifications for merge_requests_events'
+ },
+ {
+ required: false,
+ name: :note_events,
+ type: Boolean,
+ desc: 'Enable notifications for note_events'
+ },
+ {
+ required: false,
+ name: :tag_push_events,
+ type: Boolean,
+ desc: 'Enable notifications for tag_push_events'
+ },
+ {
+ required: false,
+ name: :pipeline_events,
+ type: Boolean,
+ desc: 'Enable notifications for pipeline_events'
+ },
+ {
+ required: false,
+ name: :wiki_page_events,
+ type: Boolean,
+ desc: 'Enable notifications for wiki_page_events'
+ }
+ ].freeze
+ end
+
+ def self.services
+ {
+ 'asana' => [
+ {
+ required: true,
+ name: :api_key,
+ type: String,
+ desc: 'User API token'
+ },
+ {
+ required: false,
+ name: :restrict_to_branch,
+ type: String,
+ desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches'
+ }
+ ],
+ 'assembla' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The authentication token'
+ },
+ {
+ required: false,
+ name: :subdomain,
+ type: String,
+ desc: 'Subdomain setting'
+ }
+ ],
+ 'bamboo' => [
+ {
+ required: true,
+ name: :bamboo_url,
+ type: String,
+ desc: 'Bamboo root URL like https://bamboo.example.com'
+ },
+ {
+ required: true,
+ name: :build_key,
+ type: String,
+ desc: 'Bamboo build plan key like'
+ },
+ {
+ required: true,
+ name: :username,
+ type: String,
+ desc: 'A user with API access, if applicable'
+ },
+ {
+ required: true,
+ name: :password,
+ type: String,
+ desc: 'Password of the user'
+ }
+ ],
+ 'bugzilla' => [
+ {
+ required: true,
+ name: :new_issue_url,
+ type: String,
+ desc: 'New issue URL'
+ },
+ {
+ required: true,
+ name: :issues_url,
+ type: String,
+ desc: 'Issues URL'
+ },
+ {
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'Project URL'
+ },
+ {
+ required: false,
+ name: :description,
+ type: String,
+ desc: 'Description'
+ },
+ {
+ required: false,
+ name: :title,
+ type: String,
+ desc: 'Title'
+ }
+ ],
+ 'buildkite' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'Buildkite project GitLab token'
+ },
+ {
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'The buildkite project URL'
+ },
+ {
+ required: false,
+ name: :enable_ssl_verification,
+ type: Boolean,
+ desc: 'Enable SSL verification for communication'
+ }
+ ],
+ 'campfire' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'Campfire token'
+ },
+ {
+ required: false,
+ name: :subdomain,
+ type: String,
+ desc: 'Campfire subdomain'
+ },
+ {
+ required: false,
+ name: :room,
+ type: String,
+ desc: 'Campfire room'
+ }
+ ],
+ 'custom-issue-tracker' => [
+ {
+ required: true,
+ name: :new_issue_url,
+ type: String,
+ desc: 'New issue URL'
+ },
+ {
+ required: true,
+ name: :issues_url,
+ type: String,
+ desc: 'Issues URL'
+ },
+ {
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'Project URL'
+ },
+ {
+ required: false,
+ name: :description,
+ type: String,
+ desc: 'Description'
+ },
+ {
+ required: false,
+ name: :title,
+ type: String,
+ desc: 'Title'
+ }
+ ],
+ 'discord' => [
+ {
+ required: true,
+ name: :webhook,
+ type: String,
+ desc: 'Discord webhook. e.g. https://discordapp.com/api/webhooks/…'
+ }
+ ],
+ 'drone-ci' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'Drone CI token'
+ },
+ {
+ required: true,
+ name: :drone_url,
+ type: String,
+ desc: 'Drone CI URL'
+ },
+ {
+ required: false,
+ name: :enable_ssl_verification,
+ type: Boolean,
+ desc: 'Enable SSL verification for communication'
+ }
+ ],
+ 'emails-on-push' => [
+ {
+ required: true,
+ name: :recipients,
+ type: String,
+ desc: 'Comma-separated list of recipient email addresses'
+ },
+ {
+ required: false,
+ name: :disable_diffs,
+ type: Boolean,
+ desc: 'Disable code diffs'
+ },
+ {
+ required: false,
+ name: :send_from_committer_email,
+ type: Boolean,
+ desc: 'Send from committer'
+ }
+ ],
+ 'external-wiki' => [
+ {
+ required: true,
+ name: :external_wiki_url,
+ type: String,
+ desc: 'The URL of the external Wiki'
+ }
+ ],
+ 'flowdock' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'Flowdock token'
+ }
+ ],
+ 'hangouts-chat' => [
+ {
+ required: true,
+ name: :webhook,
+ type: String,
+ desc: 'The Hangouts Chat webhook. e.g. https://chat.googleapis.com/v1/spaces…'
+ },
+ chat_notification_events
+ ].flatten,
+ 'irker' => [
+ {
+ required: true,
+ name: :recipients,
+ type: String,
+ desc: 'Recipients/channels separated by whitespaces'
+ },
+ {
+ required: false,
+ name: :default_irc_uri,
+ type: String,
+ desc: 'Default: irc://irc.network.net:6697'
+ },
+ {
+ required: false,
+ name: :server_host,
+ type: String,
+ desc: 'Server host. Default localhost'
+ },
+ {
+ required: false,
+ name: :server_port,
+ type: Integer,
+ desc: 'Server port. Default 6659'
+ },
+ {
+ required: false,
+ name: :colorize_messages,
+ type: Boolean,
+ desc: 'Colorize messages'
+ }
+ ],
+ 'jira' => [
+ {
+ required: true,
+ name: :url,
+ type: String,
+ desc: 'The base URL to the JIRA instance web interface which is being linked to this GitLab project. E.g., https://jira.example.com'
+ },
+ {
+ required: false,
+ name: :api_url,
+ type: String,
+ desc: 'The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., https://jira-api.example.com'
+ },
+ {
+ required: true,
+ name: :username,
+ type: String,
+ desc: 'The username of the user created to be used with GitLab/JIRA'
+ },
+ {
+ required: true,
+ name: :password,
+ type: String,
+ desc: 'The password of the user created to be used with GitLab/JIRA'
+ },
+ {
+ required: false,
+ name: :jira_issue_transition_id,
+ type: String,
+ desc: '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`'
+ }
+ ],
+ 'kubernetes' => [
+ {
+ required: true,
+ name: :namespace,
+ type: String,
+ desc: 'The Kubernetes namespace to use'
+ },
+ {
+ required: true,
+ name: :api_url,
+ type: String,
+ desc: 'The URL to the Kubernetes cluster API, e.g., https://kubernetes.example.com'
+ },
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The service token to authenticate against the Kubernetes cluster with'
+ },
+ {
+ required: false,
+ name: :ca_pem,
+ type: String,
+ desc: 'A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)'
+ }
+ ],
+ 'mattermost-slash-commands' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The Mattermost token'
+ }
+ ],
+ 'slack-slash-commands' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The Slack token'
+ }
+ ],
+ 'packagist' => [
+ {
+ required: true,
+ name: :username,
+ type: String,
+ desc: 'The username'
+ },
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The Packagist API token'
+ },
+ {
+ required: false,
+ name: :server,
+ type: String,
+ desc: 'The server'
+ }
+ ],
+ 'pipelines-email' => [
+ {
+ required: true,
+ name: :recipients,
+ type: String,
+ desc: 'Comma-separated list of recipient email addresses'
+ },
+ {
+ required: false,
+ name: :notify_only_broken_pipelines,
+ type: Boolean,
+ desc: 'Notify only broken pipelines'
+ }
+ ],
+ 'pivotaltracker' => [
+ {
+ required: true,
+ name: :token,
+ type: String,
+ desc: 'The Pivotaltracker token'
+ },
+ {
+ required: false,
+ name: :restrict_to_branch,
+ type: String,
+ desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.'
+ }
+ ],
+ 'prometheus' => [
+ {
+ required: true,
+ name: :api_url,
+ type: String,
+ desc: 'Prometheus API Base URL, like http://prometheus.example.com/'
+ }
+ ],
+ 'pushover' => [
+ {
+ required: true,
+ name: :api_key,
+ type: String,
+ desc: 'The application key'
+ },
+ {
+ required: true,
+ name: :user_key,
+ type: String,
+ desc: 'The user key'
+ },
+ {
+ required: true,
+ name: :priority,
+ type: String,
+ desc: 'The priority'
+ },
+ {
+ required: true,
+ name: :device,
+ type: String,
+ desc: 'Leave blank for all active devices'
+ },
+ {
+ required: true,
+ name: :sound,
+ type: String,
+ desc: 'The sound of the notification'
+ }
+ ],
+ 'redmine' => [
+ {
+ required: true,
+ name: :new_issue_url,
+ type: String,
+ desc: 'The new issue URL'
+ },
+ {
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'The project URL'
+ },
+ {
+ required: true,
+ name: :issues_url,
+ type: String,
+ desc: 'The issues URL'
+ },
+ {
+ required: false,
+ name: :description,
+ type: String,
+ desc: 'The description of the tracker'
+ }
+ ],
+ 'youtrack' => [
+ {
+ required: true,
+ name: :project_url,
+ type: String,
+ desc: 'The project URL'
+ },
+ {
+ required: true,
+ name: :issues_url,
+ type: String,
+ desc: 'The issues URL'
+ },
+ {
+ required: false,
+ name: :description,
+ type: String,
+ desc: 'The description of the tracker'
+ }
+ ],
+ 'slack' => [
+ chat_notification_settings,
+ chat_notification_flags,
+ chat_notification_channels,
+ chat_notification_events
+ ].flatten,
+ 'microsoft-teams' => [
+ {
+ required: true,
+ name: :webhook,
+ type: String,
+ desc: 'The Microsoft Teams webhook. e.g. https://outlook.office.com/webhook/…'
+ }
+ ],
+ 'mattermost' => [
+ chat_notification_settings,
+ chat_notification_flags,
+ chat_notification_channels,
+ chat_notification_events
+ ].flatten,
+ 'teamcity' => [
+ {
+ required: true,
+ name: :teamcity_url,
+ type: String,
+ desc: 'TeamCity root URL like https://teamcity.example.com'
+ },
+ {
+ required: true,
+ name: :build_type,
+ type: String,
+ desc: 'Build configuration ID'
+ },
+ {
+ required: true,
+ name: :username,
+ type: String,
+ desc: 'A user with permissions to trigger a manual build'
+ },
+ {
+ required: true,
+ name: :password,
+ type: String,
+ desc: 'The password of the user'
+ }
+ ]
+ }
+ end
+
+ def self.service_classes
+ [
+ ::AsanaService,
+ ::AssemblaService,
+ ::BambooService,
+ ::BugzillaService,
+ ::BuildkiteService,
+ ::CampfireService,
+ ::CustomIssueTrackerService,
+ ::DiscordService,
+ ::DroneCiService,
+ ::EmailsOnPushService,
+ ::ExternalWikiService,
+ ::FlowdockService,
+ ::HangoutsChatService,
+ ::IrkerService,
+ ::JiraService,
+ ::KubernetesService,
+ ::MattermostSlashCommandsService,
+ ::SlackSlashCommandsService,
+ ::PackagistService,
+ ::PipelinesEmailService,
+ ::PivotaltrackerService,
+ ::PrometheusService,
+ ::PushoverService,
+ ::RedmineService,
+ ::YoutrackService,
+ ::SlackService,
+ ::MattermostService,
+ ::MicrosoftTeamsService,
+ ::TeamcityService
+ ]
+ end
+
+ def self.development_service_classes
+ [
+ ::MockCiService,
+ ::MockDeploymentService,
+ ::MockMonitoringService
+ ]
+ end
+ end
+ end
+end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 70b32f7d758..7f4a00f1389 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -15,6 +15,12 @@ module API
status code
{ status: success, message: message }.merge(extra_options).compact
end
+
+ def lfs_authentication_url(project)
+ # This is a separate method so that EE can alter its behaviour more
+ # easily.
+ project.http_url_to_repo
+ end
end
namespace 'internal' do
@@ -81,11 +87,6 @@ module API
gl_id: Gitlab::GlId.gl_id(user),
gl_username: user&.username,
git_config_options: [],
-
- # This repository_path is a bogus value but gitlab-shell still requires
- # its presence. https://gitlab.com/gitlab-org/gitlab-shell/issues/135
- repository_path: '/',
-
gitaly: gitaly_payload(params[:action])
}
@@ -118,7 +119,9 @@ module API
raise ActiveRecord::RecordNotFound.new("No key_id or user_id passed!")
end
- Gitlab::LfsToken.new(actor).authentication_payload(project.http_url_to_repo)
+ Gitlab::LfsToken
+ .new(actor)
+ .authentication_payload(lfs_authentication_url(project))
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/lib/api/notes.rb b/lib/api/notes.rb
index f7bd092ce50..416cf39d3ec 100644
--- a/lib/api/notes.rb
+++ b/lib/api/notes.rb
@@ -7,9 +7,7 @@ module API
before { authenticate! }
- NOTEABLE_TYPES = [Issue, MergeRequest, Snippet].freeze
-
- NOTEABLE_TYPES.each do |noteable_type|
+ Helpers::NotesHelpers.noteable_types.each do |noteable_type|
parent_type = noteable_type.parent_class.to_s.underscore
noteables_str = noteable_type.to_s.underscore.pluralize
diff --git a/lib/api/project_milestones.rb b/lib/api/project_milestones.rb
index ca24742b7a3..9ecbf37b49a 100644
--- a/lib/api/project_milestones.rb
+++ b/lib/api/project_milestones.rb
@@ -103,17 +103,15 @@ module API
detail 'This feature was introduced in GitLab 11.9'
end
post ':id/milestones/:milestone_id/promote' do
- begin
- authorize! :admin_milestone, user_project
- authorize! :admin_milestone, user_project.group
+ authorize! :admin_milestone, user_project
+ authorize! :admin_milestone, user_project.group
- milestone = user_project.milestones.find(params[:milestone_id])
- Milestones::PromoteService.new(user_project, current_user).execute(milestone)
+ milestone = user_project.milestones.find(params[:milestone_id])
+ Milestones::PromoteService.new(user_project, current_user).execute(milestone)
- status(200)
- rescue Milestones::PromoteService::PromoteMilestoneError => error
- render_api_error!(error.message, 400)
- end
+ status(200)
+ rescue Milestones::PromoteService::PromoteMilestoneError => error
+ render_api_error!(error.message, 400)
end
end
end
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 32e05d84491..4106a2cdf38 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -89,11 +89,9 @@ module API
optional :format, type: String, desc: 'The archive format'
end
get ':id/repository/archive', requirements: { format: Gitlab::PathRegex.archive_formats_regex } do
- begin
- send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true
- rescue
- not_found!('File')
- end
+ send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true
+ rescue
+ not_found!('File')
end
desc 'Compare two branches, tags, or commits' do
@@ -118,12 +116,10 @@ module API
optional :sort, type: String, values: %w[asc desc], default: 'asc', desc: 'Sort by asc (ascending) or desc (descending)'
end
get ':id/repository/contributors' do
- begin
- contributors = ::Kaminari.paginate_array(user_project.repository.contributors(order_by: params[:order_by], sort: params[:sort]))
- present paginate(contributors), with: Entities::Contributor
- rescue
- not_found!
- end
+ contributors = ::Kaminari.paginate_array(user_project.repository.contributors(order_by: params[:order_by], sort: params[:sort]))
+ present paginate(contributors), with: Entities::Contributor
+ rescue
+ not_found!
end
desc 'Get the common ancestor between commits' do
diff --git a/lib/api/resource_label_events.rb b/lib/api/resource_label_events.rb
index 0c328f7268e..448bef12cec 100644
--- a/lib/api/resource_label_events.rb
+++ b/lib/api/resource_label_events.rb
@@ -7,9 +7,7 @@ module API
before { authenticate! }
- EVENTABLE_TYPES = [Issue, MergeRequest].freeze
-
- EVENTABLE_TYPES.each do |eventable_type|
+ Helpers::ResourceLabelEventsHelpers.eventable_types.each do |eventable_type|
parent_type = eventable_type.parent_class.to_s.underscore
eventables_str = eventable_type.to_s.underscore.pluralize
diff --git a/lib/api/search.rb b/lib/api/search.rb
index f5db692afe5..60095300ea1 100644
--- a/lib/api/search.rb
+++ b/lib/api/search.rb
@@ -17,7 +17,8 @@ module API
blobs: Entities::Blob,
wiki_blobs: Entities::Blob,
snippet_titles: Entities::Snippet,
- snippet_blobs: Entities::Snippet
+ snippet_blobs: Entities::Snippet,
+ users: Entities::UserBasic
}.freeze
def search(additional_params = {})
@@ -45,6 +46,18 @@ module API
def entity
SCOPE_ENTITY[params[:scope].to_sym]
end
+
+ def verify_search_scope!
+ # In EE we have additional validation requirements for searches.
+ # Defining this method here as a noop allows us to easily extend it in
+ # EE, without having to modify this file directly.
+ end
+
+ def check_users_search_allowed!
+ if params[:scope].to_sym == :users && Feature.disabled?(:users_search, default_enabled: true)
+ render_api_error!({ error: _("Scope not supported with disabled 'users_search' feature!") }, 400)
+ end
+ end
end
resource :search do
@@ -55,12 +68,14 @@ module API
requires :search, type: String, desc: 'The expression it should be searched for'
requires :scope,
type: String,
- desc: 'The scope of search, available scopes:
- projects, issues, merge_requests, milestones, snippet_titles, snippet_blobs',
- values: %w(projects issues merge_requests milestones snippet_titles snippet_blobs)
+ desc: 'The scope of the search',
+ values: Helpers::SearchHelpers.global_search_scopes
use :pagination
end
get do
+ verify_search_scope!
+ check_users_search_allowed!
+
present search, with: entity
end
end
@@ -74,12 +89,14 @@ module API
requires :search, type: String, desc: 'The expression it should be searched for'
requires :scope,
type: String,
- desc: 'The scope of search, available scopes:
- projects, issues, merge_requests, milestones',
- values: %w(projects issues merge_requests milestones)
+ desc: 'The scope of the search',
+ values: Helpers::SearchHelpers.group_search_scopes
use :pagination
end
get ':id/(-/)search' do
+ verify_search_scope!
+ check_users_search_allowed!
+
present search(group_id: user_group.id), with: entity
end
end
@@ -93,12 +110,13 @@ module API
requires :search, type: String, desc: 'The expression it should be searched for'
requires :scope,
type: String,
- desc: 'The scope of search, available scopes:
- issues, merge_requests, milestones, notes, wiki_blobs, commits, blobs',
- values: %w(issues merge_requests milestones notes wiki_blobs commits blobs)
+ desc: 'The scope of the search',
+ values: Helpers::SearchHelpers.project_search_scopes
use :pagination
end
get ':id/(-/)search' do
+ check_users_search_allowed!
+
present search(project_id: user_project.id), with: entity
end
end
diff --git a/lib/api/services.rb b/lib/api/services.rb
index bda6be51553..bc77fae87fa 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -1,696 +1,8 @@
# frozen_string_literal: true
module API
class Services < Grape::API
- CHAT_NOTIFICATION_SETTINGS = [
- {
- required: true,
- name: :webhook,
- type: String,
- desc: 'The chat webhook'
- },
- {
- required: false,
- name: :username,
- type: String,
- desc: 'The chat username'
- },
- {
- required: false,
- name: :channel,
- type: String,
- desc: 'The default chat channel'
- }
- ].freeze
-
- CHAT_NOTIFICATION_FLAGS = [
- {
- required: false,
- name: :notify_only_broken_pipelines,
- type: Boolean,
- desc: 'Send notifications for broken pipelines'
- },
- {
- required: false,
- name: :notify_only_default_branch,
- type: Boolean,
- desc: 'Send notifications only for the default branch'
- }
- ].freeze
-
- CHAT_NOTIFICATION_CHANNELS = [
- {
- required: false,
- name: :push_channel,
- type: String,
- desc: 'The name of the channel to receive push_events notifications'
- },
- {
- required: false,
- name: :issue_channel,
- type: String,
- desc: 'The name of the channel to receive issues_events notifications'
- },
- {
- required: false,
- name: :confidential_issue_channel,
- type: String,
- desc: 'The name of the channel to receive confidential_issues_events notifications'
- },
- {
- required: false,
- name: :merge_request_channel,
- type: String,
- desc: 'The name of the channel to receive merge_requests_events notifications'
- },
- {
- required: false,
- name: :note_channel,
- type: String,
- desc: 'The name of the channel to receive note_events notifications'
- },
- {
- required: false,
- name: :tag_push_channel,
- type: String,
- desc: 'The name of the channel to receive tag_push_events notifications'
- },
- {
- required: false,
- name: :pipeline_channel,
- type: String,
- desc: 'The name of the channel to receive pipeline_events notifications'
- },
- {
- required: false,
- name: :wiki_page_channel,
- type: String,
- desc: 'The name of the channel to receive wiki_page_events notifications'
- }
- ].freeze
-
- CHAT_NOTIFICATION_EVENTS = [
- {
- required: false,
- name: :push_events,
- type: Boolean,
- desc: 'Enable notifications for push_events'
- },
- {
- required: false,
- name: :issues_events,
- type: Boolean,
- desc: 'Enable notifications for issues_events'
- },
- {
- required: false,
- name: :confidential_issues_events,
- type: Boolean,
- desc: 'Enable notifications for confidential_issues_events'
- },
- {
- required: false,
- name: :merge_requests_events,
- type: Boolean,
- desc: 'Enable notifications for merge_requests_events'
- },
- {
- required: false,
- name: :note_events,
- type: Boolean,
- desc: 'Enable notifications for note_events'
- },
- {
- required: false,
- name: :tag_push_events,
- type: Boolean,
- desc: 'Enable notifications for tag_push_events'
- },
- {
- required: false,
- name: :pipeline_events,
- type: Boolean,
- desc: 'Enable notifications for pipeline_events'
- },
- {
- required: false,
- name: :wiki_page_events,
- type: Boolean,
- desc: 'Enable notifications for wiki_page_events'
- }
- ].freeze
-
- services = {
- 'asana' => [
- {
- required: true,
- name: :api_key,
- type: String,
- desc: 'User API token'
- },
- {
- required: false,
- name: :restrict_to_branch,
- type: String,
- desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches'
- }
- ],
- 'assembla' => [
- {
- required: true,
- name: :token,
- type: String,
- desc: 'The authentication token'
- },
- {
- required: false,
- name: :subdomain,
- type: String,
- desc: 'Subdomain setting'
- }
- ],
- 'bamboo' => [
- {
- required: true,
- name: :bamboo_url,
- type: String,
- desc: 'Bamboo root URL like https://bamboo.example.com'
- },
- {
- required: true,
- name: :build_key,
- type: String,
- desc: 'Bamboo build plan key like'
- },
- {
- required: true,
- name: :username,
- type: String,
- desc: 'A user with API access, if applicable'
- },
- {
- required: true,
- name: :password,
- type: String,
- desc: 'Passord of the user'
- }
- ],
- 'bugzilla' => [
- {
- required: true,
- name: :new_issue_url,
- type: String,
- desc: 'New issue URL'
- },
- {
- required: true,
- name: :issues_url,
- type: String,
- desc: 'Issues URL'
- },
- {
- required: true,
- name: :project_url,
- type: String,
- desc: 'Project URL'
- },
- {
- required: false,
- name: :description,
- type: String,
- desc: 'Description'
- },
- {
- required: false,
- name: :title,
- type: String,
- desc: 'Title'
- }
- ],
- 'buildkite' => [
- {
- required: true,
- name: :token,
- type: String,
- desc: 'Buildkite project GitLab token'
- },
- {
- required: true,
- name: :project_url,
- type: String,
- desc: 'The buildkite project URL'
- },
- {
- required: false,
- name: :enable_ssl_verification,
- type: Boolean,
- desc: 'Enable SSL verification for communication'
- }
- ],
- 'campfire' => [
- {
- required: true,
- name: :token,
- type: String,
- desc: 'Campfire token'
- },
- {
- required: false,
- name: :subdomain,
- type: String,
- desc: 'Campfire subdomain'
- },
- {
- required: false,
- name: :room,
- type: String,
- desc: 'Campfire room'
- }
- ],
- 'custom-issue-tracker' => [
- {
- required: true,
- name: :new_issue_url,
- type: String,
- desc: 'New issue URL'
- },
- {
- required: true,
- name: :issues_url,
- type: String,
- desc: 'Issues URL'
- },
- {
- required: true,
- name: :project_url,
- type: String,
- desc: 'Project URL'
- },
- {
- required: false,
- name: :description,
- type: String,
- desc: 'Description'
- },
- {
- required: false,
- name: :title,
- type: String,
- desc: 'Title'
- }
- ],
- 'discord' => [
- {
- required: true,
- name: :webhook,
- type: String,
- desc: 'Discord webhook. e.g. https://discordapp.com/api/webhooks/…'
- }
- ],
- 'drone-ci' => [
- {
- required: true,
- name: :token,
- type: String,
- desc: 'Drone CI token'
- },
- {
- required: true,
- name: :drone_url,
- type: String,
- desc: 'Drone CI URL'
- },
- {
- required: false,
- name: :enable_ssl_verification,
- type: Boolean,
- desc: 'Enable SSL verification for communication'
- }
- ],
- 'emails-on-push' => [
- {
- required: true,
- name: :recipients,
- type: String,
- desc: 'Comma-separated list of recipient email addresses'
- },
- {
- required: false,
- name: :disable_diffs,
- type: Boolean,
- desc: 'Disable code diffs'
- },
- {
- required: false,
- name: :send_from_committer_email,
- type: Boolean,
- desc: 'Send from committer'
- }
- ],
- 'external-wiki' => [
- {
- required: true,
- name: :external_wiki_url,
- type: String,
- desc: 'The URL of the external Wiki'
- }
- ],
- 'flowdock' => [
- {
- required: true,
- name: :token,
- type: String,
- desc: 'Flowdock token'
- }
- ],
- 'hangouts-chat' => [
- {
- required: true,
- name: :webhook,
- type: String,
- desc: 'The Hangouts Chat webhook. e.g. https://chat.googleapis.com/v1/spaces…'
- },
- CHAT_NOTIFICATION_EVENTS
- ].flatten,
- 'irker' => [
- {
- required: true,
- name: :recipients,
- type: String,
- desc: 'Recipients/channels separated by whitespaces'
- },
- {
- required: false,
- name: :default_irc_uri,
- type: String,
- desc: 'Default: irc://irc.network.net:6697'
- },
- {
- required: false,
- name: :server_host,
- type: String,
- desc: 'Server host. Default localhost'
- },
- {
- required: false,
- name: :server_port,
- type: Integer,
- desc: 'Server port. Default 6659'
- },
- {
- required: false,
- name: :colorize_messages,
- type: Boolean,
- desc: 'Colorize messages'
- }
- ],
- 'jira' => [
- {
- required: true,
- name: :url,
- type: String,
- desc: 'The base URL to the JIRA instance web interface which is being linked to this GitLab project. E.g., https://jira.example.com'
- },
- {
- required: false,
- name: :api_url,
- type: String,
- desc: 'The base URL to the JIRA instance API. Web URL value will be used if not set. E.g., https://jira-api.example.com'
- },
- {
- required: true,
- name: :username,
- type: String,
- desc: 'The username of the user created to be used with GitLab/JIRA'
- },
- {
- required: true,
- name: :password,
- type: String,
- desc: 'The password of the user created to be used with GitLab/JIRA'
- },
- {
- required: false,
- name: :jira_issue_transition_id,
- type: String,
- desc: '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`'
- }
- ],
-
- 'kubernetes' => [
- {
- required: true,
- name: :namespace,
- type: String,
- desc: 'The Kubernetes namespace to use'
- },
- {
- required: true,
- name: :api_url,
- type: String,
- desc: 'The URL to the Kubernetes cluster API, e.g., https://kubernetes.example.com'
- },
- {
- required: true,
- name: :token,
- type: String,
- desc: 'The service token to authenticate against the Kubernetes cluster with'
- },
- {
- required: false,
- name: :ca_pem,
- type: String,
- desc: 'A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)'
- }
- ],
- 'mattermost-slash-commands' => [
- {
- required: true,
- name: :token,
- type: String,
- desc: 'The Mattermost token'
- }
- ],
- 'slack-slash-commands' => [
- {
- required: true,
- name: :token,
- type: String,
- desc: 'The Slack token'
- }
- ],
- 'packagist' => [
- {
- required: true,
- name: :username,
- type: String,
- desc: 'The username'
- },
- {
- required: true,
- name: :token,
- type: String,
- desc: 'The Packagist API token'
- },
- {
- required: false,
- name: :server,
- type: String,
- desc: 'The server'
- }
- ],
- 'pipelines-email' => [
- {
- required: true,
- name: :recipients,
- type: String,
- desc: 'Comma-separated list of recipient email addresses'
- },
- {
- required: false,
- name: :notify_only_broken_pipelines,
- type: Boolean,
- desc: 'Notify only broken pipelines'
- }
- ],
- 'pivotaltracker' => [
- {
- required: true,
- name: :token,
- type: String,
- desc: 'The Pivotaltracker token'
- },
- {
- required: false,
- name: :restrict_to_branch,
- type: String,
- desc: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.'
- }
- ],
- 'prometheus' => [
- {
- required: true,
- name: :api_url,
- type: String,
- desc: 'Prometheus API Base URL, like http://prometheus.example.com/'
- }
- ],
- 'pushover' => [
- {
- required: true,
- name: :api_key,
- type: String,
- desc: 'The application key'
- },
- {
- required: true,
- name: :user_key,
- type: String,
- desc: 'The user key'
- },
- {
- required: true,
- name: :priority,
- type: String,
- desc: 'The priority'
- },
- {
- required: true,
- name: :device,
- type: String,
- desc: 'Leave blank for all active devices'
- },
- {
- required: true,
- name: :sound,
- type: String,
- desc: 'The sound of the notification'
- }
- ],
- 'redmine' => [
- {
- required: true,
- name: :new_issue_url,
- type: String,
- desc: 'The new issue URL'
- },
- {
- required: true,
- name: :project_url,
- type: String,
- desc: 'The project URL'
- },
- {
- required: true,
- name: :issues_url,
- type: String,
- desc: 'The issues URL'
- },
- {
- required: false,
- name: :description,
- type: String,
- desc: 'The description of the tracker'
- }
- ],
- 'youtrack' => [
- {
- required: true,
- name: :project_url,
- type: String,
- desc: 'The project URL'
- },
- {
- required: true,
- name: :issues_url,
- type: String,
- desc: 'The issues URL'
- },
- {
- required: false,
- name: :description,
- type: String,
- desc: 'The description of the tracker'
- }
- ],
- 'slack' => [
- CHAT_NOTIFICATION_SETTINGS,
- CHAT_NOTIFICATION_FLAGS,
- CHAT_NOTIFICATION_CHANNELS,
- CHAT_NOTIFICATION_EVENTS
- ].flatten,
- 'microsoft-teams' => [
- {
- required: true,
- name: :webhook,
- type: String,
- desc: 'The Microsoft Teams webhook. e.g. https://outlook.office.com/webhook/…'
- }
- ],
- 'mattermost' => [
- CHAT_NOTIFICATION_SETTINGS,
- CHAT_NOTIFICATION_FLAGS,
- CHAT_NOTIFICATION_CHANNELS,
- CHAT_NOTIFICATION_EVENTS
- ].flatten,
- 'teamcity' => [
- {
- required: true,
- name: :teamcity_url,
- type: String,
- desc: 'TeamCity root URL like https://teamcity.example.com'
- },
- {
- required: true,
- name: :build_type,
- type: String,
- desc: 'Build configuration ID'
- },
- {
- required: true,
- name: :username,
- type: String,
- desc: 'A user with permissions to trigger a manual build'
- },
- {
- required: true,
- name: :password,
- type: String,
- desc: 'The password of the user'
- }
- ]
- }
-
- service_classes = [
- AsanaService,
- AssemblaService,
- BambooService,
- BugzillaService,
- BuildkiteService,
- CampfireService,
- CustomIssueTrackerService,
- DiscordService,
- DroneCiService,
- EmailsOnPushService,
- ExternalWikiService,
- FlowdockService,
- HangoutsChatService,
- IrkerService,
- JiraService,
- KubernetesService,
- MattermostSlashCommandsService,
- SlackSlashCommandsService,
- PackagistService,
- PipelinesEmailService,
- PivotaltrackerService,
- PrometheusService,
- PushoverService,
- RedmineService,
- YoutrackService,
- SlackService,
- MattermostService,
- MicrosoftTeamsService,
- TeamcityService
- ]
+ services = Helpers::ServicesHelpers.services
+ service_classes = Helpers::ServicesHelpers.service_classes
if Rails.env.development?
services['mock-ci'] = [
@@ -704,11 +16,7 @@ module API
services['mock-deployment'] = []
services['mock-monitoring'] = []
- service_classes += [
- MockCiService,
- MockDeploymentService,
- MockMonitoringService
- ]
+ service_classes += Helpers::ServicesHelpers.development_service_classes
end
SERVICES = services.freeze
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index b16faffe335..3cb2f69c4ef 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -9,6 +9,11 @@ module API
@current_setting ||=
(ApplicationSetting.current_without_cache || ApplicationSetting.create_from_defaults)
end
+
+ def filter_attributes_using_license(attrs)
+ # This method will be redefined in EE.
+ attrs
+ end
end
desc 'Get the current application settings' do
@@ -156,6 +161,8 @@ module API
attrs[:password_authentication_enabled_for_web] = attrs.delete(:password_authentication_enabled)
end
+ attrs = filter_attributes_using_license(attrs)
+
if ApplicationSettings::UpdateService.new(current_settings, current_user, attrs).execute
present current_settings, with: Entities::ApplicationSetting
else
diff --git a/lib/api/triggers.rb b/lib/api/triggers.rb
index 8fc7c7361e1..0e829c5699b 100644
--- a/lib/api/triggers.rb
+++ b/lib/api/triggers.rb
@@ -13,7 +13,7 @@ module API
end
params do
requires :ref, type: String, desc: 'The commit sha or name of a branch or tag', allow_blank: false
- requires :token, type: String, desc: 'The unique token of trigger'
+ requires :token, type: String, desc: 'The unique token of trigger or job token'
optional :variables, type: Hash, desc: 'The list of variables to be injected into build'
end
post ":id/(ref/:ref/)trigger/pipeline", requirements: { ref: /.+/ } do
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 7d88880d412..a3d4acc63d5 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -124,7 +124,7 @@ module API
user = User.find_by(id: params[:id])
not_found!('User') unless user && can?(current_user, :read_user, user)
- opts = { with: current_user&.admin? ? Entities::UserWithAdmin : Entities::User, current_user: current_user }
+ opts = { with: current_user&.admin? ? Entities::UserDetailsWithAdmin : Entities::User, current_user: current_user }
user, opts = with_custom_attributes(user, opts)
present user, opts
diff --git a/lib/api/variables.rb b/lib/api/variables.rb
index 148deb86c4c..d0d81ebc870 100644
--- a/lib/api/variables.rb
+++ b/lib/api/variables.rb
@@ -7,6 +7,14 @@ module API
before { authenticate! }
before { authorize! :admin_build, user_project }
+ helpers do
+ def filter_variable_parameters(params)
+ # This method exists so that EE can more easily filter out certain
+ # parameters, without having to modify the source code directly.
+ params
+ end
+ end
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
@@ -50,6 +58,7 @@ module API
end
post ':id/variables' do
variable_params = declared_params(include_missing: false)
+ variable_params = filter_variable_parameters(variable_params)
variable = user_project.variables.create(variable_params)
@@ -75,6 +84,7 @@ module API
break not_found!('Variable') unless variable
variable_params = declared_params(include_missing: false).except(:key)
+ variable_params = filter_variable_parameters(variable_params)
if variable.update(variable_params)
present variable, with: Entities::Variable
diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb
index 9577df2634a..5a20b6ae0a6 100644
--- a/lib/backup/uploads.rb
+++ b/lib/backup/uploads.rb
@@ -9,7 +9,7 @@ module Backup
def initialize(progress)
@progress = progress
- super('uploads', Rails.root.join('public/uploads'))
+ super('uploads', File.join(Gitlab.config.uploads.storage_path, "uploads"))
end
end
end
diff --git a/lib/banzai/filter/output_safety.rb b/lib/banzai/filter/output_safety.rb
new file mode 100644
index 00000000000..d4ebce5d9c9
--- /dev/null
+++ b/lib/banzai/filter/output_safety.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+module Banzai
+ module Filter
+ module OutputSafety
+ def escape_once(html)
+ html.html_safe? ? html : ERB::Util.html_escape_once(html)
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index 42f9b3a689c..b3ce9200b49 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -12,6 +12,7 @@ module Banzai
# :only_path - Generate path-only links.
class ReferenceFilter < HTML::Pipeline::Filter
include RequestStoreReferenceCache
+ include OutputSafety
class << self
attr_accessor :reference_type
@@ -43,10 +44,6 @@ module Banzai
end.join(' ')
end
- def escape_once(html)
- html.html_safe? ? html : ERB::Util.html_escape_once(html)
- end
-
def ignore_ancestor_query
@ignore_ancestor_query ||= begin
parents = %w(pre code a style)
diff --git a/lib/banzai/filter/suggestion_filter.rb b/lib/banzai/filter/suggestion_filter.rb
index 9950db373d8..848aca10a20 100644
--- a/lib/banzai/filter/suggestion_filter.rb
+++ b/lib/banzai/filter/suggestion_filter.rb
@@ -6,11 +6,15 @@ module Banzai
class SuggestionFilter < HTML::Pipeline::Filter
# Class used for tagging elements that should be rendered
TAG_CLASS = 'js-render-suggestion'.freeze
+ SUGGESTION_REGEX = Gitlab::Diff::SuggestionsParser::SUGGESTION_CONTEXT
def call
return doc unless suggestions_filter_enabled?
doc.search('pre.suggestion > code').each do |node|
+ # TODO: Remove once multi-line suggestions FF get removed (#59178).
+ remove_multi_line_params(node.parent)
+
node.add_class(TAG_CLASS)
end
@@ -20,6 +24,20 @@ module Banzai
def suggestions_filter_enabled?
context[:suggestions_filter_enabled]
end
+
+ private
+
+ def project
+ context[:project]
+ end
+
+ def remove_multi_line_params(node)
+ return if Feature.enabled?(:multi_line_suggestions, project)
+
+ if node[SyntaxHighlightFilter::LANG_PARAMS_ATTR]&.match?(SUGGESTION_REGEX)
+ node.remove_attribute(SyntaxHighlightFilter::LANG_PARAMS_ATTR)
+ end
+ end
end
end
end
diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb
index 9ffde52b5f2..fe56f9a1e33 100644
--- a/lib/banzai/filter/syntax_highlight_filter.rb
+++ b/lib/banzai/filter/syntax_highlight_filter.rb
@@ -8,6 +8,11 @@ module Banzai
# HTML Filter to highlight fenced code blocks
#
class SyntaxHighlightFilter < HTML::Pipeline::Filter
+ include OutputSafety
+
+ PARAMS_DELIMITER = ':'.freeze
+ LANG_PARAMS_ATTR = 'data-lang-params'.freeze
+
def call
doc.search('pre > code').each do |node|
highlight_node(node)
@@ -18,7 +23,7 @@ module Banzai
def highlight_node(node)
css_classes = +'code highlight js-syntax-highlight'
- lang = node.attr('lang')
+ lang, lang_params = parse_lang_params(node.attr('lang'))
retried = false
if use_rouge?(lang)
@@ -46,7 +51,10 @@ module Banzai
retry
end
- highlighted = %(<pre class="#{css_classes}" lang="#{language}" v-pre="true"><code>#{code}</code></pre>)
+ highlighted = %(<pre class="#{css_classes}"
+ lang="#{language}"
+ #{lang_params}
+ v-pre="true"><code>#{code}</code></pre>)
# Extracted to a method to measure it
replace_parent_pre_element(node, highlighted)
@@ -54,6 +62,15 @@ module Banzai
private
+ def parse_lang_params(language)
+ return unless language
+
+ lang, params = language.split(PARAMS_DELIMITER, 2)
+ formatted_params = %(#{LANG_PARAMS_ATTR}="#{escape_once(params)}") if params
+
+ [lang, formatted_params]
+ end
+
# Separate method so it can be instrumented.
def lex(lexer, code)
lexer.lex(code)
diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb
index ec090aea784..6c8ca8f219c 100644
--- a/lib/gitlab/access.rb
+++ b/lib/gitlab/access.rb
@@ -46,6 +46,12 @@ module Gitlab
)
end
+ def options_with_none
+ options_with_owner.merge(
+ "None" => NO_ACCESS
+ )
+ end
+
def sym_options
{
guest: GUEST,
@@ -75,12 +81,20 @@ module Gitlab
def human_access(access)
options_with_owner.key(access)
end
+
+ def human_access_with_none(access)
+ options_with_none.key(access)
+ end
end
def human_access
Gitlab::Access.human_access(access_field)
end
+ def human_access_with_none
+ Gitlab::Access.human_access_with_none(access_field)
+ end
+
def owner?
access_field == OWNER
end
diff --git a/lib/gitlab/auth/ldap/person.rb b/lib/gitlab/auth/ldap/person.rb
index 13d67e0f871..c1517222956 100644
--- a/lib/gitlab/auth/ldap/person.rb
+++ b/lib/gitlab/auth/ldap/person.rb
@@ -69,7 +69,7 @@ module Gitlab
end
def name
- attribute_value(:name).first
+ attribute_value(:name)&.first
end
def uid
diff --git a/lib/gitlab/auth/o_auth/user.rb b/lib/gitlab/auth/o_auth/user.rb
index f38c5d57c44..09d1d79fefc 100644
--- a/lib/gitlab/auth/o_auth/user.rb
+++ b/lib/gitlab/auth/o_auth/user.rb
@@ -146,7 +146,6 @@ module Gitlab
Gitlab::Auth::LDAP::Person.find_by_uid(auth_hash.uid, adapter) ||
Gitlab::Auth::LDAP::Person.find_by_email(auth_hash.uid, adapter) ||
Gitlab::Auth::LDAP::Person.find_by_dn(auth_hash.uid, adapter)
-
rescue Gitlab::Auth::LDAP::LDAPConnectionError
nil
end
@@ -200,22 +199,19 @@ module Gitlab
# Give preference to LDAP for sensitive information when creating a linked account
if creating_linked_ldap_user?
username = ldap_person.username.presence
+ name = ldap_person.name.presence
email = ldap_person.email.first.presence
end
username ||= auth_hash.username
+ name ||= auth_hash.name
email ||= auth_hash.email
valid_username = ::Namespace.clean_path(username)
-
- uniquify = Uniquify.new
- valid_username = uniquify.string(valid_username) { |s| !NamespacePathValidator.valid_path?(s) }
-
- name = auth_hash.name
- name = valid_username if name.strip.empty?
+ valid_username = Uniquify.new.string(valid_username) { |s| !NamespacePathValidator.valid_path?(s) }
{
- name: name,
+ name: name.strip.presence || valid_username,
username: valid_username,
email: email,
password: auth_hash.password,
@@ -248,8 +244,9 @@ module Gitlab
metadata.provider = auth_hash.provider
end
- if creating_linked_ldap_user? && gl_user.email == ldap_person.email.first
- metadata.set_attribute_synced(:email, true)
+ if creating_linked_ldap_user?
+ metadata.set_attribute_synced(:name, true) if gl_user.name == ldap_person.name
+ metadata.set_attribute_synced(:email, true) if gl_user.email == ldap_person.email.first
metadata.provider = ldap_person.provider
end
end
diff --git a/lib/gitlab/authorized_keys.rb b/lib/gitlab/authorized_keys.rb
new file mode 100644
index 00000000000..3fe72f5fd43
--- /dev/null
+++ b/lib/gitlab/authorized_keys.rb
@@ -0,0 +1,149 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class AuthorizedKeys
+ KeyError = Class.new(StandardError)
+
+ attr_reader :logger
+
+ # Initializes the class
+ #
+ # @param [Gitlab::Logger] logger
+ def initialize(logger = Gitlab::AppLogger)
+ @logger = logger
+ end
+
+ # Add id and its key to the authorized_keys file
+ #
+ # @param [String] id identifier of key prefixed by `key-`
+ # @param [String] key public key to be added
+ # @return [Boolean]
+ def add_key(id, key)
+ lock do
+ public_key = strip(key)
+ logger.info("Adding key (#{id}): #{public_key}")
+ open_authorized_keys_file('a') { |file| file.puts(key_line(id, public_key)) }
+ end
+
+ true
+ end
+
+ # Atomically add all the keys to the authorized_keys file
+ #
+ # @param [Array<::Key>] keys list of Key objects to be added
+ # @return [Boolean]
+ def batch_add_keys(keys)
+ lock(300) do # Allow 300 seconds (5 minutes) for batch_add_keys
+ open_authorized_keys_file('a') do |file|
+ keys.each do |key|
+ public_key = strip(key.key)
+ logger.info("Adding key (#{key.shell_id}): #{public_key}")
+ file.puts(key_line(key.shell_id, public_key))
+ end
+ end
+ end
+
+ true
+ rescue Gitlab::AuthorizedKeys::KeyError
+ false
+ end
+
+ # Remove key by ID from the authorized_keys file
+ #
+ # @param [String] id identifier of the key to be removed prefixed by `key-`
+ # @return [Boolean]
+ def rm_key(id)
+ lock do
+ logger.info("Removing key (#{id})")
+ open_authorized_keys_file('r+') do |f|
+ while line = f.gets
+ next unless line.start_with?("command=\"#{command(id)}\"")
+
+ f.seek(-line.length, IO::SEEK_CUR)
+ # Overwrite the line with #'s. Because the 'line' variable contains
+ # a terminating '\n', we write line.length - 1 '#' characters.
+ f.write('#' * (line.length - 1))
+ end
+ end
+ end
+
+ true
+ rescue Errno::ENOENT
+ false
+ end
+
+ # Clear the authorized_keys file
+ #
+ # @return [Boolean]
+ def clear
+ open_authorized_keys_file('w') { |file| file.puts '# Managed by gitlab-rails' }
+
+ true
+ end
+
+ # Read the authorized_keys file and return IDs of each key
+ #
+ # @return [Array<Integer>]
+ def list_key_ids
+ logger.info('Listing all key IDs')
+
+ [].tap do |a|
+ open_authorized_keys_file('r') do |f|
+ f.each_line do |line|
+ key_id = line.match(/key-(\d+)/)
+
+ next unless key_id
+
+ a << key_id[1].chomp.to_i
+ end
+ end
+ end
+ rescue Errno::ENOENT
+ []
+ end
+
+ private
+
+ def lock(timeout = 10)
+ File.open("#{authorized_keys_file}.lock", "w+") do |f|
+ f.flock File::LOCK_EX
+ Timeout.timeout(timeout) { yield }
+ ensure
+ f.flock File::LOCK_UN
+ end
+ end
+
+ def open_authorized_keys_file(mode)
+ File.open(authorized_keys_file, mode, 0o600) do |file|
+ file.chmod(0o600)
+ yield file
+ end
+ end
+
+ def key_line(id, key)
+ key = key.chomp
+
+ if key.include?("\n") || key.include?("\t")
+ raise KeyError, "Invalid public_key: #{key.inspect}"
+ end
+
+ %Q(command="#{command(id)}",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty #{strip(key)})
+ end
+
+ def command(id)
+ unless /\A[a-z0-9-]+\z/ =~ id
+ raise KeyError, "Invalid ID: #{id.inspect}"
+ end
+
+ "#{File.join(Gitlab.config.gitlab_shell.path, 'bin', 'gitlab-shell')} #{id}"
+ end
+
+ def strip(key)
+ key.split(/[ ]+/)[0, 2].join(' ')
+ end
+
+ def authorized_keys_file
+ Gitlab.config.gitlab_shell.authorized_keys_file
+ end
+ end
+end
diff --git a/lib/gitlab/background_migration/archive_legacy_traces.rb b/lib/gitlab/background_migration/archive_legacy_traces.rb
index 92096e29ef1..7ee783b8489 100644
--- a/lib/gitlab/background_migration/archive_legacy_traces.rb
+++ b/lib/gitlab/background_migration/archive_legacy_traces.rb
@@ -11,11 +11,10 @@ module Gitlab
# So we chose a way to use ::Ci::Build directly and we don't change the `archive!` method until 11.1
::Ci::Build.finished.without_archived_trace
.where(id: start_id..stop_id).find_each do |build|
- begin
- build.trace.archive!
- rescue => e
- Rails.logger.error "Failed to archive live trace. id: #{build.id} message: #{e.message}"
- end
+
+ build.trace.archive!
+ rescue => e
+ Rails.logger.error "Failed to archive live trace. id: #{build.id} message: #{e.message}"
end
end
end
diff --git a/lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb b/lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb
index 698f5e46c0c..48aa369705f 100644
--- a/lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb
+++ b/lib/gitlab/background_migration/normalize_ldap_extern_uids_range.rb
@@ -302,14 +302,12 @@ module Gitlab
ldap_identities = Identity.where("provider like 'ldap%'").where(id: start_id..end_id)
ldap_identities.each do |identity|
- begin
- identity.extern_uid = Gitlab::Auth::LDAP::DN.new(identity.extern_uid).to_normalized_s
- unless identity.save
- Rails.logger.info "Unable to normalize \"#{identity.extern_uid}\". Skipping."
- end
- rescue Gitlab::Auth::LDAP::DN::FormatError => e
- Rails.logger.info "Unable to normalize \"#{identity.extern_uid}\" due to \"#{e.message}\". Skipping."
+ identity.extern_uid = Gitlab::Auth::LDAP::DN.new(identity.extern_uid).to_normalized_s
+ unless identity.save
+ Rails.logger.info "Unable to normalize \"#{identity.extern_uid}\". Skipping."
end
+ rescue Gitlab::Auth::LDAP::DN::FormatError => e
+ Rails.logger.info "Unable to normalize \"#{identity.extern_uid}\" due to \"#{e.message}\". Skipping."
end
end
diff --git a/lib/gitlab/background_migration/populate_untracked_uploads.rb b/lib/gitlab/background_migration/populate_untracked_uploads.rb
index a19dc9747fb..755b5ee725a 100644
--- a/lib/gitlab/background_migration/populate_untracked_uploads.rb
+++ b/lib/gitlab/background_migration/populate_untracked_uploads.rb
@@ -34,18 +34,16 @@ module Gitlab
def filter_error_files(files)
files.partition do |file|
- begin
- file.to_h
- true
- rescue => e
- msg = <<~MSG
+ file.to_h
+ true
+ rescue => e
+ msg = <<~MSG
Error parsing path "#{file.path}":
#{e.message}
#{e.backtrace.join("\n ")}
MSG
- Rails.logger.error(msg)
- false
- end
+ Rails.logger.error(msg)
+ false
end
end
diff --git a/lib/gitlab/badge/pipeline/template.rb b/lib/gitlab/badge/pipeline/template.rb
index 64c3dfcd10b..2c5f9654496 100644
--- a/lib/gitlab/badge/pipeline/template.rb
+++ b/lib/gitlab/badge/pipeline/template.rb
@@ -15,6 +15,7 @@ module Gitlab
failed: '#e05d44',
running: '#dfb317',
pending: '#dfb317',
+ preparing: '#dfb317',
canceled: '#9f9f9f',
skipped: '#9f9f9f',
unknown: '#9f9f9f'
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index 441fdec8a1e..769d3279f91 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -79,31 +79,29 @@ module Gitlab
create_labels
client.issues(repo).each do |issue|
- begin
- description = ''
- description += @formatter.author_line(issue.author) unless find_user_id(issue.author)
- description += issue.description
-
- label_name = issue.kind
- milestone = issue.milestone ? project.milestones.find_or_create_by(title: issue.milestone) : nil
-
- gitlab_issue = project.issues.create!(
- iid: issue.iid,
- title: issue.title,
- description: description,
- state: issue.state,
- author_id: gitlab_user_id(project, issue.author),
- milestone: milestone,
- created_at: issue.created_at,
- updated_at: issue.updated_at
- )
-
- gitlab_issue.labels << @labels[label_name]
-
- import_issue_comments(issue, gitlab_issue) if gitlab_issue.persisted?
- rescue StandardError => e
- errors << { type: :issue, iid: issue.iid, errors: e.message }
- end
+ description = ''
+ description += @formatter.author_line(issue.author) unless find_user_id(issue.author)
+ description += issue.description
+
+ label_name = issue.kind
+ milestone = issue.milestone ? project.milestones.find_or_create_by(title: issue.milestone) : nil
+
+ gitlab_issue = project.issues.create!(
+ iid: issue.iid,
+ title: issue.title,
+ description: description,
+ state: issue.state,
+ author_id: gitlab_user_id(project, issue.author),
+ milestone: milestone,
+ created_at: issue.created_at,
+ updated_at: issue.updated_at
+ )
+
+ gitlab_issue.labels << @labels[label_name]
+
+ import_issue_comments(issue, gitlab_issue) if gitlab_issue.persisted?
+ rescue StandardError => e
+ errors << { type: :issue, iid: issue.iid, errors: e.message }
end
end
# rubocop: enable CodeReuse/ActiveRecord
@@ -150,37 +148,35 @@ module Gitlab
pull_requests = client.pull_requests(repo)
pull_requests.each do |pull_request|
- begin
- description = ''
- description += @formatter.author_line(pull_request.author) unless find_user_id(pull_request.author)
- description += pull_request.description
-
- source_branch_sha = pull_request.source_branch_sha
- target_branch_sha = pull_request.target_branch_sha
- source_branch_sha = project.repository.commit(source_branch_sha)&.sha || source_branch_sha
- target_branch_sha = project.repository.commit(target_branch_sha)&.sha || target_branch_sha
-
- merge_request = project.merge_requests.create!(
- iid: pull_request.iid,
- title: pull_request.title,
- description: description,
- source_project: project,
- source_branch: pull_request.source_branch_name,
- source_branch_sha: source_branch_sha,
- target_project: project,
- target_branch: pull_request.target_branch_name,
- target_branch_sha: target_branch_sha,
- state: pull_request.state,
- author_id: gitlab_user_id(project, pull_request.author),
- assignee_id: nil,
- created_at: pull_request.created_at,
- updated_at: pull_request.updated_at
- )
-
- import_pull_request_comments(pull_request, merge_request) if merge_request.persisted?
- rescue StandardError => e
- errors << { type: :pull_request, iid: pull_request.iid, errors: e.message, trace: e.backtrace.join("\n"), raw_response: pull_request.raw }
- end
+ description = ''
+ description += @formatter.author_line(pull_request.author) unless find_user_id(pull_request.author)
+ description += pull_request.description
+
+ source_branch_sha = pull_request.source_branch_sha
+ target_branch_sha = pull_request.target_branch_sha
+ source_branch_sha = project.repository.commit(source_branch_sha)&.sha || source_branch_sha
+ target_branch_sha = project.repository.commit(target_branch_sha)&.sha || target_branch_sha
+
+ merge_request = project.merge_requests.create!(
+ iid: pull_request.iid,
+ title: pull_request.title,
+ description: description,
+ source_project: project,
+ source_branch: pull_request.source_branch_name,
+ source_branch_sha: source_branch_sha,
+ target_project: project,
+ target_branch: pull_request.target_branch_name,
+ target_branch_sha: target_branch_sha,
+ state: pull_request.state,
+ author_id: gitlab_user_id(project, pull_request.author),
+ assignee_id: nil,
+ created_at: pull_request.created_at,
+ updated_at: pull_request.updated_at
+ )
+
+ import_pull_request_comments(pull_request, merge_request) if merge_request.persisted?
+ rescue StandardError => e
+ errors << { type: :pull_request, iid: pull_request.iid, errors: e.message, trace: e.backtrace.join("\n"), raw_response: pull_request.raw }
end
end
@@ -211,23 +207,21 @@ module Gitlab
end
inline_comments.each do |comment|
- begin
- attributes = pull_request_comment_attributes(comment)
- attributes[:discussion_id] = discussion_map[comment.parent_id] if comment.has_parent?
+ attributes = pull_request_comment_attributes(comment)
+ attributes[:discussion_id] = discussion_map[comment.parent_id] if comment.has_parent?
- attributes.merge!(
- position: position_map[comment.iid],
- type: 'DiffNote')
+ attributes.merge!(
+ position: position_map[comment.iid],
+ type: 'DiffNote')
- note = merge_request.notes.create!(attributes)
+ note = merge_request.notes.create!(attributes)
- # We can't store a discussion ID until a note is created, so if
- # replies are created before the parent the discussion ID won't be
- # linked properly.
- discussion_map[comment.iid] = note.discussion_id
- rescue StandardError => e
- errors << { type: :pull_request, iid: comment.iid, errors: e.message }
- end
+ # We can't store a discussion ID until a note is created, so if
+ # replies are created before the parent the discussion ID won't be
+ # linked properly.
+ discussion_map[comment.iid] = note.discussion_id
+ rescue StandardError => e
+ errors << { type: :pull_request, iid: comment.iid, errors: e.message }
end
end
@@ -245,11 +239,9 @@ module Gitlab
def import_standalone_pr_comments(pr_comments, merge_request)
pr_comments.each do |comment|
- begin
- merge_request.notes.create!(pull_request_comment_attributes(comment))
- rescue StandardError => e
- errors << { type: :pull_request, iid: comment.iid, errors: e.message }
- end
+ merge_request.notes.create!(pull_request_comment_attributes(comment))
+ rescue StandardError => e
+ errors << { type: :pull_request, iid: comment.iid, errors: e.message }
end
end
diff --git a/lib/gitlab/bitbucket_server_import/importer.rb b/lib/gitlab/bitbucket_server_import/importer.rb
index 4a789ae457f..1d3ddeeb0f1 100644
--- a/lib/gitlab/bitbucket_server_import/importer.rb
+++ b/lib/gitlab/bitbucket_server_import/importer.rb
@@ -162,27 +162,23 @@ module Gitlab
restore_branches(batch) if recover_missing_commits
batch.each do |pull_request|
- begin
- import_bitbucket_pull_request(pull_request)
- rescue StandardError => e
- backtrace = Gitlab::Profiler.clean_backtrace(e.backtrace)
- log_error(stage: 'import_pull_requests', iid: pull_request.iid, error: e.message, backtrace: backtrace)
-
- errors << { type: :pull_request, iid: pull_request.iid, errors: e.message, backtrace: backtrace.join("\n"), raw_response: pull_request.raw }
- end
+ import_bitbucket_pull_request(pull_request)
+ rescue StandardError => e
+ backtrace = Gitlab::Profiler.clean_backtrace(e.backtrace)
+ log_error(stage: 'import_pull_requests', iid: pull_request.iid, error: e.message, backtrace: backtrace)
+
+ errors << { type: :pull_request, iid: pull_request.iid, errors: e.message, backtrace: backtrace.join("\n"), raw_response: pull_request.raw }
end
end
end
def delete_temp_branches
@temp_branches.each do |branch|
- begin
- client.delete_branch(project_key, repository_slug, branch.name, branch.sha)
- project.repository.delete_branch(branch.name)
- rescue BitbucketServer::Connection::ConnectionError => e
- log_error(stage: 'delete_temp_branches', branch: branch.name, error: e.message)
- @errors << { type: :delete_temp_branches, branch_name: branch.name, errors: e.message }
- end
+ client.delete_branch(project_key, repository_slug, branch.name, branch.sha)
+ project.repository.delete_branch(branch.name)
+ rescue BitbucketServer::Connection::ConnectionError => e
+ log_error(stage: 'delete_temp_branches', branch: branch.name, error: e.message)
+ @errors << { type: :delete_temp_branches, branch_name: branch.name, errors: e.message }
end
end
@@ -323,16 +319,14 @@ module Gitlab
def import_standalone_pr_comments(pr_comments, merge_request)
pr_comments.each do |comment|
- begin
- merge_request.notes.create!(pull_request_comment_attributes(comment))
+ merge_request.notes.create!(pull_request_comment_attributes(comment))
- comment.comments.each do |replies|
- merge_request.notes.create!(pull_request_comment_attributes(replies))
- end
- rescue StandardError => e
- log_error(stage: 'import_standalone_pr_comments', merge_request_id: merge_request.id, comment_id: comment.id, error: e.message)
- errors << { type: :pull_request, comment_id: comment.id, errors: e.message }
+ comment.comments.each do |replies|
+ merge_request.notes.create!(pull_request_comment_attributes(replies))
end
+ rescue StandardError => e
+ log_error(stage: 'import_standalone_pr_comments', merge_request_id: merge_request.id, comment_id: comment.id, error: e.message)
+ errors << { type: :pull_request, comment_id: comment.id, errors: e.message }
end
end
diff --git a/lib/gitlab/checks/branch_check.rb b/lib/gitlab/checks/branch_check.rb
index ad926739752..1dbd564fb6f 100644
--- a/lib/gitlab/checks/branch_check.rb
+++ b/lib/gitlab/checks/branch_check.rb
@@ -59,6 +59,8 @@ module Gitlab
def protected_branch_creation_checks
logger.log_timed(LOG_MESSAGES[:protected_branch_creation_checks]) do
+ break if user_access.can_push_to_branch?(branch_name)
+
unless user_access.can_merge_to_branch?(branch_name)
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:create_protected_branch]
end
diff --git a/lib/gitlab/ci/build/artifacts/metadata.rb b/lib/gitlab/ci/build/artifacts/metadata.rb
index d45a044686e..7011dd1aaf2 100644
--- a/lib/gitlab/ci/build/artifacts/metadata.rb
+++ b/lib/gitlab/ci/build/artifacts/metadata.rb
@@ -98,7 +98,7 @@ module Gitlab
def read_uint32(gz)
binary = gz.read(4)
- binary.unpack('L>')[0] if binary
+ binary.unpack1('L>') if binary
end
def read_string(gz)
diff --git a/lib/gitlab/ci/build/prerequisite/base.rb b/lib/gitlab/ci/build/prerequisite/base.rb
new file mode 100644
index 00000000000..156aa22d95b
--- /dev/null
+++ b/lib/gitlab/ci/build/prerequisite/base.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Build
+ module Prerequisite
+ class Base
+ include Utils::StrongMemoize
+
+ attr_reader :build
+
+ def initialize(build)
+ @build = build
+ end
+
+ def unmet?
+ raise NotImplementedError
+ end
+
+ def complete!
+ raise NotImplementedError
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/build/prerequisite/factory.rb b/lib/gitlab/ci/build/prerequisite/factory.rb
new file mode 100644
index 00000000000..60cdf7af418
--- /dev/null
+++ b/lib/gitlab/ci/build/prerequisite/factory.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Build
+ module Prerequisite
+ class Factory
+ attr_reader :build
+
+ def self.prerequisites
+ [KubernetesNamespace]
+ end
+
+ def initialize(build)
+ @build = build
+ end
+
+ def unmet
+ build_prerequisites.select(&:unmet?)
+ end
+
+ private
+
+ def build_prerequisites
+ self.class.prerequisites.map do |prerequisite|
+ prerequisite.new(build)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
new file mode 100644
index 00000000000..41135ae62bb
--- /dev/null
+++ b/lib/gitlab/ci/build/prerequisite/kubernetes_namespace.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Build
+ module Prerequisite
+ class KubernetesNamespace < Base
+ def unmet?
+ deployment_cluster.present? && kubernetes_namespace.new_record?
+ end
+
+ def complete!
+ return unless unmet?
+
+ create_or_update_namespace
+ end
+
+ private
+
+ def deployment_cluster
+ build.deployment&.cluster
+ end
+
+ def kubernetes_namespace
+ strong_memoize(:kubernetes_namespace) do
+ deployment_cluster.find_or_initialize_kubernetes_namespace_for_project(build.project)
+ end
+ end
+
+ def create_or_update_namespace
+ Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService.new(
+ cluster: deployment_cluster,
+ kubernetes_namespace: kubernetes_namespace
+ ).execute
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/model.rb b/lib/gitlab/ci/model.rb
index fbdb84c0522..1625cb841b6 100644
--- a/lib/gitlab/ci/model.rb
+++ b/lib/gitlab/ci/model.rb
@@ -8,7 +8,7 @@ module Gitlab
end
def model_name
- @model_name ||= ActiveModel::Name.new(self, nil, self.name.split("::").last)
+ @model_name ||= ActiveModel::Name.new(self, nil, self.name.demodulize)
end
end
end
diff --git a/lib/gitlab/ci/pipeline/chain/command.rb b/lib/gitlab/ci/pipeline/chain/command.rb
index 7b77e86feae..bf9f03f6134 100644
--- a/lib/gitlab/ci/pipeline/chain/command.rb
+++ b/lib/gitlab/ci/pipeline/chain/command.rb
@@ -11,7 +11,7 @@ module Gitlab
:trigger_request, :schedule, :merge_request,
:ignore_skip_ci, :save_incompleted,
:seeds_block, :variables_attributes, :push_options,
- :chat_data
+ :chat_data, :allow_mirror_update
) do
include Gitlab::Utils::StrongMemoize
diff --git a/lib/gitlab/ci/status/build/factory.rb b/lib/gitlab/ci/status/build/factory.rb
index 6e4bfe23f2b..f7d0715e617 100644
--- a/lib/gitlab/ci/status/build/factory.rb
+++ b/lib/gitlab/ci/status/build/factory.rb
@@ -11,6 +11,7 @@ module Gitlab
Status::Build::Manual,
Status::Build::Canceled,
Status::Build::Created,
+ Status::Build::Preparing,
Status::Build::Pending,
Status::Build::Skipped],
[Status::Build::Cancelable,
diff --git a/lib/gitlab/ci/status/build/failed.rb b/lib/gitlab/ci/status/build/failed.rb
index d40454df737..76dfe7b7639 100644
--- a/lib/gitlab/ci/status/build/failed.rb
+++ b/lib/gitlab/ci/status/build/failed.rb
@@ -15,7 +15,8 @@ module Gitlab
runner_unsupported: 'unsupported runner',
stale_schedule: 'stale schedule',
job_execution_timeout: 'job execution timeout',
- archived_failure: 'archived failure'
+ archived_failure: 'archived failure',
+ unmet_prerequisites: 'unmet prerequisites'
}.freeze
private_constant :REASONS
diff --git a/lib/gitlab/ci/status/build/preparing.rb b/lib/gitlab/ci/status/build/preparing.rb
new file mode 100644
index 00000000000..1fddcb05f79
--- /dev/null
+++ b/lib/gitlab/ci/status/build/preparing.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Status
+ module Build
+ class Preparing < Status::Extended
+ ##
+ # TODO: image is shared with 'pending'
+ # until we get a dedicated one
+ #
+ def illustration
+ {
+ image: 'illustrations/job_not_triggered.svg',
+ size: 'svg-306',
+ title: _('This job is preparing to start'),
+ content: _('This job is performing tasks that must complete before it can start')
+ }
+ end
+
+ def self.matches?(build, _)
+ build.preparing?
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/status/preparing.rb b/lib/gitlab/ci/status/preparing.rb
new file mode 100644
index 00000000000..62985d0a9f9
--- /dev/null
+++ b/lib/gitlab/ci/status/preparing.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Ci
+ module Status
+ class Preparing < Status::Core
+ def text
+ s_('CiStatusText|preparing')
+ end
+
+ def label
+ s_('CiStatusLabel|preparing')
+ end
+
+ ##
+ # TODO: shared with 'created'
+ # until we get one for 'preparing'
+ #
+ def icon
+ 'status_created'
+ end
+
+ ##
+ # TODO: shared with 'created'
+ # until we get one for 'preparing'
+ #
+ def favicon
+ 'favicon_status_created'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml
index 9c534b2b8e7..8e767b22360 100644
--- a/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml
@@ -118,4 +118,3 @@ promoteProduction:
- master
script:
- bundle exec fastlane promote_beta_to_production
- \ No newline at end of file
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index 6c99e20e7af..7ec786b6d5d 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -262,7 +262,7 @@ review:
- persist_environment_url
environment:
name: review/$CI_COMMIT_REF_NAME
- url: http://$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_SLUG.$KUBE_INGRESS_BASE_DOMAIN
+ url: http://$CI_PROJECT_ID-$CI_ENVIRONMENT_SLUG.$KUBE_INGRESS_BASE_DOMAIN
on_stop: stop_review
artifacts:
paths: [environment_url.txt]
@@ -522,7 +522,7 @@ rollout 100%:
registry_login
docker run -d --name db arminc/clair-db:latest
- docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:v2.0.1
+ docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:v2.0.6
apk add -U wget ca-certificates
docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
@@ -697,6 +697,8 @@ rollout 100%:
helm upgrade --install \
--wait \
--set service.enabled="$service_enabled" \
+ --set gitlab.app="$CI_PROJECT_PATH_SLUG" \
+ --set gitlab.env="$CI_ENVIRONMENT_SLUG" \
--set releaseOverride="$CI_ENVIRONMENT_SLUG" \
--set image.repository="$CI_APPLICATION_REPOSITORY" \
--set image.tag="$CI_APPLICATION_TAG" \
@@ -734,6 +736,8 @@ rollout 100%:
helm upgrade --install \
--wait \
--set service.enabled="$service_enabled" \
+ --set gitlab.app="$CI_PROJECT_PATH_SLUG" \
+ --set gitlab.env="$CI_ENVIRONMENT_SLUG" \
--set releaseOverride="$CI_ENVIRONMENT_SLUG" \
--set image.repository="$CI_APPLICATION_REPOSITORY" \
--set image.tag="$CI_APPLICATION_TAG" \
@@ -780,18 +784,18 @@ rollout 100%:
function install_dependencies() {
apk add -U openssl curl tar gzip bash ca-certificates git
- curl -L -o /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub
- curl -L -O https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.28-r0/glibc-2.28-r0.apk
+ curl -sSL -o /etc/apk/keys/sgerrand.rsa.pub https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub
+ curl -sSL -O https://github.com/sgerrand/alpine-pkg-glibc/releases/download/2.28-r0/glibc-2.28-r0.apk
apk add glibc-2.28-r0.apk
rm glibc-2.28-r0.apk
- curl "https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | tar zx
+ curl -sS "https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | tar zx
mv linux-amd64/helm /usr/bin/
mv linux-amd64/tiller /usr/bin/
helm version --client
tiller -version
- curl -L -o /usr/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl"
+ curl -sSL -o /usr/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl"
chmod +x /usr/bin/kubectl
kubectl version --client
}
diff --git a/lib/gitlab/ci/templates/Bash.gitlab-ci.yml b/lib/gitlab/ci/templates/Bash.gitlab-ci.yml
index 2d218b2e164..368069844ea 100644
--- a/lib/gitlab/ci/templates/Bash.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Bash.gitlab-ci.yml
@@ -7,28 +7,28 @@ before_script:
- echo "Before script section"
- echo "For example you might run an update here or install a build dependency"
- echo "Or perhaps you might print out some debugging details"
-
+
after_script:
- echo "After script section"
- echo "For example you might do some cleanup here"
-
+
build1:
stage: build
script:
- echo "Do your build here"
-
+
test1:
stage: test
- script:
+ script:
- echo "Do a test here"
- echo "For example run a test suite"
-
+
test2:
stage: test
- script:
+ script:
- echo "Do another parallel test here"
- echo "For example run a lint test"
-
+
deploy1:
stage: deploy
script:
diff --git a/lib/gitlab/ci/templates/C++.gitlab-ci.yml b/lib/gitlab/ci/templates/C++.gitlab-ci.yml
index c83c49d8c95..9a8fa9d7091 100644
--- a/lib/gitlab/ci/templates/C++.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/C++.gitlab-ci.yml
@@ -7,9 +7,9 @@ build:
stage: build
# instead of calling g++ directly you can also use some build toolkit like make
# install the necessary build tools when needed
- # before_script:
- # - apt update && apt -y install make autoconf
- script:
+ # before_script:
+ # - apt update && apt -y install make autoconf
+ script:
- g++ helloworld.cpp -o mybinary
artifacts:
paths:
diff --git a/lib/gitlab/ci/templates/Chef.gitlab-ci.yml b/lib/gitlab/ci/templates/Chef.gitlab-ci.yml
index 4d5b6484d6e..33507aa58e4 100644
--- a/lib/gitlab/ci/templates/Chef.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Chef.gitlab-ci.yml
@@ -41,7 +41,7 @@ chefspec:
# - apt-get -y install rsync
# script:
# - kitchen verify default-centos-6 --destroy=always
-#
+#
#verify-centos-7:
# stage: functional
# before_script:
diff --git a/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml b/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml
index f066285b1ad..0610cb9ccc0 100644
--- a/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Clojure.gitlab-ci.yml
@@ -5,9 +5,9 @@ image: clojure:lein-2.7.0
# Make sure you configure the connection as well
before_script:
- # If you need to install any external applications, like a
+ # If you need to install any external applications, like a
# postgres client, you may want to uncomment the line below:
- #
+ #
#- apt-get update -y
#
# Retrieve project dependencies
@@ -17,6 +17,6 @@ before_script:
test:
script:
- # If you need to run any migrations or configure the database, this
- # would be the point to do it.
+ # If you need to run any migrations or configure the database, this
+ # would be the point to do it.
- lein test
diff --git a/lib/gitlab/ci/templates/Code-Quality.gitlab-ci.yml b/lib/gitlab/ci/templates/Code-Quality.gitlab-ci.yml
new file mode 100644
index 00000000000..10b25af904f
--- /dev/null
+++ b/lib/gitlab/ci/templates/Code-Quality.gitlab-ci.yml
@@ -0,0 +1,17 @@
+code_quality:
+ image: docker:stable
+ allow_failure: true
+ services:
+ - docker:stable-dind
+ variables:
+ DOCKER_DRIVER: overlay2
+ script:
+ - docker run
+ --env SOURCE_CODE="$PWD"
+ --volume "$PWD":/code
+ --volume /var/run/docker.sock:/var/run/docker.sock
+ "registry.gitlab.com/gitlab-org/security-products/codequality:11-8-stable" /code
+ artifacts:
+ reports:
+ codequality: gl-code-quality-report.json
+ expire_in: 1 week
diff --git a/lib/gitlab/ci/templates/Django.gitlab-ci.yml b/lib/gitlab/ci/templates/Django.gitlab-ci.yml
index 57afcbbe8b5..1d8be6f017e 100644
--- a/lib/gitlab/ci/templates/Django.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Django.gitlab-ci.yml
@@ -21,7 +21,7 @@ cache:
# This is a basic example for a gem or script which doesn't use
# services such as redis or postgres
before_script:
- - python -V # Print out python version for debugging
+ - python -V # Print out python version for debugging
# Uncomment next line if your Django app needs a JS runtime:
# - apt-get update -q && apt-get install nodejs -yqq
- pip install -r requirements.txt
diff --git a/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml b/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml
index 48d98dddfad..cbf4d58bdad 100644
--- a/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Gradle.gitlab-ci.yml
@@ -23,7 +23,6 @@ build:
- build
- .gradle
-
test:
stage: test
script: gradle check
@@ -33,4 +32,3 @@ test:
paths:
- build
- .gradle
-
diff --git a/lib/gitlab/ci/templates/Grails.gitlab-ci.yml b/lib/gitlab/ci/templates/Grails.gitlab-ci.yml
index 7fc698d50cf..dbc868238f8 100644
--- a/lib/gitlab/ci/templates/Grails.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Grails.gitlab-ci.yml
@@ -13,7 +13,7 @@ image: java:8
variables:
GRAILS_VERSION: "3.1.9"
GRADLE_VERSION: "2.13"
-
+
# We use SDKMan as tool for managing versions
before_script:
- apt-get update -qq && apt-get install -y -qq unzip
@@ -23,10 +23,10 @@ before_script:
- sdk install gradle $GRADLE_VERSION < /dev/null
- sdk use gradle $GRADLE_VERSION
# As it's not a good idea to version gradle.properties feel free to add your
-# environments variable here
+# environments variable here
- echo grailsVersion=$GRAILS_VERSION > gradle.properties
- echo gradleWrapperVersion=2.14 >> gradle.properties
-# refresh dependencies from your project
+# refresh dependencies from your project
- ./gradlew --refresh-dependencies
# Be aware that if you are using Angular profile,
# Bower cannot be run as root if you don't allow it before.
@@ -36,5 +36,5 @@ before_script:
# This build job does the full grails pipeline
# (compile, test, integrationTest, war, assemble).
build:
- script:
- - ./gradlew build \ No newline at end of file
+ script:
+ - ./gradlew build
diff --git a/lib/gitlab/ci/templates/Julia.gitlab-ci.yml b/lib/gitlab/ci/templates/Julia.gitlab-ci.yml
index 04c21b4725d..2c4683fbfbb 100644
--- a/lib/gitlab/ci/templates/Julia.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Julia.gitlab-ci.yml
@@ -30,7 +30,7 @@
test:0.7:
image: julia:0.7
<<: *test_definition
-
+
test:1.0:
image: julia:1.0
<<: *test_definition
diff --git a/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml b/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml
index d0cad285572..e1cd29ecc94 100644
--- a/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml
@@ -22,33 +22,25 @@ cache:
# This is a basic example for a gem or script which doesn't use
# services such as redis or postgres
before_script:
- # Update packages
+ # Update packages
- apt-get update -yqq
-
# Prep for Node
- apt-get install gnupg -yqq
-
# Upgrade to Node 8
- curl -sL https://deb.nodesource.com/setup_8.x | bash -
-
# Install dependencies
- apt-get install git nodejs libcurl4-gnutls-dev libicu-dev libmcrypt-dev libvpx-dev libjpeg-dev libpng-dev libxpm-dev zlib1g-dev libfreetype6-dev libxml2-dev libexpat1-dev libbz2-dev libgmp3-dev libldap2-dev unixodbc-dev libpq-dev libsqlite3-dev libaspell-dev libsnmp-dev libpcre3-dev libtidy-dev -yqq
-
# Install php extensions
- docker-php-ext-install mbstring pdo_mysql curl json intl gd xml zip bz2 opcache
-
# Install & enable Xdebug for code coverage reports
- pecl install xdebug
- docker-php-ext-enable xdebug
-
# Install Composer and project dependencies.
- curl -sS https://getcomposer.org/installer | php
- php composer.phar install
-
# Install Node dependencies.
# comment this out if you don't have a node dependency
- npm install
-
# Copy over testing configuration.
# Don't forget to set the database config in .env.testing correctly
# DB_HOST=mysql
@@ -56,20 +48,16 @@ before_script:
# DB_USERNAME=root
# DB_PASSWORD=secret
- cp .env.testing .env
-
# Run npm build
# comment this out if you don't have a frontend build
# you can change this to to your frontend building script like
# npm run build
- npm run dev
-
# Generate an application key. Re-cache.
- php artisan key:generate
- php artisan config:cache
-
# Run database migrations.
- php artisan migrate
-
# Run database seed
- php artisan db:seed
@@ -77,7 +65,6 @@ test:
script:
# run laravel tests
- php vendor/bin/phpunit --coverage-text --colors=never
-
# run frontend tests
# if you have any task for testing frontend
# set it in your package.json script
diff --git a/lib/gitlab/ci/templates/Maven.gitlab-ci.yml b/lib/gitlab/ci/templates/Maven.gitlab-ci.yml
index 492b3d03db2..c9838c7a7ff 100644
--- a/lib/gitlab/ci/templates/Maven.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Maven.gitlab-ci.yml
@@ -66,7 +66,6 @@ verify:jdk8:
<<: *verify
image: maven:3.3.9-jdk-8
-
# For `master` branch run `mvn deploy` automatically.
# Here you need to decide whether you want to use JDK7 or 8.
# To get this working you need to define a volume while configuring your gitlab-ci-multi-runner.
@@ -85,7 +84,6 @@ deploy:jdk8:
- target/staging
image: maven:3.3.9-jdk-8
-
pages:
image: busybox:latest
stage: deploy
diff --git a/lib/gitlab/ci/templates/Mono.gitlab-ci.yml b/lib/gitlab/ci/templates/Mono.gitlab-ci.yml
index 3585f99760f..86d62b93313 100644
--- a/lib/gitlab/ci/templates/Mono.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Mono.gitlab-ci.yml
@@ -32,11 +32,11 @@ release:
# The output path is relative to the position of the csproj-file
- msbuild /p:Configuration="Release" /p:Platform="Any CPU"
/p:OutputPath="./../../build/release/" "MyProject.sln"
-
+
debug:
stage: test
script:
# The output path is relative to the position of the csproj-file
- msbuild /p:Configuration="Debug" /p:Platform="Any CPU"
/p:OutputPath="./../../build/debug/" "MyProject.sln"
- - mono packages/NUnit.ConsoleRunner.3.6.0/tools/nunit3-console.exe build/debug/MyProject.Test.dll \ No newline at end of file
+ - mono packages/NUnit.ConsoleRunner.3.6.0/tools/nunit3-console.exe build/debug/MyProject.Test.dll
diff --git a/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml
index 7fcc0b436b5..d6de8cab5d1 100644
--- a/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Brunch.gitlab-ci.yml
@@ -5,7 +5,6 @@ pages:
cache:
paths:
- node_modules/
-
script:
- npm install -g brunch
- brunch build --production
diff --git a/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml
index dd3ef149668..4b58003ee10 100644
--- a/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Harp.gitlab-ci.yml
@@ -5,7 +5,6 @@ pages:
cache:
paths:
- node_modules
-
script:
- npm install -g harp
- harp compile ./ public
diff --git a/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml
index b8cfb0f56f6..f9ddcc6fb0a 100644
--- a/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Hugo.gitlab-ci.yml
@@ -9,7 +9,7 @@ pages:
- public
only:
- master
-
+
test:
script:
- hugo
diff --git a/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml
index 7abfaf53e8e..7a485f8d135 100644
--- a/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/JBake.gitlab-ci.yml
@@ -13,7 +13,6 @@ image: java:8
variables:
JBAKE_VERSION: 2.5.1
-
# We use SDKMan as tool for managing versions
before_script:
- apt-get update -qq && apt-get install -y -qq unzip zip
@@ -29,4 +28,4 @@ pages:
- jbake . public
artifacts:
paths:
- - public \ No newline at end of file
+ - public
diff --git a/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml
index 0e5fb410a4e..5ca4619e200 100644
--- a/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Jigsaw.gitlab-ci.yml
@@ -11,24 +11,19 @@ cache:
- node_modules/
before_script:
- # Update packages
+ # Update packages
- apt-get update -yqq
-
# Install dependencies
- apt-get install -yqq gnupg zlib1g-dev libpng-dev
-
# Install Node 8
- curl -sL https://deb.nodesource.com/setup_8.x | bash -
- apt-get install -yqq nodejs
-
# Install php extensions
- docker-php-ext-install zip
-
- # Install Composer and project dependencies.
+ # Install Composer and project dependencies
- curl -sS https://getcomposer.org/installer | php
- - php composer.phar install
-
- # Install Node dependencies.
+ - php composer.phar install
+ # Install Node dependencies
- npm install
pages:
diff --git a/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml b/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml
index 50e8b7ccd46..c6ded272150 100644
--- a/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Pages/Metalsmith.gitlab-ci.yml
@@ -5,7 +5,6 @@ pages:
cache:
paths:
- node_modules/
-
script:
- npm install -g metalsmith
- npm install
diff --git a/lib/gitlab/ci/templates/Python.gitlab-ci.yml b/lib/gitlab/ci/templates/Python.gitlab-ci.yml
index 098abe4daf5..3eaed4e91cd 100644
--- a/lib/gitlab/ci/templates/Python.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Python.gitlab-ci.yml
@@ -18,7 +18,7 @@ cache:
- venv/
before_script:
- - python -V # Print out python version for debugging
+ - python -V # Print out python version for debugging
- pip install virtualenv
- virtualenv venv
- source venv/bin/activate
@@ -26,7 +26,7 @@ before_script:
test:
script:
- python setup.py test
- - pip install tox flake8 # you can also use tox
+ - pip install tox flake8 # you can also use tox
- tox -e py36,flake8
run:
diff --git a/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml b/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml
index 0d12cbc6460..93196dbd475 100644
--- a/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Ruby.gitlab-ci.yml
@@ -21,7 +21,7 @@ cache:
# This is a basic example for a gem or script which doesn't use
# services such as redis or postgres
before_script:
- - ruby -v # Print out ruby version for debugging
+ - ruby -v # Print out ruby version for debugging
# Uncomment next line if your rails app needs a JS runtime:
# - apt-get update -q && apt-get install nodejs -yqq
- bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby
diff --git a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
index 42cb452ec99..ea1e6ae5fdc 100644
--- a/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/Container-Scanning.gitlab-ci.yml
@@ -22,7 +22,7 @@ container_scanning:
- docker:stable-dind
script:
- docker run -d --name db arminc/clair-db:latest
- - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:v2.0.1
+ - docker run -p 6060:6060 --link db:postgres -d --name clair --restart on-failure arminc/clair-local-scan:v2.0.6
- apk add -U wget ca-certificates
- docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG}
- wget https://github.com/arminc/clair-scanner/releases/download/v8/clair-scanner_linux_amd64
diff --git a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
index 4e708f229cd..ef6d7866e85 100644
--- a/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Security/DAST.gitlab-ci.yml
@@ -21,20 +21,19 @@ dast:
allow_failure: true
services:
- docker:stable-dind
- before_script:
+ script:
- export DAST_VERSION=${SP_VERSION:-$(echo "$CI_SERVER_VERSION" | sed 's/^\([0-9]*\)\.\([0-9]*\).*/\1-\2-stable/')}
- |
function dast_run() {
docker run \
- --env DAST_TARGET_AVAILABILITY_TIMEOUT \
- --volume "$PWD:/output" \
- --volume /var/run/docker.sock:/var/run/docker.sock \
- -w /output \
- "registry.gitlab.com/gitlab-org/security-products/dast:$DAST_VERSION" \
- /analyze -t $DAST_WEBSITE \
- "$@"
+ --env DAST_TARGET_AVAILABILITY_TIMEOUT \
+ --volume "$PWD:/output" \
+ --volume /var/run/docker.sock:/var/run/docker.sock \
+ -w /output \
+ "registry.gitlab.com/gitlab-org/security-products/dast:$DAST_VERSION" \
+ /analyze -t $DAST_WEBSITE \
+ "$@"
}
- script:
- |
if [ -n "$DAST_AUTH_URL" ]
then
diff --git a/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml b/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml
index 25a32ba0f74..5e128b793d0 100644
--- a/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml
@@ -26,7 +26,6 @@ variables:
MSI_RELEASE_FOLDER: 'Setup\bin\Release'
TEST_FOLDER: 'Tests\bin\Release'
DEPLOY_FOLDER: 'P:\Projects\YourApp\Builds'
-
NUGET_PATH: 'C:\NuGet\nuget.exe'
MSBUILD_PATH: 'C:\Program Files (x86)\MSBuild\14.0\Bin\msbuild.exe'
NUNIT_PATH: 'C:\Program Files (x86)\NUnit.org\nunit-console\nunit3-console.exe'
@@ -84,4 +83,3 @@ deploy_job:
dependencies:
- build_job
- test_job
- \ No newline at end of file
diff --git a/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml b/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml
index 245e6bec60a..df6ac4d340d 100644
--- a/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/iOS-Fastlane.gitlab-ci.yml
@@ -17,7 +17,7 @@ variables:
LC_ALL: "en_US.UTF-8"
LANG: "en_US.UTF-8"
GIT_STRATEGY: clone
-
+
build:
stage: build
script:
diff --git a/lib/gitlab/danger/helper.rb b/lib/gitlab/danger/helper.rb
index d2b7ca015d4..0d76e324d10 100644
--- a/lib/gitlab/danger/helper.rb
+++ b/lib/gitlab/danger/helper.rb
@@ -108,7 +108,7 @@ module Gitlab
%r{\A(ee/)?public/} => :frontend,
%r{\A(ee/)?spec/(javascripts|frontend)/} => :frontend,
%r{\A(ee/)?vendor/assets/} => :frontend,
- %r{\A(jest\.config\.js|package\.json|yarn\.lock)\z} => :frontend,
+ %r{\A(jest\.config\.js|package\.json|yarn\.lock|\.eslintrc\.yml)\z} => :frontend,
%r{\A(ee/)?app/(?!assets|views)[^/]+} => :backend,
%r{\A(ee/)?(bin|config|danger|generator_templates|lib|rubocop|scripts)/} => :backend,
diff --git a/lib/gitlab/danger/teammate.rb b/lib/gitlab/danger/teammate.rb
index 4b822aa86c5..bfada512727 100644
--- a/lib/gitlab/danger/teammate.rb
+++ b/lib/gitlab/danger/teammate.rb
@@ -21,21 +21,21 @@ module Gitlab
# Traintainers also count as reviewers
def reviewer?(project, category)
- capabilities(project) == "reviewer #{category}" || traintainer?(project, category)
+ capabilities(project).include?("reviewer #{category}") || traintainer?(project, category)
end
def traintainer?(project, category)
- capabilities(project) == "trainee_maintainer #{category}"
+ capabilities(project).include?("trainee_maintainer #{category}")
end
def maintainer?(project, category)
- capabilities(project) == "maintainer #{category}"
+ capabilities(project).include?("maintainer #{category}")
end
private
def capabilities(project)
- projects.fetch(project, '')
+ Array(projects.fetch(project, []))
end
end
end
diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb
index b6ca777e029..8da98cc3909 100644
--- a/lib/gitlab/database.rb
+++ b/lib/gitlab/database.rb
@@ -27,6 +27,10 @@ module Gitlab
config['adapter']
end
+ def self.human_adapter_name
+ postgresql? ? 'PostgreSQL' : 'MySQL'
+ end
+
def self.mysql?
adapter_name.casecmp('mysql2').zero?
end
@@ -76,7 +80,7 @@ module Gitlab
postgresql? && version.to_f >= 9.4
end
- def self.pg_stat_wal_receiver_supported?
+ def self.postgresql_minimum_supported_version?
postgresql? && version.to_f >= 9.6
end
@@ -98,6 +102,10 @@ module Gitlab
Gitlab::Database.postgresql_9_or_less? ? 'pg_last_xlog_replay_location' : 'pg_last_wal_replay_lsn'
end
+ def self.pg_last_xact_replay_timestamp
+ 'pg_last_xact_replay_timestamp'
+ end
+
def self.nulls_last_order(field, direction = 'ASC')
order = "#{field} #{direction}"
diff --git a/lib/gitlab/database/count/reltuples_count_strategy.rb b/lib/gitlab/database/count/reltuples_count_strategy.rb
index c3a674aeb7e..695f6fa766e 100644
--- a/lib/gitlab/database/count/reltuples_count_strategy.rb
+++ b/lib/gitlab/database/count/reltuples_count_strategy.rb
@@ -37,6 +37,22 @@ module Gitlab
private
+ # Models using single-type inheritance (STI) don't work with
+ # reltuple count estimates. We just have to ignore them and
+ # use another strategy to compute them.
+ def non_sti_models
+ models.reject { |model| sti_model?(model) }
+ end
+
+ def non_sti_table_names
+ non_sti_models.map(&:table_name)
+ end
+
+ def sti_model?(model)
+ model.column_names.include?(model.inheritance_column) &&
+ model.base_class != model
+ end
+
def table_names
models.map(&:table_name)
end
@@ -47,7 +63,7 @@ module Gitlab
# Querying tuple stats only works on the primary. Due to load balancing, the
# easiest way to do this is to start a transaction.
ActiveRecord::Base.transaction do
- get_statistics(table_names, check_statistics: check_statistics).each_with_object({}) do |row, data|
+ get_statistics(non_sti_table_names, check_statistics: check_statistics).each_with_object({}) do |row, data|
model = table_to_model[row.table_name]
data[model] = row.estimate
end
diff --git a/lib/gitlab/database/count/tablesample_count_strategy.rb b/lib/gitlab/database/count/tablesample_count_strategy.rb
index fedf6ca4fe1..7777f31f702 100644
--- a/lib/gitlab/database/count/tablesample_count_strategy.rb
+++ b/lib/gitlab/database/count/tablesample_count_strategy.rb
@@ -48,12 +48,21 @@ module Gitlab
end
end
+ def where_clause(model)
+ return unless sti_model?(model)
+
+ "WHERE #{model.inheritance_column} = '#{model.name}'"
+ end
+
def tablesample_count(model, estimate)
portion = (TABLESAMPLE_ROW_TARGET.to_f / estimate).round(4)
inverse = 1 / portion
query = <<~SQL
SELECT (COUNT(*)*#{inverse})::integer AS count
- FROM #{model.table_name} TABLESAMPLE SYSTEM (#{portion * 100})
+ FROM #{model.table_name}
+ TABLESAMPLE SYSTEM (#{portion * 100})
+ REPEATABLE (0)
+ #{where_clause(model)}
SQL
rows = ActiveRecord::Base.connection.select_all(query)
diff --git a/lib/gitlab/database/multi_threaded_migration.rb b/lib/gitlab/database/multi_threaded_migration.rb
index 1d39a3d0b57..65a6cb8e369 100644
--- a/lib/gitlab/database/multi_threaded_migration.rb
+++ b/lib/gitlab/database/multi_threaded_migration.rb
@@ -35,12 +35,10 @@ module Gitlab
threads = Array.new(thread_count) do
Thread.new do
pool.with_connection do |connection|
- begin
- Thread.current[MULTI_THREAD_AR_CONNECTION] = connection
- yield
- ensure
- Thread.current[MULTI_THREAD_AR_CONNECTION] = nil
- end
+ Thread.current[MULTI_THREAD_AR_CONNECTION] = connection
+ yield
+ ensure
+ Thread.current[MULTI_THREAD_AR_CONNECTION] = nil
end
end
end
diff --git a/lib/gitlab/database/sha_attribute.rb b/lib/gitlab/database/sha_attribute.rb
index 8d97adaff99..109ae7893da 100644
--- a/lib/gitlab/database/sha_attribute.rb
+++ b/lib/gitlab/database/sha_attribute.rb
@@ -22,7 +22,7 @@ module Gitlab
# Casts binary data to a SHA1 in hexadecimal.
def deserialize(value)
value = super(value)
- value ? value.unpack(PACK_FORMAT)[0] : nil
+ value ? value.unpack1(PACK_FORMAT) : nil
end
# Casts a SHA1 in hexadecimal to the proper binary format.
diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb
index dbee47a19ee..dce80bf21de 100644
--- a/lib/gitlab/diff/file.rb
+++ b/lib/gitlab/diff/file.rb
@@ -158,7 +158,10 @@ module Gitlab
new_blob || old_blob
end
- attr_writer :highlighted_diff_lines
+ def highlighted_diff_lines=(value)
+ clear_memoization(:diff_lines_for_serializer)
+ @highlighted_diff_lines = value
+ end
# Array of Gitlab::Diff::Line objects
def diff_lines
@@ -314,19 +317,21 @@ module Gitlab
# This adds the bottom match line to the array if needed. It contains
# the data to load more context lines.
def diff_lines_for_serializer
- lines = highlighted_diff_lines
+ strong_memoize(:diff_lines_for_serializer) do
+ lines = highlighted_diff_lines
- return if lines.empty?
- return if blob.nil?
+ next if lines.empty?
+ next if blob.nil?
- last_line = lines.last
+ last_line = lines.last
- if last_line.new_pos < total_blob_lines(blob) && !deleted_file?
- match_line = Gitlab::Diff::Line.new("", 'match', nil, last_line.old_pos, last_line.new_pos)
- lines.push(match_line)
- end
+ if last_line.new_pos < total_blob_lines(blob) && !deleted_file?
+ match_line = Gitlab::Diff::Line.new("", 'match', nil, last_line.old_pos, last_line.new_pos)
+ lines.push(match_line)
+ end
- lines
+ lines
+ end
end
def fully_expanded?
diff --git a/lib/gitlab/diff/suggestion_diff.rb b/lib/gitlab/diff/suggestion_diff.rb
new file mode 100644
index 00000000000..ee153c226b7
--- /dev/null
+++ b/lib/gitlab/diff/suggestion_diff.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Diff
+ class SuggestionDiff
+ include Gitlab::Utils::StrongMemoize
+
+ delegate :from_content, :to_content, :from_line, to: :@suggestible
+
+ def initialize(suggestible)
+ @suggestible = suggestible
+ end
+
+ def diff_lines
+ Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a
+ end
+
+ private
+
+ def raw_diff
+ "#{diff_header}\n#{from_content_as_diff}#{to_content_as_diff}"
+ end
+
+ def diff_header
+ "@@ -#{from_line} +#{from_line}"
+ end
+
+ def from_content_as_diff
+ from_content.lines.map { |line| line.prepend('-') }.join
+ end
+
+ def to_content_as_diff
+ to_content.lines.map { |line| line.prepend('+') }.join
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/diff/suggestions_parser.rb b/lib/gitlab/diff/suggestions_parser.rb
new file mode 100644
index 00000000000..043bd9a4bcb
--- /dev/null
+++ b/lib/gitlab/diff/suggestions_parser.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Diff
+ class SuggestionsParser
+ # Matches for instance "-1", "+1" or "-1+2".
+ SUGGESTION_CONTEXT = /^(\-(?<above>\d+))?(\+(?<below>\d+))?$/.freeze
+ end
+ end
+end
diff --git a/lib/gitlab/fake_application_settings.rb b/lib/gitlab/fake_application_settings.rb
index bd806269bf0..77f7d9490f3 100644
--- a/lib/gitlab/fake_application_settings.rb
+++ b/lib/gitlab/fake_application_settings.rb
@@ -7,6 +7,8 @@
# column type without parsing db/schema.rb.
module Gitlab
class FakeApplicationSettings < OpenStruct
+ include ApplicationSettingImplementation
+
# Mimic ActiveRecord predicate methods for boolean values
def self.define_predicate_methods(options)
options.each do |key, value|
@@ -26,20 +28,7 @@ module Gitlab
FakeApplicationSettings.define_predicate_methods(options)
end
- def key_restriction_for(type)
- 0
- end
-
- def allowed_key_types
- ApplicationSetting::SUPPORTED_KEY_TYPES
- end
-
- def pick_repository_storage
- repository_storages.sample
- end
-
- def commit_email_hostname
- super.presence || ApplicationSetting.default_commit_email_hostname
- end
+ alias_method :read_attribute, :[]
+ alias_method :has_attribute?, :[]
end
end
diff --git a/lib/gitlab/favicon.rb b/lib/gitlab/favicon.rb
index 1ae2f9dfd93..6e31064f737 100644
--- a/lib/gitlab/favicon.rb
+++ b/lib/gitlab/favicon.rb
@@ -10,7 +10,7 @@ module Gitlab
elsif Gitlab::Utils.to_boolean(ENV['CANARY'])
'favicon-yellow.png'
elsif Rails.env.development?
- 'favicon-blue.png'
+ development_favicon
else
'favicon.png'
end
@@ -18,6 +18,12 @@ module Gitlab
ActionController::Base.helpers.image_path(image_name, host: host)
end
+ def development_favicon
+ # This is a separate method so that EE can return a different favicon
+ # for development environments.
+ 'favicon-blue.png'
+ end
+
def status_overlay(status_name)
path = File.join(
'ci_favicons',
diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb
index 491e4b47196..88ff9fbceb4 100644
--- a/lib/gitlab/git/commit.rb
+++ b/lib/gitlab/git/commit.rb
@@ -184,11 +184,12 @@ module Gitlab
end
end
- def initialize(repository, raw_commit, head = nil)
+ def initialize(repository, raw_commit, head = nil, lazy_load_parents: false)
raise "Nil as raw commit passed" unless raw_commit
@repository = repository
@head = head
+ @lazy_load_parents = lazy_load_parents
init_commit(raw_commit)
end
@@ -225,6 +226,12 @@ module Gitlab
author_name != committer_name || author_email != committer_email
end
+ def parent_ids
+ return @parent_ids unless @lazy_load_parents
+
+ @parent_ids ||= @repository.commit(id).parent_ids
+ end
+
def parent_id
parent_ids.first
end
@@ -314,11 +321,16 @@ module Gitlab
def tree_entry(path)
return unless path.present?
+ commit_tree_entry(path)
+ end
+
+ def commit_tree_entry(path)
# We're only interested in metadata, so limit actual data to 1 byte
# since Gitaly doesn't support "send no data" option.
entry = @repository.gitaly_commit_client.tree_entry(id, path, 1)
return unless entry
+ # To be compatible with the rugged format
entry = entry.to_h
entry.delete(:data)
entry[:name] = File.basename(path)
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 35dd042ba6a..7d6851a4b8d 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -344,12 +344,12 @@ module Gitlab
end
end
- def new_blobs(newrev)
+ def new_blobs(newrev, dynamic_timeout: nil)
return [] if newrev.blank? || newrev == ::Gitlab::Git::BLANK_SHA
strong_memoize("new_blobs_#{newrev}") do
wrapped_gitaly_errors do
- gitaly_ref_client.list_new_blobs(newrev, REV_LIST_COMMIT_LIMIT)
+ gitaly_ref_client.list_new_blobs(newrev, REV_LIST_COMMIT_LIMIT, dynamic_timeout: dynamic_timeout)
end
end
end
diff --git a/lib/gitlab/git/rugged_impl/commit.rb b/lib/gitlab/git/rugged_impl/commit.rb
index 251802878c3..f6777dfa0c3 100644
--- a/lib/gitlab/git/rugged_impl/commit.rb
+++ b/lib/gitlab/git/rugged_impl/commit.rb
@@ -43,6 +43,30 @@ module Gitlab
end
end
+ override :commit_tree_entry
+ def commit_tree_entry(path)
+ if Feature.enabled?(:rugged_commit_tree_entry)
+ rugged_tree_entry(path)
+ else
+ super
+ end
+ end
+
+ # Is this the same as Blob.find_entry_by_path ?
+ def rugged_tree_entry(path)
+ rugged_commit.tree.path(path)
+ rescue Rugged::TreeError
+ nil
+ end
+
+ def rugged_commit
+ @rugged_commit ||= if raw_commit.is_a?(Rugged::Commit)
+ raw_commit
+ else
+ @repository.rev_parse_target(id)
+ end
+ end
+
def init_from_rugged(commit)
author = commit.author
committer = commit.committer
diff --git a/lib/gitlab/git/rugged_impl/repository.rb b/lib/gitlab/git/rugged_impl/repository.rb
index fe0120b1199..c0a91f59ab9 100644
--- a/lib/gitlab/git/rugged_impl/repository.rb
+++ b/lib/gitlab/git/rugged_impl/repository.rb
@@ -12,7 +12,7 @@ module Gitlab
module Repository
extend ::Gitlab::Utils::Override
- FEATURE_FLAGS = %i(rugged_find_commit rugged_tree_entries rugged_tree_entry rugged_commit_is_ancestor).freeze
+ FEATURE_FLAGS = %i(rugged_find_commit rugged_tree_entries rugged_tree_entry rugged_commit_is_ancestor rugged_commit_tree_entry).freeze
def alternate_object_directories
relative_object_directories.map { |d| File.join(path, d) }
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 48c113a8b14..e3b9a7a1a89 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -75,13 +75,11 @@ module Gitlab
@certs = stub_cert_paths.flat_map do |cert_file|
File.read(cert_file).scan(PEM_REGEX).map do |cert|
- begin
- OpenSSL::X509::Certificate.new(cert).to_pem
- rescue OpenSSL::OpenSSLError => e
- Rails.logger.error "Could not load certificate #{cert_file} #{e}"
- Gitlab::Sentry.track_exception(e, extra: { cert_file: cert_file })
- nil
- end
+ OpenSSL::X509::Certificate.new(cert).to_pem
+ rescue OpenSSL::OpenSSLError => e
+ Rails.logger.error "Could not load certificate #{cert_file} #{e}"
+ Gitlab::Sentry.track_exception(e, extra: { cert_file: cert_file })
+ nil
end.compact
end.uniq.join("\n")
end
@@ -257,8 +255,7 @@ module Gitlab
# This is this actual number of times this call was made. Used for information purposes only
actual_call_count = increment_call_count("gitaly_#{call_site}_actual")
- # Do no enforce limits in production
- return if Rails.env.production? || ENV["GITALY_DISABLE_REQUEST_LIMITS"]
+ return unless enforce_gitaly_request_limits?
# Check if this call is nested within a allow_n_plus_1_calls
# block and skip check if it is
@@ -275,6 +272,19 @@ module Gitlab
raise TooManyInvocationsError.new(call_site, actual_call_count, max_call_count, max_stacks)
end
+ def self.enforce_gitaly_request_limits?
+ # We typically don't want to enforce request limits in production
+ # However, we have some production-like test environments, i.e., ones
+ # where `Rails.env.production?` returns `true`. We do want to be able to
+ # check if the limit is being exceeded while testing in those environments
+ # In that case we can use a feature flag to indicate that we do want to
+ # enforce request limits.
+ return true if feature_enabled?('enforce_requests_limits')
+
+ !(Rails.env.production? || ENV["GITALY_DISABLE_REQUEST_LIMITS"])
+ end
+ private_class_method :enforce_gitaly_request_limits?
+
def self.allow_n_plus_1_calls
return yield unless Gitlab::SafeRequestStore.active?
diff --git a/lib/gitlab/gitaly_client/blobs_stitcher.rb b/lib/gitlab/gitaly_client/blobs_stitcher.rb
index 01bab854082..f860d8ce517 100644
--- a/lib/gitlab/gitaly_client/blobs_stitcher.rb
+++ b/lib/gitlab/gitaly_client/blobs_stitcher.rb
@@ -13,17 +13,15 @@ module Gitlab
current_blob_data = nil
@rpc_response.each do |msg|
- begin
- if msg.oid.blank? && msg.data.blank?
- next
- elsif msg.oid.present?
- yield new_blob(current_blob_data) if current_blob_data
-
- current_blob_data = msg.to_h.slice(:oid, :path, :size, :revision, :mode)
- current_blob_data[:data] = msg.data.dup
- else
- current_blob_data[:data] << msg.data
- end
+ if msg.oid.blank? && msg.data.blank?
+ next
+ elsif msg.oid.present?
+ yield new_blob(current_blob_data) if current_blob_data
+
+ current_blob_data = msg.to_h.slice(:oid, :path, :size, :revision, :mode)
+ current_blob_data[:data] = msg.data.dup
+ else
+ current_blob_data[:data] << msg.data
end
end
diff --git a/lib/gitlab/gitaly_client/ref_service.rb b/lib/gitlab/gitaly_client/ref_service.rb
index d5633d167ac..6f6698607d9 100644
--- a/lib/gitlab/gitaly_client/ref_service.rb
+++ b/lib/gitlab/gitaly_client/ref_service.rb
@@ -84,15 +84,22 @@ module Gitlab
commits
end
- def list_new_blobs(newrev, limit = 0)
+ def list_new_blobs(newrev, limit = 0, dynamic_timeout: nil)
request = Gitaly::ListNewBlobsRequest.new(
repository: @gitaly_repo,
commit_id: newrev,
limit: limit
)
+ timeout =
+ if dynamic_timeout
+ [dynamic_timeout, GitalyClient.medium_timeout].min
+ else
+ GitalyClient.medium_timeout
+ end
+
response = GitalyClient
- .call(@storage, :ref_service, :list_new_blobs, request, timeout: GitalyClient.medium_timeout)
+ .call(@storage, :ref_service, :list_new_blobs, request, timeout: timeout)
response.flat_map do |msg|
# Returns an Array of Gitaly::NewBlobObject objects
diff --git a/lib/gitlab/gitaly_client/remote_service.rb b/lib/gitlab/gitaly_client/remote_service.rb
index 81fac37ee68..f3589fea39f 100644
--- a/lib/gitlab/gitaly_client/remote_service.rb
+++ b/lib/gitlab/gitaly_client/remote_service.rb
@@ -38,7 +38,7 @@ module Gitlab
def remove_remote(name)
request = Gitaly::RemoveRemoteRequest.new(repository: @gitaly_repo, name: name)
- response = GitalyClient.call(@storage, :remote_service, :remove_remote, request, timeout: GitalyClient.fast_timeout)
+ response = GitalyClient.call(@storage, :remote_service, :remove_remote, request)
response.result
end
diff --git a/lib/gitlab/github_import/importer/pull_request_importer.rb b/lib/gitlab/github_import/importer/pull_request_importer.rb
index e294173f992..1b293ddc7c7 100644
--- a/lib/gitlab/github_import/importer/pull_request_importer.rb
+++ b/lib/gitlab/github_import/importer/pull_request_importer.rb
@@ -89,7 +89,7 @@ module Gitlab
return if project.repository.branch_exists?(source_branch)
- project.repository.add_branch(merge_request.author, source_branch, pull_request.source_branch_sha)
+ project.repository.add_branch(project.creator, source_branch, pull_request.source_branch_sha)
rescue Gitlab::Git::CommandError => e
Gitlab::Sentry.track_acceptable_exception(e,
extra: {
diff --git a/lib/gitlab/github_import/importer/repository_importer.rb b/lib/gitlab/github_import/importer/repository_importer.rb
index e2dfb00dcc5..6d48c6a15b4 100644
--- a/lib/gitlab/github_import/importer/repository_importer.rb
+++ b/lib/gitlab/github_import/importer/repository_importer.rb
@@ -5,6 +5,7 @@ module Gitlab
module Importer
class RepositoryImporter
include Gitlab::ShellAdapter
+ include Gitlab::Utils::StrongMemoize
attr_reader :project, :client, :wiki_formatter
@@ -17,7 +18,7 @@ module Gitlab
# Returns true if we should import the wiki for the project.
# rubocop: disable CodeReuse/ActiveRecord
def import_wiki?
- client.repository(project.import_source)&.has_wiki &&
+ client_repository&.has_wiki &&
!project.wiki_repository_exists? &&
Gitlab::GitalyClient::RemoteService.exists?(wiki_url)
end
@@ -52,6 +53,7 @@ module Gitlab
refmap = Gitlab::GithubImport.refmap
project.repository.fetch_as_mirror(project.import_url, refmap: refmap, forced: true, remote_name: 'github')
+ project.change_head(default_branch) if default_branch
true
rescue Gitlab::Git::Repository::NoRepository, Gitlab::Shell::Error => e
fail_import("Failed to import the repository: #{e.message}")
@@ -82,6 +84,18 @@ module Gitlab
project.import_state.mark_as_failed(message)
false
end
+
+ private
+
+ def default_branch
+ client_repository&.default_branch
+ end
+
+ def client_repository
+ strong_memoize(:client_repository) do
+ client.repository(project.import_source)
+ end
+ end
end
end
end
diff --git a/lib/gitlab/group_search_results.rb b/lib/gitlab/group_search_results.rb
new file mode 100644
index 00000000000..7255293b194
--- /dev/null
+++ b/lib/gitlab/group_search_results.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Gitlab
+ class GroupSearchResults < SearchResults
+ def initialize(current_user, limit_projects, group, query, default_project_filter: false, per_page: 20)
+ super(current_user, limit_projects, query, default_project_filter: default_project_filter, per_page: per_page)
+
+ @group = group
+ end
+
+ # rubocop:disable CodeReuse/ActiveRecord
+ def users
+ # 1: get all groups the current user has access to
+ groups = GroupsFinder.new(current_user).execute.joins(:users)
+
+ # 2: Get the group's whole hierarchy
+ group_users = @group.direct_and_indirect_users
+
+ # 3: get all users the current user has access to (->
+ # `SearchResults#users`), which also applies the query.
+ users = super
+
+ # 4: filter for users that belong to the previously selected groups
+ users
+ .where(id: group_users.select('id'))
+ .where(id: groups.select('members.user_id'))
+ end
+ # rubocop:enable CodeReuse/ActiveRecord
+ end
+end
diff --git a/lib/gitlab/hashed_storage/migrator.rb b/lib/gitlab/hashed_storage/migrator.rb
index 7046b4e2a43..1f0deebea39 100644
--- a/lib/gitlab/hashed_storage/migrator.rb
+++ b/lib/gitlab/hashed_storage/migrator.rb
@@ -81,8 +81,26 @@ module Gitlab
Rails.logger.error("#{err.message} rolling-back storage of #{project.full_path} (ID=#{project.id}), trace - #{err.backtrace}")
end
+ # Returns whether we have any pending storage migration
+ #
+ def migration_pending?
+ any_non_empty_queue?(::HashedStorage::MigratorWorker, ::HashedStorage::ProjectMigrateWorker)
+ end
+
+ # Returns whether we have any pending storage rollback
+ #
+ def rollback_pending?
+ any_non_empty_queue?(::HashedStorage::RollbackerWorker, ::HashedStorage::ProjectRollbackWorker)
+ end
+
private
+ def any_non_empty_queue?(*workers)
+ workers.any? do |worker|
+ !Sidekiq::Queue.new(worker.queue).size.zero?
+ end
+ end
+
# rubocop: disable CodeReuse/ActiveRecord
def build_relation(start, finish)
relation = Project
diff --git a/lib/gitlab/hook_data/issue_builder.rb b/lib/gitlab/hook_data/issue_builder.rb
index c99353b9d49..d39ff8c21cc 100644
--- a/lib/gitlab/hook_data/issue_builder.rb
+++ b/lib/gitlab/hook_data/issue_builder.rb
@@ -48,7 +48,7 @@ module Gitlab
}
issue.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes)
- .merge!(attrs)
+ .merge!(attrs)
end
end
end
diff --git a/lib/gitlab/hook_data/merge_request_builder.rb b/lib/gitlab/hook_data/merge_request_builder.rb
index ad38e26e40a..d77b1d04644 100644
--- a/lib/gitlab/hook_data/merge_request_builder.rb
+++ b/lib/gitlab/hook_data/merge_request_builder.rb
@@ -55,7 +55,7 @@ module Gitlab
}
merge_request.attributes.with_indifferent_access.slice(*self.class.safe_hook_attributes)
- .merge!(attrs)
+ .merge!(attrs)
end
end
end
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index fa54fc17d95..a0aab9fcbaf 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -117,6 +117,7 @@ excluded_attributes:
- :description_html
- :repository_languages
- :bfg_object_map
+ - :tag_list
namespaces:
- :runners_token
- :runners_token_encrypted
diff --git a/lib/gitlab/json_cache.rb b/lib/gitlab/json_cache.rb
index 24daad638f4..e4bc437d787 100644
--- a/lib/gitlab/json_cache.rb
+++ b/lib/gitlab/json_cache.rb
@@ -80,8 +80,23 @@ module Gitlab
# when the new_record? method incorrectly returns false.
#
# See https://gitlab.com/gitlab-org/gitlab-ee/issues/9903#note_145329964
- attributes = klass.attributes_builder.build_from_database(raw, {})
- klass.allocate.init_with("attributes" => attributes, "new_record" => new_record?(raw, klass))
+ klass
+ .allocate
+ .init_with(
+ "attributes" => attributes_for(klass, raw),
+ "new_record" => new_record?(raw, klass)
+ )
+ end
+
+ def attributes_for(klass, raw)
+ # We have models that leave out some fields from the JSON export for
+ # security reasons, e.g. models that include the CacheMarkdownField.
+ # The ActiveRecord::AttributeSet we build from raw does know about
+ # these columns so we need manually set them.
+ missing_attributes = (klass.columns.map(&:name) - raw.keys)
+ missing_attributes.each { |column| raw[column] = nil }
+
+ klass.attributes_builder.build_from_database(raw, {})
end
def new_record?(raw, klass)
diff --git a/lib/gitlab/kubernetes.rb b/lib/gitlab/kubernetes.rb
index a9957a85d48..d46b5e3aee3 100644
--- a/lib/gitlab/kubernetes.rb
+++ b/lib/gitlab/kubernetes.rb
@@ -24,6 +24,30 @@ module Gitlab
end
end
+ # Filters an array of pods (as returned by the kubernetes API) by their annotations
+ def filter_by_annotation(items, annotations = {})
+ items.select do |item|
+ metadata = item.fetch("metadata", {})
+ item_annotations = metadata.fetch("annotations", nil)
+ next unless item_annotations
+
+ annotations.all? { |k, v| item_annotations[k.to_s] == v }
+ end
+ end
+
+ # Filters an array of pods (as returned by the kubernetes API) by their project and environment
+ def filter_by_project_environment(items, app, env)
+ pods = filter_by_annotation(items, {
+ 'app.gitlab.com/app' => app,
+ 'app.gitlab.com/env' => env
+ })
+ return pods unless pods.empty?
+
+ filter_by_label(items, {
+ 'app' => env, # deprecated: replaced by app.gitlab.com/env
+ })
+ end
+
# Converts a pod (as returned by the kubernetes API) into a terminal
def terminals_for_pod(api_url, namespace, pod)
metadata = pod.fetch("metadata", {})
diff --git a/lib/gitlab/legacy_github_import/importer.rb b/lib/gitlab/legacy_github_import/importer.rb
index f3323c98af2..70b18221a66 100644
--- a/lib/gitlab/legacy_github_import/importer.rb
+++ b/lib/gitlab/legacy_github_import/importer.rb
@@ -89,12 +89,10 @@ module Gitlab
def import_labels
fetch_resources(:labels, repo, per_page: 100) do |labels|
labels.each do |raw|
- begin
- gh_label = LabelFormatter.new(project, raw)
- gh_label.create!
- rescue => e
- errors << { type: :label, url: Gitlab::UrlSanitizer.sanitize(gh_label.url), errors: e.message }
- end
+ gh_label = LabelFormatter.new(project, raw)
+ gh_label.create!
+ rescue => e
+ errors << { type: :label, url: Gitlab::UrlSanitizer.sanitize(gh_label.url), errors: e.message }
end
end
@@ -104,12 +102,10 @@ module Gitlab
def import_milestones
fetch_resources(:milestones, repo, state: :all, per_page: 100) do |milestones|
milestones.each do |raw|
- begin
- gh_milestone = MilestoneFormatter.new(project, raw)
- gh_milestone.create!
- rescue => e
- errors << { type: :milestone, url: Gitlab::UrlSanitizer.sanitize(gh_milestone.url), errors: e.message }
- end
+ gh_milestone = MilestoneFormatter.new(project, raw)
+ gh_milestone.create!
+ rescue => e
+ errors << { type: :milestone, url: Gitlab::UrlSanitizer.sanitize(gh_milestone.url), errors: e.message }
end
end
end
@@ -223,24 +219,22 @@ module Gitlab
def create_comments(comments)
ActiveRecord::Base.no_touching do
comments.each do |raw|
- begin
- comment = CommentFormatter.new(project, raw, client)
+ comment = CommentFormatter.new(project, raw, client)
- # GH does not return info about comment's parent, so we guess it by checking its URL!
- *_, parent, iid = URI(raw.html_url).path.split('/')
+ # GH does not return info about comment's parent, so we guess it by checking its URL!
+ *_, parent, iid = URI(raw.html_url).path.split('/')
- issuable = if parent == 'issues'
- Issue.find_by(project_id: project.id, iid: iid)
- else
- MergeRequest.find_by(target_project_id: project.id, iid: iid)
- end
+ issuable = if parent == 'issues'
+ Issue.find_by(project_id: project.id, iid: iid)
+ else
+ MergeRequest.find_by(target_project_id: project.id, iid: iid)
+ end
- next unless issuable
+ next unless issuable
- issuable.notes.create!(comment.attributes)
- rescue => e
- errors << { type: :comment, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
- end
+ issuable.notes.create!(comment.attributes)
+ rescue => e
+ errors << { type: :comment, url: Gitlab::UrlSanitizer.sanitize(raw.url), errors: e.message }
end
end
end
@@ -281,12 +275,10 @@ module Gitlab
def import_releases
fetch_resources(:releases, repo, per_page: 100) do |releases|
releases.each do |raw|
- begin
- gh_release = ReleaseFormatter.new(project, raw)
- gh_release.create! if gh_release.valid?
- rescue => e
- errors << { type: :release, url: Gitlab::UrlSanitizer.sanitize(gh_release.url), errors: e.message }
- end
+ gh_release = ReleaseFormatter.new(project, raw)
+ gh_release.create! if gh_release.valid?
+ rescue => e
+ errors << { type: :release, url: Gitlab::UrlSanitizer.sanitize(gh_release.url), errors: e.message }
end
end
end
diff --git a/lib/gitlab/metrics/influx_db.rb b/lib/gitlab/metrics/influx_db.rb
index 0b04340fbb5..269d90fa971 100644
--- a/lib/gitlab/metrics/influx_db.rb
+++ b/lib/gitlab/metrics/influx_db.rb
@@ -52,10 +52,8 @@ module Gitlab
pool&.with do |connection|
prepared.each_slice(settings[:packet_size]) do |slice|
- begin
- connection.write_points(slice)
- rescue StandardError
- end
+ connection.write_points(slice)
+ rescue StandardError
end
end
rescue Errno::EADDRNOTAVAIL, SocketError => ex
diff --git a/lib/gitlab/middleware/basic_health_check.rb b/lib/gitlab/middleware/basic_health_check.rb
index acf8c301b8f..84e49805428 100644
--- a/lib/gitlab/middleware/basic_health_check.rb
+++ b/lib/gitlab/middleware/basic_health_check.rb
@@ -24,7 +24,13 @@ module Gitlab
def call(env)
return @app.call(env) unless env['PATH_INFO'] == HEALTH_PATH
- request = ActionDispatch::Request.new(env)
+ # We should be using ActionDispatch::Request instead of
+ # Rack::Request to be consistent with Rails, but due to a Rails
+ # bug described in
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/58573#note_149799010
+ # hosts behind a load balancer will only see 127.0.0.1 for the
+ # load balancer's IP.
+ request = Rack::Request.new(env)
return OK_RESPONSE if client_ip_whitelisted?(request)
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index a68f8801c2a..58f06b6708c 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -22,11 +22,17 @@ module Gitlab
paginated_blobs(wiki_blobs, page)
when 'commits'
Kaminari.paginate_array(commits).page(page).per(per_page)
+ when 'users'
+ users.page(page).per(per_page)
else
super(scope, page, false)
end
end
+ def users
+ super.where(id: @project.team.members) # rubocop:disable CodeReuse/ActiveRecord
+ end
+
def blobs_count
@blobs_count ||= blobs.count
end
diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb
index 8a908291637..99885be8755 100644
--- a/lib/gitlab/project_template.rb
+++ b/lib/gitlab/project_template.rb
@@ -30,6 +30,7 @@ module Gitlab
ProjectTemplate.new('express', 'NodeJS Express', _('Includes an MVC structure to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/express', 'illustrations/logos/express.svg'),
ProjectTemplate.new('iosswift', 'iOS (Swift)', _('A ready-to-go template for use with iOS Swift apps.'), 'https://gitlab.com/gitlab-org/project-templates/iosswift'),
ProjectTemplate.new('dotnetcore', '.NET Core', _('A .NET Core console application template, customizable for any .NET Core project'), 'https://gitlab.com/gitlab-org/project-templates/dotnetcore', 'illustrations/logos/dotnet.svg'),
+ ProjectTemplate.new('android', 'Android', _('A ready-to-go template for use with Android apps.'), 'https://gitlab.com/gitlab-org/project-templates/android', 'illustrations/logos/android.svg'),
ProjectTemplate.new('gomicro', 'Go Micro', _('Go Micro is a framework for micro service development.'), 'https://gitlab.com/gitlab-org/project-templates/go-micro'),
ProjectTemplate.new('hugo', 'Pages/Hugo', _('Everything you need to create a GitLab Pages site using Hugo.'), 'https://gitlab.com/pages/hugo'),
ProjectTemplate.new('jekyll', 'Pages/Jekyll', _('Everything you need to create a GitLab Pages site using Jekyll.'), 'https://gitlab.com/pages/jekyll'),
diff --git a/lib/gitlab/recaptcha.rb b/lib/gitlab/recaptcha.rb
index 6559c3e3c57..772d743c9b0 100644
--- a/lib/gitlab/recaptcha.rb
+++ b/lib/gitlab/recaptcha.rb
@@ -5,8 +5,8 @@ module Gitlab
def self.load_configurations!
if Gitlab::CurrentSettings.recaptcha_enabled
::Recaptcha.configure do |config|
- config.public_key = Gitlab::CurrentSettings.recaptcha_site_key
- config.private_key = Gitlab::CurrentSettings.recaptcha_private_key
+ config.site_key = Gitlab::CurrentSettings.recaptcha_site_key
+ config.secret_key = Gitlab::CurrentSettings.recaptcha_private_key
end
true
diff --git a/lib/gitlab/request_context.rb b/lib/gitlab/request_context.rb
index d9811e036d3..f6d289476c5 100644
--- a/lib/gitlab/request_context.rb
+++ b/lib/gitlab/request_context.rb
@@ -13,7 +13,13 @@ module Gitlab
end
def call(env)
- req = ActionDispatch::Request.new(env)
+ # We should be using ActionDispatch::Request instead of
+ # Rack::Request to be consistent with Rails, but due to a Rails
+ # bug described in
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/58573#note_149799010
+ # hosts behind a load balancer will only see 127.0.0.1 for the
+ # load balancer's IP.
+ req = Rack::Request.new(env)
Gitlab::SafeRequestStore[:client_ip] = req.ip
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 491148ec1a6..8988b9ad7be 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -32,6 +32,8 @@ module Gitlab
merge_requests.page(page).per(per_page)
when 'milestones'
milestones.page(page).per(per_page)
+ when 'users'
+ users.page(page).per(per_page)
else
Kaminari.paginate_array([]).page(page).per(per_page)
end
@@ -71,6 +73,12 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
+ # rubocop:disable CodeReuse/ActiveRecord
+ def limited_users_count
+ @limited_users_count ||= users.limit(count_limit).count
+ end
+ # rubocop:enable CodeReuse/ActiveRecord
+
def single_commit_result?
false
end
@@ -79,6 +87,12 @@ module Gitlab
1001
end
+ def users
+ return User.none unless Ability.allowed?(current_user, :read_users_list)
+
+ UsersFinder.new(current_user, search: query).execute
+ end
+
private
def projects
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index 40b641b8317..93182607616 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -10,18 +10,6 @@ module Gitlab
Error = Class.new(StandardError)
- KeyAdder = Struct.new(:io) do
- def add_key(id, key)
- key = Gitlab::Shell.strip_key(key)
- # Newline and tab are part of the 'protocol' used to transmit id+key to the other end
- if key.include?("\t") || key.include?("\n")
- raise Error.new("Invalid key: #{key.inspect}")
- end
-
- io.puts("#{id}\t#{key}")
- end
- end
-
class << self
def secret_token
@secret_token ||= begin
@@ -40,10 +28,6 @@ module Gitlab
.join('GITLAB_SHELL_VERSION')).strip
end
- def strip_key(key)
- key.split(/[ ]+/)[0, 2].join(' ')
- end
-
private
# Create (if necessary) and link the secret token file
@@ -173,7 +157,7 @@ module Gitlab
false
end
- # Add new key to gitlab-shell
+ # Add new key to authorized_keys
#
# Ex.
# add_key("key-42", "sha-rsa ...")
@@ -181,33 +165,53 @@ module Gitlab
def add_key(key_id, key_content)
return unless self.authorized_keys_enabled?
- gitlab_shell_fast_execute([gitlab_shell_keys_path,
- 'add-key', key_id, self.class.strip_key(key_content)])
+ if shell_out_for_gitlab_keys?
+ gitlab_shell_fast_execute([
+ gitlab_shell_keys_path,
+ 'add-key',
+ key_id,
+ strip_key(key_content)
+ ])
+ else
+ gitlab_authorized_keys.add_key(key_id, key_content)
+ end
end
# Batch-add keys to authorized_keys
#
# Ex.
- # batch_add_keys { |adder| adder.add_key("key-42", "sha-rsa ...") }
- def batch_add_keys(&block)
+ # batch_add_keys(Key.all)
+ def batch_add_keys(keys)
return unless self.authorized_keys_enabled?
- IO.popen(%W(#{gitlab_shell_path}/bin/gitlab-keys batch-add-keys), 'w') do |io|
- yield(KeyAdder.new(io))
+ if shell_out_for_gitlab_keys?
+ begin
+ IO.popen("#{gitlab_shell_keys_path} batch-add-keys", 'w') do |io|
+ add_keys_to_io(keys, io)
+ end
+
+ $?.success?
+ rescue Error
+ false
+ end
+ else
+ gitlab_authorized_keys.batch_add_keys(keys)
end
end
- # Remove ssh key from gitlab shell
+ # Remove ssh key from authorized_keys
#
# Ex.
- # remove_key("key-342", "sha-rsa ...")
+ # remove_key("key-342")
#
- def remove_key(key_id, key_content = nil)
+ def remove_key(id, _ = nil)
return unless self.authorized_keys_enabled?
- args = [gitlab_shell_keys_path, 'rm-key', key_id]
- args << key_content if key_content
- gitlab_shell_fast_execute(args)
+ if shell_out_for_gitlab_keys?
+ gitlab_shell_fast_execute([gitlab_shell_keys_path, 'rm-key', id])
+ else
+ gitlab_authorized_keys.rm_key(id)
+ end
end
# Remove all ssh keys from gitlab shell
@@ -218,7 +222,11 @@ module Gitlab
def remove_all_keys
return unless self.authorized_keys_enabled?
- gitlab_shell_fast_execute([gitlab_shell_keys_path, 'clear'])
+ if shell_out_for_gitlab_keys?
+ gitlab_shell_fast_execute([gitlab_shell_keys_path, 'clear'])
+ else
+ gitlab_authorized_keys.clear
+ end
end
# Remove ssh keys from gitlab shell that are not in the DB
@@ -247,33 +255,6 @@ module Gitlab
end
# rubocop: enable CodeReuse/ActiveRecord
- # Iterate over all ssh key IDs from gitlab shell, in batches
- #
- # Ex.
- # batch_read_key_ids { |batch| keys = Key.where(id: batch) }
- #
- def batch_read_key_ids(batch_size: 100, &block)
- return unless self.authorized_keys_enabled?
-
- list_key_ids do |key_id_stream|
- key_id_stream.lazy.each_slice(batch_size) do |lines|
- key_ids = lines.map { |l| l.chomp.to_i }
- yield(key_ids)
- end
- end
- end
-
- # Stream all ssh key IDs from gitlab shell, separated by newlines
- #
- # Ex.
- # list_key_ids
- #
- def list_key_ids(&block)
- return unless self.authorized_keys_enabled?
-
- IO.popen(%W(#{gitlab_shell_path}/bin/gitlab-keys list-key-ids), &block)
- end
-
# Add empty directory for storing repositories
#
# Ex.
@@ -378,6 +359,10 @@ module Gitlab
private
+ def shell_out_for_gitlab_keys?
+ Gitlab.config.gitlab_shell.authorized_keys_file.blank?
+ end
+
def gitlab_shell_fast_execute(cmd)
output, status = gitlab_shell_fast_execute_helper(cmd)
@@ -415,6 +400,40 @@ module Gitlab
raise Error, e
end
+ def gitlab_authorized_keys
+ @gitlab_authorized_keys ||= Gitlab::AuthorizedKeys.new
+ end
+
+ def batch_read_key_ids(batch_size: 100, &block)
+ return unless self.authorized_keys_enabled?
+
+ if shell_out_for_gitlab_keys?
+ IO.popen("#{gitlab_shell_keys_path} list-key-ids") do |key_id_stream|
+ key_id_stream.lazy.each_slice(batch_size) do |lines|
+ yield(lines.map { |l| l.chomp.to_i })
+ end
+ end
+ else
+ gitlab_authorized_keys.list_key_ids.lazy.each_slice(batch_size) do |key_ids|
+ yield(key_ids)
+ end
+ end
+ end
+
+ def strip_key(key)
+ key.split(/[ ]+/)[0, 2].join(' ')
+ end
+
+ def add_keys_to_io(keys, io)
+ keys.each do |k|
+ key = strip_key(k.key)
+
+ raise Error.new("Invalid key: #{key.inspect}") if key.include?("\t") || key.include?("\n")
+
+ io.puts("#{k.shell_id}\t#{key}")
+ end
+ end
+
class GitalyGitlabProjects
attr_reader :shard_name, :repository_relative_path, :output, :gl_project_path
diff --git a/lib/gitlab/sidekiq_config.rb b/lib/gitlab/sidekiq_config.rb
index 3b8de64913b..fb303e3fb0c 100644
--- a/lib/gitlab/sidekiq_config.rb
+++ b/lib/gitlab/sidekiq_config.rb
@@ -48,7 +48,9 @@ module Gitlab
end
def self.workers
- @workers ||= find_workers(Rails.root.join('app', 'workers'))
+ @workers ||=
+ find_workers(Rails.root.join('app', 'workers')) +
+ find_workers(Rails.root.join('ee', 'app', 'workers'))
end
def self.find_workers(root)
diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb
index ed2c7ee9a2d..671d795ec33 100644
--- a/lib/gitlab/sidekiq_middleware/memory_killer.rb
+++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb
@@ -63,7 +63,7 @@ module Gitlab
sleep(time)
Sidekiq.logger.warn "sending Sidekiq worker PGRP-#{pid} #{signal} (#{explanation})"
- Process.kill(signal, "-#{pid}")
+ Process.kill(signal, 0)
end
def wait_and_signal(time, signal, explanation)
diff --git a/lib/gitlab/sql/pattern.rb b/lib/gitlab/sql/pattern.rb
index 07d0acdbae9..b698391c8bd 100644
--- a/lib/gitlab/sql/pattern.rb
+++ b/lib/gitlab/sql/pattern.rb
@@ -23,8 +23,12 @@ module Gitlab
end
end
+ def min_chars_for_partial_matching
+ MIN_CHARS_FOR_PARTIAL_MATCHING
+ end
+
def partial_matching?(query)
- query.length >= MIN_CHARS_FOR_PARTIAL_MATCHING
+ query.length >= min_chars_for_partial_matching
end
# column - The column name to search in.
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index f9c9ea4f936..75477c7cf18 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -84,6 +84,7 @@ module Gitlab
projects: count(Project),
projects_imported_from_github: count(Project.where(import_type: 'github')),
projects_with_repositories_enabled: count(ProjectFeature.where('repository_access_level > ?', ProjectFeature::DISABLED)),
+ projects_with_error_tracking_enabled: count(::ErrorTracking::ProjectErrorTrackingSetting.where(enabled: true)),
protected_branches: count(ProtectedBranch),
releases: count(Release),
remote_mirrors: count(RemoteMirror),
diff --git a/lib/gitlab/user_extractor.rb b/lib/gitlab/user_extractor.rb
index 874599688bb..f0557f6ad68 100644
--- a/lib/gitlab/user_extractor.rb
+++ b/lib/gitlab/user_extractor.rb
@@ -11,11 +11,14 @@ module Gitlab
USERNAME_REGEXP = User.reference_pattern
def initialize(text)
- @text = text
+ # EE passes an Array to `text` in a few places, so we want to support both
+ # here.
+ @text = Array(text).join(' ')
end
def users
return User.none unless @text.present?
+ return User.none if references.empty?
@users ||= User.from_union(union_relations)
end
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
index 99fa65e0e90..16ec8a8bb28 100644
--- a/lib/gitlab/utils.rb
+++ b/lib/gitlab/utils.rb
@@ -104,6 +104,12 @@ module Gitlab
nil
end
+ def try_megabytes_to_bytes(size)
+ Integer(size).megabytes
+ rescue ArgumentError
+ size
+ end
+
def bytes_to_megabytes(bytes)
bytes.to_f / Numeric::MEGABYTE
end
diff --git a/lib/google_api/cloud_platform/client.rb b/lib/google_api/cloud_platform/client.rb
index e74ff6a9129..b5f99ea012b 100644
--- a/lib/google_api/cloud_platform/client.rb
+++ b/lib/google_api/cloud_platform/client.rb
@@ -10,6 +10,7 @@ module GoogleApi
class Client < GoogleApi::Auth
SCOPE = 'https://www.googleapis.com/auth/cloud-platform'.freeze
LEAST_TOKEN_LIFE_TIME = 10.minutes
+ CLUSTER_MASTER_AUTH_USERNAME = 'admin'.freeze
class << self
def session_key_for_token
@@ -64,6 +65,12 @@ module GoogleApi
"node_config": {
"machine_type": machine_type
},
+ "master_auth": {
+ "username": CLUSTER_MASTER_AUTH_USERNAME,
+ "client_certificate_config": {
+ issue_client_certificate: true
+ }
+ },
"legacy_abac": {
"enabled": legacy_abac
}
diff --git a/lib/sentry/client.rb b/lib/sentry/client.rb
index 49ec196b103..5e0c9101de5 100644
--- a/lib/sentry/client.rb
+++ b/lib/sentry/client.rb
@@ -57,7 +57,7 @@ module Sentry
raise Client::Error, "Sentry response status code: #{response.code}"
end
- response.as_json
+ response
end
def projects_api_url
diff --git a/lib/tasks/gitlab/artifacts/migrate.rake b/lib/tasks/gitlab/artifacts/migrate.rake
index e7634d2ed4f..9012e55a70c 100644
--- a/lib/tasks/gitlab/artifacts/migrate.rake
+++ b/lib/tasks/gitlab/artifacts/migrate.rake
@@ -11,14 +11,13 @@ namespace :gitlab do
Ci::Build.joins(:project)
.with_artifacts_stored_locally
.find_each(batch_size: 10) do |build|
- begin
- build.artifacts_file.migrate!(ObjectStorage::Store::REMOTE)
- build.artifacts_metadata.migrate!(ObjectStorage::Store::REMOTE)
- logger.info("Transferred artifact ID #{build.id} with size #{build.artifacts_size} to object storage")
- rescue => e
- logger.error("Failed to transfer artifacts of #{build.id} with error: #{e.message}")
- end
+ build.artifacts_file.migrate!(ObjectStorage::Store::REMOTE)
+ build.artifacts_metadata.migrate!(ObjectStorage::Store::REMOTE)
+
+ logger.info("Transferred artifact ID #{build.id} with size #{build.artifacts_size} to object storage")
+ rescue => e
+ logger.error("Failed to transfer artifacts of #{build.id} with error: #{e.message}")
end
end
end
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index 451ba651674..760331620ef 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -60,7 +60,7 @@ namespace :gitlab do
.new(server.storage)
.rename(path, new_path)
rescue StandardError => e
- puts "Error occured while moving the repository: #{e.message}".color(:red)
+ puts "Error occurred while moving the repository: #{e.message}".color(:red)
end
end
end
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index b8798fb3cfd..7872e5b08c0 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -34,9 +34,6 @@ namespace :gitlab do
puts "Sidekiq Version:#{Sidekiq::VERSION}"
puts "Go Version:\t#{go_version[1] || "unknown".color(:red)}"
- # check database adapter
- database_adapter = ActiveRecord::Base.connection.adapter_name.downcase
-
project = Group.new(path: "some-group").projects.build(path: "some-project")
# construct clone URLs
http_clone_url = project.http_url_to_repo
@@ -49,7 +46,8 @@ namespace :gitlab do
puts "Version:\t#{Gitlab::VERSION}"
puts "Revision:\t#{Gitlab.revision}"
puts "Directory:\t#{Rails.root}"
- puts "DB Adapter:\t#{database_adapter}"
+ puts "DB Adapter:\t#{Gitlab::Database.human_adapter_name}"
+ puts "DB Version:\t#{Gitlab::Database.version}"
puts "URL:\t\t#{Gitlab.config.gitlab.url}"
puts "HTTP Clone URL:\t#{http_clone_url}"
puts "SSH Clone URL:\t#{ssh_clone_url}"
diff --git a/lib/tasks/gitlab/lfs/migrate.rake b/lib/tasks/gitlab/lfs/migrate.rake
index a45e5ca91e0..97c15175a23 100644
--- a/lib/tasks/gitlab/lfs/migrate.rake
+++ b/lib/tasks/gitlab/lfs/migrate.rake
@@ -9,13 +9,12 @@ namespace :gitlab do
LfsObject.with_files_stored_locally
.find_each(batch_size: 10) do |lfs_object|
- begin
- lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
- logger.info("Transferred LFS object #{lfs_object.oid} of size #{lfs_object.size.to_i.bytes} to object storage")
- rescue => e
- logger.error("Failed to transfer LFS object #{lfs_object.oid} with error: #{e.message}")
- end
+ lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
+
+ logger.info("Transferred LFS object #{lfs_object.oid} of size #{lfs_object.size.to_i.bytes} to object storage")
+ rescue => e
+ logger.error("Failed to transfer LFS object #{lfs_object.oid} with error: #{e.message}")
end
end
end
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index 0ebc6f00793..ee3ef9dad6e 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -103,19 +103,12 @@ namespace :gitlab do
Gitlab::Shell.new.remove_all_keys
- Gitlab::Shell.new.batch_add_keys do |adder|
- Key.find_each(batch_size: 1000) do |key|
- adder.add_key(key.shell_id, key.key)
- print '.'
+ Key.find_in_batches(batch_size: 1000) do |keys|
+ unless Gitlab::Shell.new.batch_add_keys(keys)
+ puts "Failed to add keys...".color(:red)
+ exit 1
end
end
- puts ""
-
- unless $?.success?
- puts "Failed to add keys...".color(:red)
- exit 1
- end
-
rescue Gitlab::TaskAbortedByUserError
puts "Quitting...".color(:red)
exit 1
diff --git a/lib/tasks/gitlab/storage.rake b/lib/tasks/gitlab/storage.rake
index a2136ce1b92..954f827f716 100644
--- a/lib/tasks/gitlab/storage.rake
+++ b/lib/tasks/gitlab/storage.rake
@@ -11,6 +11,13 @@ namespace :gitlab do
storage_migrator = Gitlab::HashedStorage::Migrator.new
helper = Gitlab::HashedStorage::RakeHelper
+ if storage_migrator.rollback_pending?
+ warn "There is already a rollback operation in progress, " \
+ "running a migration at the same time may have unexpected consequences."
+
+ next
+ end
+
if helper.range_single_item?
project = Project.with_unmigrated_storage.find_by(id: helper.range_from)
@@ -56,6 +63,13 @@ namespace :gitlab do
storage_migrator = Gitlab::HashedStorage::Migrator.new
helper = Gitlab::HashedStorage::RakeHelper
+ if storage_migrator.migration_pending?
+ warn "There is already a migration operation in progress, " \
+ "running a rollback at the same time may have unexpected consequences."
+
+ next
+ end
+
if helper.range_single_item?
project = Project.with_storage_feature(:repository).find_by(id: helper.range_from)
@@ -82,7 +96,6 @@ namespace :gitlab do
print "Enqueuing rollback of #{hashed_projects_count} projects in batches of #{helper.batch_size}"
helper.project_id_batches_rollback do |start, finish|
- puts "Start: #{start} FINISH: #{finish}"
storage_migrator.bulk_schedule_rollback(start: start, finish: finish)
print '.'
diff --git a/lib/tasks/gitlab/traces.rake b/lib/tasks/gitlab/traces.rake
index 5a232091a7e..5e1ec481ece 100644
--- a/lib/tasks/gitlab/traces.rake
+++ b/lib/tasks/gitlab/traces.rake
@@ -26,13 +26,12 @@ namespace :gitlab do
Ci::Build.joins(:project)
.with_archived_trace_stored_locally
.find_each(batch_size: 10) do |build|
- begin
- build.job_artifacts_trace.file.migrate!(ObjectStorage::Store::REMOTE)
- logger.info("Transferred job trace of #{build.id} to object storage")
- rescue => e
- logger.error("Failed to transfer artifacts of #{build.id} with error: #{e.message}")
- end
+ build.job_artifacts_trace.file.migrate!(ObjectStorage::Store::REMOTE)
+
+ logger.info("Transferred job trace of #{build.id} to object storage")
+ rescue => e
+ logger.error("Failed to transfer artifacts of #{build.id} with error: #{e.message}")
end
end
end
diff --git a/lib/tasks/karma.rake b/lib/tasks/karma.rake
index 53325d492d1..02987f2beef 100644
--- a/lib/tasks/karma.rake
+++ b/lib/tasks/karma.rake
@@ -1,13 +1,24 @@
unless Rails.env.production?
namespace :karma do
desc 'GitLab | Karma | Generate fixtures for JavaScript tests'
- RSpec::Core::RakeTask.new(:fixtures, [:pattern]) do |t, args|
+ task fixtures: ['karma:copy_emojis_from_public_folder', 'karma:rspec_fixtures']
+
+ desc 'GitLab | Karma | Generate fixtures using RSpec'
+ RSpec::Core::RakeTask.new(:rspec_fixtures, [:pattern]) do |t, args|
args.with_defaults(pattern: '{spec,ee/spec}/javascripts/fixtures/*.rb')
ENV['NO_KNAPSACK'] = 'true'
t.pattern = args[:pattern]
t.rspec_opts = '--format documentation'
end
+ desc 'GitLab | Karma | Copy emojis file'
+ task :copy_emojis_from_public_folder do
+ # Copying the emojis.json from the public folder
+ fixture_file_name = Rails.root.join('spec/javascripts/fixtures/emojis/emojis.json')
+ FileUtils.mkdir_p(File.dirname(fixture_file_name))
+ FileUtils.cp(Rails.root.join('public/-/emojis/1/emojis.json'), fixture_file_name)
+ end
+
desc 'GitLab | Karma | Run JavaScript tests'
task tests: ['yarn:check'] do
sh "yarn run karma" do |ok, res|
diff --git a/lib/tasks/lint.rake b/lib/tasks/lint.rake
index 5d673a1a285..c5d0f2c292f 100644
--- a/lib/tasks/lint.rake
+++ b/lib/tasks/lint.rake
@@ -19,11 +19,9 @@ unless Rails.env.production?
desc "GitLab | lint | Lint HAML files"
task :haml do
- begin
- Rake::Task['haml_lint'].invoke
- rescue RuntimeError # The haml_lint tasks raise a RuntimeError
- exit(1)
- end
+ Rake::Task['haml_lint'].invoke
+ rescue RuntimeError # The haml_lint tasks raise a RuntimeError
+ exit(1)
end
desc "GitLab | lint | Run several lint checks"
diff --git a/lib/tasks/migrate/migrate_iids.rake b/lib/tasks/migrate/migrate_iids.rake
index aa2d01730d7..cb7c496c31c 100644
--- a/lib/tasks/migrate/migrate_iids.rake
+++ b/lib/tasks/migrate/migrate_iids.rake
@@ -2,49 +2,43 @@ desc "GitLab | Build internal ids for issues and merge requests"
task migrate_iids: :environment do
puts 'Issues'.color(:yellow)
Issue.where(iid: nil).find_each(batch_size: 100) do |issue|
- begin
- issue.set_iid
+ issue.set_iid
- if issue.update_attribute(:iid, issue.iid)
- print '.'
- else
- print 'F'
- end
- rescue
+ if issue.update_attribute(:iid, issue.iid)
+ print '.'
+ else
print 'F'
end
+ rescue
+ print 'F'
end
puts 'done'
puts 'Merge Requests'.color(:yellow)
MergeRequest.where(iid: nil).find_each(batch_size: 100) do |mr|
- begin
- mr.set_iid
+ mr.set_iid
- if mr.update_attribute(:iid, mr.iid)
- print '.'
- else
- print 'F'
- end
- rescue
+ if mr.update_attribute(:iid, mr.iid)
+ print '.'
+ else
print 'F'
end
+ rescue
+ print 'F'
end
puts 'done'
puts 'Milestones'.color(:yellow)
Milestone.where(iid: nil).find_each(batch_size: 100) do |m|
- begin
- m.set_iid
+ m.set_iid
- if m.update_attribute(:iid, m.iid)
- print '.'
- else
- print 'F'
- end
- rescue
+ if m.update_attribute(:iid, m.iid)
+ print '.'
+ else
print 'F'
end
+ rescue
+ print 'F'
end
puts 'done'
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index abca0256726..177bd189817 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -19,6 +19,9 @@ msgstr ""
msgid " Status"
msgstr ""
+msgid " You need to do this before %{grace_period_deadline}."
+msgstr ""
+
msgid " or "
msgstr ""
@@ -111,6 +114,9 @@ msgstr ""
msgid "%{firstLabel} +%{labelCount} more"
msgstr ""
+msgid "%{gitlab_ci_yml} not found in this commit"
+msgstr ""
+
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
msgstr ""
@@ -315,12 +321,18 @@ msgstr ""
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr ""
+msgid "A new impersonation token has been created."
+msgstr ""
+
msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
msgstr ""
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr ""
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
msgid "A ready-to-go template for use with iOS Swift apps."
msgstr ""
@@ -444,6 +456,9 @@ msgstr ""
msgid "Add users to group"
msgstr ""
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr ""
@@ -489,6 +504,9 @@ msgstr ""
msgid "AdminSettings|Auto DevOps domain"
msgstr ""
+msgid "AdminSettings|Enable shared runners for new projects"
+msgstr ""
+
msgid "AdminSettings|Environment variables are protected by default"
msgstr ""
@@ -597,6 +615,12 @@ msgstr ""
msgid "Allow commits from members who can merge to the target branch."
msgstr ""
+msgid "Allow mirrors to be set up for projects"
+msgstr ""
+
+msgid "Allow only the selected protocols to be used for Git access."
+msgstr ""
+
msgid "Allow projects within this group to use Git LFS"
msgstr ""
@@ -609,6 +633,12 @@ msgstr ""
msgid "Allow requests to the local network from hooks and services."
msgstr ""
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
+msgid "Allow users to register any application to use GitLab as an OAuth provider"
+msgstr ""
+
msgid "Allow users to request access"
msgstr ""
@@ -621,6 +651,9 @@ msgstr ""
msgid "Allows you to add and manage Kubernetes clusters."
msgstr ""
+msgid "Alternate support URL for help page"
+msgstr ""
+
msgid "Alternatively, you can use a %{personal_access_token_link}. When you create your Personal Access Token, you will need to select the <code>repo</code> scope, so we can display a list of your public and private repositories which are available to import."
msgstr ""
@@ -768,12 +801,27 @@ msgstr ""
msgid "Appearance"
msgstr ""
+msgid "Appearance was successfully created."
+msgstr ""
+
+msgid "Appearance was successfully updated."
+msgstr ""
+
msgid "Application"
msgstr ""
msgid "Application ID"
msgstr ""
+msgid "Application settings saved successfully"
+msgstr ""
+
+msgid "Application was successfully destroyed."
+msgstr ""
+
+msgid "Application was successfully updated."
+msgstr ""
+
msgid "Application: %{name}"
msgstr ""
@@ -792,6 +840,9 @@ msgstr ""
msgid "April"
msgstr ""
+msgid "Archive jobs"
+msgstr ""
+
msgid "Archived project! Repository and other project resources are read-only"
msgstr ""
@@ -957,15 +1008,6 @@ msgstr ""
msgid "AutoDevOps|The Auto DevOps pipeline has been enabled and will be used if no alternative CI configuration file is found. %{more_information_link}"
msgstr ""
-msgid "AutoDevOps|You can automatically build and test your application if you %{link_to_auto_devops_settings} for this project. You can automatically deploy it as well, if you %{link_to_add_kubernetes_cluster}."
-msgstr ""
-
-msgid "AutoDevOps|add a Kubernetes cluster"
-msgstr ""
-
-msgid "AutoDevOps|enable Auto DevOps"
-msgstr ""
-
msgid "Automatically marked as default internal user"
msgstr ""
@@ -1245,6 +1287,12 @@ msgstr ""
msgid "Branches|protected"
msgstr ""
+msgid "Broadcast Message was successfully created."
+msgstr ""
+
+msgid "Broadcast Message was successfully updated."
+msgstr ""
+
msgid "Browse Directory"
msgstr ""
@@ -1263,6 +1311,9 @@ msgstr ""
msgid "By %{user_name}"
msgstr ""
+msgid "By default GitLab sends emails in HTML and plain text formats so mail clients can choose what format to use. Disable this option if you only want to send emails in plain text format."
+msgstr ""
+
msgid "ByAuthor|by"
msgstr ""
@@ -1308,6 +1359,9 @@ msgstr ""
msgid "CICD|Default to Auto DevOps pipeline"
msgstr ""
+msgid "CICD|Default to Auto DevOps pipeline for all projects"
+msgstr ""
+
msgid "CICD|Deployment strategy"
msgstr ""
@@ -1323,6 +1377,9 @@ msgstr ""
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
+msgid "CICD|group enabled"
+msgstr ""
+
msgid "CICD|instance enabled"
msgstr ""
@@ -1350,6 +1407,9 @@ msgstr ""
msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
msgstr ""
+msgid "Cannot skip two factor authentication setup"
+msgstr ""
+
msgid "Certificate"
msgstr ""
@@ -1482,6 +1542,9 @@ msgstr ""
msgid "CiStatusLabel|pending"
msgstr ""
+msgid "CiStatusLabel|preparing"
+msgstr ""
+
msgid "CiStatusLabel|skipped"
msgstr ""
@@ -1515,6 +1578,9 @@ msgstr ""
msgid "CiStatusText|pending"
msgstr ""
+msgid "CiStatusText|preparing"
+msgstr ""
+
msgid "CiStatusText|skipped"
msgstr ""
@@ -2303,6 +2369,9 @@ msgstr ""
msgid "Contribution"
msgstr ""
+msgid "Contribution Analytics"
+msgstr ""
+
msgid "Contribution Charts"
msgstr ""
@@ -2381,6 +2450,12 @@ msgstr ""
msgid "Could not retrieve the pipeline status. For troubleshooting steps, read the %{linkStart}documentation.%{linkEnd}"
msgstr ""
+msgid "Could not revoke impersonation token %{token_name}."
+msgstr ""
+
+msgid "Coverage"
+msgstr ""
+
msgid "Create"
msgstr ""
@@ -2588,6 +2663,9 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default artifacts expiration"
+msgstr ""
+
msgid "Default first day of the week"
msgstr ""
@@ -2653,6 +2731,9 @@ msgstr[1] ""
msgid "Deploy Keys"
msgstr ""
+msgid "Deploy key was successfully updated."
+msgstr ""
+
msgid "DeployKeys|+%{count} others"
msgstr ""
@@ -2926,6 +3007,9 @@ msgstr ""
msgid "Edit"
msgstr ""
+msgid "Edit Deploy Key"
+msgstr ""
+
msgid "Edit Label"
msgstr ""
@@ -2962,6 +3046,9 @@ msgstr ""
msgid "Edit issues"
msgstr ""
+msgid "Edit public deploy key"
+msgstr ""
+
msgid "Email"
msgstr ""
@@ -2986,6 +3073,9 @@ msgstr ""
msgid "Enable Auto DevOps"
msgstr ""
+msgid "Enable HTML emails"
+msgstr ""
+
msgid "Enable Sentry for error reporting and logging."
msgstr ""
@@ -3007,6 +3097,9 @@ msgstr ""
msgid "Enable header and footer in emails"
msgstr ""
+msgid "Enable mirror configuration"
+msgstr ""
+
msgid "Enable or disable version check and usage ping."
msgstr ""
@@ -3031,6 +3124,12 @@ msgstr ""
msgid "Enabled"
msgstr ""
+msgid "Enabled Git access protocols"
+msgstr ""
+
+msgid "Enabled sources for code import during project creation. OmniAuth must be configured for GitHub"
+msgstr ""
+
msgid "Ends at (UTC)"
msgstr ""
@@ -3181,6 +3280,9 @@ msgstr ""
msgid "Environments|You don't have any environments right now"
msgstr ""
+msgid "Environments|protected"
+msgstr ""
+
msgid "Epic"
msgstr ""
@@ -3241,6 +3343,18 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error occurred. User was not blocked"
+msgstr ""
+
+msgid "Error occurred. User was not confirmed"
+msgstr ""
+
+msgid "Error occurred. User was not unblocked"
+msgstr ""
+
+msgid "Error occurred. User was not unlocked"
+msgstr ""
+
msgid "Error rendering markdown preview"
msgstr ""
@@ -3259,6 +3373,9 @@ msgstr ""
msgid "Error while loading the merge request. Please try again."
msgstr ""
+msgid "Error with Akismet. Please check the logs for more info."
+msgstr ""
+
msgid "Error:"
msgstr ""
@@ -3352,12 +3469,6 @@ msgstr ""
msgid "Except policy:"
msgstr ""
-msgid "Existing Git repository"
-msgstr ""
-
-msgid "Existing folder"
-msgstr ""
-
msgid "Existing members and groups"
msgstr ""
@@ -3439,6 +3550,12 @@ msgstr ""
msgid "Failed to remove the pipeline schedule"
msgstr ""
+msgid "Failed to remove user identity."
+msgstr ""
+
+msgid "Failed to remove user key."
+msgstr ""
+
msgid "Failed to update issues, please try again."
msgstr ""
@@ -3454,6 +3571,9 @@ msgstr ""
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
+msgid "Favicon was successfully removed."
+msgstr ""
+
msgid "Feb"
msgstr ""
@@ -3528,6 +3648,9 @@ msgstr ""
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr ""
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr ""
@@ -3597,6 +3720,9 @@ msgstr ""
msgid "Format"
msgstr ""
+msgid "Found errors in your %{gitlab_ci_yml}:"
+msgstr ""
+
msgid "Found errors in your .gitlab-ci.yml:"
msgstr ""
@@ -3696,6 +3822,9 @@ msgstr ""
msgid "GitLab project export"
msgstr ""
+msgid "GitLab restart is required to apply changes"
+msgstr ""
+
msgid "GitLab.com import"
msgstr ""
@@ -3759,9 +3888,18 @@ msgstr ""
msgid "Graph"
msgstr ""
+msgid "Gravatar enabled"
+msgstr ""
+
msgid "Group"
msgstr ""
+msgid "Group %{group_name} was scheduled for deletion."
+msgstr ""
+
+msgid "Group %{group_name} was successfully created."
+msgstr ""
+
msgid "Group CI/CD settings"
msgstr ""
@@ -3771,6 +3909,9 @@ msgstr ""
msgid "Group ID"
msgstr ""
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr ""
@@ -3798,24 +3939,42 @@ msgstr ""
msgid "Group name"
msgstr ""
+msgid "Group was successfully updated."
+msgstr ""
+
msgid "Group:"
msgstr ""
msgid "Group: %{group_name}"
msgstr ""
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr ""
msgid "GroupSettings|Customize your group badges."
msgstr ""
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr ""
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr ""
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -3906,6 +4065,9 @@ msgstr ""
msgid "GroupsTree|Search by name"
msgstr ""
+msgid "Header logo was successfully removed."
+msgstr ""
+
msgid "Header message"
msgstr ""
@@ -3945,6 +4107,9 @@ msgstr ""
msgid "Hide host keys manual input"
msgstr ""
+msgid "Hide marketing-related entries from help"
+msgstr ""
+
msgid "Hide payload"
msgstr ""
@@ -3956,9 +4121,15 @@ msgstr[1] ""
msgid "Hide values"
msgstr ""
+msgid "Highest role:"
+msgstr ""
+
msgid "History"
msgstr ""
+msgid "Hook was successfully created."
+msgstr ""
+
msgid "Housekeeping successfully started"
msgstr ""
@@ -4025,13 +4196,13 @@ msgstr ""
msgid "If any job surpasses this timeout threshold, it will be marked as failed. Human readable time input language is accepted like \"1 hour\". Values without specification represent seconds."
msgstr ""
-msgid "If disabled, the access level will depend on the user's permissions in the project."
+msgid "If disabled, only admins will be able to set up mirrors in projects."
msgstr ""
-msgid "If enabled"
+msgid "If disabled, the access level will depend on the user's permissions in the project."
msgstr ""
-msgid "If you already have files you can push them using the %{link_to_cli} below."
+msgid "If enabled"
msgstr ""
msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
@@ -4145,6 +4316,9 @@ msgstr ""
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include author name in notification email body"
+msgstr ""
+
msgid "Include merge request description"
msgstr ""
@@ -4166,6 +4340,9 @@ msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Inform users without uploaded SSH keys that they can't push over SSH until one is added"
+msgstr ""
+
msgid "Information about additional Pages templates and how to install them can be found in our %{pages_getting_started_guide}."
msgstr ""
@@ -4235,6 +4412,9 @@ msgstr ""
msgid "Invalid input, please avoid emojis"
msgstr ""
+msgid "Invalid pin code"
+msgstr ""
+
msgid "Invitation"
msgstr ""
@@ -4292,6 +4472,9 @@ msgstr ""
msgid "Job"
msgstr ""
+msgid "Job ID"
+msgstr ""
+
msgid "Job has been erased"
msgstr ""
@@ -4328,6 +4511,9 @@ msgstr ""
msgid "Job|Keep"
msgstr ""
+msgid "Job|Pipeline"
+msgstr ""
+
msgid "Job|Scroll to bottom"
msgstr ""
@@ -4346,6 +4532,15 @@ msgstr ""
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
msgstr ""
+msgid "Job|for"
+msgstr ""
+
+msgid "Job|into"
+msgstr ""
+
+msgid "Job|with"
+msgstr ""
+
msgid "Jul"
msgstr ""
@@ -4403,6 +4598,15 @@ msgstr ""
msgid "Label actions dropdown"
msgstr ""
+msgid "Label was created"
+msgstr ""
+
+msgid "Label was removed"
+msgstr ""
+
+msgid "Label was successfully updated."
+msgstr ""
+
msgid "LabelSelect|%{firstLabelName} +%{remainingLabelCount} more"
msgstr ""
@@ -4498,9 +4702,6 @@ msgstr ""
msgid "Learn more about Kubernetes"
msgstr ""
-msgid "Learn more about protected branches"
-msgstr ""
-
msgid "Learn more about signing commits"
msgstr ""
@@ -4584,6 +4785,9 @@ msgstr ""
msgid "Locked to current projects"
msgstr ""
+msgid "Logo was successfully removed."
+msgstr ""
+
msgid "Logs"
msgstr ""
@@ -4665,9 +4869,18 @@ msgstr ""
msgid "Max access level"
msgstr ""
+msgid "Maximum artifacts size (MB)"
+msgstr ""
+
+msgid "Maximum attachment size (MB)"
+msgstr ""
+
msgid "Maximum job timeout"
msgstr ""
+msgid "Maximum push size (MB)"
+msgstr ""
+
msgid "May"
msgstr ""
@@ -4770,9 +4983,6 @@ msgstr ""
msgid "MergeRequest|No files found"
msgstr ""
-msgid "MergeRequest|Search files"
-msgstr ""
-
msgid "Merged"
msgstr ""
@@ -4878,6 +5088,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit message"
+msgstr ""
+
msgid "Modify commit messages"
msgstr ""
@@ -4994,6 +5207,9 @@ msgstr ""
msgid "New branch unavailable"
msgstr ""
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr ""
@@ -5006,6 +5222,9 @@ msgstr ""
msgid "New group"
msgstr ""
+msgid "New health check access token has been generated!"
+msgstr ""
+
msgid "New identity"
msgstr ""
@@ -5027,6 +5246,9 @@ msgstr ""
msgid "New project"
msgstr ""
+msgid "New runners registration token has been generated!"
+msgstr ""
+
msgid "New schedule"
msgstr ""
@@ -5039,9 +5261,15 @@ msgstr ""
msgid "New tag"
msgstr ""
+msgid "New users set to external"
+msgstr ""
+
msgid "New..."
msgstr ""
+msgid "Newly registered users will by default be external"
+msgstr ""
+
msgid "No"
msgstr ""
@@ -5162,9 +5390,6 @@ msgstr ""
msgid "Not started"
msgstr ""
-msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
-msgstr ""
-
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
msgstr ""
@@ -5356,9 +5581,6 @@ msgstr ""
msgid "Other Labels"
msgstr ""
-msgid "Otherwise it is recommended you start with one of the options below."
-msgstr ""
-
msgid "Outbound requests"
msgstr ""
@@ -5404,6 +5626,9 @@ msgstr ""
msgid "Past due"
msgstr ""
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
+msgstr ""
+
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr ""
@@ -5569,6 +5794,9 @@ msgstr ""
msgid "Pipeline|Commit"
msgstr ""
+msgid "Pipeline|Coverage"
+msgstr ""
+
msgid "Pipeline|Create for"
msgstr ""
@@ -5614,9 +5842,21 @@ msgstr ""
msgid "Pipeline|all"
msgstr ""
+msgid "Pipeline|for"
+msgstr ""
+
+msgid "Pipeline|into"
+msgstr ""
+
+msgid "Pipeline|on"
+msgstr ""
+
msgid "Pipeline|success"
msgstr ""
+msgid "Pipeline|with"
+msgstr ""
+
msgid "Pipeline|with stage"
msgstr ""
@@ -5869,7 +6109,7 @@ msgstr ""
msgid "Profiles|This email will be displayed on your public profile"
msgstr ""
-msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
+msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{commit_email_link_start}Learn more%{commit_email_link_end}"
msgstr ""
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
@@ -6010,6 +6250,9 @@ msgstr ""
msgid "Project export could not be deleted."
msgstr ""
+msgid "Project export enabled"
+msgstr ""
+
msgid "Project export has been deleted."
msgstr ""
@@ -6091,6 +6334,9 @@ msgstr ""
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr ""
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr ""
@@ -6184,6 +6430,9 @@ msgstr ""
msgid "Promote to group label"
msgstr ""
+msgid "Prompt users to upload SSH keys"
+msgstr ""
+
msgid "Protected"
msgstr ""
@@ -6202,12 +6451,21 @@ msgstr ""
msgid "Public - The project can be accessed without any authentication."
msgstr ""
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr ""
msgid "Push"
msgstr ""
+msgid "Push an existing Git repository"
+msgstr ""
+
+msgid "Push an existing folder"
+msgstr ""
+
msgid "Push events"
msgstr ""
@@ -6384,6 +6642,9 @@ msgstr ""
msgid "Repository URL"
msgstr ""
+msgid "Repository check was triggered."
+msgstr ""
+
msgid "Repository cleanup"
msgstr ""
@@ -6491,6 +6752,9 @@ msgstr ""
msgid "Revoke"
msgstr ""
+msgid "Revoked impersonation token %{token_name}!"
+msgstr ""
+
msgid "Run untagged jobs"
msgstr ""
@@ -6509,6 +6773,12 @@ msgstr ""
msgid "Runner token"
msgstr ""
+msgid "Runner was not updated."
+msgstr ""
+
+msgid "Runner was successfully updated."
+msgstr ""
+
msgid "Runner will not receive any new jobs"
msgstr ""
@@ -6593,6 +6863,9 @@ msgstr ""
msgid "Scope"
msgstr ""
+msgid "Scope not supported with disabled 'users_search' feature!"
+msgstr ""
+
msgid "Scroll down to <strong>Google Code Project Hosting</strong> and enable the switch on the right."
msgstr ""
@@ -6722,6 +6995,9 @@ msgstr ""
msgid "Select the branch you want to set as the default for this project. All merge requests and commits will automatically be made against this branch unless you specify a different one."
msgstr ""
+msgid "Selected levels cannot be used by non-admin users for groups, projects or snippets. If the public level is restricted, user profiles are only visible to logged in users."
+msgstr ""
+
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr ""
@@ -6791,6 +7067,9 @@ msgstr ""
msgid "Service Templates"
msgstr ""
+msgid "Session duration (minutes)"
+msgstr ""
+
msgid "Session expiration, projects limit and attachment size."
msgstr ""
@@ -6812,6 +7091,15 @@ msgstr ""
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr ""
+msgid "Set the default expiration time for each job's artifacts. 0 for unlimited. The default unit is in seconds, but you can define an alternative. For example: <code>4 mins 2 sec</code>, <code>2h42min</code>."
+msgstr ""
+
+msgid "Set the duration for which the jobs will be considered as old and expired. Once that time passes, the jobs will be archived and no longer able to be retried. Make it empty to never expire jobs. It has to be no less than 1 day, for example: <code>15 days</code>, <code>1 month</code>, <code>2 years</code>."
+msgstr ""
+
+msgid "Set the maximum file size for each job's artifacts"
+msgstr ""
+
msgid "Set up CI/CD"
msgstr ""
@@ -6958,6 +7246,9 @@ msgstr ""
msgid "SnippetsEmptyState|They can be either public or private."
msgstr ""
+msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
+msgstr ""
+
msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
msgstr ""
@@ -7141,6 +7432,9 @@ msgstr ""
msgid "Spam and Anti-bot Protection"
msgstr ""
+msgid "Spam log successfully submitted as ham."
+msgstr ""
+
msgid "Specific Runners"
msgstr ""
@@ -7228,6 +7522,9 @@ msgstr ""
msgid "Started %{startsIn}"
msgstr ""
+msgid "Started asynchronous removal of all repository check states."
+msgstr ""
+
msgid "Starts %{startsIn}"
msgstr ""
@@ -7297,6 +7594,21 @@ msgstr ""
msgid "Subscribed"
msgstr ""
+msgid "Successfully blocked"
+msgstr ""
+
+msgid "Successfully confirmed"
+msgstr ""
+
+msgid "Successfully removed email."
+msgstr ""
+
+msgid "Successfully unblocked"
+msgstr ""
+
+msgid "Successfully unlocked"
+msgstr ""
+
msgid "Suggested change"
msgstr ""
@@ -7306,6 +7618,9 @@ msgstr ""
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
+msgid "Support page URL"
+msgstr ""
+
msgid "Switch branch/tag"
msgstr ""
@@ -7321,6 +7636,9 @@ msgstr ""
msgid "System header and footer"
msgstr ""
+msgid "System hook was successfully updated."
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -7462,6 +7780,12 @@ msgstr ""
msgid "The fork relationship has been removed."
msgstr ""
+msgid "The global settings require you to enable Two-Factor Authentication for your account."
+msgstr ""
+
+msgid "The group settings for %{group_links} require you to enable Two-Factor Authentication for your account. You can %{leave_group_links}."
+msgstr ""
+
msgid "The import will time out after %{timeout}. For repositories that take longer, use a clone/push combination."
msgstr ""
@@ -7531,6 +7855,9 @@ msgstr ""
msgid "The usage ping is disabled, and cannot be configured through this form."
msgstr ""
+msgid "The user is being deleted."
+msgstr ""
+
msgid "The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of <code>:</code>. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side."
msgstr ""
@@ -7573,6 +7900,9 @@ msgstr ""
msgid "There was an error loading users activity calendar."
msgstr ""
+msgid "There was an error removing the e-mail."
+msgstr ""
+
msgid "There was an error saving your changes."
msgstr ""
@@ -7714,6 +8044,12 @@ msgstr ""
msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr ""
+msgid "This job is performing tasks that must complete before it can start"
+msgstr ""
+
+msgid "This job is preparing to start"
+msgstr ""
+
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
msgstr ""
@@ -7747,7 +8083,7 @@ msgstr ""
msgid "This page will be removed in a future release."
msgstr ""
-msgid "This pipeline is run in a merge request context"
+msgid "This pipeline is run on the source branch"
msgstr ""
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
@@ -7786,6 +8122,9 @@ msgstr ""
msgid "This timeout will take precedence when lower than project-defined timeout and accepts a human readable time input language like \"1 hour\". Values without specification represent seconds."
msgstr ""
+msgid "This user cannot be unlocked manually from GitLab"
+msgstr ""
+
msgid "This user has no identities"
msgstr ""
@@ -8135,6 +8474,9 @@ msgstr ""
msgid "Twitter"
msgstr ""
+msgid "Two-factor Authentication has been disabled for this user"
+msgstr ""
+
msgid "Two-factor authentication"
msgstr ""
@@ -8279,18 +8621,45 @@ msgstr ""
msgid "Use your global notification setting"
msgstr ""
+msgid "User %{current_user_username} has started impersonating %{username}"
+msgstr ""
+
+msgid "User %{username} was successfully removed."
+msgstr ""
+
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr ""
+msgid "User OAuth applications"
+msgstr ""
+
msgid "User Settings"
msgstr ""
msgid "User and IP Rate Limits"
msgstr ""
+msgid "User identity was successfully created."
+msgstr ""
+
+msgid "User identity was successfully removed."
+msgstr ""
+
+msgid "User identity was successfully updated."
+msgstr ""
+
+msgid "User key was successfully removed."
+msgstr ""
+
msgid "User map"
msgstr ""
+msgid "User was successfully created."
+msgstr ""
+
+msgid "User was successfully updated."
+msgstr ""
+
msgid "UserProfile|Activity"
msgstr ""
@@ -8372,6 +8741,9 @@ msgstr ""
msgid "Users requesting access to"
msgstr ""
+msgid "Users were successfully added."
+msgstr ""
+
msgid "Validate"
msgstr ""
@@ -8660,6 +9032,9 @@ msgstr ""
msgid "Write a comment or drag your files here…"
msgstr ""
+msgid "Write access allowed"
+msgstr ""
+
msgid "Write milestone description..."
msgstr ""
@@ -8693,6 +9068,9 @@ msgstr ""
msgid "You are going to transfer %{project_full_name} to another owner. Are you ABSOLUTELY sure?"
msgstr ""
+msgid "You are now impersonating %{username}"
+msgstr ""
+
msgid "You are on a read-only GitLab instance."
msgstr ""
@@ -8708,6 +9086,15 @@ msgstr ""
msgid "You can also star a label to make it a priority label."
msgstr ""
+msgid "You can also test your %{gitlab_ci_yml} in %{lint_link_start}CI Lint%{lint_link_end}"
+msgstr ""
+
+msgid "You can also upload existing files from your computer using the instructions below."
+msgstr ""
+
+msgid "You can create files directly in GitLab using one of the following options."
+msgstr ""
+
msgid "You can easily contribute to them by requesting to join these groups."
msgstr ""
@@ -8738,6 +9125,15 @@ msgstr ""
msgid "You can test your .gitlab-ci.yml in %{linkStart}CI Lint%{linkEnd}."
msgstr ""
+msgid "You cannot impersonate a blocked user"
+msgstr ""
+
+msgid "You cannot impersonate a user who cannot log in"
+msgstr ""
+
+msgid "You cannot impersonate an internal user"
+msgstr ""
+
msgid "You cannot write to this read-only GitLab instance."
msgstr ""
@@ -8843,6 +9239,12 @@ msgstr ""
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
msgstr ""
+msgid "Your U2F device was registered!"
+msgstr ""
+
+msgid "Your account uses dedicated credentials for the \"%{group_name}\" group and can only be updated through SSO."
+msgstr ""
+
msgid "Your applications (%{size})"
msgstr ""
@@ -8888,6 +9290,9 @@ msgstr ""
msgid "a deleted user"
msgstr ""
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr ""
@@ -8909,9 +9314,6 @@ msgstr ""
msgid "branch name"
msgstr ""
-msgid "command line instructions"
-msgstr ""
-
msgid "commented on %{link_to_project}"
msgstr ""
@@ -8938,6 +9340,9 @@ msgstr ""
msgid "deploy token"
msgstr ""
+msgid "detached"
+msgstr ""
+
msgid "disabled"
msgstr ""
@@ -8961,6 +9366,18 @@ msgstr ""
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr ""
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch}"
+msgstr ""
+
+msgid "for %{link_to_merge_request} with %{link_to_merge_request_source_branch} into %{link_to_merge_request_target_branch}"
+msgstr ""
+
+msgid "for %{link_to_pipeline_ref}"
+msgstr ""
+
+msgid "for %{ref}"
+msgstr ""
+
msgid "for this project"
msgstr ""
@@ -9015,6 +9432,9 @@ msgstr ""
msgid "latest version"
msgstr ""
+msgid "leave %{group_name}"
+msgstr ""
+
msgid "manual"
msgstr ""
@@ -9029,6 +9449,9 @@ msgstr ""
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
+msgstr ""
+
msgid "mrWidgetCommitsAdded|1 merge commit"
msgstr ""
diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po
index 67d9a556621..86b7048ad3d 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-02-11 08:11\n"
+"PO-Revision-Date: 2019-03-14 07:35\n"
msgid " Status"
msgstr " Статус"
@@ -36,17 +36,24 @@ msgstr[2] " покращилося на %d одиниць"
msgstr[3] " покращилося на %d одиниць"
msgid " or "
-msgstr ""
+msgstr " або "
msgid " or <#epic id>"
-msgstr ""
+msgstr " або <#epic id>"
msgid " or <#issue id>"
-msgstr ""
+msgstr " або <#issue id>"
msgid "\"%{query}\" in projects"
msgstr "\"%{query}\" в проектах"
+msgid "%d comment"
+msgid_plural "%d comments"
+msgstr[0] "%d коментар"
+msgstr[1] "%d коментарі"
+msgstr[2] "%d коментарів"
+msgstr[3] "%d коментарів"
+
msgid "%d commit"
msgid_plural "%d commits"
msgstr[0] "%d коміт"
@@ -62,7 +69,7 @@ msgstr[2] "%d комітів позаду"
msgstr[3] "%d комітів позаду"
msgid "%d commits"
-msgstr ""
+msgstr "%d комітів"
msgid "%d exporter"
msgid_plural "%d exporters"
@@ -94,10 +101,10 @@ msgstr[3] "%d задач"
msgid "%d issue selected"
msgid_plural "%d issues selected"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "%d вибрана задача"
+msgstr[1] "%d вибрані задачі"
+msgstr[2] "%d вибраних задач"
+msgstr[3] "%d вибраних задач"
msgid "%d layer"
msgid_plural "%d layers"
@@ -156,12 +163,28 @@ msgstr "%{counter_storage} (%{counter_repositories} репозиторій, %{co
msgid "%{count} %{alerts}"
msgstr "%{count} %{alerts}"
-msgid "%{count} more"
+msgid "%{count} approval required from %{name}"
+msgid_plural "%{count} approvals required from %{name}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "%{count} approvals from %{name}"
msgstr ""
+msgid "%{count} more"
+msgstr "%{count} більше"
+
msgid "%{count} more assignees"
msgstr "%{count} більше виконавців"
+msgid "%{count} of %{required} approvals from %{name}"
+msgstr ""
+
+msgid "%{count} of %{total}"
+msgstr "%{count} з %{total}"
+
msgid "%{count} participant"
msgid_plural "%{count} participants"
msgstr[0] "%{count} участник"
@@ -182,18 +205,18 @@ msgstr "%{filePath} видалено"
msgid "%{firstLabel} +%{labelCount} more"
msgstr "%{firstLabel} +%{labelCount} більше"
-msgid "%{firstOption} +%{extraOptionCount} more"
-msgstr ""
-
msgid "%{group_docs_link_start}Groups%{group_docs_link_end} allow you to manage and collaborate across multiple projects. Members of a group have access to all of its projects."
msgstr "%{group_docs_link_start}Групи%{group_docs_link_end} дозволяють вам керувати і взаємодіяти між кількома проектами. Члени групи мають доступ до усіх її проектів."
msgid "%{issuableType} will be removed! Are you sure?"
msgstr "%{issuableType} буде видалено! Ви впевнені?"
-msgid "%{link_start}Read more%{link_end} about role permissions"
+msgid "%{label_for_message} unavailable"
msgstr ""
+msgid "%{link_start}Read more%{link_end} about role permissions"
+msgstr "%{link_start}Читати більше%{link_end} про дозволи ролей"
+
msgid "%{loadingIcon} Started"
msgstr "%{loadingIcon} Початок"
@@ -213,31 +236,31 @@ msgid "%{percent}%% complete"
msgstr "%{percent}%% завершено"
msgid "%{state} epics"
-msgstr ""
+msgstr "%{state} епіки"
msgid "%{strong_start}%{branch_count}%{strong_end} Branch"
msgid_plural "%{strong_start}%{branch_count}%{strong_end} Branches"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "%{strong_start}%{branch_count}%{strong_end} Гілка"
+msgstr[1] "%{strong_start}%{branch_count}%{strong_end} Гілки"
+msgstr[2] "%{strong_start}%{branch_count}%{strong_end} Гілок"
+msgstr[3] "%{strong_start}%{branch_count}%{strong_end} Гілок"
msgid "%{strong_start}%{commit_count}%{strong_end} Commit"
msgid_plural "%{strong_start}%{commit_count}%{strong_end} Commits"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "%{strong_start}%{commit_count}%{strong_end} Коміт"
+msgstr[1] "%{strong_start}%{commit_count}%{strong_end} Коміти"
+msgstr[2] "%{strong_start}%{commit_count}%{strong_end} Комітів"
+msgstr[3] "%{strong_start}%{commit_count}%{strong_end} Комітів"
msgid "%{strong_start}%{human_size}%{strong_end} Files"
-msgstr ""
+msgstr "%{strong_start}%{human_size}%{strong_end} Файлів"
msgid "%{strong_start}%{tag_count}%{strong_end} Tag"
msgid_plural "%{strong_start}%{tag_count}%{strong_end} Tags"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "%{strong_start}%{tag_count}%{strong_end} Тег"
+msgstr[1] "%{strong_start}%{tag_count}%{strong_end} Теги"
+msgstr[2] "%{strong_start}%{tag_count}%{strong_end} Тегів"
+msgstr[3] "%{strong_start}%{tag_count}%{strong_end} Тегів"
msgid "%{text} %{files}"
msgid_plural "%{text} %{files} files"
@@ -259,10 +282,10 @@ msgid "%{usage_ping_link_start}Learn more%{usage_ping_link_end} about what infor
msgstr "%{usage_ping_link_start}Довідатись більше%{usage_ping_link_end} про те, якою інформацією Ви ділитесь із GitLab Inc."
msgid "%{user_name} profile page"
-msgstr ""
+msgstr "%{user_name} сторінка профілю"
msgid "(external source)"
-msgstr ""
+msgstr "(зовнішнє джерело)"
msgid "+ %{count} more"
msgstr "+ ще %{count}"
@@ -270,8 +293,11 @@ msgstr "+ ще %{count}"
msgid "+ %{moreCount} more"
msgstr "+ ще %{moreCount}"
+msgid "+%{extraOptionCount} more"
+msgstr "+%{extraOptionCount} більше"
+
msgid ", or "
-msgstr ""
+msgstr ", або "
msgid "- Runner is active and can process any new jobs"
msgstr "- Runner активний і може виконувати нові завдання"
@@ -296,6 +322,13 @@ msgstr[1] "%{count} %{type} зміни"
msgstr[2] "%{count} %{type} змін"
msgstr[3] "%{count} %{type} змін"
+msgid "1 Day"
+msgid_plural "%d Days"
+msgstr[0] "1 день"
+msgstr[1] "%d дні"
+msgstr[2] "%d днів"
+msgstr[3] "%d днів"
+
msgid "1 closed issue"
msgid_plural "%d closed issues"
msgstr[0] "1 закрита задача"
@@ -363,13 +396,13 @@ msgid "1st contribution!"
msgstr "Перший внесок!"
msgid "2FA"
-msgstr ""
+msgstr "двофакторна автентифікація"
msgid "2FA enabled"
-msgstr "Двоетапна аутентифікація увімкнена"
+msgstr "Двофакторна автентифікація увімкнена"
msgid "403|Please contact your GitLab administrator to get permission."
-msgstr ""
+msgstr "Будь ласка, зверніться до адміністратора GitLab, щоб отримати дозвіл."
msgid "403|You don't have the permission to access this page."
msgstr "У вас немає доступу до цієї сторінки."
@@ -411,11 +444,26 @@ msgid "<strong>%{pushes}</strong> pushes, more than <strong>%{commits}</strong>
msgstr "<strong>%{pushes}</strong> відправок (push), більше ніж <strong>%{commits}</strong> зафіксовано<strong>%{people}</strong> учасниками."
msgid "<strong>Deletes</strong> source branch"
-msgstr ""
+msgstr "<strong>Видаляє</strong> гілку-джерело"
msgid "A 'Runner' is a process which runs a job. You can set up as many Runners as you need."
msgstr "'Runner' — це процес, який виконує завдання. Ви можете створити потрібну кількість Runner'ів."
+msgid "A .NET Core console application template, customizable for any .NET Core project"
+msgstr ""
+
+msgid "A GitBook site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hexo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Hugo site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
+msgid "A Jekyll site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A collection of graphs regarding Continuous Integration"
msgstr "Набір графіків відносно безперервної інтеграції"
@@ -431,9 +479,18 @@ msgstr "Учасник команди GitLab по боротьбі з поруш
msgid "A new branch will be created in your fork and a new merge request will be started."
msgstr "У вашому форку буде створено нову гілку, а також буде ініційований новий запит на злиття."
+msgid "A plain HTML site that uses Netlify for CI/CD instead of GitLab, but still with all the other great GitLab features."
+msgstr ""
+
msgid "A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}."
msgstr "Проект — це місце де ви можете розміщувати свої файли (репозиторій), планувати роботу (задачі) і публікувати документацію (вікі), %{among_other_things_link}."
+msgid "A ready-to-go template for use with Android apps."
+msgstr ""
+
+msgid "A ready-to-go template for use with iOS Swift apps."
+msgstr ""
+
msgid "A regular expression that will be used to find the test coverage output in the job trace. Leave blank to disable"
msgstr "Регулярний вираз, який буде використовуватися для пошуку результатів покриття тестами в завданні. Залиште пустим для вимкнення"
@@ -459,7 +516,7 @@ msgid "Abuse reports"
msgstr "Звіти про зловживання"
msgid "Accept invitation"
-msgstr ""
+msgstr "Прийняти запрошення"
msgid "Accept terms"
msgstr "Прийняти умови"
@@ -498,41 +555,68 @@ msgid "Add"
msgstr "Додати"
msgid "Add CHANGELOG"
-msgstr ""
+msgstr "Додати список змін (CHANGELOG)"
msgid "Add CONTRIBUTING"
-msgstr ""
+msgstr "Додати CONTRIBUTING"
msgid "Add Group Webhooks and GitLab Enterprise Edition."
msgstr "Додайте групові веб-гуки та GitLab Enterprise Edition."
msgid "Add Jaeger URL"
-msgstr "Додати URL-адрес Jaeger"
+msgstr "Додати URL-адресу Jaeger"
msgid "Add Kubernetes cluster"
msgstr "Додати Kubernetes-кластер"
msgid "Add README"
-msgstr ""
+msgstr "Додати інструкцію (README)"
+
+msgid "Add a bullet list"
+msgstr "Додати ненумерований список"
msgid "Add a general comment to this %{noteable_name}."
-msgstr ""
+msgstr "Додайте загальний коментар до цього %{noteable_name}."
msgid "Add a homepage to your wiki that contains information about your project and GitLab will display it here instead of this message."
msgstr "Додати домашню сторінку в вікі, яка містить інформацію про ваш проект, і GitLab відображатиме його тут замість цього повідомлення."
+msgid "Add a link"
+msgstr "Додати посилання"
+
+msgid "Add a numbered list"
+msgstr "Додати нумерований список"
+
msgid "Add a table"
msgstr "Додати таблицю"
+msgid "Add a task list"
+msgstr "Додати список завдань"
+
msgid "Add additional text to appear in all email communications. %{character_limit} character limit"
msgstr "Створіть додатковий текст, який буде присутній у всіх повідомленнях електронної пошти. Максимальна кількість символів — %{character_limit}"
+msgid "Add approver(s)"
+msgstr "Додати затверджуючих осіб"
+
+msgid "Add approvers"
+msgstr "Додати затверджуючих осіб"
+
+msgid "Add bold text"
+msgstr "Додати жирний текст"
+
msgid "Add comment now"
msgstr "Додати коментар"
+msgid "Add header and footer to emails. Please note that color settings will only be applied within the application interface"
+msgstr ""
+
msgid "Add image comment"
msgstr "Додати коментар до зображення"
+msgid "Add italic text"
+msgstr "Додати курсивний текст"
+
msgid "Add license"
msgstr "Додати ліцензію"
@@ -549,23 +633,26 @@ msgid "Add reaction"
msgstr "Додати реакцію"
msgid "Add to project"
-msgstr ""
+msgstr "Додати до проекту"
msgid "Add to review"
msgstr "Додати до перевірки"
msgid "Add todo"
-msgstr "Додати задачу"
+msgstr "Додати нагадування"
msgid "Add user(s) to the group:"
msgstr "Додати користувачів до групу:"
msgid "Add users or groups who are allowed to approve every merge request"
-msgstr ""
+msgstr "Додайте користувачів або групи, яким дозволено затверджувати будь-який запит на злиття"
msgid "Add users to group"
msgstr "Додати користувача до групи"
+msgid "Added at"
+msgstr ""
+
msgid "Adding new applications is disabled in your GitLab instance. Please contact your GitLab administrator to get the permission"
msgstr "Додавання нових проектів для вашого інстансу GitLab заборонено. Зверніться до свого адміністратора GitLab, щоб отримати дозвіл"
@@ -612,40 +699,40 @@ msgid "AdminProjects|Delete project"
msgstr "Видалити проект"
msgid "AdminSettings|Auto DevOps domain"
-msgstr ""
+msgstr "Домен Auto DevOps"
msgid "AdminSettings|Environment variables are protected by default"
-msgstr ""
+msgstr "Змінні середовища є захищеними за замовчуванням"
msgid "AdminSettings|Specify a domain to use by default for every project's Auto Review Apps and Auto Deploy stages."
msgstr "Вкажіть домен, який буде використовуватися в проекті за замовчуванням для стадій Auto Review Apps і Auto Deploy."
msgid "AdminSettings|When creating a new environment variable it will be protected by default."
-msgstr ""
+msgstr "При створенні нової змінної середовища вона буде захищена за замовчуванням."
msgid "AdminUsers|2FA Disabled"
-msgstr ""
+msgstr "2FA вимкнено"
msgid "AdminUsers|2FA Enabled"
-msgstr ""
+msgstr "2FA увімкнено"
msgid "AdminUsers|Active"
-msgstr ""
+msgstr "Активні"
msgid "AdminUsers|Admin"
-msgstr ""
+msgstr "Адміністратор"
msgid "AdminUsers|Admins"
-msgstr ""
+msgstr "Адміністратори"
msgid "AdminUsers|Block user"
msgstr "Заблоквати користувача"
msgid "AdminUsers|Blocked"
-msgstr ""
+msgstr "Заблоковано"
msgid "AdminUsers|Cannot unblock LDAP blocked users"
-msgstr ""
+msgstr "Неможливо розблокувати користувачів заблокованих в LDAP"
msgid "AdminUsers|Delete User %{username} and contributions?"
msgstr "Видалити користувача %{username} та його внески?"
@@ -660,28 +747,28 @@ msgid "AdminUsers|Delete user and contributions"
msgstr "Видалити користувача і його внески"
msgid "AdminUsers|External"
-msgstr ""
+msgstr "Зовнішні"
msgid "AdminUsers|It's you!"
-msgstr ""
+msgstr "Це ви!"
msgid "AdminUsers|New user"
-msgstr ""
+msgstr "Новий користувач"
msgid "AdminUsers|No users found"
-msgstr ""
+msgstr "Користувачів не знайдено"
msgid "AdminUsers|Search by name, email or username"
-msgstr ""
+msgstr "Шукати за іменем, електронною поштою або іменем користувача"
msgid "AdminUsers|Search users"
-msgstr ""
+msgstr "Пошук користувачів"
msgid "AdminUsers|Send email to users"
-msgstr ""
+msgstr "Відправити повідмолення електронної пошти користувачам"
msgid "AdminUsers|Sort by"
-msgstr ""
+msgstr "Сортувати за"
msgid "AdminUsers|To confirm, type %{projectName}"
msgstr "Для підтвердження введіть %{projectName}"
@@ -690,9 +777,12 @@ msgid "AdminUsers|To confirm, type %{username}"
msgstr "Для підтвердження введіть %{username}"
msgid "AdminUsers|User will be blocked"
-msgstr ""
+msgstr "Користувач буде заблокований"
msgid "AdminUsers|Without projects"
+msgstr "Без проектів"
+
+msgid "Advanced"
msgstr ""
msgid "Advanced permissions, Large File Storage and Two-Factor authentication settings."
@@ -701,6 +791,9 @@ msgstr "Додаткові дозволи, сховище великих фай
msgid "Advanced settings"
msgstr "Додаткові параметри"
+msgid "After a successful password update you will be redirected to login screen."
+msgstr ""
+
msgid "Alert"
msgid_plural "Alerts"
msgstr[0] "Попередження"
@@ -709,11 +802,14 @@ msgstr[2] "Попереджень"
msgstr[3] "Попереджень"
msgid "Alerts"
-msgstr ""
+msgstr "Попередження"
msgid "All"
msgstr "Всі"
+msgid "All Members"
+msgstr "Всі учасники"
+
msgid "All changes are committed"
msgstr "Всі зміни закомічені"
@@ -721,7 +817,7 @@ msgid "All features are enabled for blank projects, from templates, or when impo
msgstr "Всі функції для нових проектів беруться із шаблонів або під час імпортування, але ви можете вимикати їх пізніше в налаштуваннях проекту."
msgid "All issues for this milestone are closed. You may close this milestone now."
-msgstr ""
+msgstr "Всі задачі для цього етапу закриті. Ви можете закрити цей етап."
msgid "All users"
msgstr "Всі користувачі"
@@ -744,6 +840,9 @@ msgstr "Дозволити відображення діаграм PlantUML в
msgid "Allow requests to the local network from hooks and services."
msgstr "Дозволити запити до локальної мережі із гуків та сервісів."
+msgid "Allow this key to push to repository as well? (Default only allows pull access.)"
+msgstr ""
+
msgid "Allow users to request access"
msgstr "Дозволити користувачам запитувати доступ"
@@ -783,9 +882,6 @@ msgstr "Порожнє поле Gitlab-користувача буде запо
msgid "An error has occurred"
msgstr "Трапилася помилка"
-msgid "An error occured while fetching the releases. Please try again."
-msgstr ""
-
msgid "An error occurred adding a draft to the discussion."
msgstr "Виникла помилка під час додавання чернетки до обговорення."
@@ -793,6 +889,15 @@ msgid "An error occurred adding a new draft."
msgstr "Виникла помилка під час додавання нової чернетки."
msgid "An error occurred creating the new branch."
+msgstr "Помилка при створенні нової гілки."
+
+msgid "An error occurred fetching the approval rules."
+msgstr ""
+
+msgid "An error occurred fetching the approvers for the new rule."
+msgstr "Помилка при отриманні затверджуючих осіб для нового правила."
+
+msgid "An error occurred fetching the dropdown data."
msgstr ""
msgid "An error occurred previewing the blob"
@@ -805,7 +910,10 @@ msgid "An error occurred when updating the issue weight"
msgstr "Збій під час оновлення ваги задачі"
msgid "An error occurred while adding approver"
-msgstr "Помилка при додаванні учасника для затвердження"
+msgstr "Помилка при додаванні затверджуючої особи"
+
+msgid "An error occurred while deleting the approvers group"
+msgstr "Помилка при видаленні групи затверджуючих осіб"
msgid "An error occurred while deleting the comment"
msgstr "Сталася помилка під час видалення коментаря"
@@ -843,6 +951,9 @@ msgstr "Помилка при отриманні завдань."
msgid "An error occurred while fetching the pipeline."
msgstr "Помилка при отриманні данних конвеєра."
+msgid "An error occurred while fetching the releases. Please try again."
+msgstr ""
+
msgid "An error occurred while getting projects"
msgstr "Сталася помилка при отриманні проектів"
@@ -858,6 +969,9 @@ msgstr "Виникла помилка під час завантаження д
msgid "An error occurred while loading commit signatures"
msgstr "Трапилася помилка при завантаженні сигнатур коміту"
+msgid "An error occurred while loading designs. Please try again."
+msgstr ""
+
msgid "An error occurred while loading diff"
msgstr "Помилка при завантаженні разниці (diff)"
@@ -868,19 +982,19 @@ msgid "An error occurred while loading the file"
msgstr "Помилка при завантаженні файлу"
msgid "An error occurred while loading the subscription details."
-msgstr ""
+msgstr "Помилка при завантаженні даних підписки."
msgid "An error occurred while making the request."
msgstr "Помилка при створенні запиту."
msgid "An error occurred while removing approver"
-msgstr "Помилка при видаленні учасника для затвердження"
+msgstr "Помилка при видаленні затверджуючої особи"
msgid "An error occurred while removing epics."
-msgstr ""
+msgstr "Помилка при видаленні епіків."
msgid "An error occurred while removing issues."
-msgstr ""
+msgstr "Помилка при видаленні задач."
msgid "An error occurred while rendering KaTeX"
msgstr "Сталася помилка при рендерингу KaTeX"
@@ -900,12 +1014,18 @@ msgstr "Помилка при збереженні статусу перевиз
msgid "An error occurred while saving assignees"
msgstr "Помилка при збереженні виконавців"
+msgid "An error occurred while saving the approval settings"
+msgstr ""
+
msgid "An error occurred while subscribing to notifications."
msgstr "Помилка при підписці на сповіщення."
msgid "An error occurred while unsubscribing to notifications."
msgstr "Помилка при відписці від сповіщень."
+msgid "An error occurred while updating approvers"
+msgstr "Помилка при оновленні затверджуючих осіб"
+
msgid "An error occurred while updating the comment"
msgstr "Під час оновлення коментаря сталася помилка"
@@ -913,56 +1033,59 @@ msgid "An error occurred while validating username"
msgstr "Сталася помилка під час перевірки імені користувача"
msgid "An error occurred whilst committing your changes."
-msgstr ""
+msgstr "Помилка при коміті ваших змін."
msgid "An error occurred whilst fetching the job trace."
-msgstr ""
+msgstr "Помилка при отриманні логу завдання."
msgid "An error occurred whilst fetching the latest pipeline."
-msgstr ""
+msgstr "Помилка при отриманні останнього конвеєра."
msgid "An error occurred whilst loading all the files."
-msgstr ""
+msgstr "Помилка при завантаженні всіх файлів."
msgid "An error occurred whilst loading the file content."
-msgstr ""
+msgstr "Помилка при завантаженні вмісту файлу."
msgid "An error occurred whilst loading the file."
-msgstr ""
+msgstr "Помилка при завантаженні файлу."
msgid "An error occurred whilst loading the merge request changes."
-msgstr ""
+msgstr "Помилка при завантаженні змін запиту на злиття."
msgid "An error occurred whilst loading the merge request version data."
-msgstr ""
+msgstr "Помилка при завантаженні даних версії запиту на злиття."
msgid "An error occurred whilst loading the merge request."
-msgstr ""
+msgstr "Помилка при завантаженні запиту на злиття."
msgid "An error occurred whilst loading the pipelines jobs."
-msgstr ""
+msgstr "Помилка при завантаженні завдань конвеєру."
msgid "An error occurred. Please try again."
msgstr "Сталась помилка. Спробуйте ще раз."
msgid "An unexpected error occurred while checking the project environment."
-msgstr ""
+msgstr "Неочікувана помилка при перевірці середовища проекту."
msgid "An unexpected error occurred while checking the project runners."
-msgstr ""
+msgstr "Неочікувана помилка при перевірці runner'ів проекту."
msgid "An unexpected error occurred while communicating with the Web Terminal."
-msgstr ""
+msgstr "Неочікувана помилка з'єднання із Веб-терміналом."
msgid "An unexpected error occurred while starting the Web Terminal."
-msgstr ""
+msgstr "Неочікувана помилка при запуску Веб-терміналу."
msgid "An unexpected error occurred while stopping the Web Terminal."
-msgstr ""
+msgstr "Неочікувана помилка при зупинці Веб-терміналу."
msgid "Analytics"
msgstr "Аналітика"
+msgid "Ancestors"
+msgstr ""
+
msgid "Anonymous"
msgstr "Анонімно"
@@ -991,19 +1114,65 @@ msgid "Applications"
msgstr "Застосунки"
msgid "Applied"
-msgstr ""
+msgstr "Застосовано"
msgid "Apply suggestion"
+msgstr "Застосувати пропозицію"
+
+msgid "ApprovalRuleRemove|%d member"
+msgid_plural "ApprovalRuleRemove|%d members"
+msgstr[0] "%d учасник"
+msgstr[1] "%d учасника"
+msgstr[2] "%d учасників"
+msgstr[3] "%d учасників"
+
+msgid "ApprovalRuleRemove|Approvals from this member are not revoked."
+msgid_plural "ApprovalRuleRemove|Approvals from these members are not revoked."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRuleRemove|You are about to remove the %{name} approver group which has %{nMembers}."
+msgstr "Ви збираєтеся видалити групу затверджуючих осіб %{name} яка налічує %{nMembers} учасників."
+
+msgid "ApprovalRuleSummary|%d member"
+msgid_plural "ApprovalRuleSummary|%d members"
+msgstr[0] "%d учасник"
+msgstr[1] "%d учасника"
+msgstr[2] "%d учасників"
+msgstr[3] "%d учасників"
+
+msgid "ApprovalRuleSummary|%{count} approval required from %{membersCount}"
+msgid_plural "ApprovalRuleSummary|%{count} approvals required from %{membersCount}"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ApprovalRule|All members with Developer role or higher and code owners (if any)"
msgstr ""
-msgid "Approvals"
+msgid "ApprovalRule|Members"
+msgstr "Учасники"
+
+msgid "ApprovalRule|Name"
+msgstr "Ім'я"
+
+msgid "ApprovalRule|No. approvals required"
msgstr ""
-msgid "Approvals required"
+msgid "ApprovalRule|e.g. QA, Security, etc."
msgstr ""
+msgid "Approvals"
+msgstr "Затвердження"
+
+msgid "Approvals required"
+msgstr "Потрібні затвердження"
+
msgid "Approvers"
-msgstr ""
+msgstr "Затверджуючі особи"
msgid "Apr"
msgstr "квіт."
@@ -1018,7 +1187,7 @@ msgid "Archived projects"
msgstr "Заархівовані проекти"
msgid "Are you sure"
-msgstr ""
+msgstr "Ви впевнені"
msgid "Are you sure you want to delete this pipeline schedule?"
msgstr "Ви впевнені, що хочете видалити цей розклад для конвеєра?"
@@ -1030,7 +1199,7 @@ msgid "Are you sure you want to lose unsaved changes?"
msgstr "Ви впевнені, що бажаєте втратити незбережені зміни?"
msgid "Are you sure you want to lose your issue information?"
-msgstr ""
+msgstr "Ви впевнені, що хочете втратити інформацію про задачу?"
msgid "Are you sure you want to regenerate the public key? You will have to update the public key on the remote server before mirroring will work again."
msgstr "Ви впевнені, що хочете повторно згенерувати відкритий ключ? Вам доведеться оновити відкритий ключ на віддаленому сервері, перш ніж дзеркалювання запрацює знову."
@@ -1038,14 +1207,20 @@ msgstr "Ви впевнені, що хочете повторно згенеру
msgid "Are you sure you want to remove %{group_name}?"
msgstr "Ви впевнені, що хочете видалити %{group_name}?"
+msgid "Are you sure you want to remove approver %{name}"
+msgstr "Ви впевнені, що хочете видалити затверджуючу особу %{name}"
+
msgid "Are you sure you want to remove approver %{name}?"
-msgstr ""
+msgstr "Ви впевнені, що хочете видалити затверджуючу особу %{name}?"
+
+msgid "Are you sure you want to remove group %{name}"
+msgstr "Ви впевнені, що хочете видалити групу %{name}"
msgid "Are you sure you want to remove group %{name}?"
-msgstr ""
+msgstr "Ви впевнені, що хочете видалити групу %{name}?"
msgid "Are you sure you want to remove the attachment?"
-msgstr ""
+msgstr "Ви впевнені, що бажаєте видалити вкладення?"
msgid "Are you sure you want to remove this identity?"
msgstr "Ви впевнені, що хочете видалити цю ідентифікацію?"
@@ -1063,7 +1238,7 @@ msgid "Are you sure you want to unlock %{path_lock_path}?"
msgstr "Ви впевнені, що хочете розблокувати %{path_lock_path}?"
msgid "Are you sure you want to unsubscribe from the %{type}: %{link_to_noteable_text}?"
-msgstr ""
+msgstr "Ви дійсно бажаєте скасувати підписку на %{type}: %{link_to_noteable_text}?"
msgid "Are you sure?"
msgstr "Ви впевнені?"
@@ -1096,7 +1271,7 @@ msgid "Assign milestone"
msgstr "Призначити етап"
msgid "Assign some issues to this milestone."
-msgstr ""
+msgstr "Призначити деякі задачі до цього етапу."
msgid "Assign to"
msgstr "Призначити"
@@ -1125,9 +1300,12 @@ msgstr "Списки виконавців показують усі задачі
msgid "Assignee(s)"
msgstr "Виконавець(ці)"
-msgid "Attach a file"
+msgid "At least one approval from a code owner is required to change files matching the respective CODEOWNER rules."
msgstr ""
+msgid "Attach a file"
+msgstr "Прикріпити файл"
+
msgid "Attach a file by drag &amp; drop or %{upload_link}"
msgstr "Прикріпити файл за допомогою перетягування або %{upload_link}"
@@ -1140,9 +1318,6 @@ msgstr "серп."
msgid "August"
msgstr "серпень"
-msgid "Auth Token"
-msgstr ""
-
msgid "Authentication Log"
msgstr "Журнал автентифікації"
@@ -1159,7 +1334,7 @@ msgid "Authorization code:"
msgstr "Код авторизації:"
msgid "Authorization key"
-msgstr ""
+msgstr "Ключ авторизації"
msgid "Authorization was granted by entering your username and password in the application."
msgstr "Авторизація відбулася після вводу вашого імені та паролю у застосунку."
@@ -1222,25 +1397,25 @@ msgid "Automatically marked as default internal user"
msgstr "Автоматично позначено як внутрішній користувач за замовчуванням"
msgid "Automatically resolved"
-msgstr ""
+msgstr "Вирішено автоматично"
msgid "Available"
msgstr "Доступно"
msgid "Available group Runners: %{runners}"
-msgstr ""
+msgstr "Доступні групові Runner'и: %{runners}"
msgid "Available shared Runners:"
-msgstr ""
+msgstr "Доступні загальні Runner'и:"
msgid "Available specific runners"
-msgstr ""
+msgstr "Доступні спеціальні Runner’и"
msgid "Avatar for %{assigneeName}"
-msgstr ""
+msgstr "Аватар для %{assigneeName}"
msgid "Avatar for %{name}"
-msgstr ""
+msgstr "Аватар для %{name}"
msgid "Avatar will be removed. Are you sure?"
msgstr "Аватар буде видалено. Ви впевнені?"
@@ -1426,10 +1601,10 @@ msgid "Bitbucket import"
msgstr "Імпорт з Bitbucket"
msgid "Block"
-msgstr ""
+msgstr "Блок"
msgid "Blocked"
-msgstr ""
+msgstr "Заблокований"
msgid "Blog"
msgstr "Блог"
@@ -1594,34 +1769,34 @@ msgid "Browse files"
msgstr "Перегляд файлів"
msgid "Built-in"
-msgstr ""
+msgstr "Вбудований"
msgid "Business"
-msgstr ""
+msgstr "Бізнес"
msgid "Business metrics (Custom)"
msgstr "Бізнес метрики (Власні)"
msgid "By %{user_name}"
-msgstr ""
+msgstr "Від %{user_name}"
msgid "ByAuthor|by"
msgstr "від"
msgid "CHANGELOG"
-msgstr ""
+msgstr "Список змін (CHANGELOG)"
msgid "CI / CD"
msgstr "CI / CD"
msgid "CI / CD Charts"
-msgstr ""
+msgstr "Статистика CI / CD"
msgid "CI / CD Settings"
msgstr "Налаштування CI/CD"
msgid "CI Lint"
-msgstr ""
+msgstr "Перевірка CI конфігурації"
msgid "CI will run using the credentials assigned above."
msgstr "CI буде працювати з використанням облікових даних, визначених вище."
@@ -1669,6 +1844,9 @@ msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration
msgstr "Конвеєр Auto DevOps буде запущено, якщо не буде знайдено альтернативного файлу конфігуріції CI."
msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
+msgstr "Ви повинні додати %{kubernetes_cluster_start}кластерну інтеграцію Kubernetes%{kubernetes_cluster_end} до цього проекту з доменом для коректної роботи стратегії розгортання."
+
+msgid "CICD|group enabled"
msgstr ""
msgid "CICD|instance enabled"
@@ -1681,14 +1859,17 @@ msgid "Callback URL"
msgstr "URL зворотнього виклику"
msgid "Can override approvers and approvals required per merge request"
-msgstr ""
+msgstr "Можна змінювати необхідні затвердження та затверджуючих осіб для запитів на злиття"
msgid "Can't find HEAD commit for this branch"
msgstr "Не можу знайти HEAD-коміт для цієї гілки"
-msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
+msgid "Can't remove group members without group managed account"
msgstr ""
+msgid "Canary Deployments is a popular CI strategy, where a small portion of the fleet is updated to the new version of your application."
+msgstr "Canary Deployments — це популярна стратегія безперервної інтеграції, коли нова версія вашого застосунку встановлюється на невелику кількість машин."
+
msgid "Cancel"
msgstr "Скасувати"
@@ -1701,11 +1882,14 @@ msgstr "Неможливо злити автоматично"
msgid "Cannot modify managed Kubernetes cluster"
msgstr "Неможливо змінити керований кластер Kubernetes"
-msgid "Certificate"
+msgid "Cannot render the image. Maximum character count (%{charLimit}) has been exceeded."
msgstr ""
+msgid "Certificate"
+msgstr "Сертифікат"
+
msgid "Certificate (PEM)"
-msgstr ""
+msgstr "Сертифікат (PEM)"
msgid "Certificate fingerprint"
msgstr "Відбиток сертифіката"
@@ -1714,7 +1898,7 @@ msgid "Change Weight"
msgstr "Змінити вагу"
msgid "Change permissions"
-msgstr ""
+msgstr "Змінити права доступу"
msgid "Change template"
msgstr "Змінити шаблон"
@@ -1737,6 +1921,9 @@ msgstr "Анулювати коміт"
msgid "ChangeTypeAction|This will create a new commit in order to revert the existing changes."
msgstr "Це створить новий коміт, щоб анулювати існуючі зміни."
+msgid "Changes"
+msgstr "Зміни"
+
msgid "Changes are shown as if the <b>source</b> revision was being merged into the <b>target</b> revision."
msgstr "Зміни відображаються так, ніби <b>редакція-джерело</b> була злита в <b>цільову редакцію</b>."
@@ -1749,17 +1936,20 @@ msgstr "Статистика"
msgid "Chat"
msgstr "Чат"
+msgid "Check again"
+msgstr "Перевірити знову"
+
msgid "Check the %{docs_link_start}documentation%{docs_link_end}."
msgstr "Перегляньте %{docs_link_start}документацію%{docs_link_end}."
msgid "Check your .gitlab-ci.yml"
-msgstr ""
+msgstr "Перевірте свій .gitlab-ci.yml"
msgid "Checking %{text} availability…"
msgstr "Перевірка доступності %{text}…"
msgid "Checking approval status"
-msgstr ""
+msgstr "Перевірка статусу затвердження"
msgid "Checking branch availability..."
msgstr "Перевірка доступності гілки..."
@@ -1783,7 +1973,7 @@ msgid "Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to s
msgstr "Виберіть гілку чи тег (напр. %{master}) або введіть коміт (напр. %{sha}) для перегляду змін або для створення запиту на злиття."
msgid "Choose a file"
-msgstr ""
+msgstr "Виберіть файл"
msgid "Choose a role permission"
msgstr ""
@@ -1806,21 +1996,21 @@ msgstr "Виберіть файл..."
msgid "Choose the top-level group for your repository imports."
msgstr "Оберіть групу найвищого рівня для імпорту репозиторіїв."
-msgid "Choose what content you want to see on a group’s overview page"
+msgid "Choose visibility level, enable/disable project features (issues, repository, wiki, snippets) and set permissions."
msgstr ""
-msgid "Choose which groups you wish to synchronize to this secondary node."
-msgstr "Виберіть групи для синхронізації на цей вторинний вузол."
+msgid "Choose what content you want to see on a group’s overview page"
+msgstr "Вибрати вміст оглядової сторінки групи"
msgid "Choose which repositories you want to connect and run CI/CD pipelines."
msgstr "Виберіть, які репозиторії ви хочете підключити і запустити конвеєри CI/CD."
-msgid "Choose which repositories you want to import."
-msgstr "Виберіть, які репозиторії ви хочете імпортувати."
-
msgid "Choose which shards you wish to synchronize to this secondary node."
msgstr "Виберіть сегменти для синхронізації на цей вторинний вузол."
+msgid "Choose your merge method, set up a default merge request description template."
+msgstr ""
+
msgid "CiStatusLabel|canceled"
msgstr "скасовано"
@@ -1903,7 +2093,7 @@ msgid "CiVariable|Create wildcard"
msgstr "Створити шаблон"
msgid "CiVariable|Error occurred while saving variables"
-msgstr ""
+msgstr "Помилка при збереженні змінних"
msgid "CiVariable|New environment"
msgstr "Нове середовище"
@@ -1924,10 +2114,10 @@ msgid "ClassificationLabelUnavailable|is unavailable: %{reason}"
msgstr "не доступно: %{reason}"
msgid "Clear"
-msgstr ""
+msgstr "Очистити"
msgid "Clear input"
-msgstr ""
+msgstr "Очистити ввід"
msgid "Clear search"
msgstr "Очистити пошук"
@@ -1963,22 +2153,25 @@ msgid "Client authentication key"
msgstr "Ключ автентифікації клієнта"
msgid "Client authentication key password"
-msgstr "Пароль ключа аутентифікації клієнта"
+msgstr "Пароль ключа автентифікації клієнта"
msgid "Clients"
msgstr "Клієнти"
msgid "Clone"
-msgstr ""
+msgstr "Клонувати"
msgid "Clone repository"
msgstr "Клонувати репозиторій"
msgid "Clone with %{http_label}"
+msgstr "Клонувати з %{http_label}"
+
+msgid "Clone with KRB5"
msgstr ""
msgid "Clone with SSH"
-msgstr ""
+msgstr "Клонувати з SSH"
msgid "Close"
msgstr "Закрити"
@@ -1987,22 +2180,19 @@ msgid "Close epic"
msgstr "Закрити епік"
msgid "Close milestone"
-msgstr ""
+msgstr "Закрити етап"
msgid "Closed"
msgstr "Закрито"
-msgid "Closed (moved)"
-msgstr ""
-
msgid "Closed issues"
msgstr "Закриті задачі"
msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
-msgstr ""
+msgstr "%{custom_domain_start}Детальніше%{custom_domain_end}."
msgid "ClusterIntegration| can be used instead of a custom domain."
-msgstr ""
+msgstr "може використовуватися замість власного домену."
msgid "ClusterIntegration| is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster. %{environment_scope_start}More information%{environment_scope_end}"
msgstr ""
@@ -2010,9 +2200,6 @@ msgstr ""
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr "%{appList} були успішно встановлені на ваш Kubernetes-кластер"
-msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
-msgstr "%{boldNotice} Це додасть додаткові ресурси, такі як балансувальник навантаження, який може збільшити витрати в залежності від провайдера хостингу, на якому встановлено кластер Kubernetes. Якщо ви використовуєте Google Kubernetes Engine, ви можете %{pricingLink}."
-
msgid "ClusterIntegration|%{title} upgraded successfully."
msgstr ""
@@ -2034,20 +2221,17 @@ msgstr "Додавання інтеграції до вашої групи на
msgid "ClusterIntegration|Advanced options on this Kubernetes cluster's integration"
msgstr "Детальні налаштування інтеграції із цим Kubernetes-кластером"
-msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
-msgstr "Після встановлення Ingress, вам необхідно направити свій DNS на згенеровану зовнішню IP-адресу, щоб переглянути ваш застосунок після його розгортання. %{ingressHelpLink}"
-
msgid "ClusterIntegration|Alternatively"
msgstr ""
-msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
-msgstr "Помилка при отриманні зон проекту: %{error}"
-
msgid "ClusterIntegration|An error occurred when trying to contact the Google Cloud API. Please try again later."
msgstr "Помилка під час з'єднання з Google Cloud API. Будь ласка, спробуйте знову пізніше."
+msgid "ClusterIntegration|An error occurred while trying to fetch project zones: %{error}"
+msgstr "Помилка при отриманні зон проекту: %{error}"
+
msgid "ClusterIntegration|An error occurred while trying to fetch your projects: %{error}"
-msgstr ""
+msgstr "Помилка при отриманні ваших проектів: %{error}"
msgid "ClusterIntegration|An error occurred while trying to fetch zone machine types: %{error}"
msgstr ""
@@ -2062,13 +2246,13 @@ msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluste
msgstr "Ви впевнені, що хочете видалити інтеграцію із цим Kubernetes-кластером? Це не призведе до видалення самого кластера."
msgid "ClusterIntegration|Base domain"
-msgstr ""
+msgstr "Основний домен"
msgid "ClusterIntegration|CA Certificate"
msgstr "Сертифікат центру сертифікації"
msgid "ClusterIntegration|Cert-Manager"
-msgstr ""
+msgstr "Cert-Manager"
msgid "ClusterIntegration|Cert-Manager is a native Kubernetes certificate management controller that helps with issuing certificates. Installing Cert-Manager on your cluster will issue a certificate by %{letsEncrypt} and ensure that certificates are valid and up-to-date."
msgstr ""
@@ -2082,6 +2266,9 @@ msgstr "Виберіть, які застосунки необхідно вст
msgid "ClusterIntegration|Choose which of your environments will use this cluster."
msgstr "Виберіть, яке із ваших середовищ буде використовувати цей кластер."
+msgid "ClusterIntegration|Cluster health"
+msgstr ""
+
msgid "ClusterIntegration|Clusters are utilized by selecting the nearest ancestor with a matching environment scope. For example, project clusters will override group clusters."
msgstr ""
@@ -2091,13 +2278,13 @@ msgstr "Скопіювати API URL"
msgid "ClusterIntegration|Copy CA Certificate"
msgstr "Скопіювати сертифікат центру сертифікації"
-msgid "ClusterIntegration|Copy Ingress IP Address to clipboard"
-msgstr "Копіювати IP-адресу Ingress в буфер обміну"
+msgid "ClusterIntegration|Copy Ingress Endpoint to clipboard"
+msgstr ""
msgid "ClusterIntegration|Copy Jupyter Hostname to clipboard"
msgstr "Скопіювати ім’я хоста Jupyter в буфер обміну"
-msgid "ClusterIntegration|Copy Knative IP Address to clipboard"
+msgid "ClusterIntegration|Copy Knative Endpoint to clipboard"
msgstr ""
msgid "ClusterIntegration|Copy Kubernetes cluster name"
@@ -2178,14 +2365,14 @@ msgstr "Приховати"
msgid "ClusterIntegration|If you are setting up multiple clusters and are using Auto DevOps, %{help_link_start}read this first%{help_link_end}."
msgstr "Якщо ви налаштовуєте декілька кластерів і використовуєте Auto DevOps, %{help_link_start}прочитайте для початку це%{help_link_end}."
-msgid "ClusterIntegration|In order to show the health of the cluster, we'll need to provision your cluster with Prometheus to collect the required data."
-msgstr "Для того, щоб показати працездатність кластера, нам потрібно буде забезпечити ваш кластер з Prometheus для збору необхідних даних."
+msgid "ClusterIntegration|In order to view the health of your cluster, you must first install Prometheus below."
+msgstr ""
msgid "ClusterIntegration|Ingress"
msgstr "Ingress"
-msgid "ClusterIntegration|Ingress IP Address"
-msgstr "Ingress IP-адреса"
+msgid "ClusterIntegration|Ingress Endpoint"
+msgstr ""
msgid "ClusterIntegration|Ingress gives you a way to route requests to services based on the request host or path, centralizing a number of services into a single entrypoint."
msgstr "Ingress дозволяє вам маршрутизувати запити до служб на основі запитаного хоста або шляху, об'єднуючи ряд сервісів в одну точку входу."
@@ -2193,15 +2380,18 @@ msgstr "Ingress дозволяє вам маршрутизувати запит
msgid "ClusterIntegration|Install"
msgstr "Встановити"
-msgid "ClusterIntegration|Install Prometheus"
-msgstr "Встановити Prometheus"
-
msgid "ClusterIntegration|Installed"
msgstr "Встановлений"
msgid "ClusterIntegration|Installing"
msgstr "Встановлення"
+msgid "ClusterIntegration|Installing Ingress may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
+msgid "ClusterIntegration|Installing Knative may incur additional costs. Learn more about %{pricingLink}."
+msgstr ""
+
msgid "ClusterIntegration|Integrate Kubernetes cluster automation"
msgstr "Інтеграція кластерної автоматизації Kubernetes"
@@ -2209,7 +2399,7 @@ msgid "ClusterIntegration|Integration status"
msgstr "Статус інтеграції"
msgid "ClusterIntegration|Issuer Email"
-msgstr ""
+msgstr "Електронна пошта емітента"
msgid "ClusterIntegration|Issuers represent a certificate authority. You must provide an email address for your Issuer. "
msgstr ""
@@ -2229,7 +2419,7 @@ msgstr "Knative"
msgid "ClusterIntegration|Knative Domain Name:"
msgstr "Доменне ім'я Knative:"
-msgid "ClusterIntegration|Knative IP Address:"
+msgid "ClusterIntegration|Knative Endpoint:"
msgstr ""
msgid "ClusterIntegration|Knative extends Kubernetes to provide a set of middleware components that are essential to build modern, source-centric, and container-based applications that can run anywhere: on premises, in the cloud, or even in a third-party data center."
@@ -2241,9 +2431,6 @@ msgstr "Kubernetes-кластер"
msgid "ClusterIntegration|Kubernetes cluster details"
msgstr "Параметри Kubernetes-кластера"
-msgid "ClusterIntegration|Kubernetes cluster health"
-msgstr "Стан Kubernetes-кластера"
-
msgid "ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine..."
msgstr "Kubernetes-кластер створюється на Google Kubernetes Engine..."
@@ -2272,7 +2459,7 @@ msgid "ClusterIntegration|Learn more about group Kubernetes clusters"
msgstr "Дізнайтеся більше про групові кластери Kubernetes"
msgid "ClusterIntegration|Let's Encrypt"
-msgstr ""
+msgstr "Let's Encrypt"
msgid "ClusterIntegration|Machine type"
msgstr "Тип машини"
@@ -2286,9 +2473,6 @@ msgstr "Управління"
msgid "ClusterIntegration|Manage your Kubernetes cluster by visiting %{link_gke}"
msgstr "Керуйте вашим Kubernetes-кластером за допомогою %{link_gke}"
-msgid "ClusterIntegration|More information"
-msgstr "Додаткова інформація"
-
msgid "ClusterIntegration|No machine types matched your search"
msgstr "Жоден тип машин не відповідає вашому пошуку"
@@ -2301,9 +2485,6 @@ msgstr "Жоден проект не відповідає вашому пошу
msgid "ClusterIntegration|No zones matched your search"
msgstr "Жодна зона не відповідає вашому пошуку"
-msgid "ClusterIntegration|Note:"
-msgstr "Примітка:"
-
msgid "ClusterIntegration|Number of nodes"
msgstr "Кількість вузлів"
@@ -2313,8 +2494,8 @@ msgstr "Введіть інформацію про доступ до свого
msgid "ClusterIntegration|Please make sure that your Google account meets the following requirements:"
msgstr "Будь-ласка впевніться, що ваш обліковий запис Google задовольняє наступним вимогам:"
-msgid "ClusterIntegration|Point a wildcard DNS to this generated IP address in order to access your application after it has been deployed."
-msgstr "Направте ваш DNS на цю згенеровану IP-адресу, щоб отримати доступ до вашого додатку, після його розгортання."
+msgid "ClusterIntegration|Point a wildcard DNS to this generated endpoint in order to access your application after it has been deployed."
+msgstr ""
msgid "ClusterIntegration|Project cluster"
msgstr "Кластер проекту"
@@ -2352,7 +2533,7 @@ msgstr "При бажанні ви можете замінити це на ва
msgid "ClusterIntegration|Request to begin installing failed"
msgstr "Запит про початок встановлення не виконано"
-msgid "ClusterIntegration|Retry upgrade"
+msgid "ClusterIntegration|Retry update"
msgstr ""
msgid "ClusterIntegration|Save changes"
@@ -2397,9 +2578,6 @@ msgstr "Показати"
msgid "ClusterIntegration|Something went wrong on our end."
msgstr "Щось пішло не так з нашого боку."
-msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
-msgstr ""
-
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
msgstr "Помилка при створенні вашого Kubernetes-кластера на Google Kubernetes Engine"
@@ -2407,10 +2585,10 @@ msgid "ClusterIntegration|Something went wrong while installing %{title}"
msgstr "Під час встановлення %{title} сталася помилка"
msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
-msgstr ""
+msgstr "Зазначення домену дозволить вам використовувати фази Auto Review Apps та Auto Deploy для %{auto_devops_start}Auto DevOps%{auto_devops_end}. Домен повинен мати шаблон DNS, що задовільняє цей домен."
-msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
-msgstr "Відбувається призначення IP-адреси. Будь ласка, перевірте квоти вашого Kubernetes кластера на Google Kubernetes Engine, якщо це займає багато часу."
+msgid "ClusterIntegration|The endpoint is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
+msgstr ""
msgid "ClusterIntegration|This account must have permissions to create a Kubernetes cluster in the %{link_to_container_project} specified below"
msgstr "Цей обліковий запис повинен мати наступні права для створення Kubernetes-кластера в %{link_to_container_project}"
@@ -2418,12 +2596,21 @@ msgstr "Цей обліковий запис повинен мати насту
msgid "ClusterIntegration|This option will allow you to install applications on RBAC clusters."
msgstr "Цей параметр дозволить вам встановлювати застосунки на кластери RBAC."
+msgid "ClusterIntegration|To access your application after deployment, point a wildcard DNS to the Knative Endpoint."
+msgstr ""
+
msgid "ClusterIntegration|Toggle Kubernetes cluster"
msgstr "Увімкнути/вимкнути Kubernetes-кластер"
msgid "ClusterIntegration|Token"
msgstr "Токен"
+msgid "ClusterIntegration|Update failed. Please check the logs and try again."
+msgstr ""
+
+msgid "ClusterIntegration|Updating"
+msgstr ""
+
msgid "ClusterIntegration|Upgrade"
msgstr ""
@@ -2460,9 +2647,6 @@ msgstr "Зона"
msgid "ClusterIntegration|access to Google Kubernetes Engine"
msgstr "доступ до Google Kubernetes Engine"
-msgid "ClusterIntegration|check the pricing here"
-msgstr "переглянути ціни тут"
-
msgid "ClusterIntegration|documentation"
msgstr "документації"
@@ -2472,6 +2656,9 @@ msgstr "сторінка допомоги"
msgid "ClusterIntegration|meets the requirements"
msgstr "задовольняє вимогам"
+msgid "ClusterIntegration|pricing"
+msgstr ""
+
msgid "ClusterIntegration|properly configured"
msgstr "правильно налаштований"
@@ -2479,31 +2666,43 @@ msgid "ClusterIntegration|sign up"
msgstr "реєстрації"
msgid "Code"
+msgstr "Код"
+
+msgid "Code Owners"
+msgstr "Власники коду"
+
+msgid "Code owner approval is required"
msgstr ""
msgid "Code owners"
msgstr "Власники коду"
+msgid "CodeOwner|Pattern"
+msgstr ""
+
msgid "Cohorts"
msgstr "Когорти"
msgid "Collapse"
msgstr "Згорнути"
+msgid "Collapse approvers"
+msgstr "Згорнути список затверджуючих осіб"
+
msgid "Collapse sidebar"
msgstr "Згорнути панель"
msgid "Command line instructions"
-msgstr ""
+msgstr "Інструкції для командного рядка"
msgid "Comment"
msgstr "Коментар"
msgid "Comment & close %{noteable_name}"
-msgstr ""
+msgstr "Коментувати та закрити %{noteable_name}"
msgid "Comment & reopen %{noteable_name}"
-msgstr ""
+msgstr "Коментувати та повторно відкрити %{noteable_name}"
msgid "Comment & resolve discussion"
msgstr "Залишити коментар і завершити обговорення"
@@ -2525,19 +2724,19 @@ msgstr[2] "Комітів"
msgstr[3] "Комітів"
msgid "Commit %{commit_id}"
-msgstr ""
+msgstr "Коміт %{commit_id}"
msgid "Commit Message"
-msgstr "Коміт-повідомелння"
+msgstr "Повідомлення коміту"
msgid "Commit deleted"
-msgstr ""
+msgstr "Коміт видалено"
msgid "Commit duration in minutes for last 30 commits"
msgstr "Тривалість останніх 30 комітів у хвилинах"
msgid "Commit message"
-msgstr "Коміт-повідомлення"
+msgstr "Повідомлення коміту"
msgid "Commit statistics for %{ref} %{start_time} - %{end_time}"
msgstr "Статистика комітів для %{ref} %{start_time} - %{end_time}"
@@ -2597,7 +2796,7 @@ msgid "Compare Revisions"
msgstr "Порівняння редакцій"
msgid "Compare changes"
-msgstr ""
+msgstr "Порівняти зміни"
msgid "Compare changes with the last commit"
msgstr "Порівняти зміни з останнім комітом"
@@ -2627,7 +2826,7 @@ msgid "Confidentiality"
msgstr "Конфіденційність"
msgid "Configure GitLab runners to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
-msgstr ""
+msgstr "Налаштувати runner'ів GitLab для початку використання Веб-терміналу. %{helpStart}Докладніше.%{helpEnd}"
msgid "Configure Gitaly timeouts."
msgstr "Налаштувати таймаути Gitaly."
@@ -2636,7 +2835,7 @@ msgid "Configure Tracing"
msgstr "Налаштування Відстеження"
msgid "Configure a <code>.gitlab-webide.yml</code> file in the <code>.gitlab</code> directory to start using the Web Terminal. %{helpStart}Learn more.%{helpEnd}"
-msgstr ""
+msgstr "Налаштуйте файл <code>.gitlab-webide.yml</code> у директорії <code>.gitlab</code>, щоб почати використовувати Веб-термінал. %{helpStart}Докладніше.%{helpEnd}"
msgid "Configure automatic git checks and housekeeping on repositories."
msgstr "Налаштувати автоматичні перевірки git і очищення в репозиторіях."
@@ -2672,7 +2871,7 @@ msgid "Connecting..."
msgstr "З'єднання..."
msgid "Contact sales to upgrade"
-msgstr ""
+msgstr "Зверніться до відділу продажів для оновлення"
msgid "Container Registry"
msgstr "Реєстр Контейнерів"
@@ -2723,7 +2922,7 @@ msgid "ContainerRegistry|You can also use a %{deploy_token} for read-only access
msgstr "Ви також можете використовувати %{deploy_token} для доступу тільки для читання до образів у реєстрі."
msgid "Contents of .gitlab-ci.yml"
-msgstr ""
+msgstr "Вміст .gitlab-ci.yml"
msgid "Continue"
msgstr "Продовжити"
@@ -2741,7 +2940,7 @@ msgid "Contribution"
msgstr "Внесок"
msgid "Contribution Charts"
-msgstr ""
+msgstr "Статистика внесків"
msgid "Contributions for <strong>%{calendar_date}</strong>"
msgstr "Внески за <strong>%{calendar_date}</strong>"
@@ -2767,23 +2966,14 @@ msgstr "Будь ласка, зачекайте, ця сторінка авто
msgid "Control the display of third party offers."
msgstr "Керувати відображенням сторонніх пропозицій."
-msgid "Control the maximum concurrency of LFS/attachment backfill for this secondary node"
-msgstr "Задати максимальну кількість потоків для фонового завантаження LFS/вкладень для цього вторинного вузла"
-
msgid "Control the maximum concurrency of repository backfill for this secondary node"
msgstr "Задати максимальну кількість потоків для фонового завантаження репозиторіїв для цього вторинного вузла"
-msgid "Control the maximum concurrency of verification operations for this Geo node"
-msgstr "Встановіть максимальну кількість паралельних операцій перевірки для цього Geo-вузла"
-
-msgid "Control the minimum interval in days that a repository should be reverified for this primary node"
-msgstr ""
-
msgid "ConvDev Index"
msgstr "Індекс ConvDev"
msgid "Copy %{http_label} clone URL"
-msgstr ""
+msgstr "Скопіювати URL для клонування через %{http_label}"
msgid "Copy %{protocol} clone URL"
msgstr "Скопіювати URL для клонування через %{protocol}"
@@ -2791,11 +2981,14 @@ msgstr "Скопіювати URL для клонування через %{protoc
msgid "Copy ID to clipboard"
msgstr "Скопіювати ID в буфер обміну"
+msgid "Copy KRB5 clone URL"
+msgstr ""
+
msgid "Copy SSH clone URL"
msgstr "Скопіювати URL для клонування через SSH"
msgid "Copy SSH public key"
-msgstr ""
+msgstr "Скопіювати публічний ключ SSH"
msgid "Copy SSH public key to clipboard"
msgstr "Скопіюйте відкритий SSH-ключ в буфер обміну"
@@ -2843,7 +3036,7 @@ msgid "Create New Directory"
msgstr "Створити новий каталог"
msgid "Create New Domain"
-msgstr ""
+msgstr "Створити новий домен"
msgid "Create a new branch"
msgstr "Створити нову гілку"
@@ -2855,7 +3048,7 @@ msgid "Create a new issue"
msgstr "Створити нову задачу"
msgid "Create a new repository"
-msgstr ""
+msgstr "Створити новий репозиторій"
msgid "Create a personal access token on your account to pull or push via %{protocol}."
msgstr "Створіть токен доступу для вашого аккаунта, щоб відправляти та отримувати через %{protocol}."
@@ -2884,9 +3077,6 @@ msgstr "Створити групу"
msgid "Create group label"
msgstr "Створити мітку групи"
-msgid "Create issue"
-msgstr "Створити задачу"
-
msgid "Create lists from labels. Issues with that label appear in that list."
msgstr "Створити список на основі міток. В ньому будуть задачі з такими мітками."
@@ -2897,7 +3087,7 @@ msgid "Create merge request and branch"
msgstr "Створити запит на злиття та гілку"
msgid "Create milestone"
-msgstr ""
+msgstr "Створити етап"
msgid "Create new branch"
msgstr "Створити нову гілку"
@@ -2956,6 +3146,9 @@ msgstr "Синтаксис Cron"
msgid "Current Branch"
msgstr "Поточна гілка"
+msgid "Current Project"
+msgstr "Поточний проект"
+
msgid "Current node"
msgstr "Поточний вузол"
@@ -2981,7 +3174,7 @@ msgid "Custom project templates"
msgstr "Власні шаблони проектів"
msgid "Custom project templates have not been set up for groups that you are a member of. They are enabled from a group’s settings page. Contact your group’s Owner or Maintainer to setup custom project templates."
-msgstr ""
+msgstr "Власні шаблони проектів не налаштовані для груп, до яких ви входите. Вони активуються на сторінці налаштувань групи. Зверніться до власника або керівника вашої групи для налаштування власних шаблонів проектів."
msgid "Customize colors"
msgstr "Налаштування кольорів"
@@ -2993,9 +3186,9 @@ msgid "Customize how Google Code email addresses and usernames are imported into
msgstr "Налаштуйте, як адреси електронної пошти та імена користувачів Google Code імпортуються в GitLab. На наступному кроці ви зможете вибрати проекти, які потрібно імпортувати."
msgid "Customize language and region related settings."
-msgstr ""
+msgstr "Налаштування мови і параметрів, пов'язаних із регіоном."
-msgid "Customize your merge request approval settings."
+msgid "Customize your issue restrictions."
msgstr ""
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
@@ -3029,7 +3222,7 @@ msgid "CycleAnalyticsStage|Test"
msgstr "Тестування"
msgid "DNS"
-msgstr ""
+msgstr "DNS"
msgid "Dashboard"
msgstr "Панель керування"
@@ -3041,7 +3234,7 @@ msgid "DashboardProjects|Personal"
msgstr "Особисті"
msgid "Data is still calculating..."
-msgstr ""
+msgstr "Дані все ще обчислюються..."
msgid "Date picker"
msgstr "Вибір дати"
@@ -3056,7 +3249,7 @@ msgid "December"
msgstr "грудень"
msgid "Decline"
-msgstr ""
+msgstr "Відхилити"
msgid "Decline and sign out"
msgstr "Відхити та вийти"
@@ -3068,10 +3261,10 @@ msgid "Default classification label"
msgstr "Мітка класифікації за замовчуванням"
msgid "Default first day of the week"
-msgstr ""
+msgstr "Перший день тижня за замовчуванням"
msgid "Default first day of the week in calendars and date pickers."
-msgstr ""
+msgstr "Перший день тижня за замовчуванням в календарях та при виборі дати."
msgid "Default: Directly import the Google Code email address or username"
msgstr "За замовчуванням: безпосередньо імпортувати адресу електронної пошти або ім'я користувача Google Code"
@@ -3083,7 +3276,7 @@ msgid "Define a custom pattern with cron syntax"
msgstr "Визначте власний шаблон за допомогою синтаксису cron"
msgid "Define environments in the deploy stage(s) in <code>.gitlab-ci.yml</code> to track deployments here."
-msgstr ""
+msgstr "Визначте середовища на стадії розгортання у <code>.gitlab-ci.yml</code> щоб відстежувати розгортання тут."
msgid "DelayedJobs|Are you sure you want to run %{jobName} immediately? Otherwise this job will run automatically after it's timer finishes."
msgstr "Ви впевнені, що ви хочете запустити %{jobName} відразу? В іншому випадку це завдання буде виконано автоматично по завершенню таймера."
@@ -3116,10 +3309,10 @@ msgid "Delete list"
msgstr "Видалити список"
msgid "Delete source branch"
-msgstr ""
+msgstr "Видалити гілку-джерело"
msgid "Delete this attachment"
-msgstr ""
+msgstr "Видалити це вкладення"
msgid "Deleted"
msgstr "Видалено"
@@ -3258,7 +3451,7 @@ msgid "DeployTokens|Your new project deploy token has been created."
msgstr "Створено ваш новий токен розгортання для проекту."
msgid "Deployed"
-msgstr ""
+msgstr "Розгорнуто"
msgid "Deployed to"
msgstr "Розгорнуто на"
@@ -3281,6 +3474,9 @@ msgstr "Шаблони опису дозволяють визначити кон
msgid "Description:"
msgstr "Опис:"
+msgid "Designs"
+msgstr ""
+
msgid "Destroy"
msgstr "Знищити"
@@ -3288,7 +3484,7 @@ msgid "Details"
msgstr "Деталі"
msgid "Details (default)"
-msgstr ""
+msgstr "Деталі (за замовчуванням)"
msgid "Detect host keys"
msgstr "Виявлення ключів хоста"
@@ -3321,10 +3517,10 @@ msgid "Disable group Runners"
msgstr "Вимкнути групові Runner'и"
msgid "Disable shared Runners"
-msgstr ""
+msgstr "Вимкнути загальні Runner'и"
msgid "Disabled"
-msgstr ""
+msgstr "Вимкнено"
msgid "Discard"
msgstr "Відхилити"
@@ -3348,22 +3544,25 @@ msgid "Discard review"
msgstr "Відхилити перевірку"
msgid "Discover GitLab Geo"
-msgstr ""
+msgstr "Відкрийте GitLab Geo"
msgid "Discover projects, groups and snippets. Share your projects with others"
msgstr "Відкрийте для себе групи, проекти та фрагменти коду. Поділіться своїми проектами з іншими"
msgid "Discuss a specific suggestion or question"
-msgstr ""
+msgstr "Обговорити конкретну пропозицію чи питання"
msgid "Discuss a specific suggestion or question that needs to be resolved"
-msgstr ""
+msgstr "Обговорити конкретну пропозицію або питання, що необхідно вирішити"
+
+msgid "Discussion"
+msgstr "Обговорення"
msgid "Dismiss"
msgstr "Відхилити"
msgid "Dismiss ConvDev introduction"
-msgstr ""
+msgstr "Відхилити вступ до ConvDev"
msgid "Dismiss Cycle Analytics introduction box"
msgstr "Відхилити блок вступу до Аналитики Циклу"
@@ -3393,7 +3592,7 @@ msgid "Download"
msgstr "Завантажити"
msgid "Download artifacts"
-msgstr ""
+msgstr "Завантажити артефакти"
msgid "Download asset"
msgstr ""
@@ -3441,13 +3640,16 @@ msgid "Edit"
msgstr "Редагувати"
msgid "Edit %{name}"
-msgstr ""
+msgstr "Редагувати %{name}"
+
+msgid "Edit Deploy Key"
+msgstr "Редагувати ключ для розгортування"
msgid "Edit Label"
msgstr "Редагувати мітку"
msgid "Edit Milestone"
-msgstr ""
+msgstr "Редагувати етап"
msgid "Edit Pipeline Schedule %{id}"
msgstr "Редагувати Розклад Конвеєра %{id}"
@@ -3459,10 +3661,13 @@ msgid "Edit application"
msgstr "Редагувати застосунок"
msgid "Edit comment"
-msgstr ""
+msgstr "Редагувати коментар"
msgid "Edit environment"
-msgstr ""
+msgstr "Редагувати середовище"
+
+msgid "Edit file"
+msgstr "Редагувати файл"
msgid "Edit files in the editor and commit changes here"
msgstr "Редагуйте файли в редакторі і закомітьте зміни тут"
@@ -3474,6 +3679,9 @@ msgid "Edit identity for %{user_name}"
msgstr "Редагувати ідентифікацію для %{user_name}"
msgid "Edit issues"
+msgstr "Редагувати задачі"
+
+msgid "Edit public deploy key"
msgstr ""
msgid "Elasticsearch"
@@ -3494,9 +3702,12 @@ msgstr "Адреси електронної пошти"
msgid "Embed"
msgstr "Вбудувати"
-msgid "Empty file"
+msgid "Emojis|Something went wrong while loading emojis."
msgstr ""
+msgid "Empty file"
+msgstr "Порожній файл"
+
msgid "Enable"
msgstr "Увімкнути"
@@ -3522,7 +3733,7 @@ msgid "Enable classification control using an external service"
msgstr "Увімкнути контроль за класифікацією за допомогою зовнішньої служби"
msgid "Enable error tracking"
-msgstr ""
+msgstr "Увімкнути відстеження помилок"
msgid "Enable for this project"
msgstr "Увімкнути для цього проекту"
@@ -3530,6 +3741,9 @@ msgstr "Увімкнути для цього проекту"
msgid "Enable group Runners"
msgstr "Увімкнути групові Runner'и"
+msgid "Enable header and footer in emails"
+msgstr ""
+
msgid "Enable or disable the Pseudonymizer data collection."
msgstr "Увімкнути чи вимкнути збір даних для Pseudonymizer."
@@ -3540,16 +3754,16 @@ msgid "Enable reCAPTCHA or Akismet and set IP limits."
msgstr "Увімкнути reCAPTCHA або Akismet та встановити обмеження по IP."
msgid "Enable self approval of merge requests"
-msgstr ""
+msgstr "Дозволити самостійне затвердження для запитів на злиття"
msgid "Enable shared Runners"
-msgstr ""
+msgstr "Увімкнути загальні Runner'и"
msgid "Enable the Performance Bar for a given group."
msgstr "Увімкнути панель продуктивності для даної групи."
msgid "Enable two-factor authentication"
-msgstr ""
+msgstr "Увімкнути двофакторну автентифікацію"
msgid "Enable usage ping"
msgstr "Увімкнути використання ping"
@@ -3566,6 +3780,9 @@ msgstr "Завершується о (за Грінвічем)"
msgid "Enforce SSO-only authentication for this group"
msgstr ""
+msgid "Enforce users to have dedicated group managed accounts for this group"
+msgstr ""
+
msgid "Enforced SSO"
msgstr ""
@@ -3584,26 +3801,23 @@ msgstr "Введіть опис запиту на злиття"
msgid "Enter the merge request title"
msgstr "Введіть назву запиту на злиття"
-msgid "Enter your Sentry API URL"
-msgstr ""
-
msgid "Environment variables"
-msgstr ""
+msgstr "Змінні середовища"
msgid "Environment variables are applied to environments via the runner. They can be protected by only exposing them to protected branches or tags. You can use environment variables for passwords, secret keys, or whatever you want."
msgstr ""
msgid "Environment variables are configured by your administrator to be %{link_start}protected%{link_end} by default"
-msgstr ""
+msgstr "Змінні середовища налаштовані адміністратором бути %{link_start}захищеними%{link_end} за замовчуванням"
msgid "Environment:"
-msgstr ""
+msgstr "Середовище:"
msgid "Environments"
msgstr "Середовища"
msgid "Environments allow you to track deployments of your application %{link_to_read_more}."
-msgstr ""
+msgstr "Середовища дозволяють відстежувати розгортання вашого застосунку %{link_to_read_more}."
msgid "Environments|An error occurred while fetching the environments."
msgstr "Виникла помилка при завантаженні середовищ."
@@ -3611,6 +3825,12 @@ msgstr "Виникла помилка при завантаженні серед
msgid "Environments|An error occurred while making the request."
msgstr "Під час виконання запиту сталася помилка."
+msgid "Environments|An error occurred while re-deploying the environment, please try again"
+msgstr ""
+
+msgid "Environments|An error occurred while rolling back the environment, please try again"
+msgstr ""
+
msgid "Environments|An error occurred while stopping the environment, please try again"
msgstr "Виникла помилка під час зупинки середовища, будь ласка, спробуйте ще раз"
@@ -3662,15 +3882,33 @@ msgstr "Відкрити працююче середовище"
msgid "Environments|Pod logs from"
msgstr "Журнал Pod’а"
+msgid "Environments|Re-deploy"
+msgstr "Повторно розгорнути"
+
+msgid "Environments|Re-deploy environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Re-deploy environment %{name}?"
+msgstr ""
+
msgid "Environments|Re-deploy to environment"
msgstr "Повторно розгорнути в середовищі"
msgid "Environments|Read more about environments"
msgstr "Дізнайтеся більше про середовища"
+msgid "Environments|Rollback"
+msgstr ""
+
msgid "Environments|Rollback environment"
msgstr "Відкотити середовище"
+msgid "Environments|Rollback environment %{environment_name}?"
+msgstr ""
+
+msgid "Environments|Rollback environment %{name}?"
+msgstr ""
+
msgid "Environments|Show all"
msgstr "Показати всі"
@@ -3681,6 +3919,18 @@ msgid "Environments|Stop environment"
msgstr "Зупинити середовище"
msgid "Environments|Stopping"
+msgstr "Зупинка"
+
+msgid "Environments|This action will relaunch the job for commit %{commit_id}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will relaunch the job for commit %{linkStart}%{commitId}%{linkEnd}, putting the environment in a previous version. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by %{name} for commit %{linkStart}%{commitId}%{linkEnd} putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
+msgstr ""
+
+msgid "Environments|This action will run the job defined by staging for commit %{commit_id}, putting the environment in a previous version. You can revert it by re-deploying the latest version of your application. Are you sure you want to continue?"
msgstr ""
msgid "Environments|Updated"
@@ -3705,7 +3955,7 @@ msgid "Epics let you manage your portfolio of projects more efficiently and with
msgstr "Епіки дозволяють керувати вашим портфелем проектів ефективніше та з меншими зусиллями"
msgid "Epics|An error occurred while saving the %{epicDateType} date"
-msgstr ""
+msgstr "Сталася помилка при збереженні дати %{epicDateType}"
msgid "Epics|How can I solve this?"
msgstr "Як я можу це вирішити?"
@@ -3732,13 +3982,16 @@ msgid "Error Reporting and Logging"
msgstr "Звіти про помилки та логування"
msgid "Error Tracking"
-msgstr ""
+msgstr "Відстеження помилок"
+
+msgid "Error creating a new path"
+msgstr "Помилка при створенні нового шляху"
msgid "Error creating epic"
msgstr "Помилка при створенні епіку"
msgid "Error deleting %{issuableType}"
-msgstr ""
+msgstr "Помилка при видаленні %{issuableType}"
msgid "Error fetching contributors data."
msgstr "Помилка отримання даних учасників."
@@ -3783,29 +4036,56 @@ msgid "Error occurred when toggling the notification subscription"
msgstr "Сталася помилка під час підключення підписки на сповіщення"
msgid "Error rendering markdown preview"
-msgstr ""
+msgstr "Помилка при попередньому перегляді markdown"
msgid "Error saving label update."
msgstr "Помилка при збереженні мітки."
msgid "Error updating %{issuableType}"
-msgstr ""
+msgstr "Помилка при оновленні %{issuableType}"
msgid "Error updating status for all todos."
-msgstr "Помилка оновлення статусу для всіх задач."
+msgstr "Помилка оновлення статусу для всіх нагадувань."
msgid "Error updating todo status."
-msgstr "Помилка при оновленні статусу задачі."
+msgstr "Помилка при оновленні статусу нагадування."
msgid "Error while loading the merge request. Please try again."
msgstr "Помилка при завантаженні запита на злиття. Будь ласка, спробуйте знову."
msgid "Error:"
+msgstr "Помилка:"
+
+msgid "ErrorTracking|Active"
msgstr ""
-msgid "Errors"
+msgid "ErrorTracking|After adding your Auth Token, use the 'Connect' button to load projects"
msgstr ""
+msgid "ErrorTracking|Auth Token"
+msgstr ""
+
+msgid "ErrorTracking|Click 'Connect' to re-establish the connection to Sentry and activate the dropdown."
+msgstr ""
+
+msgid "ErrorTracking|Connection has failed. Re-check Auth Token and try again."
+msgstr ""
+
+msgid "ErrorTracking|Find your hostname in your Sentry account settings page"
+msgstr ""
+
+msgid "ErrorTracking|No projects available"
+msgstr ""
+
+msgid "ErrorTracking|Select project"
+msgstr ""
+
+msgid "ErrorTracking|To enable project selection, enter a valid Auth Token"
+msgstr ""
+
+msgid "Errors"
+msgstr "Помилки"
+
msgid "Estimated"
msgstr "За оцінками"
@@ -3828,10 +4108,10 @@ msgid "EventFilterBy|Filter by team"
msgstr "Фільтрувати по команді"
msgid "Events"
-msgstr ""
+msgstr "Події"
msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
-msgstr ""
+msgstr "Усі спроби %{action} закінчилися невдачею: %{job_error_message}. Будь ласка, спробуйте знову."
msgid "Every day (at 4:00am)"
msgstr "Кожен день (в 4:00 ранку)"
@@ -3843,37 +4123,37 @@ msgid "Every week (Sundays at 4:00am)"
msgstr "Щотижня (в неділю о 4:00 ранку)"
msgid "Everyone"
-msgstr ""
+msgstr "Будь-хто"
msgid "Everyone can contribute"
msgstr "Кожен може зробити свій внесок"
msgid "Everything you need to create a GitLab Pages site using GitBook."
-msgstr ""
+msgstr "Все, що потрібно для створення сайту на GitLab Pages за допомогою GitBook."
msgid "Everything you need to create a GitLab Pages site using Hexo."
-msgstr ""
+msgstr "Все, що потрібно для створення сайту на GitLab Pages за допомогою Hexo."
msgid "Everything you need to create a GitLab Pages site using Hugo."
-msgstr ""
+msgstr "Все, що потрібно для створення сайту на GitLab Pages за допомогою Hugo."
msgid "Everything you need to create a GitLab Pages site using Jekyll."
-msgstr ""
+msgstr "Все, що потрібно для створення сайту на GitLab Pages за допомогою Jekyll."
msgid "Everything you need to create a GitLab Pages site using plain HTML."
-msgstr ""
+msgstr "Все, що потрібно для створення сайту на GitLab Pages за допомогою простого HTML."
msgid "Except policy:"
msgstr ""
msgid "Existing Git repository"
-msgstr ""
+msgstr "Існуючий репозиторій Git"
msgid "Existing folder"
-msgstr ""
+msgstr "Існуюча папка"
msgid "Existing members and groups"
-msgstr ""
+msgstr "Існуючі учасники та групи"
msgid "Expand"
msgstr "Розгорнути"
@@ -3881,6 +4161,9 @@ msgstr "Розгорнути"
msgid "Expand all"
msgstr "Розгорнути все"
+msgid "Expand approvers"
+msgstr "Розгорнути список затверджуючих осіб"
+
msgid "Expand sidebar"
msgstr "Розгорніть бічну панель"
@@ -3915,22 +4198,22 @@ msgid "Explore public groups"
msgstr "Переглянути публічні групи"
msgid "Export as CSV"
-msgstr ""
+msgstr "Експортувати як CSV"
msgid "Export issues"
-msgstr ""
+msgstr "Експортувати задачі"
msgid "External Classification Policy Authorization"
msgstr "Зовнішня Класифікація Політики Авторизації"
msgid "External URL"
-msgstr ""
+msgstr "Зовнішній URL"
msgid "External Wiki"
-msgstr ""
+msgstr "Зовнішня вікі"
msgid "External authentication"
-msgstr "Зовнішня аутентифікація"
+msgstr "Зовнішня автентифікація"
msgid "External authorization denied access to this project"
msgstr "Зовнішня авторизація заборонила доступ до цього проекту"
@@ -3968,7 +4251,7 @@ msgstr "Не вдалося розгорнути до"
msgid "Failed to load emoji list."
msgstr "Не вдалося завантажити список смайликів."
-msgid "Failed to load errors from Sentry"
+msgid "Failed to load errors from Sentry. Error message: %{errorMessage}"
msgstr ""
msgid "Failed to remove issue from board, please try again."
@@ -4005,10 +4288,10 @@ msgid "Feature Flags"
msgstr "Перемикачі функцій"
msgid "FeatureFlags|* (All Environments)"
-msgstr ""
+msgstr "* (Усі середовища)"
msgid "FeatureFlags|* (All environments)"
-msgstr ""
+msgstr "* (всі середовища)"
msgid "FeatureFlags|API URL"
msgstr "URL-адреса API"
@@ -4026,49 +4309,46 @@ msgid "FeatureFlags|Create feature flag"
msgstr "Створити перемикач функції"
msgid "FeatureFlags|Delete %{name}?"
-msgstr ""
+msgstr "Видалити %{name}?"
msgid "FeatureFlags|Delete feature flag"
-msgstr ""
+msgstr "Вимкнути перемикач функції"
msgid "FeatureFlags|Description"
msgstr "Опис"
-msgid "FeatureFlags|Edit %{feature_flag_name}"
-msgstr "Редагувати %{feature_flag_name}"
-
msgid "FeatureFlags|Edit Feature Flag"
msgstr "Редагувати перемикач функції"
msgid "FeatureFlags|Environment Spec"
-msgstr ""
+msgstr "Специфікація середовища"
msgid "FeatureFlags|Environment Specs"
-msgstr ""
+msgstr "Специфікації середовищ"
msgid "FeatureFlags|Feature Flag"
msgstr "Перемикач функції"
msgid "FeatureFlags|Feature Flag behavior is built up by creating a set of rules to define the status of target environments. A default wildcare rule %{codeStart}*%{codeEnd} for %{boldStart}All Environments%{boldEnd} is set, and you are able to add as many rules as you need by choosing environment specs below. You can toggle the behavior for each of your rules to set them %{boldStart}Active%{boldEnd} or %{boldStart}Inactive%{boldEnd}."
-msgstr ""
+msgstr "Поведінка перемикачів функцій створюється шляхом створення набору правил для визначення стану цільових середовищ. Правило за замовчуванням %{codeStart}*%{codeEnd} для %{boldStart}Всіх середовищ%{boldEnd} вже налаштовано, і ви можете додати стільки правил, скільки вам потрібно, шляхом задавання специфікацій середовищ нижче. Ви можете перемикати стан для кожного з ваших правил, щоб зробити їх %{boldStart}Активними%{boldEnd} або %{boldStart}Неактивними%{boldEnd}."
msgid "FeatureFlags|Feature Flags"
-msgstr ""
+msgstr "Перемикачі функцій"
msgid "FeatureFlags|Feature Flags allow you to configure your code into different flavors by dynamically toggling certain functionality."
-msgstr ""
+msgstr "Перемикачі функцій дозволяють налаштовувати ваш код по-різному за допомогою динамічного увімкнення чи вимкнення певної функціональності."
msgid "FeatureFlags|Feature flag %{name} will be removed. Are you sure?"
-msgstr ""
+msgstr "Перемикач функції %{name} буде видалено. Ви впевнені?"
msgid "FeatureFlags|Get started with Feature Flags"
-msgstr ""
+msgstr "Розпочати роботу з перемикачами функцій"
msgid "FeatureFlags|Inactive"
msgstr "Неактивний"
msgid "FeatureFlags|Inactive flag for %{scope}"
-msgstr ""
+msgstr "Неактивний перемикач функції для %{scope}"
msgid "FeatureFlags|Install a %{docs_link_start}compatible client library%{docs_link_end} and specify the API URL, application name, and instance ID during the configuration setup."
msgstr "Встановіть %{docs_link_start}сумісну клієнтську бібліотеку%{docs_link_end} і вкажіть URL адресу API, назву застосунку та ідентифікатор інстанса під час налаштування."
@@ -4077,10 +4357,10 @@ msgid "FeatureFlags|Instance ID"
msgstr "Ідентифікатор Інстансу"
msgid "FeatureFlags|Loading Feature Flags"
-msgstr ""
+msgstr "Завантаження перемикачів функцій"
msgid "FeatureFlags|More Information"
-msgstr ""
+msgstr "Більше інформації"
msgid "FeatureFlags|More information"
msgstr "Більше інформації"
@@ -4094,26 +4374,23 @@ msgstr "Новий"
msgid "FeatureFlags|New Feature Flag"
msgstr "Новий перемикач функції"
-msgid "FeatureFlags|Save changes"
-msgstr "Зберегти зміни"
-
msgid "FeatureFlags|Status"
msgstr "Статус"
msgid "FeatureFlags|Target environments"
-msgstr ""
+msgstr "Цільові середовища"
msgid "FeatureFlags|There are no active Feature Flags"
-msgstr ""
+msgstr "Немає активних перемикачів функцій"
msgid "FeatureFlags|There are no inactive Feature Flags"
-msgstr ""
+msgstr "Немає неактивних перемикачів функцій"
msgid "FeatureFlags|There was an error fetching the feature flags."
-msgstr ""
+msgstr "Помилка при отриманні перемикачів функцій."
msgid "FeatureFlags|Try again in a few moments or contact your support team."
-msgstr ""
+msgstr "Повторіть спробу через деякий час або зверніться до вашої служби підтримки."
msgid "Feb"
msgstr "лют."
@@ -4126,31 +4403,31 @@ msgstr "Поля на цій сторінці зараз недоступні д
msgid "File"
msgid_plural "Files"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "Файл"
+msgstr[1] "Файли"
+msgstr[2] "Файлів"
+msgstr[3] "Файлів"
msgid "File added"
-msgstr ""
+msgstr "Файл додано"
msgid "File browser"
-msgstr ""
+msgstr "Файловий менеджер"
msgid "File deleted"
-msgstr ""
+msgstr "Файл видалено"
msgid "File mode changed from %{a_mode} to %{b_mode}"
msgstr ""
msgid "File moved"
-msgstr ""
+msgstr "Файл переміщено"
msgid "File templates"
msgstr "Шаблони файлів"
msgid "File upload error."
-msgstr ""
+msgstr "Помилка завантаження файлу."
msgid "Files"
msgstr "Файли"
@@ -4168,31 +4445,28 @@ msgid "Filter by %{issuable_type} that are currently opened."
msgstr "Фільтрувати відкриті за %{issuable_type}."
msgid "Filter by commit message"
-msgstr "Фільтрувати за коміт-повідомленням"
+msgstr "Фільтрувати за повідомленням для коміту"
msgid "Filter by milestone name"
-msgstr ""
+msgstr "Фільтрувати за назвою етапу"
msgid "Filter by two-factor authentication"
-msgstr ""
+msgstr "Фільтрувати за двофакторною автентифікацією"
msgid "Filter results by group"
-msgstr ""
+msgstr "Фільтрувати результати за групою"
msgid "Filter results by project"
-msgstr ""
+msgstr "Фільтрувати результати за проектом"
msgid "Filter..."
msgstr "Фільтр..."
-msgid "Find and manage Auth Tokens in your Sentry account settings page."
-msgstr ""
-
msgid "Find by path"
msgstr "Пошук по шляху"
msgid "Find existing members by name"
-msgstr ""
+msgstr "Знайти існуючих учасників за ім'ям"
msgid "Find file"
msgstr "Знайти файл"
@@ -4203,11 +4477,14 @@ msgstr "Знайдіть завантажений ZIP-файл і розпаку
msgid "Find the newly extracted <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> file."
msgstr "Знайдіть щойно розпакований <code>Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json</code> файл."
+msgid "Fingerprint"
+msgstr ""
+
msgid "Fingerprints"
msgstr "Відбитки пальців"
msgid "Finish editing this message first!"
-msgstr ""
+msgstr "Спочатку завершіть редагування цього повідомлення!"
msgid "Finish review"
msgstr "Завершити перевірку"
@@ -4216,7 +4493,7 @@ msgid "Finished"
msgstr "Завершено"
msgid "First day of the week"
-msgstr ""
+msgstr "Перший день тижня"
msgid "FirstPushedBy|First"
msgstr "Перший"
@@ -4264,7 +4541,7 @@ msgid "For internal projects, any logged in user can view pipelines and access j
msgstr "Для внутрішніх проектів будь-який зареєстрований користувач може переглядати конвеєри та отримати доступ до інформації про роботу (логи та артефакти)"
msgid "For more info, read the documentation."
-msgstr ""
+msgstr "Для отримання додаткової інформації читайте документацію."
msgid "For more information, go to the "
msgstr "Для отримання додаткової інформації, відвідайте "
@@ -4291,7 +4568,7 @@ msgid "Forking in progress"
msgstr "Відбувається створення форку"
msgid "Forks"
-msgstr ""
+msgstr "Форки"
msgid "Format"
msgstr "Формат"
@@ -4302,8 +4579,8 @@ msgstr "Знайдено помилки у вашому .gitlab-ci.yml:"
msgid "Free Trial of GitLab.com Gold"
msgstr "Безкоштовна пробна версія GitLab.com Gold"
-msgid "From %{provider_title}"
-msgstr "З %{provider_title}"
+msgid "From %{providerTitle}"
+msgstr ""
msgid "From Bitbucket"
msgstr "З Bitbucket"
@@ -4332,12 +4609,21 @@ msgstr "З етапів:"
msgid "From the Kubernetes cluster details view, install Runner from the applications list"
msgstr "Із сторінки деталей Kubernetes-кластера, встановіть runner зі списку застосунків"
+msgid "GPG Key ID:"
+msgstr ""
+
msgid "GPG Keys"
msgstr "GPG ключі"
+msgid "GPG signature (loading...)"
+msgstr ""
+
msgid "General"
msgstr "Загальні"
+msgid "General Settings"
+msgstr ""
+
msgid "General pipelines"
msgstr "Загальні конвеєри"
@@ -4345,7 +4631,7 @@ msgid "Generate a default set of labels"
msgstr "Створити стандартний набір міток"
msgid "Generate key"
-msgstr ""
+msgstr "Згенерувати ключ"
msgid "Geo"
msgstr "Geo"
@@ -4362,6 +4648,9 @@ msgstr "Вузол не працює або зламаний."
msgid "GeoNodeSyncStatus|Node is slow, overloaded, or it just recovered after an outage."
msgstr "Вузол працює повільно, перевантажений або тільки що відновився після збою."
+msgid "GeoNodes|Alternate URL"
+msgstr ""
+
msgid "GeoNodes|Checksummed"
msgstr "Із контрольною сумою"
@@ -4426,7 +4715,7 @@ msgid "GeoNodes|New node"
msgstr "Новий вузол"
msgid "GeoNodes|Node Authentication was successfully repaired."
-msgstr "Аутентифікацію вузла успішно полагоджено."
+msgstr "Автентифікацію вузла успішно полагоджено."
msgid "GeoNodes|Node was successfully removed."
msgstr "Вузол успішно видалено."
@@ -4438,10 +4727,10 @@ msgid "GeoNodes|Out of sync"
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 ""
+msgstr "Видалення основного вузла зупиняє процес синхронізації для всіх вузлів. Синхронізацію неможливо буде відновити без втрати деяких даних на всіх вторинних вузлах. У цьому випадку рекомендовано налаштувати всі вузли з нуля. Ви впевнені?"
msgid "GeoNodes|Removing a secondary node stops the sync process. It is not currently possible to add back the same node without losing some data. We only recommend setting up a new secondary node in this case. Are you sure?"
-msgstr ""
+msgstr "Видалення вторинного вузла зупиняє процес синхронізації. Наразі неможливо додати назад той самий вузол без втрати деяких даних. У цьому випадку рекомендовано створити новий вторинний вузол. Ви впевнені?"
msgid "GeoNodes|Replication slot WAL"
msgstr "Слот реплікації WAL"
@@ -4516,7 +4805,7 @@ msgid "GeoNodes|Wikis verified with their counterparts on the Primary node"
msgstr "Вікі перевірено із їхніми копіями на первинному вузлі"
msgid "GeoNodes|With %{geo} you can install a special read-only and replicated instance anywhere. Before you add nodes, follow the %{instructions} in the exact order they appear."
-msgstr ""
+msgstr "З %{geo} ви можете встановити будь-де спеціальній реплікований інстанс лише для читання. Перед тим, як додавати вузли, дотримайтеся %{instructions} в точному порядку."
msgid "GeoNodes|You have configured Geo nodes using an insecure HTTP connection. We recommend the use of HTTPS."
msgstr "Ви налаштували Geo-вузли через незахищене HTTP-з’єднання. Ми рекомендуємо використовувати HTTPS."
@@ -4542,9 +4831,24 @@ msgstr "Всі проекти плануються для повторної п
msgid "Geo|All projects are being scheduled for re-sync"
msgstr "Всі проекти плануються для повторної синхронізації"
+msgid "Geo|Alternate URL"
+msgstr ""
+
msgid "Geo|Batch operations"
msgstr "Групові операції"
+msgid "Geo|Choose which groups you wish to synchronize to this secondary node."
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of LFS/attachment backfill for this secondary node"
+msgstr ""
+
+msgid "Geo|Control the maximum concurrency of verification operations for this Geo node"
+msgstr ""
+
+msgid "Geo|Control the minimum interval in days that a repository should be reverified for this primary node"
+msgstr ""
+
msgid "Geo|Could not remove tracking entry for an existing project."
msgstr "Не вдалося видалити запис відстеження для існуючого проекту."
@@ -4564,7 +4868,7 @@ msgid "Geo|In sync"
msgstr "Синхронізовано"
msgid "Geo|Last repository check run"
-msgstr ""
+msgstr "Остання запуск перевірки репозиторію"
msgid "Geo|Last successful sync"
msgstr "Остання успішна синхронізація"
@@ -4603,7 +4907,7 @@ msgid "Geo|Projects in certain storage shards"
msgstr "Проекти в певних сегментах сховищ"
msgid "Geo|Re-verification interval"
-msgstr ""
+msgstr "Інтервал повторної перевірки"
msgid "Geo|Recheck"
msgstr "Повторна перевірка"
@@ -4632,6 +4936,9 @@ msgstr "Кількість спроб"
msgid "Geo|Select groups to replicate."
msgstr "Виберіть групи для реплікації."
+msgid "Geo|Selective synchronization"
+msgstr ""
+
msgid "Geo|Shards to synchronize"
msgstr "Сегменти для синхронізації"
@@ -4644,12 +4951,21 @@ msgstr "Синхронізовано"
msgid "Geo|Synchronization failed - %{error}"
msgstr "Синхронізація невдала: %{error}"
+msgid "Geo|This is a primary node"
+msgstr ""
+
+msgid "Geo|To support OAuth logins to this node at a different domain than URL"
+msgstr ""
+
msgid "Geo|Tracking entry for project (%{project_id}) was successfully removed."
msgstr "Запис відстеження для проекту (%{project_id}) успішно видалено."
msgid "Geo|Tracking entry will be removed. Are you sure?"
msgstr "Буде видалено запис про відстеження. Ви впевнені?"
+msgid "Geo|URL"
+msgstr ""
+
msgid "Geo|Unknown state"
msgstr "Невідомий стан"
@@ -4669,7 +4985,7 @@ msgid "Geo|You are on a secondary, <b>read-only</b> Geo node. You may be able to
msgstr "Ви знаходитесь на вторинному <b>лише для читання</b> Geo-вузлі. Ви зможете вносити лише обмежену кількість змін та виконувати обмежений набір операцій з цієї сторінки."
msgid "Geo|You need a different license to use Geo replication"
-msgstr "Вам потрібна інша ліцензія на використання географічної реплікації"
+msgstr "Вам потрібна інша ліцензія на використання Geo реплікації"
msgid "Geo|misconfigured"
msgstr "неправильно налаштований"
@@ -4684,16 +5000,16 @@ msgid "Get a free instance review"
msgstr "Отримайте безкоштовну оцінку інстанса"
msgid "Get started with error tracking"
-msgstr ""
+msgstr "Розпочати роботу з відстеженням помилок"
msgid "Getting started with releases"
-msgstr ""
+msgstr "Розпочати роботу з релізами"
msgid "Git"
msgstr "Git"
msgid "Git global setup"
-msgstr ""
+msgstr "Глобальні налаштування Git"
msgid "Git repository URL"
msgstr "URL Git-репозиторія"
@@ -4762,20 +5078,29 @@ msgid "Gitea Import"
msgstr "Імпорт з Gitea"
msgid "Given access %{time_ago}"
-msgstr ""
+msgstr "Надано доступ %{time_ago}"
msgid "Go Back"
msgstr "Повернутися"
+msgid "Go Micro is a framework for micro service development."
+msgstr ""
+
msgid "Go back"
msgstr "Повернутися"
+msgid "Go full screen"
+msgstr "На повний екран"
+
msgid "Go to"
msgstr "Перейти до"
msgid "Go to %{link_to_google_takeout}."
msgstr "Перейти до %{link_to_google_takeout}."
+msgid "Go to project"
+msgstr "Перейти до проекту"
+
msgid "Google Code import"
msgstr "Імпорт з Google Code"
@@ -4783,13 +5108,13 @@ msgid "Google Takeout"
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, якщо ви хочете скористатися цим сервісом."
+msgstr "Автентифікація Google не %{link_to_documentation}. Попросіть свого адміністратора GitLab, якщо ви хочете скористатися цим сервісом."
msgid "Got it!"
msgstr "Зрозуміло!"
msgid "Grant access"
-msgstr ""
+msgstr "Надати доступ"
msgid "Graph"
msgstr "Графік"
@@ -4806,6 +5131,9 @@ msgstr "Статус групи Git LFS:"
msgid "Group ID"
msgstr "Ідентифікатор групи"
+msgid "Group ID: %{group_id}"
+msgstr ""
+
msgid "Group Runners"
msgstr "Групові Runner'и"
@@ -4833,14 +5161,17 @@ msgstr "Інформація про групу:"
msgid "Group maintainers can register group runners in the %{link}"
msgstr "Керівники групи можуть зареєструвати групові runner'и через %{link}"
+msgid "Group managed accounts"
+msgstr ""
+
msgid "Group name"
msgstr "Назва групи"
msgid "Group overview content"
-msgstr ""
+msgstr "Вміст оглядової сторінки групи"
msgid "Group:"
-msgstr ""
+msgstr "Група:"
msgid "Group: %{group_name}"
msgstr "Група: %{group_name}"
@@ -4858,33 +5189,48 @@ msgid "GroupRoadmap|The roadmap shows the progress of your epics along a timelin
msgstr "План-графік епіків відображає стан ваших епіків у часі"
msgid "GroupRoadmap|To view the roadmap, add a start or due date to one of your epics in this group or its subgroups; from %{startDate} to %{endDate}."
-msgstr ""
+msgstr "Для перегляду плану-графіку, додайте дату початку чи закінчення до одного з ваших епіків в цій групі або її підгрупах; від %{startDate} до %{endDate}."
msgid "GroupRoadmap|To widen your search, change or remove filters; from %{startDate} to %{endDate}."
-msgstr ""
+msgstr "Щоб розширити пошук, змініть або видаліть фільтри; від %{startDate} до %{endDate}."
msgid "GroupRoadmap|Until %{dateWord}"
msgstr "До %{dateWord}"
+msgid "GroupSettings|Auto DevOps pipeline was updated for the group"
+msgstr ""
+
+msgid "GroupSettings|Auto DevOps will automatically build, test and deploy your application based on a predefined Continuous Integration and Delivery configuration. %{auto_devops_start}Learn more about Auto DevOps%{auto_devops_end}"
+msgstr ""
+
msgid "GroupSettings|Badges"
msgstr "Значки"
msgid "GroupSettings|Custom project templates"
-msgstr ""
+msgstr "Власні шаблони проектів"
msgid "GroupSettings|Customize your group badges."
msgstr "Налаштувати значки групи."
+msgid "GroupSettings|Default to Auto DevOps pipeline for all projects within this group"
+msgstr ""
+
msgid "GroupSettings|Learn more about badges."
msgstr "Дізнайтеся більше про значки."
msgid "GroupSettings|Learn more about group-level project templates."
-msgstr ""
+msgstr "Докладніше про шаблони проектів на рівні групи."
msgid "GroupSettings|Prevent sharing a project within %{group} with other groups"
msgstr "Заборонити спільний доступ до проекту в рамках %{group} з іншими групами"
msgid "GroupSettings|Select a sub-group as the custom project template source for this group."
+msgstr "Виберіть підгрупу як джерело власних шаблонів проектів для цієї групи."
+
+msgid "GroupSettings|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
+msgstr ""
+
+msgid "GroupSettings|There was a problem updating Auto DevOps pipeline: %{error_messages}."
msgstr ""
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
@@ -4912,7 +5258,7 @@ msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroup
msgstr "Групи також можуть бути вкладеними при використанні %{subgroup_docs_link_start}підгруп%{subgroup_docs_link_end}."
msgid "Groups with access to <strong>%{project_name}</strong>"
-msgstr ""
+msgstr "Групи з доступом до <strong>%{project_name}</strong>"
msgid "GroupsDropdown|Frequently visited"
msgstr "Часто відвідувані"
@@ -5014,7 +5360,7 @@ msgid "Here is the public SSH key that needs to be added to the remote server. F
msgstr "Це відкритий (публічний) SSH ключ, який потрібно додати на віддалений сервер. Для отримання додаткової інформації, зверніться до документації."
msgid "Hide file browser"
-msgstr ""
+msgstr "Сховати файловий менеджер"
msgid "Hide host keys manual input"
msgstr "Сховати ввід ключів хоста"
@@ -5030,7 +5376,7 @@ msgstr[2] "Сховати значень"
msgstr[3] "Сховати значень"
msgid "Hide values"
-msgstr ""
+msgstr "Сховати значення"
msgid "History"
msgstr "Історія"
@@ -5038,9 +5384,12 @@ msgstr "Історія"
msgid "Housekeeping successfully started"
msgstr "Очищення успішно розпочато"
-msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
+msgid "Housekeeping, export, path, transfer, remove, archive."
msgstr ""
+msgid "However, you are already a member of this %{member_source}. Sign in using a different account to accept the invitation."
+msgstr "Проте ви вже є учасником цього %{member_source}. Увійдіть, використовуючи інший обліковий запис, щоб прийняти запрошення."
+
msgid "I accept the %{terms_link}"
msgstr "Я приймаю %{terms_link}"
@@ -5120,7 +5469,7 @@ msgid "If you already have files you can push them using the %{link_to_cli} belo
msgstr "Якщо у вас уже є файли, ви можете відправити їх за допомогою %{link_to_cli} нижче."
msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
-msgstr "Якщо ваш HTTP-репозиторій не є публічним, додайте дані для аутентифікації до URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
+msgstr "Якщо ваш HTTP-репозиторій не є публічним, додайте дані для автентифікації до URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
msgid "ImageDiffViewer|2-up"
msgstr "2 поруч"
@@ -5132,13 +5481,13 @@ msgid "ImageDiffViewer|Swipe"
msgstr "Накладені (проведення)"
msgid "Impersonation has been disabled"
-msgstr ""
+msgstr "Уособлення було вимкнено"
msgid "Import"
msgstr "Імпорт"
msgid "Import CSV"
-msgstr ""
+msgstr "Імпортувати CSV"
msgid "Import Projects from Gitea"
msgstr "Імпортувати проекти з Gitea"
@@ -5159,13 +5508,13 @@ msgid "Import in progress"
msgstr "Імпорт триває"
msgid "Import issues"
-msgstr ""
+msgstr "Імпорт задач"
msgid "Import members"
-msgstr ""
+msgstr "Імпортувати учасників"
msgid "Import members from another project"
-msgstr ""
+msgstr "Імпортувати учасників з іншого проекту"
msgid "Import multiple repositories by uploading a manifest file."
msgstr "Імпортувати кілька репозиторіїв, надіславши файл маніфесту."
@@ -5174,7 +5523,7 @@ msgid "Import project"
msgstr "Імпорт проекту"
msgid "Import project members"
-msgstr ""
+msgstr "Імпортувати учасників проекту"
msgid "Import projects from Bitbucket"
msgstr "Імпортувати проекти з Bitbucket"
@@ -5203,9 +5552,24 @@ msgstr "Імпорт репозиторію"
msgid "Import timed out. Import took longer than %{import_jobs_expiration} seconds"
msgstr ""
+msgid "Import/Export illustration"
+msgstr ""
+
msgid "ImportButtons|Connect repositories from"
msgstr "Підключити репозиторії із"
+msgid "ImportProjects|Importing the project failed"
+msgstr ""
+
+msgid "ImportProjects|Requesting your %{provider} repositories failed"
+msgstr ""
+
+msgid "ImportProjects|Select the projects you want to import"
+msgstr ""
+
+msgid "ImportProjects|Updating the imported projects failed"
+msgstr ""
+
msgid "Improve Issue boards with GitLab Enterprise Edition."
msgstr "Покращити дошки обговорень за допомогою версії GitLab Enterprise Edition."
@@ -5228,19 +5592,19 @@ msgid "Include a Terms of Service agreement and Privacy Policy that all users mu
msgstr "Включити угоду про надання послуг та правила конфіденційності, які повинні прийняти всі користувачі."
msgid "Include merge request description"
-msgstr ""
+msgstr "Додайте опис запиту на злиття"
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr "Якщо необхідно додайте ім'я користувача в URL: <code>https: //username@gitlab.company.com/group/project.git</code>."
msgid "Includes an MVC structure to help you get started."
-msgstr ""
+msgstr "Включає структуру MVC, щоб допомогти вам розпочати роботу."
msgid "Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started."
-msgstr ""
+msgstr "Включає структуру MVC, Gemfile, Rakefile, а також багато іншого, щоб допомогти вам розпочати роботу."
msgid "Includes an MVC structure, mvnw and pom.xml to help you get started."
-msgstr ""
+msgstr "Включає структуру MVC, mvnw і pom.xml, щоб допомогти вам розпочати роботу."
msgid "Incompatible Project"
msgstr "Несумісний проект"
@@ -5257,7 +5621,16 @@ msgstr "Введіть ключі хоста вручну"
msgid "Input your repository URL"
msgstr "Введіть ваш URL репозиторію"
+msgid "Insert a quote"
+msgstr "Вставити цитату"
+
+msgid "Insert code"
+msgstr "Вставити код"
+
msgid "Insert suggestion"
+msgstr "Додати пропозицію"
+
+msgid "Insights"
msgstr ""
msgid "Install GitLab Runner"
@@ -5292,7 +5665,7 @@ msgid "Interested parties can even contribute by pushing commits if they want to
msgstr "Зацікавлені сторони за бажанням можуть навіть робити внески шляхом відправлення комітів."
msgid "Internal"
-msgstr ""
+msgstr "Внутрішній"
msgid "Internal - The group and any internal projects can be viewed by any logged in user."
msgstr "Внутрішня — будь-який автентифікований користувач має доступ до цієї групи та усіх її внутрішніх проектів."
@@ -5312,8 +5685,11 @@ msgstr "Представляємо аналітику циклу"
msgid "Introducing Your Conversational Development Index"
msgstr ""
+msgid "Invalid input, please avoid emojis"
+msgstr "Некорректний ввід, будь ласка, уникайте смайликів"
+
msgid "Invitation"
-msgstr ""
+msgstr "Запрошення"
msgid "Invite"
msgstr "Запрошення"
@@ -5322,7 +5698,7 @@ msgid "Invite group"
msgstr ""
msgid "Invite member"
-msgstr ""
+msgstr "Запросити учасника"
msgid "Invoke Count"
msgstr ""
@@ -5330,6 +5706,9 @@ msgstr ""
msgid "Invoke Time"
msgstr ""
+msgid "IssuableStatus|Closed (%{moved_link_start}moved%{moved_link_end})"
+msgstr ""
+
msgid "Issue"
msgstr "Задача"
@@ -5342,6 +5721,9 @@ msgstr "Режим фокусування для дошки задач"
msgid "Issue events"
msgstr "Задачі"
+msgid "Issue settings"
+msgstr ""
+
msgid "IssueBoards|Board"
msgstr "Дошка"
@@ -5349,19 +5731,19 @@ msgid "IssueBoards|Boards"
msgstr "Дошки"
msgid "IssueBoards|Create new board"
-msgstr ""
+msgstr "Створити нову дошку"
msgid "IssueBoards|Delete board"
-msgstr ""
+msgstr "Видалити дошку"
msgid "IssueBoards|No matching boards found"
-msgstr ""
+msgstr "Не знайдено відповідних дошок"
msgid "IssueBoards|Some of your boards are hidden, activate a license to see them again."
-msgstr ""
+msgstr "Деякі з ваших дошок є прихованими, активуйте ліцензію, щоб побачити їх знову."
msgid "IssueBoards|Switch board"
-msgstr ""
+msgstr "Перемкнути дошку"
msgid "Issues"
msgstr "Задачі"
@@ -5372,8 +5754,8 @@ msgstr "Задачі можуть бути помилками, нагадува
msgid "Issues closed"
msgstr "Задачі закриті"
-msgid "Issues, merge requests, pushes and comments."
-msgstr "Задачі, запити на злиття, відправлення коду і коментарі."
+msgid "Issues, merge requests, pushes, and comments."
+msgstr ""
msgid "IssuesAnalytics|After you begin creating issues for your projects, we can start tracking and displaying metrics for them"
msgstr "Після створення задач для ваших проектів, ми зможемо почати відстежувати і відображати метрики для них"
@@ -5400,7 +5782,7 @@ msgid "It must have a header row and at least two columns: the first column is t
msgstr ""
msgid "It's you"
-msgstr ""
+msgstr "Це ви"
msgid "Jaeger URL"
msgstr "URL-адреса Jaeger"
@@ -5421,10 +5803,10 @@ msgid "Job has been erased"
msgstr "Завдання було стерте"
msgid "Job is stuck. Check runners."
-msgstr ""
+msgstr "Завдання заблоковане. Перевірте runner'и."
msgid "Job was retried"
-msgstr ""
+msgstr "Завдання було перезапущене"
msgid "Jobs"
msgstr "Завдання"
@@ -5466,10 +5848,10 @@ msgid "Job|The artifacts were removed"
msgstr "Артефакти були видалені"
msgid "Job|The artifacts will be removed"
-msgstr ""
+msgstr "Артефакти будуть видалені"
msgid "Job|This job is stuck because the project doesn't have any runners online assigned to it."
-msgstr ""
+msgstr "Це завдання заблоковане, тому що цей проект не має жодних runner'ів призначених для нього."
msgid "Jul"
msgstr "лип."
@@ -5484,7 +5866,7 @@ msgid "June"
msgstr "червень"
msgid "Key (PEM)"
-msgstr ""
+msgstr "Ключ (PEM)"
msgid "Kubernetes"
msgstr "Kubernetes"
@@ -5493,7 +5875,7 @@ msgid "Kubernetes Cluster"
msgstr "Кластер Kubernetes"
msgid "Kubernetes Clusters"
-msgstr ""
+msgstr "Кластери Kubernetes"
msgid "Kubernetes cluster creation time exceeds timeout; %{timeout}"
msgstr "Перевищення ліміту часу при створенні Kubernetes-кластера; %{timeout}"
@@ -5561,6 +5943,9 @@ msgstr "Перенести мітку"
msgid "Labels|Promoting %{labelTitle} will make it available for all projects inside %{groupName}. Existing project labels with the same title will be merged. This action cannot be reversed."
msgstr "Перенесення %{labelTitle} на рівень групи зробить її доступною для всіх проектів в групі %{groupName}. Існуючі проектні мітки із такими ж іменами будуть об'єднані. Дана дія не може бути скасована."
+msgid "Language"
+msgstr "Мова"
+
msgid "Large File Storage"
msgstr "Сховище великих файлів (LFS)"
@@ -5575,7 +5960,7 @@ msgid "Last Pipeline"
msgstr "Останній Конвеєр"
msgid "Last activity"
-msgstr ""
+msgstr "Остання активність"
msgid "Last commit"
msgstr "Останній коміт"
@@ -5611,7 +5996,7 @@ msgid "Latest changes"
msgstr "Останні зміни"
msgid "Latest pipeline for this branch"
-msgstr ""
+msgstr "Останній конвеєр для цієї гілки"
msgid "Lead"
msgstr ""
@@ -5626,26 +6011,32 @@ msgid "Learn more about %{issue_boards_url}, to keep track of issues in multiple
msgstr "Дізнайтеся більше про %{issue_boards_url}, щоб стежити за задачами в кількох списках, використовуючи мітки, виконавців та етапи. Якщо вам чогось не вистачає в дошках обговорень задач, створіть задачу на %{gitlab_issues_url}."
msgid "Learn more about Auto DevOps"
-msgstr ""
+msgstr "Дізнайтеся більше про Auto DevOps"
msgid "Learn more about Kubernetes"
msgstr "Дізнайтеся більше про Kubernetes"
msgid "Learn more about Web Terminal"
+msgstr "Дізнатися більше про Веб-термінал"
+
+msgid "Learn more about approvals."
msgstr ""
msgid "Learn more about custom project templates"
-msgstr ""
+msgstr "Докладніше про власні шаблони проектів"
msgid "Learn more about group-level project templates"
-msgstr ""
+msgstr "Докладніше про шаблони проектів на рівні групи"
msgid "Learn more about incoming email addresses"
-msgstr ""
+msgstr "Докладніше про вхідні адреси електронної пошти"
msgid "Learn more about protected branches"
msgstr "Дізнайтеся більше про захищені гілки"
+msgid "Learn more about signing commits"
+msgstr ""
+
msgid "Learn more in the"
msgstr "Дізнайтесь більше"
@@ -5783,10 +6174,10 @@ msgid "Loading..."
msgstr "Завантаження..."
msgid "Loading…"
-msgstr ""
+msgstr "Завантаження…"
msgid "Localization"
-msgstr ""
+msgstr "Регіональні налаштування"
msgid "Lock"
msgstr "Блокувати"
@@ -5821,6 +6212,21 @@ msgstr "Вхід за допомогою смарт-картки"
msgid "Logs"
msgstr "Логи"
+msgid "MRApprovals|Approved by"
+msgstr ""
+
+msgid "MRApprovals|Approvers"
+msgstr "Затверджуючі особи"
+
+msgid "MRApprovals|Pending approvals"
+msgstr ""
+
+msgid "MRDiff|Show changes only"
+msgstr ""
+
+msgid "MRDiff|Show full file"
+msgstr ""
+
msgid "Make everyone on your team more productive regardless of their location. GitLab Geo creates read-only mirrors of your GitLab instance so you can reduce the time it takes to clone and fetch large repos."
msgstr "Зробіть кожного учасника команди більш продуктивним незалежно від його місцезнаходження. GitLab Geo створює копії \"тільки для читання\" вашого GitLab сервера, щоб скоротити час для клонування і отримання коду з великих репозиторіїв."
@@ -5855,7 +6261,7 @@ msgid "Manage project labels"
msgstr "Керування мітками проекту"
msgid "Manage two-factor authentication"
-msgstr ""
+msgstr "Керування двофакторною автентифікацією"
msgid "Manage your group’s membership while adding another level of security with SAML."
msgstr "Керуйте членством у вашій групі додаючи ще один рівень безпеки із SAML."
@@ -5866,6 +6272,9 @@ msgstr "Маніфест"
msgid "Manifest file import"
msgstr "Імпортувати файл маніфесту"
+msgid "Manual job"
+msgstr ""
+
msgid "Map a FogBugz account ID to a GitLab user"
msgstr "Зв’язати обліковий запис FogBugz з користувачем GitLab"
@@ -5888,41 +6297,11 @@ msgid "Mark todo as done"
msgstr "Відмітити завдання виконаним"
msgid "Markdown"
-msgstr ""
+msgstr "Markdown"
msgid "Markdown enabled"
msgstr "Markdown увімкнено"
-msgid "MarkdownToolbar|Add a bullet list"
-msgstr "Додати ненумерований список"
-
-msgid "MarkdownToolbar|Add a link"
-msgstr "Додати посилання"
-
-msgid "MarkdownToolbar|Add a numbered list"
-msgstr "Додати нумерований список"
-
-msgid "MarkdownToolbar|Add a table"
-msgstr "Додати таблицю"
-
-msgid "MarkdownToolbar|Add a task list"
-msgstr "Додати список завдань"
-
-msgid "MarkdownToolbar|Add bold text"
-msgstr "Додати жирний текст"
-
-msgid "MarkdownToolbar|Add italic text"
-msgstr "Додати курсивний текст"
-
-msgid "MarkdownToolbar|Go full screen"
-msgstr "Повний екран"
-
-msgid "MarkdownToolbar|Insert a quote"
-msgstr "Вставити цитату"
-
-msgid "MarkdownToolbar|Insert code"
-msgstr "Вставити код"
-
msgid "Maven Metadata"
msgstr "Maven-метадані"
@@ -5948,10 +6327,10 @@ msgid "Members"
msgstr "Користувачі"
msgid "Members can be added by project <i>Maintainers</i> or <i>Owners</i>"
-msgstr ""
+msgstr "Учасники можуть будуть додані <i>Керівниками</i> або <i>Власниками</i> проекту"
msgid "Members of <strong>%{project_name}</strong>"
-msgstr ""
+msgstr "Учасники <strong>%{project_name}</strong>"
msgid "Members will be forwarded here when signing in to your group. Get this from your identity provider, where it can also be called \"SSO Service Location\", \"SAML Token Issuance Endpoint\", or \"SAML 2.0/W-Federation URL\"."
msgstr "Учасники будуть перенаправлені сюди, коли будуть заходити до вашої групи. Отримайте його від свого провайдера ідентифікації, де вона також може називатися \"SSO Service Location\", \"SAML Token Issuance Endpoint\", або \"SAML 2.0/W-Federation URL\"."
@@ -5966,15 +6345,18 @@ msgid "Merge Requests created"
msgstr "Запит на злиття було створено"
msgid "Merge commit message"
-msgstr ""
+msgstr "Повідомлення для коміту-злиття"
msgid "Merge events"
msgstr "Події злиття"
msgid "Merge immediately"
-msgstr ""
+msgstr "Злити негайно"
msgid "Merge in progress"
+msgstr "Злиття в процесі"
+
+msgid "Merge pipelines will try to validate the post-merge result prior to merging"
msgstr ""
msgid "Merge request"
@@ -5990,31 +6372,31 @@ msgid "Merge requests are a place to propose changes you've made to a project an
msgstr "Запит на злиття — це спосіб запропонувати свої зміни до проекту і обговорити їх із іншими"
msgid "Merge when pipeline succeeds"
-msgstr ""
+msgstr "Злити, коли конвеєр успішно завершиться"
msgid "MergeRequests|Add a reply"
-msgstr ""
+msgstr "Додати відповідь"
msgid "MergeRequests|An error occurred while saving the draft comment."
msgstr "Виникла помилка під час збереження чернетки коментаря."
msgid "MergeRequests|Discussion stays resolved"
-msgstr ""
+msgstr "Обговорення залишається вирішеним"
msgid "MergeRequests|Discussion stays unresolved"
-msgstr ""
+msgstr "Обговорення залишається невирішеним"
msgid "MergeRequests|Discussion will be resolved"
-msgstr ""
+msgstr "Обговорення буде вирішеним"
msgid "MergeRequests|Discussion will be unresolved"
-msgstr ""
+msgstr "Обговорення буде невирішеним"
msgid "MergeRequests|Jump to next unresolved discussion"
-msgstr ""
+msgstr "Перейти до наступного невирішеного обговорення"
msgid "MergeRequests|Reply..."
-msgstr ""
+msgstr "Відповісти..."
msgid "MergeRequests|Resolve this discussion in a new issue"
msgstr "Вирішити це обговорення в новій задачі"
@@ -6032,26 +6414,29 @@ msgid "MergeRequests|View replaced file @ %{commitId}"
msgstr "Переглянути замінений файл станом на %{commitId}"
msgid "MergeRequests|commented on commit %{commitLink}"
-msgstr ""
+msgstr "прокоментував (-ла) коміт %{commitLink}"
msgid "MergeRequests|started a discussion"
-msgstr ""
+msgstr "розпочав (-ла) обговорення"
msgid "MergeRequests|started a discussion on %{linkStart}an old version of the diff%{linkEnd}"
-msgstr ""
+msgstr "розпочав (-ла) обговорення %{linkStart}старої версії порівняння (diff)%{linkEnd}"
msgid "MergeRequests|started a discussion on %{linkStart}the diff%{linkEnd}"
-msgstr ""
+msgstr "розпочав (-ла) обговорення %{linkStart}порівняння (diff)%{linkEnd}"
msgid "MergeRequests|started a discussion on an outdated change in commit %{linkStart}%{commitId}%{linkEnd}"
-msgstr ""
+msgstr "розпочав (-ла) обговорення застарілих змін в коміті %{linkStart}%{commitId}%{linkEnd}"
msgid "MergeRequests|started a discussion on commit %{linkStart}%{commitId}%{linkEnd}"
-msgstr ""
+msgstr "розпочав (-ла) коміту %{linkStart}%{commitId}%{linkEnd}"
msgid "MergeRequest| %{paragraphStart}changed the description %{descriptionChangedTimes} times %{timeDifferenceMinutes}%{paragraphEnd}"
msgstr "%{paragraphStart} опис змінено %{descriptionChangedTimes} раз(а,ів) %{timeDifferenceMinutes}%{paragraphEnd}"
+msgid "MergeRequest|Error loading full diff. Please try again."
+msgstr ""
+
msgid "MergeRequest|Filter files"
msgstr "Фільтр файлів"
@@ -6059,7 +6444,7 @@ msgid "MergeRequest|No files found"
msgstr "Файлів не знайдено"
msgid "MergeRequest|Search files"
-msgstr ""
+msgstr "Пошук файлів"
msgid "Merged"
msgstr "Злито"
@@ -6080,7 +6465,7 @@ msgid "Metrics and profiling"
msgstr "Метрики та профілювання"
msgid "Metrics for environment"
-msgstr ""
+msgstr "Метрики для середовища"
msgid "Metrics|Check out the CI/CD documentation on deploying to an environment"
msgstr "Перевірте документацію CI/CD щодо розгортання в середовищі"
@@ -6089,10 +6474,10 @@ msgid "Metrics|Create metric"
msgstr "Створити метрику"
msgid "Metrics|Delete metric"
-msgstr ""
+msgstr "Видалити метрику"
msgid "Metrics|Delete metric?"
-msgstr ""
+msgstr "Видалити метрику?"
msgid "Metrics|Edit metric"
msgstr "Редагувати метрику"
@@ -6104,7 +6489,7 @@ msgid "Metrics|For grouping similar metrics"
msgstr "Для групування подібних метрик"
msgid "Metrics|Label of the y-axis (usually the unit). The x-axis always represents time."
-msgstr ""
+msgstr "Мітка осі - y (зазвичай одиниця вимірювання). Осі - x завжди позначає час."
msgid "Metrics|Learn about environments"
msgstr "Дізнайтеся більше про середовища"
@@ -6122,14 +6507,11 @@ msgid "Metrics|No deployed environments"
msgstr "Немає розгорнутих середовищ"
msgid "Metrics|PromQL query is valid"
-msgstr ""
+msgstr "Запит PromQL є дійсним"
msgid "Metrics|Prometheus Query Documentation"
msgstr "Документація по запитам Prometheus"
-msgid "Metrics|System"
-msgstr "Система"
-
msgid "Metrics|There was an error fetching the environments data, please try again"
msgstr "Трапилася помилка під час отримання інформації про середовища. Будь ласка, спробуйте ще раз"
@@ -6140,7 +6522,7 @@ msgid "Metrics|There was an error getting environments information."
msgstr "Трапилася помилка під час отримання інформації про середовища."
msgid "Metrics|There was an error trying to validate your query"
-msgstr ""
+msgstr "Помилка при перевірці вашого запиту"
msgid "Metrics|There was an error while retrieving metrics"
msgstr "Трапилася помилка під час отримання метрик"
@@ -6164,7 +6546,7 @@ msgid "Metrics|Y-axis label"
msgstr "Назва осі Y"
msgid "Metrics|You're about to permanently delete this metric. This cannot be undone."
-msgstr ""
+msgstr "Ви збираєтеся остаточно видалити цю метрику. Це не можна відмінити."
msgid "Metrics|e.g. Throughput"
msgstr "напр. пропускна здатність"
@@ -6241,17 +6623,20 @@ msgstr "Скасувати"
msgid "Modal|Close"
msgstr "Закрити"
+msgid "Modify commit message"
+msgstr "Змінити повідомлення для комітів"
+
msgid "Modify commit messages"
-msgstr ""
+msgstr "Змінити повідомлення для комітів"
msgid "Modify merge commit"
-msgstr ""
+msgstr "Змінити коміт-злиття"
msgid "Monday"
-msgstr ""
+msgstr "Понеділок"
msgid "Monitor your errors by integrating with Sentry"
-msgstr ""
+msgstr "Моніторинг ваших помилок шляхом інтеграції із Sentry"
msgid "Monitoring"
msgstr "Моніторинг"
@@ -6274,6 +6659,9 @@ msgstr "Детальніше"
msgid "More information is available|here"
msgstr "тут"
+msgid "More than %{number_commits_distance} commits different with %{default_branch}"
+msgstr ""
+
msgid "Most stars"
msgstr "Найбільше в обраних"
@@ -6298,6 +6686,9 @@ msgstr "Назвіть ваш індивідуальний ключ за доп
msgid "Name:"
msgstr "Ім’я:"
+msgid "Naming, tags, avatar"
+msgstr ""
+
msgid "Naming, visibility"
msgstr "Найменування, видимість"
@@ -6314,7 +6705,7 @@ msgid "Nav|Sign out and sign in with a different account"
msgstr "Вийти і зайти під іншим обліковим записом"
msgid "Need help?"
-msgstr ""
+msgstr "Потрібна допомога?"
msgid "Network"
msgstr "Мережа"
@@ -6329,7 +6720,7 @@ msgid "New Application"
msgstr "Новий додаток"
msgid "New Environment"
-msgstr ""
+msgstr "Нове середовище"
msgid "New Group"
msgstr "Нова група"
@@ -6348,9 +6739,12 @@ msgid "New Label"
msgstr "Нова мітка"
msgid "New Milestone"
-msgstr ""
+msgstr "Новий етап"
msgid "New Pages Domain"
+msgstr "Новий домен Pages"
+
+msgid "New Password"
msgstr ""
msgid "New Pipeline Schedule"
@@ -6359,20 +6753,20 @@ msgstr "Новий розклад Конвеєра"
msgid "New Snippet"
msgstr "Новий сніпет"
-msgid "New Snippets"
-msgstr "Нові сніпети"
-
msgid "New branch"
msgstr "Нова гілка"
msgid "New branch unavailable"
msgstr "Нова гілка недоступна"
+msgid "New deploy key"
+msgstr ""
+
msgid "New directory"
msgstr "Новий каталог"
msgid "New environment"
-msgstr ""
+msgstr "Нове середовище"
msgid "New epic"
msgstr "Новий епік"
@@ -6396,7 +6790,7 @@ msgid "New merge request"
msgstr "Новий запит на злиття"
msgid "New milestone"
-msgstr ""
+msgstr "Новий етап"
msgid "New pipelines will cancel older, pending pipelines on the same branch"
msgstr "Нові конвеєри скасують старі, що очікують на тій же гілці"
@@ -6422,12 +6816,18 @@ msgstr "Новий..."
msgid "No"
msgstr "Ні"
+msgid "No %{providerTitle} repositories available to import"
+msgstr "Немає репозиторіїв %{providerTitle} для імпорту"
+
msgid "No Label"
msgstr "Без Мітки"
-msgid "No activities found"
+msgid "No Tag"
msgstr ""
+msgid "No activities found"
+msgstr "Не знайднено активностей"
+
msgid "No assignee"
msgstr "Немає виконавця"
@@ -6438,7 +6838,7 @@ msgid "No changes"
msgstr "Немає змін"
msgid "No changes between %{ref_start}%{source_branch}%{ref_end} and %{ref_start}%{target_branch}%{ref_end}"
-msgstr ""
+msgstr "Немає змін між %{ref_start}%{source_branch}%{ref_end} та %{ref_start}%{target_branch}%{ref_end}"
msgid "No connection could be made to a Gitaly Server, please check your logs!"
msgstr "Неможливо з'єднатись із сервером Gitaly, будь ласка, перевірте логи!"
@@ -6452,13 +6852,16 @@ msgstr "Внески не знайдено"
msgid "No credit card required."
msgstr "Не потрібна кредитна картка."
-msgid "No details available"
+msgid "No designs found."
msgstr ""
+msgid "No details available"
+msgstr "Немає деталей для відображення"
+
msgid "No due date"
msgstr "Немає"
-msgid "No errors to display"
+msgid "No errors to display."
msgstr ""
msgid "No estimate or time spent"
@@ -6468,7 +6871,7 @@ msgid "No file chosen"
msgstr "Файл не вибрано"
msgid "No file selected"
-msgstr ""
+msgstr "Файл не вибраний"
msgid "No files found."
msgstr "Не знайдено жодного файлу."
@@ -6483,7 +6886,7 @@ msgid "No license. All rights reserved"
msgstr "Немає ліцензії. Всі права захищені"
msgid "No matching results"
-msgstr ""
+msgstr "Немає відповідних результатів"
msgid "No merge requests for the selected time period."
msgstr "Немає запитів на злиття за вибраний період часу."
@@ -6495,13 +6898,13 @@ msgid "No messages were logged"
msgstr "Немає повідомлень у журналі"
msgid "No milestones to show"
-msgstr ""
+msgstr "Немає етапів для показу"
msgid "No other labels with such name or description"
msgstr "Немає інших міток з таким іменем або описом"
msgid "No preview for this file type"
-msgstr ""
+msgstr "Попереднього перегляду для цього типу файлів немає"
msgid "No prioritised labels with such name or description"
msgstr "Немає пріоритетних міток з таким іменем або описом"
@@ -6522,7 +6925,7 @@ msgid "No schedules"
msgstr "Немає розкладів"
msgid "No start date"
-msgstr ""
+msgstr "Немає дати початку"
msgid "No, directly import the existing email addresses and usernames."
msgstr "Ні, безпосередньо імпортувати існуючі адреси електронної пошти та імена користувачів."
@@ -6554,11 +6957,14 @@ msgstr "Недостатньо даних"
msgid "Not now"
msgstr "Пізніше"
+msgid "Not started"
+msgstr ""
+
msgid "Note that the master branch is automatically protected. %{link_to_protected_branches}"
msgstr "Майте на увазі, що гілка master захищена автоматично. %{link_to_protected_branches}"
msgid "Note that this invitation was sent to %{mail_to_invite_email}, but you are signed in as %{link_to_current_user} with email %{mail_to_current_user}."
-msgstr ""
+msgstr "Зауважте, що це запрошення було надіслано на %{mail_to_invite_email}, але ви увійшли як %{link_to_current_user} з електронною поштою %{mail_to_current_user}."
msgid "Note: As an administrator you may like to configure %{github_integration_link}, which will allow login via GitHub and allow connecting repositories without generating a Personal Access Token."
msgstr "Примітка: як адміністратор ви можете налаштувати %{github_integration_link}, що дозволить входити через GitHub і підключати репозиторії без створення особистого токену доступу."
@@ -6591,10 +6997,10 @@ msgid "Notification events"
msgstr "Повідомлення про події"
msgid "Notification setting"
-msgstr ""
+msgstr "Налаштування сповіщень"
msgid "Notification setting - %{notification_title}"
-msgstr ""
+msgstr "Параметр сповіщення - %{notification_title}"
msgid "NotificationEvent|Close issue"
msgstr "Задача закрита"
@@ -6703,13 +7109,13 @@ msgid "Only policy:"
msgstr ""
msgid "Only proceed if you trust %{idp_url} to control your GitLab account sign in."
-msgstr ""
+msgstr "Продовжуйте тільки якщо ви довіряєте %{idp_url} контроль над входом до вашого облікового запису GitLab."
msgid "Only project members can comment."
msgstr "Тільки учасники проекту можуть залишати коментарі."
msgid "Only project members will be imported. Group members will be skipped."
-msgstr ""
+msgstr "Лише учасника проекту будуть імпортовані. Учасники групи будуть пропущені."
msgid "Oops, are you sure?"
msgstr "Ой, а ви впевнені?"
@@ -6718,13 +7124,13 @@ msgid "Open"
msgstr "Відкриті"
msgid "Open Documentation"
-msgstr ""
+msgstr "Відкрити документацію"
msgid "Open comment type dropdown"
-msgstr ""
+msgstr "Випадаючий список типу коментарів"
msgid "Open errors"
-msgstr ""
+msgstr "Відкрити помилки"
msgid "Open in Xcode"
msgstr "Відкрити в Xcode"
@@ -6759,6 +7165,9 @@ msgstr "Операції"
msgid "Operations Dashboard"
msgstr "Панель керування операціями"
+msgid "Operations Settings"
+msgstr ""
+
msgid "OperationsDashboard|Add a project to the dashboard"
msgstr "Додайте проект до панелі керування"
@@ -6766,6 +7175,9 @@ msgid "OperationsDashboard|The operations dashboard provides a summary of each p
msgstr "Панель керування операціями містить інформацію про стан кожного з проектів разом зі станом його конвеєрів та попереджень."
msgid "OperationsDashboard|Unable to add %{invalidProjects}. The Operations Dashboard is available for public projects, and private projects in groups with a Gold plan."
+msgstr "Неможливо додати %{invalidProjects}. Панель керування операціями доступна для публічних та приватних проектів в групах із тарифним планом Gold."
+
+msgid "Optional"
msgstr ""
msgid "Optionally, you can %{link_to_customize} how FogBugz email addresses and usernames are imported into GitLab."
@@ -6814,10 +7226,10 @@ msgid "Pages"
msgstr "Сторінки"
msgid "Pages Domain"
-msgstr ""
+msgstr "Домен Pages"
msgid "Pages Domains"
-msgstr ""
+msgstr "Домени Pages"
msgid "Pagination|Last »"
msgstr "Остання »"
@@ -6832,10 +7244,7 @@ msgid "Pagination|« First"
msgstr "« Перша"
msgid "Parameter"
-msgstr ""
-
-msgid "Parent epic"
-msgstr ""
+msgstr "Параметр"
msgid "Part of merge request changes"
msgstr "Частина змін у запиті на злиття"
@@ -6844,13 +7253,16 @@ msgid "Password"
msgstr "Пароль"
msgid "Past due"
+msgstr "Прострочені"
+
+msgid "Paste a machine public key here. Read more about how to generate it %{link_start}here%{link_end}"
msgstr ""
msgid "Paste epic link"
-msgstr ""
+msgstr "Вставити посилання на епік"
msgid "Paste issue link"
-msgstr ""
+msgstr "Вставити посилання на задачу"
msgid "Paste your public SSH key, which is usually contained in the file '~/.ssh/id_rsa.pub' and begins with 'ssh-rsa'. Don't use your private SSH key."
msgstr "Вставте свій відкритий ключ SSH, який зазвичай знаходиться у файлі '~/.ssh/id_rsa.pub' і починається з 'ssh-rsa'. Не використовуйте свій приватний ключ SSH."
@@ -6889,17 +7301,14 @@ msgid "Personal Access Token"
msgstr "Токену персонального доступу"
msgid "Personal project creation is not allowed. Please contact your administrator with questions"
-msgstr ""
+msgstr "Створення персональних проектів не дозволено. Будь ласка, зверніться до свого адміністратор із питаннями"
msgid "Pick a name"
-msgstr ""
+msgstr "Виберіть ім'я"
msgid "Pipeline"
msgstr "Конвеєр"
-msgid "Pipeline Health"
-msgstr "Стан Конвеєра"
-
msgid "Pipeline Schedule"
msgstr "Розклад Конвеєра"
@@ -6960,11 +7369,17 @@ msgstr "Змінні"
msgid "PipelineSheduleIntervalPattern|Custom"
msgstr "Спеціальні"
+msgid "PipelineStatusTooltip|Commit: %{ci_status}"
+msgstr "Коміт: %{ci_status}"
+
+msgid "PipelineStatusTooltip|Pipeline: %{ci_status}"
+msgstr "Конвеєр: %{ci_status}"
+
msgid "Pipelines"
msgstr "Конвеєри"
msgid "Pipelines charts"
-msgstr "Чарти Конвеєрів"
+msgstr "Статистика конвеєрів"
msgid "Pipelines for last month"
msgstr "Конвеєри за останній місяць"
@@ -6975,6 +7390,9 @@ msgstr "Конвеєри за останній тиждень"
msgid "Pipelines for last year"
msgstr "Конвеєри за останній рік"
+msgid "Pipelines need to be configured to enable this feature."
+msgstr ""
+
msgid "Pipelines|Build with confidence"
msgstr "Виконуйте збірки із впевненістю"
@@ -7081,7 +7499,7 @@ msgid "Play"
msgstr "Відтворити"
msgid "Please %{link_to_register} or %{link_to_sign_in} to comment"
-msgstr ""
+msgstr "Будь ласка, %{link_to_register} або %{link_to_sign_in} щоб прокоментувати"
msgid "Please accept the Terms of Service before continuing."
msgstr "Будь ласка, для продовження прийміть умови надання послуг."
@@ -7095,9 +7513,21 @@ msgstr "Будь ласка сконвертуйте їх в %{link_to_git} і
msgid "Please convert them to Git on Google Code, and go through the %{link_to_import_flow} again."
msgstr "Будь ласка сконвертуйте їх в Git на Google Code, і виконайте знову %{link_to_import_flow}."
+msgid "Please create a username with only alphanumeric characters."
+msgstr ""
+
msgid "Please enable and migrate to hashed storage to avoid security issues and ensure data integrity. %{migrate_link}"
msgstr ""
+msgid "Please enter a non-negative number"
+msgstr ""
+
+msgid "Please enter a number greater than %{number} (from the project settings)"
+msgstr ""
+
+msgid "Please enter a valid number"
+msgstr ""
+
msgid "Please fill in a descriptive name for your group."
msgstr "Введіть описове ім'я групи."
@@ -7107,9 +7537,18 @@ msgstr ""
msgid "Please note that this application is not provided by GitLab and you should verify its authenticity before allowing access."
msgstr "Зверніть увагу, що ця програма не є частиною GitLab, і ви повинні впевнитися у її безпеці, перш ніж надавати доступ."
+msgid "Please provide a name"
+msgstr ""
+
+msgid "Please select and add a member"
+msgstr ""
+
msgid "Please select at least one filter to see results"
msgstr "Будь ласка виберіть хоча б один фільтр, щоб побачити результати"
+msgid "Please set a new password before proceeding."
+msgstr "Будь ласка, встановіть пароль перед продовженням."
+
msgid "Please solve the reCAPTCHA"
msgstr "Будь ласка, пройдіть reCAPTCHA"
@@ -7117,7 +7556,7 @@ msgid "Please try again"
msgstr "Будь ласка, спробуйте ще раз"
msgid "Please upgrade PostgreSQL to version 9.6 or greater. The status of the replication cannot be determined reliably with the current version."
-msgstr ""
+msgstr "Будь ласка, оновіть PostgreSQL до версії 9.6 або вище. Стан реплікації не може бути достовірно визначений у поточній версії."
msgid "Please use this form to report users to GitLab who create spam issues, comments or behave inappropriately."
msgstr "Будь ласка, використовуйте цю форму, щоб повідомляти GitLab про користувачів, які створюють задачі і коментарі зі спамом, або поводяться невідповідно."
@@ -7134,6 +7573,9 @@ msgstr "Налаштування"
msgid "Preferences|Navigation theme"
msgstr "Тема навігації"
+msgid "Preferences|This feature is experimental and translations are not complete yet"
+msgstr "Ця функція є експериментальною і переклади ще не завершені"
+
msgid "Press Enter or click to search"
msgstr "Для пошуку натисніть Enter або клікніть"
@@ -7162,7 +7604,7 @@ msgid "Prioritized label"
msgstr "Пріоритетні мітки"
msgid "Private"
-msgstr ""
+msgstr "Приватний"
msgid "Private - Project access must be granted explicitly to each user."
msgstr "Приватний — доступ до проекту повинен надаватися кожному користувачеві."
@@ -7186,13 +7628,13 @@ msgid "Profiles| You are going to change the username %{currentUsernameBold} to
msgstr "Ви збираєтеся змінити ім'я користувача %{currentUsernameBold} на %{newUsernameBold}. Профіль та проекти будуть перенаправлятися на простір імен %{newUsername}, але таке перенаправлення закінчиться, коли простір імен %{currentUsername} буде зареєстровано на іншого користувача або групу. Будь ласка, оновіть віддалені адреси в репозиторіях Git якомога швидше."
msgid "Profiles|@username"
-msgstr ""
+msgstr "@ім'я користувача"
msgid "Profiles|Account scheduled for removal."
msgstr "Обліковий запис запланований для видалення."
msgid "Profiles|Activate signin with one of the following services"
-msgstr ""
+msgstr "Активуйте вхід за допомогою однієї з наступних служб"
msgid "Profiles|Active"
msgstr ""
@@ -7213,28 +7655,28 @@ msgid "Profiles|Change username"
msgstr "Змінити ім'я користувача"
msgid "Profiles|Changing your username can have unintended side effects."
-msgstr ""
+msgstr "Зміна імені користувача може мати небажані побічні ефекти."
msgid "Profiles|Choose file..."
msgstr "Вибрати файл..."
msgid "Profiles|Choose to show contributions of private projects on your public profile without any project, repository or organization information"
-msgstr ""
+msgstr "Виберіть для показу внесків до приватних репозиторіїв у вашому публічному профілі без інформації про проекти, репозиторії або організації"
msgid "Profiles|City, country"
-msgstr ""
+msgstr "Місто, країна"
msgid "Profiles|Clear status"
msgstr "Очистити статус"
msgid "Profiles|Click on icon to activate signin with one of the following services"
-msgstr ""
+msgstr "Клікніть на іконку, щоб активувати вхід за допомогою одного із наступних сервісів"
msgid "Profiles|Connect"
-msgstr ""
+msgstr "Приєднати"
msgid "Profiles|Connected Accounts"
-msgstr ""
+msgstr "Підключені облікові записи"
msgid "Profiles|Current path: %{path}"
msgstr "Поточний шлях: %{path}"
@@ -7255,7 +7697,7 @@ msgid "Profiles|Deleting an account has the following effects:"
msgstr "Видалення облікового запису несе наступні наслідки:"
msgid "Profiles|Disconnect"
-msgstr ""
+msgstr "Від'єднати"
msgid "Profiles|Do not show on profile"
msgstr "Не відображати у профілі"
@@ -7267,10 +7709,10 @@ msgid "Profiles|Edit Profile"
msgstr "Редагувати профіль"
msgid "Profiles|Enter your name, so people you know can recognize you"
-msgstr ""
+msgstr "Введіть ваше ім'я, щоб люди, яких ви знаєте, могли вас упізнати"
msgid "Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)"
-msgstr ""
+msgstr "Підвищити рівень безпеки вашого облікового запису за допомогою увімкнення двофакторної автентифікації (2FA)"
msgid "Profiles|Invalid password"
msgstr "Неправильний пароль"
@@ -7309,13 +7751,13 @@ msgid "Profiles|Set new profile picture"
msgstr "Встановити нове зображення для профілю"
msgid "Profiles|Social sign-in"
-msgstr ""
+msgstr "Вхід через соціальні мережі"
msgid "Profiles|Some options are unavailable for LDAP accounts"
msgstr "Деякі параметри недоступні для облікових записів LDAP"
msgid "Profiles|Tell us about yourself in fewer than 250 characters"
-msgstr ""
+msgstr "Розкажіть про себе в межах 250 символів"
msgid "Profiles|The maximum file size allowed is 200KB."
msgstr "Максимальний розмір файлу 200КБ."
@@ -7324,7 +7766,7 @@ msgid "Profiles|This doesn't look like a public SSH key, are you sure you want t
msgstr "Це не схоже на публічниц ключ SSH. Ви впевнені, що хочете його додати?"
msgid "Profiles|This email will be displayed on your public profile"
-msgstr ""
+msgstr "Ця електронна адреса буде відображатися у вашому публічному профілі"
msgid "Profiles|This email will be used for web based operations, such as edits and merges. %{learn_more}"
msgstr "Ця адреса електронної пошти буде використовуватися для браузерних операцій, таких як редагування та злиття. %{learn_more}"
@@ -7332,14 +7774,11 @@ msgstr "Ця адреса електронної пошти буде викор
msgid "Profiles|This emoji and message will appear on your profile and throughout the interface."
msgstr "Цей смайлик та повідомлення будуть показані у вашому профілі та в інтерфейсі."
-msgid "Profiles|This feature is experimental and translations are not complete yet"
-msgstr ""
-
msgid "Profiles|This information will appear on your profile"
-msgstr ""
+msgstr "Ця інформація буде відображатися у вашому профілі"
msgid "Profiles|Two-Factor Authentication"
-msgstr ""
+msgstr "Двофакторна автентифікація"
msgid "Profiles|Type your %{confirmationValue} to confirm:"
msgstr "Введіть ваш %{confirmationValue} для підтвердження:"
@@ -7366,13 +7805,13 @@ msgid "Profiles|Username successfully changed"
msgstr "Ім’я користувача успішно збережено"
msgid "Profiles|Using emojis in names seems fun, but please try to set a status message instead"
-msgstr ""
+msgstr "Використання смайликів в іменах виглядає дотепно, але, будь ласка, краще використовуйте їх в повідомленнях про статус"
msgid "Profiles|What's your status?"
msgstr "Який ваш статус?"
msgid "Profiles|Who you represent or work for"
-msgstr ""
+msgstr "Кого ви представляєте або на кого працюєте"
msgid "Profiles|You can change your avatar here"
msgstr "Тут ви можете змінити свій аватар"
@@ -7393,19 +7832,19 @@ msgid "Profiles|You must transfer ownership or delete these groups before you ca
msgstr "Вам необхідно змінити власника або видалити ці групи перед тим як видалити ваш обліковий запис."
msgid "Profiles|Your LinkedIn profile name from linkedin.com/in/profilename"
-msgstr ""
+msgstr "Ваше ім'я профілю LinkedIn з linkedin.com/in/profilename"
msgid "Profiles|Your account is currently an owner in these groups:"
msgstr "Ваш обліковий запис є власником в цих групах:"
msgid "Profiles|Your email address was automatically set based on your %{provider_label} account"
-msgstr ""
+msgstr "Ваша адреса електронної пошти була автоматично встановлена на основі вашого облікового запису %{provider_label}"
msgid "Profiles|Your location was automatically set based on your %{provider_label} account"
-msgstr ""
+msgstr "Ваше місцезнаходження було автоматично встановлено на основі вашого облікового запису %{provider_label}"
msgid "Profiles|Your name was automatically set based on your %{provider_label} account, so people you know can recognize you"
-msgstr ""
+msgstr "Ваше ім’я було автоматично встановлено на основі вашого облікового запису %{provider_label} щоб люди могли вас впізнати"
msgid "Profiles|Your status"
msgstr "Ваш статус"
@@ -7414,10 +7853,10 @@ msgid "Profiles|e.g. My MacBook key"
msgstr "наприклад, мій ключ MacBook"
msgid "Profiles|username"
-msgstr ""
+msgstr "ім'я користувача"
msgid "Profiles|website.com"
-msgstr ""
+msgstr "сайт.укр"
msgid "Profiles|your account"
msgstr "ваш обліковий запис"
@@ -7434,6 +7873,9 @@ msgstr "Прогрес"
msgid "Project"
msgstr "Проект"
+msgid "Project \"%{name}\" is no longer available. Select another project to continue."
+msgstr "Проект \"%{name}\" більше не доступний. Щоб продовжити виберіть інший проект."
+
msgid "Project '%{project_name}' is in the process of being deleted."
msgstr "Проект '%{project_name}' перебуває в процесі видалення."
@@ -7476,8 +7918,11 @@ msgstr "Закінчився термін дії посилання на про
msgid "Project export started. A download link will be sent by email."
msgstr "Розпочато експорт проекту. Посилання для скачування буде надіслана електронною поштою."
+msgid "Project has too many %{label_for_message} to search"
+msgstr "В проекті занадто багато %{label_for_message} для пошуку"
+
msgid "Project members"
-msgstr ""
+msgstr "Учасники проекту"
msgid "Project name"
msgstr "Назва проекту"
@@ -7486,7 +7931,7 @@ msgid "Project slug"
msgstr "Шлях проекту"
msgid "Project:"
-msgstr ""
+msgstr "Проект:"
msgid "ProjectActivityRSS|Subscribe"
msgstr "Підписатися"
@@ -7575,12 +8020,18 @@ msgstr "Користувачі можуть відправляти в цей р
msgid "Projects"
msgstr "Проекти"
+msgid "Projects Successfully Retrieved"
+msgstr ""
+
msgid "Projects shared with %{group_name}"
msgstr "Спільні проекти з %{group_name}"
msgid "Projects that belong to a group are prefixed with the group namespace. Existing projects may be moved into a group."
msgstr "Проекти, що належать до групи, використовують її ім'я як префікс. Існуючі проекти можуть бути переміщені в групу."
+msgid "Projects with write access"
+msgstr ""
+
msgid "ProjectsDropdown|Frequently visited"
msgstr "Часто відвідувані"
@@ -7657,7 +8108,7 @@ msgid "PrometheusService|Custom metrics"
msgstr "Власні метрики"
msgid "PrometheusService|Enable Prometheus to define custom metrics, using either option above"
-msgstr ""
+msgstr "Увімкнути Prometheus для визначення власних метрик, використовуючи будь-який з варіантів вище"
msgid "PrometheusService|Finding and configuring metrics..."
msgstr "Пошук та налаштування метрик..."
@@ -7789,7 +8240,7 @@ msgid "Pseudonymizer data collection"
msgstr "Збір даних Pseudonymizer"
msgid "Public"
-msgstr ""
+msgstr "Публічний"
msgid "Public - The group and any public projects can be viewed without any authentication."
msgstr "Публічна — група та всі публічні проекти можуть переглядатися без автентифікації."
@@ -7797,6 +8248,9 @@ msgstr "Публічна — група та всі публічні проек
msgid "Public - The project can be accessed without any authentication."
msgstr "Публічний — проект может переглядатися без автентифікації."
+msgid "Public deploy keys (%{deploy_keys_count})"
+msgstr ""
+
msgid "Public pipelines"
msgstr "Публічні конвеєри"
@@ -7831,19 +8285,19 @@ msgid "Quarters"
msgstr "Квартали"
msgid "Query"
-msgstr ""
+msgstr "Запит"
msgid "Quick actions can be used in the issues description and comment boxes."
msgstr "Швидкі дії можна використовувати в описах задач і коментарях."
msgid "README"
-msgstr ""
+msgstr "Інструкція (README)"
msgid "Read more"
msgstr "Докладніше"
msgid "Read more about environments"
-msgstr ""
+msgstr "Читати більше про середовища"
msgid "Read more about project permissions <strong>%{link_to_help}</strong>"
msgstr "Дізнайтеся більше про права доступу в проекті <strong>%{link_to_help}</strong>"
@@ -7852,6 +8306,12 @@ msgid "Real-time features"
msgstr "Фунції реального часу"
msgid "Receive alerts from manually configured Prometheus servers."
+msgstr "Отримувати попередження від налаштованого вручну сервера Prometheus."
+
+msgid "Recent"
+msgstr ""
+
+msgid "Recent Project Activity"
msgstr ""
msgid "Recent searches"
@@ -7883,7 +8343,7 @@ msgid "Register / Sign In"
msgstr "Зареєструватися / Увійти"
msgid "Register U2F device"
-msgstr ""
+msgstr "Зареєструвати пристрій U2F"
msgid "Register and see your runners for this group."
msgstr "Зареєструйте і переглядайте ваші Runner’и для цієї групи."
@@ -7916,7 +8376,7 @@ msgid "Related merge requests"
msgstr "Пов'язані запити на злиття"
msgid "Releases"
-msgstr ""
+msgstr "Релізи"
msgid "Releases mark specific points in a project's development history, communicate information about the type of change, and deliver on prepared, often compiled, versions of the software to be reused elsewhere. Currently, releases can only be created through the API."
msgstr ""
@@ -7931,10 +8391,16 @@ msgid "Remove Runner"
msgstr "Видалити Runner"
msgid "Remove all approvals in a merge request when new commits are pushed to its source branch"
-msgstr ""
+msgstr "Видаляти всі затвердження у запитах на злиття, коли у гілці-джерелі з'являються нові коміти"
msgid "Remove approver"
-msgstr ""
+msgstr "Видалити затверджуючу особу"
+
+msgid "Remove approvers"
+msgstr "Видалити затверджуючих осіб"
+
+msgid "Remove approvers?"
+msgstr "Видалити затверджуючих осіб?"
msgid "Remove avatar"
msgstr "Видалити аватар"
@@ -7967,13 +8433,13 @@ msgid "Reopen epic"
msgstr "Повторне відкриття епіку"
msgid "Reopen milestone"
-msgstr ""
+msgstr "Повторне відкриття етапу"
msgid "Repair authentication"
-msgstr "Відновити аутентифікацію"
+msgstr "Відновити автентифікацію"
msgid "Reply to comment"
-msgstr ""
+msgstr "Відповісти на коментар"
msgid "Reply to this email directly or %{view_it_on_gitlab}."
msgstr "Відповісти на це електронне повідомлення безпосередньо або %{view_it_on_gitlab}."
@@ -8023,6 +8489,12 @@ msgstr "Результати для тестового звіту обробля
msgid "Reports|Vulnerability"
msgstr "Вразливість"
+msgid "Reports|issue"
+msgstr ""
+
+msgid "Reports|merge request"
+msgstr ""
+
msgid "Reports|no changed test results"
msgstr "результати тестів не змінилися"
@@ -8036,10 +8508,10 @@ msgid "Repository URL"
msgstr "URL репозиторія"
msgid "Repository cleanup"
-msgstr ""
+msgstr "Очищення репозиторію"
msgid "Repository cleanup has started. You will receive an email once the cleanup operation is complete."
-msgstr ""
+msgstr "Очистку репозиторію розпочато. Ви отримаєте повідомлення по електронній пошті після її завершення."
msgid "Repository has no locks."
msgstr "Репозиторій не має блокувань."
@@ -8071,11 +8543,28 @@ msgstr "Вимагати від всіх користувачів цієї гр
msgid "Require all users to accept Terms of Service and Privacy Policy when they access GitLab."
msgstr "Вимагати від усіх користувачів приймати умови надання послуг та політику конфіденційності, коли вони отримують доступ до GitLab."
-msgid "Resend invite"
+msgid "Require approval from code owners"
msgstr ""
+msgid "Requires approval from %{names}."
+msgid_plural "Requires %{count} more approvals from %{names}."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Requires approval."
+msgid_plural "Requires %d more approvals."
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "Resend invite"
+msgstr "Повторно надіслати запрошення"
+
msgid "Reset authorization key"
-msgstr ""
+msgstr "Скинути ключ авторизації"
msgid "Reset authorization key?"
msgstr ""
@@ -8102,10 +8591,10 @@ msgid "Resolve discussion"
msgstr "Завершити обговорення"
msgid "Resolved"
-msgstr ""
+msgstr "Вирішено"
msgid "Response"
-msgstr ""
+msgstr "Відповідь"
msgid "Response metrics (AWS ELB)"
msgstr "Метрики відповідей (AWS ELB)"
@@ -8117,7 +8606,7 @@ msgid "Response metrics (HA Proxy)"
msgstr "Метрики відповідей (HA Proxy)"
msgid "Response metrics (NGINX Ingress VTS)"
-msgstr ""
+msgstr "Метрики відповідей (NGINX Ingress VTS)"
msgid "Response metrics (NGINX Ingress)"
msgstr "Метрики відповідей (NGINX Ingress)"
@@ -8126,7 +8615,7 @@ msgid "Response metrics (NGINX)"
msgstr "Метрики відповідей (NGINX)"
msgid "Restart Terminal"
-msgstr ""
+msgstr "Перезапустити термінал"
msgid "Resume"
msgstr "Продовжити"
@@ -8148,7 +8637,7 @@ msgstr[2] "Показати значень"
msgstr[3] "Показати значень"
msgid "Reveal values"
-msgstr ""
+msgstr "Показати значення"
msgid "Revert this commit"
msgstr "Анулювати цей коміт"
@@ -8178,7 +8667,7 @@ msgid "Run CI/CD pipelines for external repositories"
msgstr "Запустити CI/CD конвеєри для зовнішніх репозиторіїв"
msgid "Run tests against your code live using the Web Terminal"
-msgstr ""
+msgstr "Протестувати ваш запущений код за допомогою Веб-терміналу"
msgid "Run untagged jobs"
msgstr "Виконати завдання без тегів"
@@ -8208,7 +8697,7 @@ msgid "Runners API"
msgstr "API Runner’ів"
msgid "Runners activated for this project"
-msgstr ""
+msgstr "Runner'и активовані для цього проекту"
msgid "Runners can be placed on separate users, servers, and even on your local machine."
msgstr "Runner’и можуть розміщуватися у різних користувачів, на серверах і навіть на вашій локальній машині."
@@ -8231,6 +8720,9 @@ msgstr "Ви використали усі виділенні хвилини д
msgid "Running"
msgstr "Виконується"
+msgid "Running…"
+msgstr ""
+
msgid "SAML SSO"
msgstr "Єдиний вхід SAML"
@@ -8244,7 +8736,7 @@ msgid "SAML Single Sign On Settings"
msgstr "Налаштування єдиного входу SAML"
msgid "SAML for %{group_name}"
-msgstr ""
+msgstr "SAML для %{group_name}"
msgid "SHA1 fingerprint of the SAML token signing certificate. Get this from your identity provider, where it can also be called \"Thumbprint\"."
msgstr "Відбиток SHA1 сертифікату для підписування токенів SAML. Отримайте його від провайдера ідентифікації, де він також може називатися \"Thumbprint\"."
@@ -8261,11 +8753,14 @@ msgstr "Відкритий SSH-ключ"
msgid "SSL Verification"
msgstr "Перевірка SSL"
+msgid "Saturday"
+msgstr "Субота"
+
msgid "Save"
msgstr "Зберегти"
msgid "Save Changes"
-msgstr ""
+msgstr "Зберегти зміни"
msgid "Save application"
msgstr "Зберегти застосунок"
@@ -8277,7 +8772,7 @@ msgid "Save changes before testing"
msgstr "Зберегти зміни перед тестуванням"
msgid "Save comment"
-msgstr ""
+msgstr "Зберегти коментар"
msgid "Save pipeline schedule"
msgstr "Зберегти розклад конвеєра"
@@ -8294,6 +8789,9 @@ msgstr "Заплановано"
msgid "Schedules"
msgstr "Розклади"
+msgid "Scheduling"
+msgstr ""
+
msgid "Scheduling Pipelines"
msgstr "Планування конвеєрів"
@@ -8316,7 +8814,7 @@ msgid "Search"
msgstr "Пошук"
msgid "Search an environment spec"
-msgstr ""
+msgstr "Пошук специфікації середовища"
msgid "Search branches"
msgstr "Пошук у гілках"
@@ -8331,7 +8829,7 @@ msgid "Search for projects, issues, etc."
msgstr "Пошук в проектах, задачах і т. д."
msgid "Search groups"
-msgstr ""
+msgstr "Пошук в групах"
msgid "Search merge requests"
msgstr "Пошук у запитах на злиття"
@@ -8354,6 +8852,9 @@ msgstr "Пошук проектів"
msgid "Search users"
msgstr "Пошук користувачів"
+msgid "Search users or groups"
+msgstr ""
+
msgid "Search your projects"
msgstr "Пошук у ваших проектах"
@@ -8403,7 +8904,7 @@ msgid "Security Dashboard|Issue Created"
msgstr "Створено задачу"
msgid "Security Reports|At this time, the security dashboard only supports SAST and dependency scanning."
-msgstr ""
+msgstr "На даний момент, панель безпеки підтримує тільки SAST та сканування вразливостей."
msgid "Security Reports|Create issue"
msgstr "Створити задачу"
@@ -8412,13 +8913,13 @@ msgid "Security Reports|Dismiss vulnerability"
msgstr "Відхилити вразливість"
msgid "Security Reports|Learn more about setting up your dashboard"
-msgstr ""
+msgstr "Дізнайтеся більше про налаштування вашої панелі"
msgid "Security Reports|More info"
msgstr "Детальніше"
msgid "Security Reports|No Vulnerabilities"
-msgstr ""
+msgstr "Немає вразливостей"
msgid "Security Reports|Security dashboard documentation"
msgstr "Документація до панелі безпеки"
@@ -8426,6 +8927,9 @@ msgstr "Документація до панелі безпеки"
msgid "Security Reports|There was an error creating the issue."
msgstr "Помилка при створенні задачі."
+msgid "Security Reports|There was an error creating the merge request."
+msgstr ""
+
msgid "Security Reports|There was an error dismissing the vulnerability."
msgstr "Помилка при відхиленні вразливості."
@@ -8436,16 +8940,16 @@ msgid "Security Reports|There was an error reverting this dismissal."
msgstr "Помилка при анулюванні цього відхилення."
msgid "Security Reports|Undo dismiss"
-msgstr ""
+msgstr "Відмінити відхилення"
msgid "Security Reports|We've found no vulnerabilities for your group"
-msgstr ""
+msgstr "Ми не виявили вразливостей для вашої групи"
msgid "Security Reports|While it's rare to have no vulnerabilities for your group, it can happen. In any event, we ask that you please double check your settings to make sure you've set up your dashboard correctly."
-msgstr ""
+msgstr "Хоча й рідко, але можливо, що ваша група не має вразливостей. В будь-якому разі, ми просимо вас перевірити ваші налаштування, щоб впевнитися, що ваша панель налаштована правильно."
msgid "Security dashboard"
-msgstr ""
+msgstr "Панель безпеки"
msgid "SecurityDashboard| The security dashboard displays the latest security report. Use it to find and fix vulnerabilities."
msgstr "На панелі безпеки відображається останній звіт про безпеку. Використовуйте його для пошуку та виправлення вразливостей."
@@ -8457,7 +8961,7 @@ msgid "SecurityDashboard|Pipeline %{pipelineLink} triggered"
msgstr "Конвеєр %{pipelineLink} запущено"
msgid "See metrics"
-msgstr ""
+msgstr "Переглянути метрики"
msgid "See the affected projects in the GitLab admin panel"
msgstr ""
@@ -8474,6 +8978,12 @@ msgstr "Виберіть групу для запрошення"
msgid "Select a namespace to fork the project"
msgstr "Виберіть простір імен для форку проекту"
+msgid "Select a project to read Insights configuration file"
+msgstr ""
+
+msgid "Select a repository"
+msgstr ""
+
msgid "Select a template repository"
msgstr "Вибрати шаблон репозиторію"
@@ -8490,7 +9000,7 @@ msgid "Select branch/tag"
msgstr "Виберіть гілку або тег"
msgid "Select members to invite"
-msgstr ""
+msgstr "Виберіть учасників для запрошення"
msgid "Select project"
msgstr "Вибрати проект"
@@ -8519,20 +9029,17 @@ msgstr "Вкажіть групу, де розміщені власні шабл
msgid "Selecting a GitLab user will add a link to the GitLab user in the descriptions of issues and comments (e.g. \"By <a href=\"#\">@johnsmith</a>\"). It will also associate and/or assign these issues and comments with the selected user."
msgstr "При виборі користувача Gitlab посилання на нього буде додане до опису задачі та коментарів (напр. \"<a href=\"#\"> @johnsmith</a>\"). Також це призведе до асоціації та/або призначення цих задач та коментарів на вибраного користувача."
-msgid "Selective synchronization"
-msgstr "Вибіркова синхронізація"
-
msgid "Send email"
msgstr "Надіслати листа"
msgid "Send report"
-msgstr ""
+msgstr "Надіслати звіт"
msgid "Send usage data"
msgstr "Відправити дані про використання"
msgid "Sentry API URL"
-msgstr ""
+msgstr "URL-адреса Sentry API"
msgid "Sep"
msgstr "вер."
@@ -8544,46 +9051,46 @@ msgid "Server version"
msgstr "Версія сервера"
msgid "Serverless"
-msgstr ""
+msgstr "Serverless"
msgid "ServerlessDetails|Kubernetes Pods"
-msgstr ""
+msgstr "Pod'и Kubernetes"
msgid "ServerlessDetails|Number of Kubernetes pods in use over time based on necessity."
-msgstr ""
+msgstr "Кількість pod'ів Kubernetes у використанні на основі необхідності протягом періоду часу."
msgid "ServerlessDetails|pod in use"
-msgstr ""
+msgstr "pod у використанні"
msgid "ServerlessDetails|pods in use"
-msgstr ""
+msgstr "pod'и у використанні"
msgid "ServerlessURL|Copy URL to clipboard"
-msgstr ""
+msgstr "Скопіювати URL-адресу в буфер обміну"
msgid "Serverless| In order to start using functions as a service, you must first install Knative on your Kubernetes cluster."
-msgstr ""
+msgstr "Для того, щоб почати використовувати функції як сервіс, необхідно спочатку встановити Knative на ваш кластер Kubernetes."
msgid "Serverless|An error occurred while retrieving serverless components"
-msgstr ""
+msgstr "Сталася помилка при отриманні компонентів serverless"
msgid "Serverless|Getting started with serverless"
-msgstr ""
+msgstr "Початок роботи із serverless"
msgid "Serverless|If you believe none of these apply, please check back later as the function data may be in the process of becoming available."
-msgstr ""
+msgstr "Якщо ви вважаєте, що жодна з них не підходить, будь ласка перевірте пізніше, тому що дані про функції можуть бути в процесі отримання."
msgid "Serverless|Install Knative"
-msgstr ""
+msgstr "Встановити Knative"
msgid "Serverless|Learn more about Serverless"
-msgstr ""
+msgstr "Дізнайтеся більше про Serverless"
msgid "Serverless|No functions available"
-msgstr ""
+msgstr "Немає доступних функцій"
msgid "Serverless|There is currently no function data available from Knative. This could be for a variety of reasons including:"
-msgstr ""
+msgstr "Наразі немає даних про функції від Knative. Це може бути викликано різними причинами, зокрема:"
msgid "Service Desk"
msgstr "Service Desk"
@@ -8597,6 +9104,9 @@ msgstr "URL сервісу"
msgid "Session expiration, projects limit and attachment size."
msgstr "Термін дії сесії, проектні ліміти та розміри вкладень."
+msgid "Set a number of approvals required, the approvers and other approval settings."
+msgstr ""
+
msgid "Set a password on your account to pull or push via %{protocol}."
msgstr "Встановіть пароль для свого облікового запису, щоб мати можливість відправляти та отримувати через %{protocol}."
@@ -8612,11 +9122,14 @@ msgstr "Встановити репозиторій шаблонів для вс
msgid "Set max session time for web terminal."
msgstr "Максимальний термін дії сесії для веб-терміналу."
+msgid "Set new password"
+msgstr ""
+
msgid "Set notification email for abuse reports."
msgstr "Налаштувати сповіщення по електронній пошті для повідомлень про зловживання."
msgid "Set number of approvers required before open merge requests can be merged"
-msgstr ""
+msgstr "Встановити необхідну кількість затверджуючих осіб перед тим, як відкритий запит на злиття може бути злитий"
msgid "Set requirements for a user to sign-in. Enable mandatory two-factor authentication."
msgstr "Встановіть вимоги для входу користувачів. Увімкніть обов’язкову двофакторну автентифікацію."
@@ -8634,6 +9147,9 @@ msgid "Set up assertions/attributes/claims (email, first_name, last_name) and Na
msgstr "Налаштуйте твердження/атрибути (email, ім'я, прізвище) і NameID відповідно до %{docsLinkStart} документації %{icon}%{docsLinkEnd}"
msgid "Set up new U2F device"
+msgstr "Налаштувати новий пристрій U2F"
+
+msgid "Set up new password"
msgstr ""
msgid "Set up your project to automatically push and/or pull changes to/from another repository. Branches, tags, and commits will be synced automatically."
@@ -8693,14 +9209,20 @@ msgstr "Обнулити використані хвилини в конвеєр
msgid "Sherlock Transactions"
msgstr "Sherlock транзакції"
+msgid "Show all activity"
+msgstr ""
+
msgid "Show command"
msgstr "Показати команду"
+msgid "Show comments only"
+msgstr ""
+
msgid "Show complete raw log"
msgstr "Показати повний неформатований журнал"
msgid "Show file browser"
-msgstr ""
+msgstr "Показати файловий менеджер"
msgid "Show latest version"
msgstr "Показати останню версію"
@@ -8743,19 +9265,19 @@ msgid "Sign in / Register"
msgstr "Увійти або зареєструватися"
msgid "Sign in to \"%{group_name}\""
-msgstr ""
+msgstr "Увійти до \"%{group_name}\""
msgid "Sign in using smart card"
-msgstr ""
+msgstr "Увійти за допомогою смарт-карти"
msgid "Sign in via 2FA code"
-msgstr ""
+msgstr "Увійти за допомогою коду двофакторної автентифікції"
msgid "Sign in with Single Sign-On"
msgstr "Увійти за допомогою єдиного входу"
msgid "Sign in with smart card"
-msgstr ""
+msgstr "Увійти за допомогою смарт-карти"
msgid "Sign out"
msgstr "Вийти"
@@ -8767,7 +9289,7 @@ msgid "Sign-up restrictions"
msgstr "Обмеження для реєстрації"
msgid "Similar issues"
-msgstr ""
+msgstr "Подібні задачі"
msgid "Size"
msgstr "Розмір"
@@ -8791,17 +9313,35 @@ msgid "Smartcard authentication failed: client certificate header is missing."
msgstr "Не вдалося автентифікувати смарт-карту: відсутній заголовок в сертифікаті клієнта."
msgid "Snippet Contents"
-msgstr ""
+msgstr "Вміст сніпета"
msgid "Snippets"
msgstr "Сніпети"
-msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
+msgid "SnippetsEmptyState|Explore public snippets"
msgstr ""
-msgid "Someone edited this merge request at the same time you did. Please refresh the page to see changes."
+msgid "SnippetsEmptyState|New snippet"
msgstr ""
+msgid "SnippetsEmptyState|No snippets found"
+msgstr ""
+
+msgid "SnippetsEmptyState|Snippets are small pieces of code or notes that you want to keep."
+msgstr ""
+
+msgid "SnippetsEmptyState|There are no snippets to show."
+msgstr ""
+
+msgid "SnippetsEmptyState|They can be either public or private."
+msgstr ""
+
+msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
+msgstr "Хтось відредагував цю %{issueType} одночасно з вами. Опис було оновлено, тому вам знову треба внести свої зміни."
+
+msgid "Someone edited this merge request at the same time you did. Please refresh the page to see changes."
+msgstr "Хтось відредагував цей запит на злиття одночасно з вами. Будь ласка, оновіть сторінку, щоб побачити зміни."
+
msgid "Something went wrong on our end"
msgstr "Щось пішло не так з нашого боку"
@@ -8812,7 +9352,7 @@ msgid "Something went wrong on our end. Please try again!"
msgstr "Щось пішло не так на нашій стороні. Будь-ласка, спробуйте ще раз!"
msgid "Something went wrong on our end. Please try again."
-msgstr ""
+msgstr "Щось пішло не так на нашій стороні. Будь-ласка, спробуйте ще раз."
msgid "Something went wrong trying to change the confidentiality of this issue"
msgstr "Помилка при зміні конфіденційності цієї задачі"
@@ -8824,13 +9364,13 @@ msgid "Something went wrong when toggling the button"
msgstr "Помилка при перемиканні кнопки"
msgid "Something went wrong while applying the suggestion. Please try again."
-msgstr ""
+msgstr "Помилка при застосуванні пропозиції. Будь ласка, спробуйте знову."
msgid "Something went wrong while closing the %{issuable}. Please try again later"
msgstr "Помилка при закритті %{issuable}. Будь ласка, спробуйте пізніше"
msgid "Something went wrong while deleting the source branch. Please try again."
-msgstr ""
+msgstr "Помилка при видаленні гілки-джерела. Будь ласка, спробуйте знову."
msgid "Something went wrong while fetching %{listType} list"
msgstr "Помилка при отриманні списку %{listType}"
@@ -8851,7 +9391,7 @@ msgid "Something went wrong while fetching the registry list."
msgstr "Щось пішло не так при отриманні списку із реєстру."
msgid "Something went wrong while merging this merge request. Please try again."
-msgstr ""
+msgstr "Помилка при застосуванні цього запиту на злиття. Будь ласка, спробуйте знову."
msgid "Something went wrong while reopening the %{issuable}. Please try again later"
msgstr "Помилка при повторному відкритті %{issuable}. Будь ласка, спробуйте пізніше"
@@ -8878,13 +9418,13 @@ msgid "Sorry, no projects matched your search"
msgstr "На жаль жоден проект не задовольняє критеріям вашого пошуку"
msgid "Sorry, your filter produced no results"
-msgstr ""
+msgstr "На жаль, немає результатів, які відповідають фільтру"
msgid "Sort by"
msgstr "Сортувати за"
msgid "Sort direction"
-msgstr ""
+msgstr "Порядок сортування"
msgid "SortOptions|Access level, ascending"
msgstr "Рівень доступу, в порядку зростання"
@@ -8932,7 +9472,7 @@ msgid "SortOptions|Less weight"
msgstr "Менша вага"
msgid "SortOptions|Milestone due date"
-msgstr ""
+msgstr "Дата завершення етапу"
msgid "SortOptions|Milestone due later"
msgstr "Етап запланований на пізніше"
@@ -8965,7 +9505,7 @@ msgid "SortOptions|Oldest joined"
msgstr "Приєднаний найраніше"
msgid "SortOptions|Oldest last activity"
-msgstr ""
+msgstr "Найстаріша остання активність"
msgid "SortOptions|Oldest sign in"
msgstr "Залогінений найраніше"
@@ -8980,7 +9520,7 @@ msgid "SortOptions|Priority"
msgstr "Пріоритет"
msgid "SortOptions|Recent last activity"
-msgstr ""
+msgstr "Найновіша остання активність"
msgid "SortOptions|Recent sign in"
msgstr "Нещодавно зареєстровані"
@@ -9010,7 +9550,7 @@ msgid "Source is not available"
msgstr "Джерело недоступне"
msgid "Source project cannot be found."
-msgstr ""
+msgstr "Проект-джерело не знайдено."
msgid "Spam Logs"
msgstr "Спам-журнал"
@@ -9028,7 +9568,7 @@ msgid "Specify the following URL during the Runner setup:"
msgstr "Зазначте наступний URL під час встановлення Runner-а:"
msgid "Squash commit message"
-msgstr ""
+msgstr "Повідомлення для об'єднаного (squash) коміту"
msgid "Squash commits"
msgstr "Виконати об'єднання (squash) комітів"
@@ -9066,12 +9606,18 @@ msgstr "Активність в обраних проектах"
msgid "Starred projects"
msgstr "Обрані проекти"
-msgid "Stars"
+msgid "StarredProjectsEmptyState|Visit a project page and press on a star icon. Then, you can find the project on this page."
msgstr ""
-msgid "Start Web Terminal"
+msgid "StarredProjectsEmptyState|You don't have starred projects yet."
msgstr ""
+msgid "Stars"
+msgstr "У обраному"
+
+msgid "Start Web Terminal"
+msgstr "Запустити Веб-Термінал"
+
msgid "Start a %{new_merge_request} with these changes"
msgstr "Почати %{new_merge_request} з цими змінами"
@@ -9082,19 +9628,19 @@ msgid "Start and due date"
msgstr "Дата початку та завершення"
msgid "Start cleanup"
-msgstr ""
+msgstr "Почати очищення"
msgid "Start date"
msgstr "Дата початку"
msgid "Start discussion"
-msgstr ""
+msgstr "Розпочати дискусію"
msgid "Start discussion & close %{noteable_name}"
-msgstr ""
+msgstr "Розпочати обговорення та закрити %{noteable_name}"
msgid "Start discussion & reopen %{noteable_name}"
-msgstr ""
+msgstr "Розпочати обговорення і повторно відкрити %{noteable_name}"
msgid "Start the Runner!"
msgstr "Запустіть Runner!"
@@ -9106,13 +9652,13 @@ msgid "Started"
msgstr "Запущений"
msgid "Started %{startsIn}"
-msgstr ""
+msgstr "Запущено %{startsIn}"
msgid "Starting..."
-msgstr ""
+msgstr "Запуск..."
msgid "Starts %{startsIn}"
-msgstr ""
+msgstr "Буде запущено %{startsIn}"
msgid "Starts at (UTC)"
msgstr "Починається о (за Грінвічем)"
@@ -9124,10 +9670,10 @@ msgid "Status"
msgstr "Статус"
msgid "Status:"
-msgstr ""
+msgstr "Статус:"
msgid "Stop Terminal"
-msgstr ""
+msgstr "Зупинити термінал"
msgid "Stop environment"
msgstr "Зупинити середовище"
@@ -9145,7 +9691,7 @@ msgid "Stopping this environment is currently not possible as a deployment is in
msgstr "Зупинка середовища наразі неможлива, тому що відбувається розгортання"
msgid "Stopping..."
-msgstr ""
+msgstr "Зупинення..."
msgid "Storage"
msgstr "Сховище"
@@ -9163,7 +9709,7 @@ msgid "Submit as spam"
msgstr "Позначити як спам"
msgid "Submit feedback"
-msgstr ""
+msgstr "Надіслати відгук"
msgid "Submit review"
msgstr "Надіслати перевірку"
@@ -9181,19 +9727,19 @@ msgid "Subscribe at project level"
msgstr "Підписатися на рівні проекту"
msgid "Subscribe to RSS feed"
-msgstr ""
+msgstr "Підписатися на RSS-канал"
msgid "Subscribe to calendar"
-msgstr ""
+msgstr "Підписатися на календар"
msgid "Subscribed"
msgstr "Ви підписані"
msgid "SubscriptionTable|Billing"
-msgstr ""
+msgstr "Білінг"
msgid "SubscriptionTable|Free"
-msgstr ""
+msgstr "Безкоштовно"
msgid "SubscriptionTable|GitLab allows you to continue using your subscription even if you exceed the number of seats you purchased. You will be required to pay for these seats upon renewal."
msgstr ""
@@ -9208,7 +9754,7 @@ msgid "SubscriptionTable|Loading subscriptions"
msgstr ""
msgid "SubscriptionTable|Manage"
-msgstr ""
+msgstr "Управління"
msgid "SubscriptionTable|Max seats used"
msgstr ""
@@ -9226,10 +9772,10 @@ msgid "SubscriptionTable|Seats owed"
msgstr ""
msgid "SubscriptionTable|Subscription end date"
-msgstr ""
+msgstr "Дата завершення підписки"
msgid "SubscriptionTable|Subscription start date"
-msgstr ""
+msgstr "Дата початку підписки"
msgid "SubscriptionTable|This is the last time the GitLab.com team was in contact with you to settle any outstanding balances."
msgstr ""
@@ -9244,28 +9790,28 @@ msgid "SubscriptionTable|This is the number of seats you will be required to pur
msgstr ""
msgid "SubscriptionTable|Trial"
-msgstr ""
+msgstr "Пробна версія"
msgid "SubscriptionTable|Trial end date"
-msgstr ""
+msgstr "Дата закінчення пробної версії"
msgid "SubscriptionTable|Trial start date"
-msgstr ""
+msgstr "Дата початку пробної версії"
msgid "SubscriptionTable|Upgrade"
-msgstr ""
+msgstr "Підвищити"
msgid "SubscriptionTable|Usage"
-msgstr ""
+msgstr "Використання"
msgid "SubscriptionTable|Usage count is performed once a day at 12:00 PM."
msgstr ""
msgid "Suggested change"
-msgstr ""
+msgstr "Пропонована зміна"
msgid "Sunday"
-msgstr ""
+msgstr "Неділя"
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
@@ -9276,6 +9822,9 @@ msgstr "Перейти в гілку/тег"
msgid "Sync information"
msgstr "Інформація про синхронізацію"
+msgid "System"
+msgstr ""
+
msgid "System Hooks"
msgstr "Системні гуки"
@@ -9286,7 +9835,7 @@ msgid "System default (%{default})"
msgstr ""
msgid "System header and footer"
-msgstr ""
+msgstr "Системний заголовок та футер"
msgid "System metrics (Custom)"
msgstr "Системні метрики (Власні)"
@@ -9295,10 +9844,10 @@ msgid "System metrics (Kubernetes)"
msgstr "Системні метрики (Kubernetes)"
msgid "Tag"
-msgstr ""
+msgstr "Тег"
msgid "Tag list:"
-msgstr ""
+msgstr "Список тегів:"
msgid "Tags"
msgstr "Теги"
@@ -9394,10 +9943,10 @@ msgid "Templates"
msgstr "Шаблони"
msgid "Terminal"
-msgstr ""
+msgstr "Термінал"
msgid "Terminal for environment"
-msgstr ""
+msgstr "Термінал для середовища"
msgid "Terms of Service Agreement and Privacy Policy"
msgstr "Угода про надання послуг і політика конфіденційності"
@@ -9421,7 +9970,7 @@ msgid "The Advanced Global Search in GitLab is a powerful search service that sa
msgstr "Розширений глобальний пошук в GitLab — це потужний інструмент який заощаджує ваш час. Замість дублювання коду і витрати часу, ви можете шукати код інших команд, який може допомогти у вашому проекті."
msgid "The CSV export will be created in the background. Once finished, it will be sent to <strong>%{email}</strong> in an attachment."
-msgstr ""
+msgstr "Експорт CSV буде створено у фоновому режимі. Після завершення його буде надіслано на <strong>%{email}</strong> у вкладенні."
msgid "The Git LFS objects will <strong>not</strong> be synced."
msgstr "Об'єкти Git LFS <strong>не</strong> будуть синхронізуватися."
@@ -9435,6 +9984,9 @@ msgstr "Трекер задач — це місце, де можна додат
msgid "The X509 Certificate to use when mutual TLS is required to communicate with the external authorization service. If left blank, the server certificate is still validated when accessing over HTTPS."
msgstr "Сертифікат X509 використовується для взаємної перевірки автентичності TLS і необхідний для зв'язку з зовнішньою службою авторизації. Якщо залишити порожнім, сертифікат сервера буде перевірятись при доступі через HTTPS."
+msgid "The branch for this project has no active pipeline configuration."
+msgstr ""
+
msgid "The character highlighter helps you keep the subject line to %{titleLength} characters and wrap the body at %{bodyLength} so they are readable in git."
msgstr "Підсвітка символів дозволяє обмежувати заголовок до %{titleLength} символів і обмежувати довжину рядків тіла %{bodyLength} символами для того, щоб вони залишаються читабельними в git."
@@ -9460,11 +10012,14 @@ msgid "The issue stage shows the time it takes from creating an issue to assigni
msgstr "Стадія Задача показує, скільки часу потрібно від створення задачі до включення її до якогось етапу, або додавання задачі на дошку. Почніть створювати задачі, щоб переглядати дані для цієї стадії."
msgid "The maximum file size allowed is %{size}."
-msgstr ""
+msgstr "Максимальний розмір файлу — %{size}."
msgid "The maximum file size allowed is 200KB."
msgstr "Максимальний розмір файлу — 200 Кб."
+msgid "The name %{entryName} is already taken in this directory."
+msgstr ""
+
msgid "The passphrase required to decrypt the private key. This is optional and the value is encrypted at rest."
msgstr "Пароль, який потрібен для дешифрування приватного ключа. Він є необов’язковим і зберігається у зашифрованому вигляді."
@@ -9541,19 +10096,19 @@ msgid "The value lying at the midpoint of a series of observed values. E.g., bet
msgstr "Середнє значення в рядку. Приклад: між 3, 5, 9, середніми 5, між 3, 5, 7, 8, середніми (5 + 7) / 2 = 6."
msgid "There are no approvers"
-msgstr ""
+msgstr "Немає затверджуючих осіб"
msgid "There are no archived projects yet"
msgstr "Наразі немає жодного архівованого проекту"
msgid "There are no closed issues"
-msgstr ""
+msgstr "Закритих задач немає"
msgid "There are no closed merge requests"
-msgstr ""
+msgstr "Закритих запитів на злиття немає"
msgid "There are no custom project templates set up for this GitLab instance. They are enabled from GitLab's Admin Area. Contact your GitLab instance administrator to setup custom project templates."
-msgstr ""
+msgstr "Власні шаблони проектів не налаштовані для цього серверу GitLab. Вони активуються на сторінці адміністрування GitLab. Зверніться до адміністратора GitLab для налаштування власних шаблонів проектів."
msgid "There are no issues to show"
msgstr "Немає задач для відображення"
@@ -9562,10 +10117,10 @@ msgid "There are no labels yet"
msgstr "Тут ще немає міток"
msgid "There are no open issues"
-msgstr ""
+msgstr "Відкритих задач немає"
msgid "There are no open merge requests"
-msgstr ""
+msgstr "Відкритих запитів на злиття немає"
msgid "There are no packages yet"
msgstr ""
@@ -9580,7 +10135,7 @@ msgid "There are no unstaged changes"
msgstr "Немає неіндексованих змін"
msgid "There was an error adding a todo."
-msgstr "Помилка при додаванні задачі."
+msgstr "Помилка при додаванні нагадування."
msgid "There was an error deleting the todo."
msgstr "Помилка при видаленні задачі."
@@ -9588,6 +10143,9 @@ msgstr "Помилка при видаленні задачі."
msgid "There was an error loading users activity calendar."
msgstr "Помилка при завантаженні календаря активності користувачів."
+msgid "There was an error saving your changes."
+msgstr ""
+
msgid "There was an error saving your notification settings."
msgstr "Помилка при збереженні ваших налаштувань сповіщень."
@@ -9604,7 +10162,7 @@ msgid "There was an error when unsubscribing from this label."
msgstr "Помилка при відписці від цієї мітки."
msgid "These existing issues have a similar title. It might be better to comment there instead of creating another similar issue."
-msgstr ""
+msgstr "Ці існуючі проблеми мають подібні заголовки. Можливо, краще додати коментар до однієї з них замість створення нової."
msgid "They can be managed using the %{link}."
msgstr "Ними можна керувати за допомогою %{link}."
@@ -9613,10 +10171,10 @@ msgid "Third party offers"
msgstr "Сторонні пропозиції"
msgid "This %{issuable} is locked. Only <strong>project members</strong> can comment."
-msgstr ""
+msgstr "Ця %{issuable} заблокована. Лише <strong>учасники проекту</strong> можуть коментувати."
msgid "This %{viewer} could not be displayed because %{reason}. You can %{options} instead."
-msgstr ""
+msgstr "Цей %{viewer} не може бути відображено через %{reason}. Замість цього можна %{options}."
msgid "This GitLab instance does not provide any shared Runners yet. Instance administrators can register shared Runners in the admin area."
msgstr "Цей інстанс GitLab ще немає загальних Runner'ів. Адміністратори можуть їх зареєструвати у спеціальному розділі конфігурації."
@@ -9633,6 +10191,21 @@ msgstr "Видимість цієї дошки обмежена"
msgid "This branch has changed since you started editing. Would you like to create a new branch?"
msgstr "Ця гілка була змінена після того моменту, коли ви почали її редагувати. Ви хотіли б створити нову?"
+msgid "This commit is part of merge request %{link_to_merge_request}. Comments created here will be created in the context of that merge request."
+msgstr ""
+
+msgid "This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with a different user's verified signature."
+msgstr ""
+
+msgid "This commit was signed with a verified signature, but the committer email is <strong>not verified</strong> to belong to the same user."
+msgstr ""
+
+msgid "This commit was signed with an <strong>unverified</strong> signature."
+msgstr ""
+
msgid "This container registry has been scheduled for deletion."
msgstr "Заплановане видалення цього реєстру контейнерів."
@@ -9652,6 +10225,9 @@ msgid "This directory"
msgstr "Цей каталог"
msgid "This domain is not verified. You will need to verify ownership before access is enabled."
+msgstr "Цей домен не підтверджено. Щоб увімкнути доступ, потрібно підтвердити право власності."
+
+msgid "This field is required."
msgstr ""
msgid "This group"
@@ -9718,10 +10294,10 @@ msgid "This job is in pending state and is waiting to be picked by a runner"
msgstr "Це завдання перебуває в стані очікування і чекає на запуск Runner"
msgid "This job is stuck because you don't have any active runners online with any of these tags assigned to them:"
-msgstr ""
+msgstr "Це завдання заблоковане, тому що немає жодних runner'ів з будь яким із цих тегів:"
msgid "This job is stuck because you don't have any active runners that can run this job."
-msgstr ""
+msgstr "Це завдання заблоковане, тому що немає активних runner'ів, які могли б його виконати."
msgid "This job is the most recent deployment to %{link}."
msgstr "Це завдання є останнім розгортанням на %{link}."
@@ -9730,7 +10306,7 @@ msgid "This job requires a manual action"
msgstr "Завдання вимагає ручних дій"
msgid "This job will automatically run after its timer finishes. Often they are used for incremental roll-out deploys to production environments. When unscheduled it converts into a manual action."
-msgstr ""
+msgstr "Завдання буде запущено автоматично після завершення таймера. Зазвичай вони використовуються для інкрементального розгортання в production середовище. У разі скасування завдання воно перетвориться на ручну операцію."
msgid "This means you can not push code until you create an empty repository or import existing one."
msgstr "Це означає, що ви не можете відправляти код, поки не створите порожній репозиторій або не імпортуєте існуючий."
@@ -9738,6 +10314,12 @@ msgstr "Це означає, що ви не можете відправляти
msgid "This merge request is locked."
msgstr "Цей запит на злиття заблоковано."
+msgid "This merge request must be approved by members of these groups. You can override the project settings by setting your own list of approvers."
+msgstr "Цей запит на злиття має бути затверджений учасниками цих груп. Ви можете перевизначити проектні налаштування, встановивши ваш власний список затверджуючих осіб."
+
+msgid "This merge request must be approved by these users. You can override the project settings by setting your own list of approvers."
+msgstr "Цей запит на злиття має бути затверджений цими користувачами. Ви можете перевизначити проектні налаштування, встановивши ваш власний список затверджуючих осіб."
+
msgid "This option is disabled as you don't have write permissions for the current branch"
msgstr "Цей параметр вимкнено, тому що у вас немає дозволу на запис у поточну гілку"
@@ -9751,13 +10333,13 @@ msgid "This page will be removed in a future release."
msgstr "Цю сторінку буде видалено у майбутній версії."
msgid "This pipeline is run in a merge request context"
-msgstr ""
+msgstr "Цей конвеєр виконується в контексті запиту злиття"
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by %{strongStart}Auto DevOps.%{strongEnd}"
-msgstr ""
+msgstr "Цей конвеєр використовує попередньо визначену конфігурацію CI / CD, увімкнену за допомогою %{strongStart}Auto DevOps.%{strongEnd}"
msgid "This pipeline makes use of a predefined CI/CD configuration enabled by <b>Auto DevOps.</b>"
-msgstr ""
+msgstr "Цей конвеєр використовує попередньо визначену конфігурацію CI / CD, увімкнену за допомогою <b>Auto DevOps</b>"
msgid "This project"
msgstr "Цей проект"
@@ -9799,7 +10381,7 @@ msgid "This user will be the author of all events in the activity feed that are
msgstr "Цей користувач буде автором всіх подій в каналі активності, які є результатом оновлення, наприклад створення нових гілок або відправлення нових комітів до існуючих гілок. При створенні або перепризначенні ви зможете призначити лише себе користувачем для віддзеркалення."
msgid "This will redirect you to an external sign in page."
-msgstr ""
+msgstr "Це перенаправить вас на зовнішню сторінку входу."
msgid "Those emails automatically become issues (with the comments becoming the email conversation) listed here."
msgstr "Ці повідомлення електронної пошти автоматично стануть обговореннями задач, які відображатимуться тут (причому коментарі стануть частиною переписки)."
@@ -9996,7 +10578,7 @@ msgid "Title"
msgstr "Заголовок"
msgid "Titles and Filenames"
-msgstr ""
+msgstr "Заголовки та імена файлів"
msgid "To %{link_to_help} of your domain, add the above key to a TXT record within to your DNS configuration."
msgstr ""
@@ -10005,7 +10587,7 @@ msgid "To GitLab"
msgstr "В GitLab"
msgid "To access this domain create a new DNS record"
-msgstr ""
+msgstr "Щоб отримати доступ до цього домену, створіть новий запис DNS"
msgid "To add an SSH key you need to %{generate_link_start}generate one%{link_end} or use an %{existing_link_start}existing key%{link_end}."
msgstr "Для того, щоб додати ключ SSH вам необхідно %{generate_link_start}згенерувати його%{link_end} або використати %{existing_link_start}існуючий ключ%{link_end}."
@@ -10056,7 +10638,7 @@ msgid "To keep this project going, create a new merge request"
msgstr ""
msgid "To link Sentry to GitLab, enter your Sentry URL and Auth Token."
-msgstr ""
+msgstr "Для інтеграції Sentry із Gitlab введіть URL-адресу Sentry та токен автентифікації."
msgid "To move or copy an entire GitLab project from another GitLab installation to this one, navigate to the original project's settings page, generate an export file, and upload it here."
msgstr "Щоб перемістити або скопіювати весь проект GitLab з іншої інсталяції GitLab до цього, перейдіть на сторінку налаштувань оригіналу проекту, створіть файл експорту та надішліть його сюди."
@@ -10068,19 +10650,19 @@ msgid "To open Jaeger and easily view tracing from GitLab, link the %{link} page
msgstr ""
msgid "To preserve performance only <strong>%{display_size} of %{real_size}</strong> files are displayed."
-msgstr ""
+msgstr "Для збереження швидкодії відображаються лише <strong>%{display_size} із %{real_size}</strong> файлів."
msgid "To receive alerts from manually configured Prometheus services, add the following URL and Authorization key to your Prometheus webhook config file. Learn more about %{linkStart}configuring Prometheus%{linkEnd} to send alerts to GitLab."
msgstr ""
msgid "To set up SAML authentication for your group through an identity provider like Azure, Okta, Onelogin, Ping Identity, or your custom SAML 2.0 provider:"
-msgstr "Щоб налаштувати аутентифікацію SAML для вашої групи через провайдера ідентифікації такої як Azure, Okta, Onelogin, Ping Identity або вашого власного постачальника SAML 2.0:"
+msgstr "Щоб налаштувати автентифікацію SAML для вашої групи через провайдера ідентифікації такої як Azure, Okta, Onelogin, Ping Identity або вашого власного постачальника SAML 2.0:"
msgid "To start serving your jobs you can add Runners to your group"
msgstr "Для виконання ваших завдань ви можете додати Runner’и до вашої групи"
msgid "To start serving your jobs you can either add specific Runners to your project or use shared Runners"
-msgstr ""
+msgstr "Щоб почати ваші завдання виконувалися, ви можете або додати спеціальні Runner’и до вашого проекту, або використовувати загальні"
msgid "To this GitLab instance"
msgstr "До цього інстансу GitLab"
@@ -10092,7 +10674,7 @@ msgid "To view the roadmap, add a start or due date to one of your epics in this
msgstr "Для перегляду плану-графіку, додайте дату початку чи закінчення до одного з ваших епіків в цій групі або її підгрупах. При помісячному перегляді показуються тільки епіки за попередній, поточний, та наступні 5 місяців."
msgid "To widen your search, change or remove filters above"
-msgstr ""
+msgstr "Щоб розширити пошук, змініть або видаліть фільтри вище"
msgid "To widen your search, change or remove filters."
msgstr "Щоб розширити пошук, змініть або видаліть фільтри."
@@ -10101,22 +10683,22 @@ msgid "Today"
msgstr "Сьогодні"
msgid "Todo"
-msgstr "Задача"
+msgstr "Нагдування"
msgid "Todos"
-msgstr "Задачі"
+msgstr "Нагадування"
msgid "Toggle Sidebar"
msgstr "Перемикач бічної панелі"
msgid "Toggle comments for this file"
-msgstr ""
+msgstr "Увімкнути або вимкнути коментарі для цього файлу"
msgid "Toggle commit description"
msgstr "Перемкнути опис комітів"
msgid "Toggle commit list"
-msgstr ""
+msgstr "Відкрити або закрити список комітів"
msgid "Toggle discussion"
msgstr "Перемикач дискусії"
@@ -10182,9 +10764,12 @@ msgid "Trigger this manual action"
msgstr "Запустити цю ручну дію"
msgid "Trigger token:"
-msgstr ""
+msgstr "Токен тригера:"
msgid "Trigger variables:"
+msgstr "Змінні тригера:"
+
+msgid "Triggerer"
msgstr ""
msgid "Triggers can force a specific branch or tag to get rebuilt with an API call. These tokens will impersonate their associated user including their access to projects and their project permissions."
@@ -10197,13 +10782,13 @@ msgid "Try again"
msgstr "Спробуйте ще раз"
msgid "Try again?"
-msgstr ""
+msgstr "Спробуйте ще раз?"
msgid "Try all GitLab has to offer for 30 days."
msgstr "Спробуйте всі функції GitLab протягом 30 днів."
msgid "Trying to communicate with your device. Plug it in (if you haven't already) and press the button on the device now."
-msgstr ""
+msgstr "Відбувається з'єднання із вашим пристроєм. Підключіть його (якщо ви цього ще не зробили) і натисніть кнопку на ньому зараз."
msgid "Turn on Service Desk"
msgstr "Ввімкнути Service Desk"
@@ -10218,7 +10803,7 @@ msgid "Type"
msgstr "Тип"
msgid "URL"
-msgstr ""
+msgstr "URL"
msgid "Unable to load the diff. %{button_try_again}"
msgstr "Неможливо завантажити порівняння (diff). %{button_try_again}"
@@ -10230,13 +10815,13 @@ msgid "Unable to update this epic at this time."
msgstr "Неможливо оновити цей епік в даний момент."
msgid "Unblock"
-msgstr ""
+msgstr "Розблокувати"
msgid "Undo"
msgstr "Скасувати"
msgid "Unfortunately, your email message to GitLab could not be processed."
-msgstr ""
+msgstr "На жаль, ваше повідомлення електронної пошти до GitLab не може бути оброблено."
msgid "Unknown"
msgstr "Невідомо"
@@ -10287,7 +10872,7 @@ msgid "Unsubscribe at project level"
msgstr "Відписатися на рівні проекту"
msgid "Unsubscribe from %{type}"
-msgstr ""
+msgstr "Відписатися від %{type}"
msgid "Unverified"
msgstr "Непідтверджено"
@@ -10301,8 +10886,11 @@ msgstr "Незабаром"
msgid "Update"
msgstr "Оновити"
+msgid "Update approvers"
+msgstr "Оновити затверджуючих осіб"
+
msgid "Update failed"
-msgstr ""
+msgstr "Оновлення не вдалося"
msgid "Update now"
msgstr "Оновити зараз"
@@ -10310,11 +10898,17 @@ msgstr "Оновити зараз"
msgid "Update your group name, description, avatar, and visibility."
msgstr "Оновіть ім’я групи, опис, аватар та видимість."
+msgid "Update your project name, tags, description and avatar."
+msgstr ""
+
+msgid "Updated"
+msgstr ""
+
msgid "Updating"
msgstr "Оновлення"
msgid "Upgrade plan to unlock Canary Deployments feature"
-msgstr ""
+msgstr "Перейдіть на вищий тарифний план, щоб отримати доступ до функціональності Canary Deployments"
msgid "Upgrade your plan to activate Advanced Global Search."
msgstr "Перейдіть на вищий тарифний план щоб активувати Покращений Глобальний Пошук."
@@ -10335,16 +10929,16 @@ msgid "Upload <code>GoogleCodeProjectHosting.json</code> here:"
msgstr "Надіслати <code>GoogleCodeProjectHosting.json</code> тут:"
msgid "Upload CSV file"
-msgstr ""
+msgstr "Завантажити CSV файл"
msgid "Upload New File"
msgstr "Надіслати новий файл"
msgid "Upload a certificate for your domain with all intermediates"
-msgstr ""
+msgstr "Завантажити сертифікат для вашого домену з усіма проміжними"
msgid "Upload a private key for your certificate"
-msgstr ""
+msgstr "Завантажити приватний ключ для сертифіката"
msgid "Upload file"
msgstr "Надіслати файл"
@@ -10389,13 +10983,13 @@ msgid "Use your global notification setting"
msgstr "Використовуються глобальні налаштування повідомлень"
msgid "Use your smart card to authenticate with the LDAP server."
-msgstr ""
+msgstr "Використовуйте смарт-карту для автентифікації на сервері LDAP."
msgid "Used by members to sign in to your group in GitLab"
msgstr "Використовується учасниками для входу у вашу групу в GitLab"
msgid "Used to help configure your identity provider"
-msgstr ""
+msgstr "Використовується для допомоги в налаштуванні провайдера ідентифікації"
msgid "User Cohorts are only shown when the %{usage_ping_link_start}usage ping%{usage_ping_link_end} is enabled."
msgstr "Когорти Користувачів показуються лише тоді, коли увімкнено %{usage_ping_link_start}збір даних про використання%{usage_ping_link_end}."
@@ -10422,22 +11016,22 @@ msgid "UserProfile|Edit profile"
msgstr "Редагувати профіль"
msgid "UserProfile|Explore public groups to find projects to contribute to."
-msgstr ""
+msgstr "Переглядайте публічні групи та знаходьте проекти для своїх внесків."
msgid "UserProfile|Groups"
msgstr "Групи"
msgid "UserProfile|Groups are the best way to manage projects and members."
-msgstr ""
+msgstr "Групи — це найкращий спосіб керувати проектами та учасниками."
msgid "UserProfile|Join or create a group to start contributing by commenting on issues or submitting merge requests!"
-msgstr ""
+msgstr "Приєднуйтесь до або створіть групу, щоб почати робити внески через коментарі до задач, а також надсилаючи запити на злиття!"
msgid "UserProfile|Most Recent Activity"
msgstr "Остання активність"
msgid "UserProfile|No snippets found."
-msgstr ""
+msgstr "Сніпетів не знайдено."
msgid "UserProfile|Overview"
msgstr "Огляд"
@@ -10452,19 +11046,19 @@ msgid "UserProfile|Snippets"
msgstr "Сніпети"
msgid "UserProfile|Snippets in GitLab can either be private, internal, or public."
-msgstr ""
+msgstr "Сніпети в GitLab можуть бути приватними, внутрішніми та публічними."
msgid "UserProfile|Subscribe"
msgstr "Підписатися"
msgid "UserProfile|This user doesn't have any personal projects"
-msgstr ""
+msgstr "Цей користувач не має особистих проектів"
msgid "UserProfile|This user has a private profile"
msgstr "Цей користувач має приватний профіль"
msgid "UserProfile|This user hasn't contributed to any projects"
-msgstr ""
+msgstr "Цей користувач не робив внесків до жодного із проектів"
msgid "UserProfile|View all"
msgstr "Переглянути все"
@@ -10473,31 +11067,31 @@ msgid "UserProfile|View user in admin area"
msgstr "Переглянути користувача в адмінці"
msgid "UserProfile|You can create a group for several dependent projects."
-msgstr ""
+msgstr "Ви можете створити групу для декількох залежних проектів."
msgid "UserProfile|You haven't created any personal projects."
-msgstr ""
+msgstr "Ви ще не створили жодного особистого проекту."
msgid "UserProfile|You haven't created any snippets."
-msgstr ""
+msgstr "Ви ще не створили жодних сніпетів."
msgid "UserProfile|Your projects can be available publicly, internally, or privately, at your choice."
-msgstr ""
+msgstr "За вашим вибором ваші проекти можуть бути доступні публічно, внутрішньо або приватно."
msgid "Users"
msgstr "Користувачі"
msgid "Users requesting access to"
-msgstr ""
+msgstr "Користувачі, які запитують доступ до"
msgid "Validate"
-msgstr ""
+msgstr "Перевірити"
msgid "Validate your GitLab CI configuration file"
-msgstr ""
+msgstr "Перевірити ваш файл конфігурації Gitlab CI"
msgid "Value"
-msgstr ""
+msgstr "Значення"
msgid "Various container registry settings."
msgstr "Різноманітні налаштування реєстру контейнерів."
@@ -10506,7 +11100,7 @@ msgid "Various email settings."
msgstr "Різноманітні налаштування пошти."
msgid "Various localization settings."
-msgstr ""
+msgstr "Різні параметри локалізації."
msgid "Various settings that affect GitLab performance."
msgstr "Різноманітні налаштування, що впливають на продуктивність GitLab."
@@ -10515,7 +11109,7 @@ msgid "Verification information"
msgstr "Інформація про перевірку"
msgid "Verification status"
-msgstr ""
+msgstr "Стан перевірки"
msgid "Verified"
msgstr "Підтверджено"
@@ -10530,14 +11124,17 @@ msgid "View app"
msgstr "Переглянути застосунок"
msgid "View deployment"
-msgstr ""
+msgstr "Переглянути розгортання"
msgid "View details: %{details_url}"
-msgstr ""
+msgstr "Переглянути деталі: %{details_url}"
msgid "View documentation"
msgstr "Переглянути документацію"
+msgid "View eligible approvers"
+msgstr "Переглянути доступних осіб для затвердження"
+
msgid "View epics list"
msgstr "Переглянути список епіків"
@@ -10547,8 +11144,8 @@ msgstr "Перегляд файла @ "
msgid "View group labels"
msgstr "Переглянути мітки групи"
-msgid "View issue"
-msgstr "Переглянути задачу"
+msgid "View in Sentry"
+msgstr ""
msgid "View it on GitLab"
msgstr "Переглянути це на GitLab"
@@ -10575,7 +11172,7 @@ msgid "View the documentation"
msgstr "Переглянути документацію"
msgid "Viewing commit"
-msgstr ""
+msgstr "Перегляд коміту"
msgid "Visibility and access controls"
msgstr "Налаштування видимості та доступу"
@@ -10586,6 +11183,9 @@ msgstr "Рівень видимості"
msgid "Visibility level:"
msgstr "Рівень видимості:"
+msgid "Visibility, project features, permissions"
+msgstr ""
+
msgid "Visibility:"
msgstr "Видимість:"
@@ -10602,10 +11202,10 @@ msgid "VisibilityLevel|Unknown"
msgstr "Невідомий"
msgid "Vulnerability Chart"
-msgstr ""
+msgstr "Статистика вразливостей"
msgid "Vulnerability List"
-msgstr ""
+msgstr "Список вразливостей"
msgid "Vulnerability|Class"
msgstr "Клас"
@@ -10632,7 +11232,7 @@ msgid "Vulnerability|Project"
msgstr "Проект"
msgid "Vulnerability|Report Type"
-msgstr ""
+msgstr "Тип звіту"
msgid "Vulnerability|Severity"
msgstr "Рівень"
@@ -10641,19 +11241,19 @@ msgid "Want to see the data? Please ask an administrator for access."
msgstr "Хочете побачити дані? Будь ласка, попросить у адміністратора доступ."
msgid "We can't find an epic that matches what you are looking for."
-msgstr ""
+msgstr "Ми не можемо знайти епік, який відповідає тому, що ви шукаєте."
msgid "We can't find an issue that matches what you are looking for."
-msgstr ""
+msgstr "Ми не можемо знайти задачу, яка відповідає тому, що ви шукаєте."
msgid "We could not determine the path to remove the epic"
-msgstr ""
+msgstr "Ми не змогли визначити шлях до видалення епіку"
msgid "We could not determine the path to remove the issue"
-msgstr ""
+msgstr "Ми не змогли визначити шлях до видалення задачі"
msgid "We couldn't find any results matching"
-msgstr ""
+msgstr "Не вдалося знайти відповідні результати"
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr "Ми виявили потенційний спам у %{humanized_resource_name}. Будь ласка, введіть цей код підтвердження reCAPTCHA, щоб продовжити."
@@ -10662,7 +11262,7 @@ msgid "We don't have enough data to show this stage."
msgstr "Ми не маємо достатньо даних для відображення цієї стадії."
msgid "We heard back from your U2F device. You have been authenticated."
-msgstr ""
+msgstr "Ми отримали відповідь від вашого пристрою U2F. Ви пройшли автентифікацію."
msgid "We want to be sure it is you, please confirm you are not a robot."
msgstr "Ми хочемо бути впевнені, що це ви, будь ласка, підтвердіть, що ви не робот."
@@ -10671,7 +11271,7 @@ msgid "Web IDE"
msgstr "Веб-IDE"
msgid "Web Terminal"
-msgstr ""
+msgstr "Веб-термінал"
msgid "Web terminal"
msgstr "Веб-термінал"
@@ -10698,7 +11298,7 @@ msgid "When leaving the URL blank, classification labels can still be specified
msgstr "Якщо залишити URL порожнім, можна встановлювати мітки класифікації без вимкнення функцій проекту та виконання зовнішньої авторизації."
msgid "When:"
-msgstr ""
+msgstr "Коли:"
msgid "Who can see this group?"
msgstr "Хто може бачити цю групу?"
@@ -10842,7 +11442,7 @@ msgid "Wiki|Wiki Pages"
msgstr "Вікі-сторінки"
msgid "Will deploy to"
-msgstr ""
+msgstr "Розгорне на"
msgid "With contribution analytics you can have an overview for the activity of issues, merge requests and push events of your organization and its members."
msgstr "З аналітикою контриб’юторів ви може вивчати активність в задачах, запитах на злиття і подій відправки коду для вашої організації і її учасників."
@@ -10850,11 +11450,17 @@ msgstr "З аналітикою контриб’юторів ви може ви
msgid "Withdraw Access Request"
msgstr "Скасувати запит доступу"
+msgid "Write"
+msgstr ""
+
msgid "Write a comment or drag your files here…"
+msgstr "Напишіть коментар або перетягніть файли сюди…"
+
+msgid "Write access allowed"
msgstr ""
msgid "Write milestone description..."
-msgstr ""
+msgstr "Створити опис етапу..."
msgid "Yes"
msgstr "Так"
@@ -10869,7 +11475,7 @@ msgid "Yesterday"
msgstr "Вчора"
msgid "You"
-msgstr ""
+msgstr "Ви"
msgid "You are an admin, which means granting access to <strong>%{client_name}</strong> will allow them to interact with GitLab as an admin as well. Proceed with caution."
msgstr "Ви — адміністратор, а це означає, що надання доступу для <strong>%{client_name}</strong> дозволить їм взаємодіяти з GitLab як адміністратору. Продовжуйте обережно."
@@ -10890,7 +11496,7 @@ msgid "You are on a read-only GitLab instance."
msgstr "Ви знаходитеся на інстансі Gitlab \"тільки для читання\"."
msgid "You are receiving this message because you are a GitLab administrator for %{url}."
-msgstr ""
+msgstr "Ви отримуєте це повідомлення, бо ви є адміністратором GitLab для %{url}."
msgid "You can %{linkStart}view the blob%{linkEnd} instead."
msgstr "Замість цього ви можете %{linkStart}переглянути бінарні дані%{linkEnd}."
@@ -10917,7 +11523,7 @@ msgid "You can only edit files when you are on a branch"
msgstr "Ви можете редагувати файли, лише перебуваючи у якійсь гілці"
msgid "You can only merge once the items above are resolved"
-msgstr ""
+msgstr "Ви можете зливати лише коли вищезгадані пункти будуть вирішені"
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr "Ви можете розв’язати цей конфлікт злиття за допомогою інтерактивного режиму (використовуючи кнопки %{use_ours} та %{use_theirs}), або безпосередньо редагуючи файли. Закомітити зміни у %{branch_name}"
@@ -10938,7 +11544,7 @@ msgid "You do not have any subscriptions yet"
msgstr "У вас ще немає підписок"
msgid "You do not have permission to run the Web Terminal. Please contact a project administrator."
-msgstr ""
+msgstr "Ви не маєте дозволу запускати Веб-термінал. Будь ласка, зверніться до адміністратора проекту."
msgid "You do not have the correct permissions to override the settings from the LDAP group sync."
msgstr "У вас немає необхідних прав доступу, щоб перевизначити налаштування синхронізації LDAP-груп."
@@ -10950,16 +11556,19 @@ msgid "You don't have any authorized applications"
msgstr "Ви не маєте ніяких авторизованих застосунків"
msgid "You don't have any deployments right now."
-msgstr ""
+msgstr "Наразі у вас немає ніяких розгортань."
msgid "You have no permissions"
msgstr "У вас немає прав доступу"
+msgid "You have not added any approvers. Start by adding users or groups."
+msgstr "Ви не додали жодної затверджуючої особи. Почніть з додавання користувачів або груп."
+
msgid "You have reached your project limit"
msgstr "Ви досягли свого ліміту по кількості проектів"
msgid "You may also add variables that are made available to the running application by prepending the variable key with <code>K8S_SECRET_</code>."
-msgstr ""
+msgstr "Ви також можете додати змінні, що будуть доступними для запущеного застосунку шляхом додавання префіксу <code>K8S_SECRET_</code> до їх імен."
msgid "You must accept our Terms of Service and privacy policy in order to register an account"
msgstr "Ви повинні прийняти правила користування сервісом і політику конфіденційності для того, щоб створити обліковий запис"
@@ -10971,7 +11580,7 @@ msgid "You need a different license to enable FileLocks feature"
msgstr "Для активації функції Блокування Файлів вам потрібна інша ліцензія"
msgid "You need a different license to enable Geo replication."
-msgstr ""
+msgstr "Вам потрібна інша ліцензія на використання Geo реплікації."
msgid "You need git-lfs version %{min_git_lfs_version} (or greater) to continue. Please visit https://git-lfs.github.com"
msgstr "Вам потрібна версія git-lfs версії %{min_git_lfs_version} (або новіша), щоб продовжити. Будь ласка, відвідайте сторінку https://git-lfs.github.com"
@@ -10980,13 +11589,13 @@ msgid "You need permission."
msgstr "Вам потрібен дозвіл"
msgid "You need to register a two-factor authentication app before you can set up a U2F device."
-msgstr ""
+msgstr "Вам треба зареєструвати програму для двофакторної автентифікації перед налаштуванням пристрою U2F."
msgid "You will lose all changes you've made to this file. This action cannot be undone."
-msgstr ""
+msgstr "Ви втратите всі зміни, внесені вами в цей файл. Цю дію не можна скасувати."
msgid "You will lose all the unstaged changes you've made in this project. This action cannot be undone."
-msgstr ""
+msgstr "Ви втратите всі неіндексовані зміни, внесені вами в цей проект. Цю дію не можна скасувати."
msgid "You will not get any notifications via email"
msgstr "Ви не отримаєте ніяких повідомлень по електронній пошті"
@@ -11015,6 +11624,9 @@ msgstr "Ви не зможете відправляти та отримуват
msgid "You'll need to use different branch names to get a valid comparison."
msgstr "Вам необхідно використовувати різні імена гілок для коректного порівняння."
+msgid "You're only seeing %{startTag}other activity%{endTag} in the feed. To add a comment, switch to one of the following options."
+msgstr ""
+
msgid "You're receiving this email because %{reason}."
msgstr "Ви отримали цей електронний лист, оскільки %{reason}."
@@ -11043,10 +11655,10 @@ msgid "Your Projects' Activity"
msgstr "Активність ваших проектів"
msgid "Your Todos"
-msgstr "Ваші Задачі"
+msgstr "Ваші Нагадування"
msgid "Your U2F device needs to be set up. Plug it in (if not already) and click the button on the left."
-msgstr ""
+msgstr "Ваш пристрій U2F має бути налаштований. Підключіть його (якщо ви цього ще не зробили) і натисніть кнопку зліва."
msgid "Your applications (%{size})"
msgstr "Ваші застосунки (%{size})"
@@ -11055,7 +11667,7 @@ msgid "Your authorized applications"
msgstr "Ваші авторизовані застосунки"
msgid "Your browser doesn't support U2F. Please use Google Chrome desktop (version 41 or newer)."
-msgstr ""
+msgstr "Ваш браузер не підтримує U2F. Будь ласка, використовйте Google Chrome для комп'ютера (версію 41 або новішу)."
msgid "Your changes can be committed to %{branch_name} because a merge request is open."
msgstr "Ваші зміни можуть бути закомічені до %{branch_name}, оскільки запит на злиття відкритий."
@@ -11070,22 +11682,22 @@ msgid "Your comment will not be visible to the public."
msgstr "Ваш коментар не буде видимим для всіх."
msgid "Your device was successfully set up! Give it a name and register it with the GitLab server."
-msgstr ""
+msgstr "Ваш пристрій успішно налаштовано! Дайте йому ім'я та зареєструйте його на сервері GitLab."
msgid "Your groups"
msgstr "Ваші групи"
msgid "Your issues are being imported. Once finished, you'll get a confirmation email."
-msgstr ""
+msgstr "Ваші задачі імпортуються. Після завершення ви отримаєте повідомлення по електронній пошті із підтвердженням."
msgid "Your issues will be imported in the background. Once finished, you'll get a confirmation email."
-msgstr ""
+msgstr "Ваші задачі будуть імпортовані в фоні. Після завершення ви отримаєте повідомлення по електронній пошті із підтвердженням."
msgid "Your name"
msgstr "Ваше ім'я"
msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
-msgstr ""
+msgstr "Ваш ліміт проектів складає %{limit}! Будь ласка, зверніться до адміністратора, щоб його збільшити"
msgid "Your projects"
msgstr "Ваші проекти"
@@ -11093,6 +11705,9 @@ msgstr "Ваші проекти"
msgid "a deleted user"
msgstr "видалений користувач"
+msgid "added %{created_at_timeago}"
+msgstr ""
+
msgid "ago"
msgstr "тому"
@@ -11105,7 +11720,13 @@ msgstr "тощо"
msgid "assign yourself"
msgstr "призначити себе"
+msgid "at"
+msgstr ""
+
msgid "attach a new file"
+msgstr "прикріпити новий файл"
+
+msgid "authored"
msgstr ""
msgid "branch name"
@@ -11132,6 +11753,20 @@ msgstr "%{vulnerability} впливає на %{namespace}."
msgid "ciReport|%{remainingPackagesCount} more"
msgstr "%{remainingPackagesCount} більше"
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
+msgid "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerability for the source branch only"
+msgid_plural "ciReport|%{reportType} %{status} detected %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr[0] ""
+msgstr[1] ""
+msgstr[2] ""
+msgstr[3] ""
+
msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{fixedCount} fixed vulnerabilities"
msgstr[0] "%{reportType} %{status} виявив %{fixedCount} виправлену вразливість"
@@ -11139,6 +11774,9 @@ msgstr[1] "%{reportType} %{status} виявив %{fixedCount} виправлен
msgstr[2] "%{reportType} %{status} виявив %{fixedCount} виправлених вразливостей"
msgstr[3] "%{reportType} %{status} виявив %{fixedCount} виправлених вразливостей"
+msgid "ciReport|%{reportType} %{status} detected %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new vulnerability"
msgid_plural "ciReport|%{reportType} %{status} detected %{newCount} new vulnerabilities"
msgstr[0] "%{reportType} %{status} виявив %{newCount} нову вразливість"
@@ -11146,6 +11784,15 @@ msgstr[1] "%{reportType} %{status} виявив %{newCount} нові вразл
msgstr[2] "%{reportType} %{status} виявив %{newCount} нових вразливостей"
msgstr[3] "%{reportType} %{status} виявив %{newCount} нових вразливостей"
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, %{fixedCount} fixed, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities"
+msgstr ""
+
+msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{dismissedCount} dismissed vulnerabilities for the source branch only"
+msgstr ""
+
msgid "ciReport|%{reportType} %{status} detected %{newCount} new, and %{fixedCount} fixed vulnerabilities"
msgstr "%{reportType} %{status} виявив %{newCount} нових та %{fixedCount} виправлених вразливостей"
@@ -11199,17 +11846,29 @@ msgstr "Якість коду"
msgid "ciReport|Confidence"
msgstr "Впевненість"
+msgid "ciReport|Container Scanning"
+msgstr ""
+
msgid "ciReport|Container scanning"
msgstr "Сканування контейнера"
msgid "ciReport|Container scanning detects known vulnerabilities in your docker images."
msgstr "Сканування контейнерів виявляє відомі вразливості у ваших Docker образах."
+msgid "ciReport|Create issue"
+msgstr ""
+
+msgid "ciReport|Create merge request"
+msgstr ""
+
+msgid "ciReport|Created %{eventType}"
+msgstr ""
+
msgid "ciReport|DAST"
msgstr "DAST"
msgid "ciReport|Dependency Scanning"
-msgstr ""
+msgstr "Сканування залежностей"
msgid "ciReport|Dependency Scanning detects known vulnerabilities in your source code's dependencies."
msgstr "Сканування залежностей виявляє відомі вразливості у залежностях вашого коду."
@@ -11227,10 +11886,10 @@ msgid "ciReport|Dismissed by"
msgstr "Відхилено"
msgid "ciReport|Download and apply the patch to fix this vulnerability."
-msgstr ""
+msgstr "Завантажити або застосувати патч, щоб виправити цю вразливість."
msgid "ciReport|Download patch"
-msgstr ""
+msgstr "Завантажити патч"
msgid "ciReport|Dynamic Application Security Testing (DAST) detects known vulnerabilities in your web application."
msgstr "Динамічне тестування безпеки застосунків (DAST) виявляє відомі вразливості у вашому веб-застосунку."
@@ -11247,9 +11906,15 @@ msgstr "Виправлено:"
msgid "ciReport|Identifiers"
msgstr "Ідентифікатори"
+msgid "ciReport|Implement this solution by creating a merge request"
+msgstr ""
+
msgid "ciReport|Instances"
msgstr "Інстанси"
+msgid "ciReport|Investigate this vulnerability by creating an issue"
+msgstr ""
+
msgid "ciReport|Learn more about interacting with security reports (Alpha)."
msgstr "Дізнатися більше про взаємодію з звітами безпеки (Альфа)."
@@ -11297,9 +11962,6 @@ msgstr "Немає змін у показниках продуктивності
msgid "ciReport|Performance metrics"
msgstr "Показники продуктивності"
-msgid "ciReport|Revert dismissal"
-msgstr "Відмінити відхилення"
-
msgid "ciReport|SAST"
msgstr "SAST"
@@ -11321,6 +11983,9 @@ msgstr "Статичне тестування безпеки застосунк
msgid "ciReport|There was an error creating the issue. Please try again."
msgstr "Помилка при створенні задачі. Будь ласка спробуйте знову."
+msgid "ciReport|There was an error creating the merge request. Please try again."
+msgstr ""
+
msgid "ciReport|There was an error dismissing the vulnerability. Please try again."
msgstr "Помилка при відхиленні вразливості. Будь ласка, спробуйте знову."
@@ -11339,6 +12004,9 @@ msgstr "Помилка при завантаженні звіту по скан
msgid "ciReport|There was an error reverting the dismissal. Please try again."
msgstr "Помилка при відміні відхилення. Будь ласка, спробуйте знову."
+msgid "ciReport|Undo dismiss"
+msgstr ""
+
msgid "ciReport|Upgrade %{name} from %{version} to %{fixed}."
msgstr "Оновити %{name} з %{version} до %{fixed}."
@@ -11359,7 +12027,7 @@ msgid "command line instructions"
msgstr "інструкції для командного рядка"
msgid "commented on %{link_to_project}"
-msgstr ""
+msgstr "прокоментовано в %{link_to_project}"
msgid "confidentiality|You are going to turn off the confidentiality. This means <strong>everyone</strong> will be able to see and leave a comment on this issue."
msgstr "Ви вимикаєте конфіденційність. Це означає, що <strong>будь-хто</strong> зможе бачити і залишати коментарі для цієї задачі."
@@ -11384,7 +12052,7 @@ msgstr[2] "днів"
msgstr[3] "днів"
msgid "deleted"
-msgstr ""
+msgstr "видалено"
msgid "deploy token"
msgstr "токен для розгортання"
@@ -11394,10 +12062,10 @@ msgstr "вимкнено"
msgid "discussion resolved"
msgid_plural "discussions resolved"
-msgstr[0] ""
-msgstr[1] ""
-msgstr[2] ""
-msgstr[3] ""
+msgstr[0] "обговорення завершено"
+msgstr[1] "обговорення завершено"
+msgstr[2] "обговорень завершено"
+msgstr[3] "обговорень завершено"
msgid "done"
msgstr "готово"
@@ -11413,13 +12081,13 @@ msgid "enabled"
msgstr "увімкнено"
msgid "epic"
-msgstr ""
+msgstr "епік"
msgid "error"
-msgstr ""
+msgstr "помилка"
msgid "error code:"
-msgstr ""
+msgstr "код помилки:"
msgid "estimateCommand|%{slash_command} will update the estimated time with the latest command."
msgstr "%{slash_command} перезаписує запланований час останнім значенням."
@@ -11431,7 +12099,7 @@ msgid "from"
msgstr "від"
msgid "group"
-msgstr ""
+msgstr "група"
msgid "help"
msgstr "допомога"
@@ -11439,9 +12107,6 @@ msgstr "допомога"
msgid "here"
msgstr "тут"
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
-msgstr ""
-
msgid "https://your-bitbucket-server"
msgstr "https://your-bitbucket-server"
@@ -11455,10 +12120,10 @@ msgid "importing"
msgstr "імпорт"
msgid "in group %{link_to_group}"
-msgstr ""
+msgstr "в групі %{link_to_group}"
msgid "in project %{link_to_project}"
-msgstr ""
+msgstr "в проекті %{link_to_project}"
msgid "index"
msgstr ""
@@ -11483,7 +12148,7 @@ msgid "is out of the hierarchy of the Group owning the template"
msgstr ""
msgid "issue"
-msgstr ""
+msgstr "задача"
msgid "issue boards"
msgstr "дошки обговорення задач"
@@ -11492,13 +12157,13 @@ msgid "it is stored externally"
msgstr ""
msgid "it is stored in LFS"
-msgstr ""
+msgstr "зберігається в LFS"
msgid "it is too large"
msgstr ""
msgid "latest"
-msgstr ""
+msgstr "останній"
msgid "latest deployment"
msgstr "останнє розгортання"
@@ -11523,19 +12188,22 @@ msgstr[2] "запитів на злиття"
msgstr[3] "запитів на злиття"
msgid "missing"
-msgstr ""
+msgstr "відсутні"
msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
+msgstr "%{commitCount} і %{mergeCommitCount} буде додано до %{targetBranch}."
+
+msgid "mrWidgetCommitsAdded|%{commitCount} will be added to %{targetBranch}."
msgstr ""
msgid "mrWidgetCommitsAdded|1 merge commit"
-msgstr ""
+msgstr "1 коміт-злиття"
msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
msgstr "Будь ласка відновіть її або використовуйте іншу %{missingBranchName} гілку"
msgid "mrWidget|%{link_start}Learn more about resolving conflicts%{link_end}"
-msgstr ""
+msgstr "%{link_start}Докладніше про вирішення конфліктів%{link_end}"
msgid "mrWidget|%{metricsLinkStart} Memory %{metricsLinkEnd} usage %{emphasisStart} decreased %{emphasisEnd} from %{memoryFrom}MB to %{memoryTo}MB"
msgstr "Використання %{metricsLinkStart} пам’яті %{metricsLinkEnd} %{emphasisStart} впало %{emphasisEnd} з %{memoryFrom}Мб до %{memoryTo}Мб"
@@ -11553,10 +12221,10 @@ msgid "mrWidget|Allows commits from members who can merge to the target branch"
msgstr "Дозволяє коміти від учасників, які можуть зливати до цільової гілки"
msgid "mrWidget|An error occurred while removing your approval."
-msgstr ""
+msgstr "Під час видалення вашого затвердження сталася помилка."
msgid "mrWidget|An error occurred while retrieving approval data for this merge request."
-msgstr ""
+msgstr "Помилка при отриманні даних про затвердження для цього запиту на злиття."
msgid "mrWidget|An error occurred while submitting your approval."
msgstr "Помилка при обробці вашого затвердження."
@@ -11564,6 +12232,9 @@ msgstr "Помилка при обробці вашого затвердженн
msgid "mrWidget|Approve"
msgstr "Затвердити"
+msgid "mrWidget|Approve additionally"
+msgstr ""
+
msgid "mrWidget|Approved by"
msgstr "Затверджено"
@@ -11595,7 +12266,7 @@ msgid "mrWidget|Create an issue to resolve them later"
msgstr "Створіть задачу, щоб вирішити їх пізніше"
msgid "mrWidget|Delete source branch"
-msgstr ""
+msgstr "Видалити гілку-джерело"
msgid "mrWidget|Deployment statistics are not available currently"
msgstr "Статистика розгортання наразі недоступна"
@@ -11636,17 +12307,20 @@ msgstr "Злити локально"
msgid "mrWidget|Merge request approved"
msgstr "Запит на злиття затверджено"
+msgid "mrWidget|Merge request approved."
+msgstr ""
+
msgid "mrWidget|Merge request approved; you can approve additionally"
msgstr "Запит на злиття було затверджено; Ви можете затвердити додатково"
msgid "mrWidget|Merged by"
msgstr "Злито"
-msgid "mrWidget|No Approval required"
-msgstr "Затвердження не потрібне"
+msgid "mrWidget|No approval required"
+msgstr ""
-msgid "mrWidget|No Approval required; you can still approve"
-msgstr "Затвердження не є обов’язковим; але ви все одно можете це зробити"
+msgid "mrWidget|No approval required; you can still approve"
+msgstr ""
msgid "mrWidget|Open in Web IDE"
msgstr "Відкрити у Web IDE"
@@ -11701,6 +12375,9 @@ msgstr "Анулювати"
msgid "mrWidget|Revert this merge request in a new merge request"
msgstr "Анулювати цей запит на злиття за допомогою нового запиту на злиття"
+msgid "mrWidget|Revoke approval"
+msgstr ""
+
msgid "mrWidget|Set by"
msgstr "Встановлено"
@@ -11720,19 +12397,19 @@ msgid "mrWidget|The source branch HEAD has recently changed. Please reload the p
msgstr "HEAD гілки-джерела нещодавно було змінено. Будь ласка оновіть сторінку і перегляньте зміни перед злиттям"
msgid "mrWidget|The source branch has been deleted"
-msgstr ""
+msgstr "Гілку-джерело видалено"
msgid "mrWidget|The source branch is %{commitsBehindLinkStart}%{commitsBehind}%{commitsBehindLinkEnd} the target branch"
msgstr "Гілка-джерело на %{commitsBehindLinkStart}%{commitsBehind}%{commitsBehindLinkEnd} цільової гілки"
msgid "mrWidget|The source branch is being deleted"
-msgstr ""
+msgstr "Гілка-джерело в процесі видалення"
msgid "mrWidget|The source branch will be deleted"
-msgstr ""
+msgstr "Гілку-джерело буде видалено"
msgid "mrWidget|The source branch will not be deleted"
-msgstr ""
+msgstr "Гілку-джерело не буде видалено"
msgid "mrWidget|There are merge conflicts"
msgstr "існують конфлікти при злитті"
@@ -11741,7 +12418,7 @@ msgid "mrWidget|There are unresolved discussions. Please resolve these discussio
msgstr "Присутні незавершені обговорення. Будь ласка завершіть їх"
msgid "mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected."
-msgstr ""
+msgstr "Ця функція зливає зміни з цільової гілки до гілки-джерела. Її не можна використовувати, оскільки гілка-джерело є захищеною."
msgid "mrWidget|This merge request failed to be merged automatically"
msgstr "Відбулася помилка при автоматичному злитті цього запиту"
@@ -11756,7 +12433,7 @@ msgid "mrWidget|You are not allowed to edit this project directly. Please fork t
msgstr "Ви не можете безпосередньо редагувати цей проект. Будь ласка, зробіть форк, щоб внести зміни."
msgid "mrWidget|You can delete the source branch now"
-msgstr ""
+msgstr "Тепер ви можете видалити гілку-джерело"
msgid "mrWidget|You can merge this merge request manually using the"
msgstr "Ви можете прийняти цей запит на злиття вручну за допомогою"
@@ -11780,19 +12457,19 @@ msgid "new merge request"
msgstr "Новий запит на злиття"
msgid "none"
-msgstr ""
+msgstr "немає"
msgid "notification emails"
msgstr "Повідомлення електронною поштою"
msgid "nounSeries|%{firstItem} and %{lastItem}"
-msgstr ""
+msgstr "%{firstItem} і %{lastItem}"
msgid "nounSeries|%{item}, %{nextItem}"
-msgstr ""
+msgstr "%{item}, %{nextItem}"
msgid "nounSeries|%{item}, and %{lastItem}"
-msgstr ""
+msgstr "%{item}, і %{lastItem}"
msgid "or"
msgstr "або"
@@ -11818,7 +12495,7 @@ msgid "personal access token"
msgstr "особистий токен доступу"
msgid "private"
-msgstr ""
+msgstr "приватний"
msgid "private key does not match certificate."
msgstr "приватний ключ не відповідає сертифікату."
@@ -11831,10 +12508,10 @@ msgstr[2] "проектів"
msgstr[3] "проектів"
msgid "quick actions"
-msgstr ""
+msgstr "швидкі дії"
msgid "register"
-msgstr ""
+msgstr "зареєструватися"
msgid "remaining"
msgstr "залишилось"
@@ -11859,16 +12536,19 @@ msgstr[2] "відповідей"
msgstr[3] "відповідей"
msgid "score"
+msgstr "результат"
+
+msgid "security Reports|There was an error creating the merge request"
msgstr ""
msgid "should be higher than %{access} inherited membership from group %{group_name}"
-msgstr ""
+msgstr "має бути вищим за %{access}, успадкованого з групи %{group_name}"
msgid "show less"
-msgstr ""
+msgstr "показати менше"
msgid "sign in"
-msgstr ""
+msgstr "увійти"
msgid "source"
msgstr "джерело"
@@ -11883,13 +12563,13 @@ msgid "started"
msgstr "розпочато"
msgid "stuck"
-msgstr ""
+msgstr "заблоковано"
msgid "syntax is correct"
-msgstr ""
+msgstr "синтаксис правильний"
msgid "syntax is incorrect"
-msgstr ""
+msgstr "синтаксис неправильний"
msgid "this document"
msgstr "цей документ"
@@ -11898,10 +12578,10 @@ msgid "to help your contributors communicate effectively!"
msgstr "щоб допомогти вашим контриб’юторам ефективно спілкуватися!"
msgid "triggered"
-msgstr ""
+msgstr "запущено"
msgid "updated"
-msgstr ""
+msgstr "оновлено"
msgid "username"
msgstr "ім'я користувача"
diff --git a/package.json b/package.json
index 7cdb11dca14..1154d57cad2 100644
--- a/package.json
+++ b/package.json
@@ -17,6 +17,7 @@
"prettier-all": "node ./scripts/frontend/prettier.js check-all",
"prettier-all-save": "node ./scripts/frontend/prettier.js save-all",
"stylelint": "node node_modules/stylelint/bin/stylelint.js app/assets/stylesheets/**/*.* --custom-formatter node_modules/stylelint-error-string-formatter",
+ "test": "yarn jest && yarn karma",
"webpack": "webpack --config config/webpack.config.js",
"webpack-prod": "NODE_ENV=production webpack --config config/webpack.config.js"
},
@@ -30,9 +31,10 @@
"@babel/preset-env": "^7.3.1",
"@gitlab/csslab": "^1.8.0",
"@gitlab/svgs": "^1.54.0",
- "@gitlab/ui": "^2.2.2",
+ "@gitlab/ui": "^3.0.0",
"apollo-boost": "^0.3.1",
"apollo-client": "^2.5.1",
+ "at.js": "^1.5.4",
"autosize": "^4.0.0",
"axios": "^0.17.1",
"babel-loader": "^8.0.5",
@@ -75,6 +77,7 @@
"jest-transform-graphql": "^2.1.0",
"jquery": "^3.2.1",
"jquery-ujs": "1.2.2",
+ "jquery.caret": "^0.3.1",
"jquery.waitforimages": "^2.2.0",
"js-cookie": "^2.1.3",
"jszip": "^3.1.3",
@@ -169,7 +172,7 @@
"nodemon": "^1.18.9",
"pixelmatch": "^4.0.2",
"postcss": "^7.0.14",
- "prettier": "1.16.1",
+ "prettier": "1.16.4",
"stylelint": "^9.10.1",
"stylelint-config-recommended": "^2.1.0",
"stylelint-scss": "^3.5.3",
diff --git a/qa/.gitignore b/qa/.gitignore
index 102f7e5e54d..b0ae074ac07 100644
--- a/qa/.gitignore
+++ b/qa/.gitignore
@@ -1,3 +1,3 @@
tmp/
.ruby-version
-urls.txt
+urls.yml
diff --git a/qa/Rakefile b/qa/Rakefile
index b6ad09f9b00..d0101740f1a 100644
--- a/qa/Rakefile
+++ b/qa/Rakefile
@@ -16,3 +16,25 @@ desc "Generate Performance Testdata"
task :generate_perf_testdata do
QA::Tools::GeneratePerfTestdata.new.run
end
+
+desc "Run artillery load tests"
+task :run_artillery_load_tests do
+ unless ENV['HOST_URL'] && ENV['LARGE_ISSUE_URL'] && ENV['LARGE_MR_URL']
+ urls_file = ENV['URLS_FILE_PATH'] || 'urls.yml'
+
+ unless File.exist?(urls_file)
+ raise "\n#{urls_file} file is missing. Please provide correct URLS_FILE_PATH or all of HOST_URL, LARGE_ISSUE_URL and LARGE_MR_URL\n\n"
+ end
+
+ urls = YAML.safe_load(File.read(urls_file))
+ ENV['HOST_URL'] = urls[:host]
+ ENV['LARGE_ISSUE_URL'] = urls[:large_issue]
+ ENV['LARGE_MR_URL'] = urls[:large_mr]
+ end
+
+ sh('artillery run load/artillery.yml -o report.json')
+ sh('artillery report report.json -o report.html && rm report.json')
+end
+
+desc "Generate data and run load tests"
+task generate_data_and_run_load_test: [:generate_perf_testdata, :run_artillery_load_tests]
diff --git a/qa/load/artillery.yml b/qa/load/artillery.yml
new file mode 100644
index 00000000000..e2c3c293d8b
--- /dev/null
+++ b/qa/load/artillery.yml
@@ -0,0 +1,22 @@
+config:
+ target: "{{ $processEnvironment.HOST_URL }}"
+ phases:
+ - duration: 60
+ arrivalRate: 1
+ name: "Warm up"
+ - duration: 120
+ arrivalRate: 1
+ rampTo: 50
+ name: "Gradual ramp up"
+ - duration: 60
+ arrivalRate: 50
+ name: "Sustained max load"
+scenarios:
+ - name: "Visit large issue url"
+ flow:
+ - get:
+ url: "{{ $processEnvironment.LARGE_ISSUE_URL }}"
+ - name: "Visit large MR url"
+ flow:
+ - get:
+ url: "{{ $processEnvironment.LARGE_MR_URL }}"
diff --git a/qa/qa.rb b/qa/qa.rb
index 2b3ffabbbaa..ec8aef31e48 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -61,6 +61,7 @@ module QA
autoload :File, 'qa/resource/file'
autoload :Fork, 'qa/resource/fork'
autoload :SSHKey, 'qa/resource/ssh_key'
+ autoload :Snippet, 'qa/resource/snippet'
module Events
autoload :Base, 'qa/resource/events/base'
@@ -142,6 +143,12 @@ module QA
module Dashboard
autoload :Projects, 'qa/page/dashboard/projects'
autoload :Groups, 'qa/page/dashboard/groups'
+
+ module Snippet
+ autoload :New, 'qa/page/dashboard/snippet/new'
+ autoload :Index, 'qa/page/dashboard/snippet/index'
+ autoload :Show, 'qa/page/dashboard/snippet/show'
+ end
end
module Group
@@ -342,6 +349,10 @@ module QA
module Specs
autoload :Config, 'qa/specs/config'
autoload :Runner, 'qa/specs/runner'
+
+ module Helpers
+ autoload :Quarantine, 'qa/specs/helpers/quarantine'
+ end
end
##
diff --git a/qa/qa/page/component/select2.rb b/qa/qa/page/component/select2.rb
index 98bcb96b92c..3c3461d6b20 100644
--- a/qa/qa/page/component/select2.rb
+++ b/qa/qa/page/component/select2.rb
@@ -3,7 +3,7 @@ module QA
module Component
module Select2
def select_item(item_text)
- find('.select2-result-label', text: item_text).click
+ find('.select2-result-label', text: item_text, match: :prefer_exact).click
end
def clear_current_selection_if_present
diff --git a/qa/qa/page/dashboard/projects.rb b/qa/qa/page/dashboard/projects.rb
index 0f434577b3b..271c5456efe 100644
--- a/qa/qa/page/dashboard/projects.rb
+++ b/qa/qa/page/dashboard/projects.rb
@@ -2,8 +2,6 @@ module QA
module Page
module Dashboard
class Projects < Page::Base
- view 'app/views/dashboard/projects/index.html.haml'
-
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
end
diff --git a/qa/qa/page/dashboard/snippet/index.rb b/qa/qa/page/dashboard/snippet/index.rb
new file mode 100644
index 00000000000..1f467fda9e1
--- /dev/null
+++ b/qa/qa/page/dashboard/snippet/index.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Dashboard
+ module Snippet
+ class Index < Page::Base
+ view 'app/views/layouts/header/_new_dropdown.haml' do
+ element :new_menu_toggle
+ element :global_new_snippet_link
+ end
+
+ def go_to_new_snippet_page
+ click_element :new_menu_toggle
+ click_element :global_new_snippet_link
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/dashboard/snippet/new.rb b/qa/qa/page/dashboard/snippet/new.rb
new file mode 100644
index 00000000000..a637b869d2f
--- /dev/null
+++ b/qa/qa/page/dashboard/snippet/new.rb
@@ -0,0 +1,53 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Dashboard
+ module Snippet
+ class New < Page::Base
+ view 'app/views/shared/form_elements/_description.html.haml' do
+ element :issuable_form_description
+ end
+
+ view 'app/views/shared/snippets/_form.html.haml' do
+ element :snippet_title
+ element :snippet_file_name
+ element :create_snippet_button
+ end
+
+ def fill_title(title)
+ fill_element :snippet_title, title
+ end
+
+ def fill_description(description)
+ fill_element :issuable_form_description, description
+ end
+
+ def set_visibility(visibility)
+ choose visibility
+ end
+
+ def fill_file_name(name)
+ finished_loading?
+ fill_element :snippet_file_name, name
+ end
+
+ def fill_file_content(content)
+ finished_loading?
+ text_area.set content
+ end
+
+ def create_snippet
+ click_element :create_snippet_button
+ end
+
+ private
+
+ def text_area
+ find('#editor>textarea', visible: false)
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/dashboard/snippet/show.rb b/qa/qa/page/dashboard/snippet/show.rb
new file mode 100644
index 00000000000..a75ea63eca7
--- /dev/null
+++ b/qa/qa/page/dashboard/snippet/show.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Dashboard
+ module Snippet
+ class Show < Page::Base
+ view 'app/views/shared/snippets/_header.html.haml' do
+ element :snippet_title
+ element :snippet_description
+ element :embed_type
+ element :snippet_box
+ end
+
+ view 'app/views/projects/blob/_header_content.html.haml' do
+ element :file_title_name
+ end
+
+ view 'app/views/shared/_file_highlight.html.haml' do
+ element :file_content
+ end
+
+ def has_snippet_title?(snippet_title)
+ within_element(:snippet_title) do
+ has_text?(snippet_title)
+ end
+ end
+
+ def has_snippet_description?(snippet_description)
+ within_element(:snippet_description) do
+ has_text?(snippet_description)
+ end
+ end
+
+ def has_embed_type?(embed_type)
+ within_element(:embed_type) do
+ has_text?(embed_type)
+ end
+ end
+
+ def has_visibility_type?(visibility_type)
+ within_element(:snippet_box) do
+ has_text?(visibility_type)
+ end
+ end
+
+ def has_file_name?(file_name)
+ within_element(:file_title_name) do
+ has_text?(file_name)
+ end
+ end
+
+ def has_file_content?(file_content)
+ finished_loading?
+ within_element(:file_content) do
+ has_text?(file_content)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/main/menu.rb b/qa/qa/page/main/menu.rb
index 55500e831c6..1b3445b0064 100644
--- a/qa/qa/page/main/menu.rb
+++ b/qa/qa/page/main/menu.rb
@@ -19,6 +19,7 @@ module QA
element :admin_area_link
element :projects_dropdown
element :groups_dropdown
+ element :snippets_link
end
view 'app/views/layouts/nav/projects_dropdown/_show.html.haml' do
@@ -66,6 +67,10 @@ module QA
end
end
+ def go_to_snippets
+ click_element :snippets_link
+ end
+
def has_personal_area?(wait: Capybara.default_max_wait_time)
has_element?(:user_avatar, wait: wait)
end
diff --git a/qa/qa/page/project/activity.rb b/qa/qa/page/project/activity.rb
index 56fbaa90790..afd4f49a844 100644
--- a/qa/qa/page/project/activity.rb
+++ b/qa/qa/page/project/activity.rb
@@ -6,7 +6,7 @@ module QA
element :push_events, "event_filter_link EventFilter::PUSH, _('Push events')" # rubocop:disable QA/ElementWithPattern
end
- def go_to_push_events
+ def click_push_events
click_on 'Push events'
end
end
diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb
index 46dfe87fe25..3fe048f752a 100644
--- a/qa/qa/page/project/menu.rb
+++ b/qa/qa/page/project/menu.rb
@@ -30,7 +30,7 @@ module QA
end
end
- def go_to_activity
+ def click_activity
within_sidebar do
click_element(:activity_link)
end
diff --git a/qa/qa/page/project/new.rb b/qa/qa/page/project/new.rb
index eabeae1acc4..552b2293115 100644
--- a/qa/qa/page/project/new.rb
+++ b/qa/qa/page/project/new.rb
@@ -24,11 +24,14 @@ module QA
end
def choose_test_namespace
+ choose_namespace(Runtime::Namespace.path)
+ end
+
+ def choose_namespace(namespace)
retry_on_exception do
click_body
click_element :project_namespace_select
-
- search_and_select(Runtime::Namespace.path)
+ search_and_select(namespace)
end
end
diff --git a/qa/qa/page/project/web_ide/edit.rb b/qa/qa/page/project/web_ide/edit.rb
index 2b6c01888d5..ff7cc04e352 100644
--- a/qa/qa/page/project/web_ide/edit.rb
+++ b/qa/qa/page/project/web_ide/edit.rb
@@ -43,11 +43,9 @@ module QA
def create_new_file_from_template(file_name, template)
click_element :new_file
within_element(:template_list) do
- begin
- click_on file_name
- rescue Capybara::ElementNotFound
- raise ElementNotFound, %Q(Couldn't find file template named "#{file_name}". Please confirm that it is a valid option.)
- end
+ click_on file_name
+ rescue Capybara::ElementNotFound
+ raise ElementNotFound, %Q(Couldn't find file template named "#{file_name}". Please confirm that it is a valid option.)
end
wait(reload: false) do
diff --git a/qa/qa/resource/snippet.rb b/qa/qa/resource/snippet.rb
new file mode 100644
index 00000000000..1478f197570
--- /dev/null
+++ b/qa/qa/resource/snippet.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module QA
+ module Resource
+ class Snippet < Base
+ attr_accessor :title, :description, :file_content, :visibility, :file_name
+
+ def initialize
+ @title = 'New snippet title'
+ @description = 'The snippet description'
+ @visibility = 'Public'
+ @file_content = 'The snippet content'
+ @file_name = 'New snippet file name'
+ end
+
+ def fabricate!
+ Page::Dashboard::Snippet::Index.perform(&:go_to_new_snippet_page)
+
+ Page::Dashboard::Snippet::New.perform do |page|
+ page.fill_title(@title)
+ page.fill_description(@description)
+ page.set_visibility(@visibility)
+ page.fill_file_name(@file_name)
+ page.fill_file_content(@file_content)
+ page.create_snippet
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
index 4070a225260..d8609aa037a 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/add_project_member_spec.rb
@@ -19,7 +19,7 @@ module QA
page.add_member(user.username)
end
- expect(page).to have_content("#{user.name} @#{user.username} Given access")
+ expect(page).to have_content(/#{user.name} (. )?@#{user.username} Given access/)
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
index d4cedc9362d..fe92fbd3ffe 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
@@ -14,8 +14,8 @@ module QA
end
project_push.project.visit!
- Page::Project::Menu.perform(&:go_to_activity)
- Page::Project::Activity.perform(&:go_to_push_events)
+ Page::Project::Menu.perform(&:click_activity)
+ Page::Project::Activity.perform(&:click_push_events)
expect(page).to have_content('pushed new branch master')
end
diff --git a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
index 7145b950b6c..358ab04eadc 100644
--- a/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
+++ b/qa/qa/specs/features/browser_ui/2_plan/issue/create_issue_spec.rb
@@ -5,15 +5,6 @@ module QA
describe 'Issue creation' do
let(:issue_title) { 'issue title' }
- def create_issue
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
-
- Resource::Issue.fabricate! do |issue|
- issue.title = issue_title
- end
- end
-
it 'user creates an issue' do
create_issue
@@ -46,6 +37,15 @@ module QA
end
end
end
+
+ def create_issue
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.act { sign_in_using_credentials }
+
+ Resource::Issue.fabricate! do |issue|
+ issue.title = issue_title
+ end
+ end
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
index c06f13ee204..1eada4a6c28 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/create_merge_request_spec.rb
@@ -3,7 +3,29 @@
module QA
context 'Create' do
describe 'Merge request creation' do
- it 'user creates a new merge request' do
+ it 'user creates a new merge request', :smoke do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.act { sign_in_using_credentials }
+
+ current_project = Resource::Project.fabricate! do |project|
+ project.name = 'project-with-merge-request'
+ end
+
+ merge_request_title = 'This is a merge request'
+ merge_request_description = 'Great feature'
+
+ Resource::MergeRequest.fabricate! do |merge_request|
+ merge_request.title = merge_request_title
+ merge_request.description = merge_request_description
+ merge_request.project = current_project
+ end
+
+ expect(page).to have_content(merge_request_title)
+ expect(page).to have_content(merge_request_description)
+ expect(page).to have_content(/Opened [\w\s]+ ago/)
+ end
+
+ it 'user creates a new merge request with a milestone and label' do
gitlab_account_username = "@#{Runtime::User.username}"
Runtime::Browser.visit(:gitlab, Page::Main::Login)
@@ -24,9 +46,12 @@ module QA
label.description = 'Merge Request label'
end
+ merge_request_title = 'This is a merge request with a milestone and a label'
+ merge_request_description = 'Great feature with milestone'
+
Resource::MergeRequest.fabricate! do |merge_request|
- merge_request.title = 'This is a merge request with a milestone'
- merge_request.description = 'Great feature with milestone'
+ merge_request.title = merge_request_title
+ merge_request.description = merge_request_description
merge_request.project = current_project
merge_request.milestone = current_milestone
merge_request.assignee = 'me'
@@ -34,8 +59,8 @@ module QA
end
Page::MergeRequest::Show.perform do |merge_request|
- expect(merge_request).to have_content('This is a merge request with a milestone')
- expect(merge_request).to have_content('Great feature with milestone')
+ expect(merge_request).to have_content(merge_request_title)
+ expect(merge_request).to have_content(merge_request_description)
expect(merge_request).to have_content(/Opened [\w\s]+ ago/)
expect(merge_request).to have_assignee(gitlab_account_username)
expect(merge_request).to have_label(new_label.title)
@@ -47,25 +72,4 @@ module QA
end
end
end
-
- describe 'creates a merge request', :smoke do
- it 'user creates a new merge request' do
- Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
-
- current_project = Resource::Project.fabricate! do |project|
- project.name = 'project-with-merge-request'
- end
-
- Resource::MergeRequest.fabricate! do |merge_request|
- merge_request.title = 'This is a merge request'
- merge_request.description = 'Great feature'
- merge_request.project = current_project
- end
-
- expect(page).to have_content('This is a merge request')
- expect(page).to have_content('Great feature')
- expect(page).to have_content(/Opened [\w\s]+ ago/)
- end
- end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb
new file mode 100644
index 00000000000..ab53dff464e
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/snippet/create_snippet_spec.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create', :smoke do
+ describe 'Snippet creation' do
+ it 'User creates a snippet' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ Page::Main::Menu.perform(&:go_to_snippets)
+
+ Resource::Snippet.fabricate_via_browser_ui! do |snippet|
+ snippet.title = 'Snippet title'
+ snippet.description = 'Snippet description'
+ snippet.visibility = 'Public'
+ snippet.file_name = 'New snippet file name'
+ snippet.file_content = 'Snippet file text'
+ end
+
+ Page::Dashboard::Snippet::Show.perform do |snippet|
+ expect(snippet).to have_snippet_title('Snippet title')
+ expect(snippet).to have_snippet_description('Snippet description')
+ expect(snippet).to have_embed_type('Embed')
+ expect(snippet).to have_visibility_type('Public')
+ expect(snippet).to have_file_name('New snippet file name')
+ expect(snippet).to have_file_content('Snippet file text')
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
index f176ec31abd..66cd712afb0 100644
--- a/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/web_ide/add_file_template_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/46
+ context 'Create', :quarantine do
describe 'Web IDE file templates' do
include Runtime::Fixtures
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index 4212a2b0392..0b92ea29ca4 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -3,16 +3,24 @@
require 'pathname'
module QA
- # Transient failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/68
context 'Configure' do
def login
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
end
+ # Transient failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/68
describe 'Auto DevOps support', :orchestrated, :kubernetes, :quarantine do
context 'when rbac is enabled' do
before(:all) do
+ @cluster = Service::KubernetesCluster.new.create!
+ end
+
+ after(:all) do
+ @cluster&.remove!
+ end
+
+ it 'runs auto devops' do
login
@project = Resource::Project.fabricate! do |p|
@@ -28,8 +36,14 @@ module QA
resource.value = '1'
end
- # Create and connect K8s cluster
- @cluster = Service::KubernetesCluster.new.create!
+ # Set an application secret CI variable (prefixed with K8S_SECRET_)
+ Resource::CiVariable.fabricate! do |resource|
+ resource.project = @project
+ resource.key = 'K8S_SECRET_OPTIONAL_MESSAGE'
+ resource.value = 'You can see this application secret'
+ end
+
+ # Connect K8s cluster
Resource::KubernetesCluster.fabricate! do |cluster|
cluster.project = @project
cluster.cluster = @cluster
@@ -47,14 +61,7 @@ module QA
.join('../../../../../fixtures/auto_devops_rack')
push.commit_message = 'Create Auto DevOps compatible rack application'
end
- end
- after(:all) do
- @cluster&.remove!
- end
-
- it 'runs auto devops' do
- @project.visit!
Page::Project::Menu.perform(&:click_ci_cd_pipelines)
Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline)
@@ -92,66 +99,6 @@ module QA
Page::Project::Operations::Environments::Show.perform do |show|
show.view_deployment do
expect(page).to have_content('Hello World!')
- end
- end
- end
-
- it 'user sets application secret variable and Auto DevOps passes it to container' do
- # Set an application secret CI variable (prefixed with K8S_SECRET_)
- Resource::CiVariable.fabricate! do |resource|
- resource.project = @project
- resource.key = 'K8S_SECRET_OPTIONAL_MESSAGE'
- resource.value = 'You can see this application secret'
- end
-
- # Our current Auto DevOps implementation won't update the production
- # app if we only update a CI variable with no code change.
- #
- # Workaround: push new code and use the resultant pipeline.
- Resource::Repository::ProjectPush.fabricate! do |push|
- push.project = @project
- push.commit_message = 'Force a Deployment change by pushing new code'
- push.file_name = 'new_file.txt'
- push.file_content = 'new file contents'
- end
-
- Page::Project::Menu.perform(&:click_ci_cd_pipelines)
- Page::Project::Pipeline::Index.perform(&:go_to_latest_pipeline)
-
- Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.go_to_job('build')
- end
- Page::Project::Job::Show.perform do |job|
- expect(job).to be_successful(timeout: 600)
-
- job.click_element(:pipeline_path)
- end
-
- Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.go_to_job('test')
- end
- Page::Project::Job::Show.perform do |job|
- expect(job).to be_successful(timeout: 600)
-
- job.click_element(:pipeline_path)
- end
-
- Page::Project::Pipeline::Show.perform do |pipeline|
- pipeline.go_to_job('production')
- end
- Page::Project::Job::Show.perform do |job|
- expect(job).to be_successful(timeout: 1200)
- end
-
- Page::Project::Menu.perform(&:click_operations_environments)
-
- Page::Project::Operations::Environments::Index.perform do |index|
- index.go_to_environment('production')
- end
-
- Page::Project::Operations::Environments::Show.perform do |show|
- show.view_deployment do
- expect(page).to have_content('Hello World!')
expect(page).to have_content('You can see this application secret')
end
end
@@ -159,7 +106,8 @@ module QA
end
end
- describe 'Auto DevOps', :smoke do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/87
+ describe 'Auto DevOps', :smoke, :quarantine do
it 'enables AutoDevOps by default' do
login
@@ -168,12 +116,6 @@ module QA
p.description = 'Project with AutoDevOps'
end
- project.visit!
-
- Page::Alert::AutoDevopsAlert.perform do |alert|
- expect(alert).to have_text(/.*The Auto DevOps pipeline has been enabled.*/)
- end
-
# Create AutoDevOps repo
Resource::Repository::ProjectPush.fabricate! do |push|
push.project = project
diff --git a/qa/qa/specs/helpers/quarantine.rb b/qa/qa/specs/helpers/quarantine.rb
new file mode 100644
index 00000000000..52cb05fcd13
--- /dev/null
+++ b/qa/qa/specs/helpers/quarantine.rb
@@ -0,0 +1,68 @@
+# frozen_string_literal: true
+
+require 'rspec/core'
+
+module QA::Specs::Helpers
+ module Quarantine
+ include RSpec::Core::Pending
+
+ extend self
+
+ def configure_rspec
+ RSpec.configure do |config|
+ config.before(:context, :quarantine) do
+ Quarantine.skip_or_run_quarantined_contexts(config.inclusion_filter.rules, self.class)
+ end
+
+ config.before do |example|
+ Quarantine.skip_or_run_quarantined_tests_or_contexts(config.inclusion_filter.rules, example)
+ end
+ end
+ end
+
+ # Skip tests in quarantine unless we explicitly focus on them.
+ def skip_or_run_quarantined_tests_or_contexts(filters, example)
+ if filters.key?(:quarantine)
+ included_filters = filters_other_than_quarantine(filters)
+
+ # If :quarantine is focused, skip the test/context unless its metadata
+ # includes quarantine and any other filters
+ # E.g., Suppose a test is tagged :smoke and :quarantine, and another is tagged
+ # :ldap and :quarantine. If we wanted to run just quarantined smoke tests
+ # using `--tag quarantine --tag smoke`, without this check we'd end up
+ # running that ldap test as well because of the :quarantine metadata.
+ # We could use an exclusion filter, but this way the test report will list
+ # the quarantined tests when they're not run so that we're aware of them
+ skip("Only running tests tagged with :quarantine and any of #{included_filters.keys}") if should_skip_when_focused?(example.metadata, included_filters)
+ else
+ skip('In quarantine') if example.metadata.key?(:quarantine)
+ end
+ end
+
+ # Skip the entire context if a context is quarantined. This avoids running
+ # before blocks unnecessarily.
+ def skip_or_run_quarantined_contexts(filters, example)
+ return unless example.metadata.key?(:quarantine)
+
+ skip_or_run_quarantined_tests_or_contexts(filters, example)
+ end
+
+ def filters_other_than_quarantine(filter)
+ filter.reject { |key, _| key == :quarantine }
+ end
+
+ # Checks if a test or context should be skipped.
+ #
+ # Returns true if
+ # - the metadata does not includes the :quarantine tag
+ # or if
+ # - the metadata includes the :quarantine tag
+ # - and the filter includes other tags that aren't in the metadata
+ def should_skip_when_focused?(metadata, included_filters)
+ return true unless metadata.key?(:quarantine)
+ return false if included_filters.empty?
+
+ (metadata.keys & included_filters.keys).empty?
+ end
+ end
+end
diff --git a/qa/qa/tools/generate_perf_testdata.rb b/qa/qa/tools/generate_perf_testdata.rb
index ad515014794..0a0dbdf5b15 100644
--- a/qa/qa/tools/generate_perf_testdata.rb
+++ b/qa/qa/tools/generate_perf_testdata.rb
@@ -2,6 +2,7 @@
require 'securerandom'
require 'faker'
+require 'yaml'
require_relative '../../qa'
# This script generates testdata for Performance Testing.
# Required environment variables: PERSONAL_ACCESS_TOKEN and GITLAB_ADDRESS
@@ -20,7 +21,8 @@ module QA
@api_client = Runtime::API::Client.new(ENV['GITLAB_ADDRESS'], personal_access_token: ENV['PERSONAL_ACCESS_TOKEN'])
@group_name = "gitlab-qa-perf-sandbox-#{SecureRandom.hex(8)}"
@project_name = "my-test-project-#{SecureRandom.hex(8)}"
- @urls = {}
+ @visibility = "public"
+ @urls = { host: ENV['GITLAB_ADDRESS'] }
end
def run
@@ -39,26 +41,26 @@ module QA
threads_arr = []
methods_arr.each do |m|
- threads_arr << Thread.new {m.call}
+ threads_arr << Thread.new { m.call }
end
threads_arr.each(&:join)
STDOUT.puts "\nURLs: #{@urls}"
- File.open("urls.txt", "w") { |file| file.puts @urls.to_s}
+ File.open("urls.yml", "w") { |file| file.puts @urls.to_yaml }
STDOUT.puts "\nDone"
end
private
def create_group
- group_search_response = post Runtime::API::Request.new(@api_client, "/groups").url, "name=#{@group_name}&path=#{@group_name}"
+ group_search_response = post Runtime::API::Request.new(@api_client, "/groups").url, "name=#{@group_name}&path=#{@group_name}&visibility=#{@visibility}"
group = JSON.parse(group_search_response.body)
@urls[:group_page] = group["web_url"]
group["id"]
end
def create_project(group_id)
- create_project_response = post Runtime::API::Request.new(@api_client, "/projects").url, "name=#{@project_name}&namespace_id=#{group_id}"
+ create_project_response = post Runtime::API::Request.new(@api_client, "/projects").url, "name=#{@project_name}&namespace_id=#{group_id}&visibility=#{@visibility}"
@urls[:project_page] = JSON.parse(create_project_response.body)["web_url"]
end
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index cbdd6e881b1..be13c3fb683 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -6,16 +6,10 @@ require 'rspec/retry'
end
RSpec.configure do |config|
- config.before(:context) do
- if self.class.metadata.keys.include?(:quarantine)
- skip_or_run_quarantined_tests(self.class.metadata.keys, config.inclusion_filter.rules.keys)
- end
- end
+ QA::Specs::Helpers::Quarantine.configure_rspec
config.before do |example|
QA::Runtime::Logger.debug("Starting test: #{example.full_description}") if QA::Runtime::Env.debug?
-
- skip_or_run_quarantined_tests(example.metadata.keys, config.inclusion_filter.rules.keys)
end
config.expect_with :rspec do |expectations|
@@ -44,42 +38,3 @@ RSpec.configure do |config|
example.run_with_retry retry: retry_times
end
end
-
-# Skip tests in quarantine unless we explicitly focus on them.
-# Skip the entire context if a context is tagged. This avoids running before
-# blocks unnecessarily.
-# If quarantine is focussed, skip tests/contexts that have other metadata
-# unless they're also focussed. This lets us run quarantined tests in a
-# particular category without running tests in other categories.
-# E.g., if a test is tagged 'smoke' and 'quarantine', and another is tagged
-# 'ldap' and 'quarantine', if we wanted to run just quarantined smoke tests
-# using `--tag quarantine --tag smoke`, without this check we'd end up
-# running that ldap test as well.
-# We could use an exclusion filter, but this way the test report will list
-# the quarantined tests when they're not run so that we're aware of them
-def skip_or_run_quarantined_tests(metadata_keys, filter_keys)
- included_filters = filters_other_than_quarantine(filter_keys)
-
- if filter_keys.include?(:quarantine)
- skip("Only running tests tagged with :quarantine and any of #{included_filters}") unless quarantine_and_optional_other_tag?(metadata_keys, included_filters)
- else
- skip('In quarantine') if metadata_keys.include?(:quarantine)
- end
-end
-
-def filters_other_than_quarantine(filter_keys)
- filter_keys.reject { |key| key == :quarantine }
-end
-
-# Checks if a test has the 'quarantine' tag and other tags in the inclusion filter.
-#
-# Returns true if
-# - the metadata includes the quarantine tag
-# - and the metadata and inclusion filter both have any other tag
-# - or no other tags are in the inclusion filter
-def quarantine_and_optional_other_tag?(metadata_keys, included_filters)
- return false unless metadata_keys.include? :quarantine
- return true if included_filters.empty?
-
- included_filters.any? { |key| metadata_keys.include? key }
-end
diff --git a/qa/spec/spec_helper_spec.rb b/qa/spec/spec_helper_spec.rb
deleted file mode 100644
index 27ec1ec80fe..00000000000
--- a/qa/spec/spec_helper_spec.rb
+++ /dev/null
@@ -1,355 +0,0 @@
-# frozen_string_literal: true
-
-describe 'rspec config tests' do
- let(:group) do
- RSpec.describe do
- shared_examples 'passing tests' do
- example 'not in quarantine' do
- end
- example 'in quarantine', :quarantine do
- end
- end
-
- context 'default' do
- it_behaves_like 'passing tests'
- end
-
- context 'foo', :foo do
- it_behaves_like 'passing tests'
- end
-
- context 'quarantine', :quarantine do
- it_behaves_like 'passing tests'
- end
-
- context 'bar quarantine', :bar, :quarantine do
- it_behaves_like 'passing tests'
- end
- end
- end
-
- let(:group_2) do
- RSpec.describe do
- before(:all) do
- @expectations = [1, 2, 3]
- end
-
- example 'not in quarantine' do
- expect(@expectations.shift).to be(3)
- end
-
- example 'in quarantine', :quarantine do
- expect(@expectations.shift).to be(3)
- end
- end
- end
-
- context 'with no tags focussed' do
- before do
- group.run
- end
-
- context 'in a context tagged :foo' do
- it 'skips tests in quarantine' do
- context = group.children.find { |c| c.description == "foo" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to eq(2)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('In quarantine')
- end
- end
-
- context 'in an untagged context' do
- it 'skips tests in quarantine' do
- context = group.children.find { |c| c.description == "default" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to eq(2)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('In quarantine')
- end
- end
-
- context 'in a context tagged :quarantine' do
- it 'skips all tests' do
- context = group.children.find { |c| c.description == "quarantine" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to eq(2)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('In quarantine')
- end
- end
- end
-
- context 'with :quarantine focussed' do
- before do
- RSpec.configure do |config|
- config.inclusion_filter = :quarantine
- end
-
- group.run
- end
- after do
- RSpec.configure do |config|
- config.inclusion_filter.clear
- end
- end
-
- context 'in an untagged context' do
- it 'only runs quarantined tests' do
- context = group.children.find { |c| c.description == "default" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(1)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
- end
- end
-
- context 'in a context tagged :foo' do
- it 'only runs quarantined tests' do
- context = group.children.find { |c| c.description == "foo" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(1)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
- end
- end
-
- context 'in a context tagged :quarantine' do
- it 'runs all tests' do
- context = group.children.find { |c| c.description == "quarantine" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(2)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
- end
- end
- end
-
- context 'with a non-quarantine tag (:foo) focussed' do
- before do
- RSpec.configure do |config|
- config.inclusion_filter = :foo
- end
-
- group.run
- end
- after do
- RSpec.configure do |config|
- config.inclusion_filter.clear
- end
- end
-
- context 'in an untagged context' do
- it 'runs no tests' do
- context = group.children.find { |c| c.description == "default" }
- expect(context.descendant_filtered_examples.count).to eq(0)
- end
- end
-
- context 'in a context tagged :foo' do
- it 'skips quarantined tests' do
- context = group.children.find { |c| c.description == "foo" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(2)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('In quarantine')
- end
- end
-
- context 'in a context tagged :quarantine' do
- it 'runs no tests' do
- context = group.children.find { |c| c.description == "quarantine" }
- expect(context.descendant_filtered_examples.count).to eq(0)
- end
- end
- end
-
- context 'with :quarantine and a non-quarantine tag (:foo) focussed' do
- before do
- RSpec.configure do |config|
- config.inclusion_filter = { quarantine: true, foo: true }
- end
-
- group.run
- end
- after do
- RSpec.configure do |config|
- config.inclusion_filter.clear
- end
- end
-
- context 'in an untagged context' do
- it 'ignores untagged tests and skips tests even if in quarantine' do
- context = group.children.find { |c| c.description == "default" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to eq(1)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- end
- end
-
- context 'in a context tagged :foo' do
- it 'only runs quarantined tests' do
- context = group.children.find { |c| c.description == "foo" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(2)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- end
- end
-
- context 'in a context tagged :quarantine' do
- it 'skips all tests' do
- context = group.children.find { |c| c.description == "quarantine" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(2)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- end
- end
-
- context 'in a context tagged :bar and :quarantine' do
- it 'skips all tests' do
- context = group.children.find { |c| c.description == "quarantine" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(2)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- end
- end
- end
-
- context 'with :quarantine and multiple non-quarantine tags focussed' do
- before do
- RSpec.configure do |config|
- config.inclusion_filter = { bar: true, foo: true, quarantine: true }
- end
-
- group.run
- end
- after do
- RSpec.configure do |config|
- config.inclusion_filter.clear
- end
- end
-
- context 'in a context tagged :foo' do
- it 'only runs quarantined tests' do
- context = group.children.find { |c| c.description == "foo" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(2)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('Only running tests tagged with :quarantine and any of [:bar, :foo]')
- end
- end
-
- context 'in a context tagged :quarantine' do
- it 'skips all tests' do
- context = group.children.find { |c| c.description == "quarantine" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(2)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('Only running tests tagged with :quarantine and any of [:bar, :foo]')
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:pending)
- expect(ex.execution_result.pending_message).to eq('Only running tests tagged with :quarantine and any of [:bar, :foo]')
- end
- end
-
- context 'in a context tagged :bar and :quarantine' do
- it 'runs all tests' do
- context = group.children.find { |c| c.description == "bar quarantine" }
- examples = context.descendant_filtered_examples
- expect(examples.count).to be(2)
-
- ex = examples.find { |e| e.description == "in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
-
- ex = examples.find { |e| e.description == "not in quarantine" }
- expect(ex.execution_result.status).to eq(:passed)
- end
- end
- end
-
- context 'rspec retry' do
- context 'in an untagged context' do
- before do
- group_2.run
- end
-
- it 'should run example :retry times' do
- examples = group_2.descendant_filtered_examples
- ex = examples.find { |e| e.description == 'not in quarantine' }
- expect(ex.execution_result.status).to eq(:passed)
- end
- end
-
- context 'with :quarantine focussed' do
- before do
- RSpec.configure do |config|
- config.inclusion_filter = :quarantine
- end
- group_2.run
- end
-
- after do
- RSpec.configure do |config|
- config.inclusion_filter.clear
- end
- end
-
- it 'should run example once only' do
- examples = group_2.descendant_filtered_examples
- ex = examples.find { |e| e.description == 'in quarantine' }
- expect(ex.execution_result.status).to eq(:failed)
- end
- end
- end
-end
diff --git a/qa/spec/specs/helpers/quarantine_spec.rb b/qa/spec/specs/helpers/quarantine_spec.rb
new file mode 100644
index 00000000000..78beda39b5e
--- /dev/null
+++ b/qa/spec/specs/helpers/quarantine_spec.rb
@@ -0,0 +1,271 @@
+# frozen_string_literal: true
+
+require 'rspec/core/sandbox'
+
+# We need a reporter for internal tests that's different from the reporter for
+# external tests otherwise the results will be mixed up. We don't care about
+# most reporting, but we do want to know if a test fails
+class RaiseOnFailuresReporter < RSpec::Core::NullReporter
+ def self.example_failed(example)
+ raise example.exception
+ end
+end
+
+# We use an example group wrapper to prevent the state of internal tests
+# expanding into the global state
+# See: https://github.com/rspec/rspec-core/issues/2603
+def describe_successfully(*args, &describe_body)
+ example_group = RSpec.describe(*args, &describe_body)
+ ran_successfully = example_group.run RaiseOnFailuresReporter
+ expect(ran_successfully).to eq true
+ example_group
+end
+
+RSpec.configure do |c|
+ c.around do |ex|
+ RSpec::Core::Sandbox.sandboxed do |config|
+ # If there is an example-within-an-example, we want to make sure the inner example
+ # does not get a reference to the outer example (the real spec) if it calls
+ # something like `pending`
+ config.before(:context) { RSpec.current_example = nil }
+
+ config.color_mode = :off
+
+ # Load airborne again to avoid "undefined method `match_expected_default?'" errors
+ # that happen because a hook calls a method added via a custom RSpec setting
+ # that is removed when the RSpec configuration is sandboxed.
+ # If this needs to be changed (e.g., to load other libraries as well), see
+ # this discussion for alternative solutions:
+ # https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/25223#note_143392053
+ load 'airborne.rb'
+
+ ex.run
+ end
+ end
+end
+
+describe QA::Specs::Helpers::Quarantine do
+ describe '.skip_or_run_quarantined_contexts' do
+ context 'with no tag focused' do
+ before do
+ described_class.configure_rspec
+ end
+
+ it 'skips before hooks of quarantined contexts' do
+ executed_hooks = []
+
+ group = describe_successfully('quarantine', :quarantine) do
+ before(:all) do
+ executed_hooks << :before_all
+ end
+ before do
+ executed_hooks << :before
+ end
+ example {}
+ end
+
+ expect(executed_hooks).to eq []
+ expect(group.descendant_filtered_examples.first.execution_result.status).to eq(:pending)
+ expect(group.descendant_filtered_examples.first.execution_result.pending_message)
+ .to eq('In quarantine')
+ end
+
+ it 'executes before hooks of non-quarantined contexts' do
+ executed_hooks = []
+
+ group = describe_successfully do
+ before(:all) do
+ executed_hooks << :before_all
+ end
+ before do
+ executed_hooks << :before
+ end
+ example {}
+ end
+
+ expect(executed_hooks).to eq [:before_all, :before]
+ expect(group.descendant_filtered_examples.first.execution_result.status).to eq(:passed)
+ end
+ end
+
+ context 'with :quarantine focused' do
+ before do
+ described_class.configure_rspec
+ RSpec.configure do |c|
+ c.filter_run :quarantine
+ end
+ end
+
+ it 'executes before hooks of quarantined contexts' do
+ executed_hooks = []
+
+ group = describe_successfully('quarantine', :quarantine) do
+ before(:all) do
+ executed_hooks << :before_all
+ end
+ before do
+ executed_hooks << :before
+ end
+ example {}
+ end
+
+ expect(executed_hooks).to eq [:before_all, :before]
+ expect(group.descendant_filtered_examples.first.execution_result.status).to eq(:passed)
+ end
+
+ it 'skips before hooks of non-quarantined contexts' do
+ executed_hooks = []
+
+ group = describe_successfully do
+ before(:all) do
+ executed_hooks << :before_all
+ end
+ before do
+ executed_hooks << :before
+ end
+ example {}
+ end
+
+ expect(executed_hooks).to eq []
+ expect(group.descendant_filtered_examples.first).to be_nil
+ end
+ end
+ end
+
+ describe '.skip_or_run_quarantined_tests' do
+ context 'with no tag focused' do
+ before do
+ described_class.configure_rspec
+ end
+
+ it 'skips quarantined tests' do
+ group = describe_successfully do
+ it('is pending', :quarantine) {}
+ end
+
+ expect(group.examples.first.execution_result.status).to eq(:pending)
+ expect(group.examples.first.execution_result.pending_message)
+ .to eq('In quarantine')
+ end
+
+ it 'executes non-quarantined tests' do
+ group = describe_successfully do
+ example {}
+ end
+
+ expect(group.examples.first.execution_result.status).to eq(:passed)
+ end
+ end
+
+ context 'with :quarantine focused' do
+ before do
+ described_class.configure_rspec
+ RSpec.configure do |c|
+ c.filter_run :quarantine
+ end
+ end
+
+ it 'executes quarantined tests' do
+ group = describe_successfully do
+ it('passes', :quarantine) {}
+ end
+
+ expect(group.examples.first.execution_result.status).to eq(:passed)
+ end
+
+ it 'ignores non-quarantined tests' do
+ group = describe_successfully do
+ example {}
+ end
+
+ expect(group.examples.first.execution_result.status).to be_nil
+ end
+ end
+
+ context 'with a non-quarantine tag focused' do
+ before do
+ described_class.configure_rspec
+ RSpec.configure do |c|
+ c.filter_run :foo
+ end
+ end
+
+ it 'ignores non-quarantined non-focused tests' do
+ group = describe_successfully do
+ example {}
+ end
+
+ expect(group.examples.first.execution_result.status).to be_nil
+ end
+
+ it 'executes non-quarantined focused tests' do
+ group = describe_successfully do
+ it('passes', :foo) {}
+ end
+
+ expect(group.examples.first.execution_result.status).to be(:passed)
+ end
+
+ it 'ignores quarantined tests' do
+ group = describe_successfully do
+ it('is ignored', :quarantine) {}
+ end
+
+ expect(group.examples.first.execution_result.status).to be_nil
+ end
+
+ it 'skips quarantined focused tests' do
+ group = describe_successfully do
+ it('is pending', :quarantine, :foo) {}
+ end
+
+ expect(group.examples.first.execution_result.status).to be(:pending)
+ expect(group.examples.first.execution_result.pending_message)
+ .to eq('In quarantine')
+ end
+ end
+
+ context 'with :quarantine and non-quarantine tags focused' do
+ before do
+ described_class.configure_rspec
+ RSpec.configure do |c|
+ c.filter_run :foo, :bar, :quarantine
+ end
+ end
+
+ it 'ignores non-quarantined non-focused tests' do
+ group = describe_successfully do
+ example {}
+ end
+
+ expect(group.examples.first.execution_result.status).to be_nil
+ end
+
+ it 'skips non-quarantined focused tests' do
+ group = describe_successfully do
+ it('is pending', :foo) {}
+ end
+
+ expect(group.examples.first.execution_result.status).to be(:pending)
+ expect(group.examples.first.execution_result.pending_message)
+ .to eq('Only running tests tagged with :quarantine and any of [:bar, :foo]')
+ end
+
+ it 'skips quarantined non-focused tests' do
+ group = describe_successfully do
+ it('is pending', :quarantine) {}
+ end
+
+ expect(group.examples.first.execution_result.status).to be(:pending)
+ end
+
+ it 'executes quarantined focused tests' do
+ group = describe_successfully do
+ it('passes', :quarantine, :foo) {}
+ end
+
+ expect(group.examples.first.execution_result.status).to be(:passed)
+ end
+ end
+ end
+end
diff --git a/scripts/build_assets_image b/scripts/build_assets_image
index 9afada244c8..25b6060b6c4 100755
--- a/scripts/build_assets_image
+++ b/scripts/build_assets_image
@@ -7,14 +7,11 @@ then
fi
# Generate the image name based on the project this is being run in
-ASSETS_IMAGE_NAME=$(echo ${CI_PROJECT_NAME} |
- awk '{
- split($1, p, "-");
- interim = sprintf("%s-assets-%s", p[1], p[2]);
- sub(/-$/, "", interim);
- print interim
- }'
-)
+ASSETS_IMAGE_NAME="gitlab-assets-ce"
+if [[ "${CI_PROJECT_NAME}" == "gitlab-ee" ]]
+then
+ ASSETS_IMAGE_NAME="gitlab-assets-ee"
+fi
ASSETS_IMAGE_PATH=${CI_REGISTRY}/${CI_PROJECT_PATH}/${ASSETS_IMAGE_NAME}
@@ -27,3 +24,9 @@ docker login -u gitlab-ci-token -p ${CI_JOB_TOKEN} ${CI_REGISTRY}
docker push ${ASSETS_IMAGE_PATH}:${CI_COMMIT_REF_SLUG}
docker push ${ASSETS_IMAGE_PATH}:${CI_COMMIT_SHA}
+# Also tag the image with GitLab version, if running on a tag pipeline, so
+# other projects can simply use that instead of computing the slug.
+if [ -n "$CI_COMMIT_TAG" ]; then
+ docker tag ${ASSETS_IMAGE_PATH}:${CI_COMMIT_REF_SLUG} ${ASSETS_IMAGE_PATH}:${CI_COMMIT_REF_NAME}
+ docker push ${ASSETS_IMAGE_PATH}:${CI_COMMIT_REF_NAME}
+fi
diff --git a/scripts/frontend/postinstall.js b/scripts/frontend/postinstall.js
index 682039a41b3..94977e459e3 100644
--- a/scripts/frontend/postinstall.js
+++ b/scripts/frontend/postinstall.js
@@ -13,7 +13,7 @@ if (process.platform === 'darwin') {
ensure that it is supported by the fsevents library.
You can try installing again with \`${chalk.cyan('yarn install --force')}\`
- `)
+ `),
);
process.exit(1);
}
diff --git a/scripts/frontend/prettier.js b/scripts/frontend/prettier.js
index ffb09ea9779..bf0e98da139 100644
--- a/scripts/frontend/prettier.js
+++ b/scripts/frontend/prettier.js
@@ -32,7 +32,7 @@ let globDir = process.argv[3] || '';
if (globDir && globDir.charAt(globDir.length - 1) !== '/') globDir += '/';
console.log(
- `Loading all ${allFiles ? '' : 'staged '}files ${globDir ? `within ${globDir} ` : ''}...`
+ `Loading all ${allFiles ? '' : 'staged '}files ${globDir ? `within ${globDir} ` : ''}...`,
);
const globPatterns = matchExtensions.map(ext => `${globDir}**/*.${ext}`);
@@ -105,7 +105,7 @@ Promise.all(matchedFiles.map(checkFileWithPrettierConfig))
.then(() => {
const failAction = shouldSave ? 'fixed' : 'failed';
console.log(
- `\nSummary:\n ${matchedCount} files processed (${passedCount} passed, ${failedCount} ${failAction}, ${ignoredCount} ignored)\n`
+ `\nSummary:\n ${matchedCount} files processed (${passedCount} passed, ${failedCount} ${failAction}, ${ignoredCount} ignored)\n`,
);
if (didWarn) process.exit(1);
diff --git a/scripts/gitaly_test.rb b/scripts/gitaly_test.rb
index dee4c2eba7e..b5d3facd18a 100644
--- a/scripts/gitaly_test.rb
+++ b/scripts/gitaly_test.rb
@@ -79,15 +79,13 @@ module GitalyTest
socket = read_socket_path
Integer(timeout / delay).times do
- begin
- UNIXSocket.new(socket)
- puts ' OK'
-
- return
- rescue Errno::ENOENT, Errno::ECONNREFUSED
- print '.'
- sleep delay
- end
+ UNIXSocket.new(socket)
+ puts ' OK'
+
+ return
+ rescue Errno::ENOENT, Errno::ECONNREFUSED
+ print '.'
+ sleep delay
end
puts ' FAILED'
diff --git a/scripts/insert-rspec-profiling-data b/scripts/insert-rspec-profiling-data
new file mode 100755
index 00000000000..10e337b9972
--- /dev/null
+++ b/scripts/insert-rspec-profiling-data
@@ -0,0 +1,47 @@
+#!/usr/bin/env ruby
+
+require 'csv'
+require 'rspec_profiling'
+require 'postgres-copy'
+
+module RspecProfiling
+ module Collectors
+ class PSQL
+ def establish_connection
+ # This disables the automatic creation of the database and
+ # table. In the future, we may want a way to specify the host of
+ # the database to connect so that we can call #install.
+ Result.establish_connection(results_url)
+ end
+
+ def prepared?
+ connection.data_source_exists?(table)
+ end
+
+ def results_url
+ ENV['RSPEC_PROFILING_POSTGRES_URL']
+ end
+
+ class Result < ActiveRecord::Base
+ acts_as_copy_target
+ end
+ end
+ end
+end
+
+def insert_data(path)
+ puts "#{Time.now} Inserting CI stats..."
+
+ collector = RspecProfiling::Collectors::PSQL.new
+ collector.install
+
+ files = Dir[File.join(path, "*.csv")]
+
+ files.each do |filename|
+ puts "#{Time.now} Inserting #{filename}..."
+ result = RspecProfiling::Collectors::PSQL::Result.copy_from(filename)
+ puts "#{Time.now} Inserted #{result.cmd_tuples} lines in #{filename}, DB response: #{result.cmd_status}"
+ end
+end
+
+insert_data('rspec_profiling') if ENV['RSPEC_PROFILING_POSTGRES_URL'].present?
diff --git a/scripts/merge-simplecov b/scripts/merge-simplecov
index 65f93f8830b..746be3317a7 100755
--- a/scripts/merge-simplecov
+++ b/scripts/merge-simplecov
@@ -12,11 +12,9 @@ module SimpleCov
def resultset_hashes
resultset_files.map do |path|
- begin
- JSON.parse(File.read(path))
- rescue
- {}
- end
+ JSON.parse(File.read(path))
+ rescue
+ {}
end
end
diff --git a/scripts/review_apps/review-apps.sh b/scripts/review_apps/review-apps.sh
index f610485a700..32fce946c17 100755
--- a/scripts/review_apps/review-apps.sh
+++ b/scripts/review_apps/review-apps.sh
@@ -4,6 +4,16 @@ export TILLER_NAMESPACE="$KUBE_NAMESPACE"
function echoerr() { printf "\033[0;31m%s\n\033[0m" "$*" >&2; }
function echoinfo() { printf "\033[0;33m%s\n\033[0m" "$*" >&2; }
+function perform_review_app_deployment() {
+ check_kube_domain
+ download_gitlab_chart
+ ensure_namespace
+ install_tiller
+ install_external_dns
+ time deploy
+ add_license
+}
+
function check_kube_domain() {
if [ -z ${REVIEW_APPS_DOMAIN+x} ]; then
echo "In order to deploy or use Review Apps, REVIEW_APPS_DOMAIN variable must be set"
diff --git a/scripts/trigger-build b/scripts/trigger-build
index 9dbafffddfc..9c5fc3c76a5 100755
--- a/scripts/trigger-build
+++ b/scripts/trigger-build
@@ -141,7 +141,7 @@ module Trigger
"GITLAB_#{edition}_VERSION" => ENV['CI_COMMIT_REF_NAME'],
"GITLAB_VERSION" => ENV['CI_COMMIT_REF_NAME'],
"GITLAB_TAG" => ENV['CI_COMMIT_TAG'],
- "GITLAB_ASSETS_TAG" => ENV['CI_COMMIT_REF_SLUG'],
+ "GITLAB_ASSETS_TAG" => ENV['CI_COMMIT_TAG'] ? ENV['CI_COMMIT_REF_NAME'] : ENV['CI_COMMIT_REF_SLUG'],
"#{edition}_PIPELINE" => 'true'
}
end
diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb
index 8166657f674..4caf8b46519 100644
--- a/spec/controllers/admin/projects_controller_spec.rb
+++ b/spec/controllers/admin/projects_controller_spec.rb
@@ -43,6 +43,16 @@ describe Admin::ProjectsController do
end
end
+ describe 'GET /projects.json' do
+ render_views
+
+ before do
+ get :index, format: :json
+ end
+
+ it { is_expected.to respond_with(:success) }
+ end
+
describe 'GET /projects/:id' do
render_views
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 4458a7223bf..d8b75c5151e 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -371,5 +371,36 @@ describe AutocompleteController do
expect(json_response[3]).to match('name' => 'thumbsdown')
end
end
+
+ context 'Get merge_request_target_branches' do
+ let(:user2) { create(:user) }
+ let!(:merge_request1) { create(:merge_request, source_project: project, target_branch: 'feature') }
+
+ context 'unauthorized user' do
+ it 'returns empty json' do
+ get :merge_request_target_branches
+
+ expect(json_response).to be_empty
+ end
+ end
+
+ context 'sign in as user without any accesible merge requests' do
+ it 'returns empty json' do
+ sign_in(user2)
+ get :merge_request_target_branches
+
+ expect(json_response).to be_empty
+ end
+ end
+
+ context 'sign in as user with a accesible merge request' do
+ it 'returns json' do
+ sign_in(user)
+ get :merge_request_target_branches
+
+ expect(json_response).to contain_exactly({ 'title' => 'feature' })
+ end
+ end
+ end
end
end
diff --git a/spec/controllers/dashboard/projects_controller_spec.rb b/spec/controllers/dashboard/projects_controller_spec.rb
index 2975205e09c..649441f4917 100644
--- a/spec/controllers/dashboard/projects_controller_spec.rb
+++ b/spec/controllers/dashboard/projects_controller_spec.rb
@@ -2,4 +2,30 @@ require 'spec_helper'
describe Dashboard::ProjectsController do
it_behaves_like 'authenticates sessionless user', :index, :atom
+
+ context 'json requests' do
+ render_views
+
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ describe 'GET /projects.json' do
+ before do
+ get :index, format: :json
+ end
+
+ it { is_expected.to respond_with(:success) }
+ end
+
+ describe 'GET /starred.json' do
+ before do
+ get :starred, format: :json
+ end
+
+ it { is_expected.to respond_with(:success) }
+ end
+ end
end
diff --git a/spec/controllers/explore/projects_controller_spec.rb b/spec/controllers/explore/projects_controller_spec.rb
index d57367e931e..7e20ddca249 100644
--- a/spec/controllers/explore/projects_controller_spec.rb
+++ b/spec/controllers/explore/projects_controller_spec.rb
@@ -1,6 +1,36 @@
require 'spec_helper'
describe Explore::ProjectsController do
+ describe 'GET #index.json' do
+ render_views
+
+ before do
+ get :index, format: :json
+ end
+
+ it { is_expected.to respond_with(:success) }
+ end
+
+ describe 'GET #trending.json' do
+ render_views
+
+ before do
+ get :trending, format: :json
+ end
+
+ it { is_expected.to respond_with(:success) }
+ end
+
+ describe 'GET #starred.json' do
+ render_views
+
+ before do
+ get :starred, format: :json
+ end
+
+ it { is_expected.to respond_with(:success) }
+ end
+
describe 'GET #trending' do
context 'sorting by update date' do
let(:project1) { create(:project, :public, updated_at: 3.days.ago) }
diff --git a/spec/controllers/groups/boards_controller_spec.rb b/spec/controllers/groups/boards_controller_spec.rb
index 4228e727b52..27ee37b3817 100644
--- a/spec/controllers/groups/boards_controller_spec.rb
+++ b/spec/controllers/groups/boards_controller_spec.rb
@@ -22,28 +22,6 @@ describe Groups::BoardsController do
expect(response.content_type).to eq 'text/html'
end
- it 'redirects to latest visited board' do
- board = create(:board, group: group)
- create(:board_group_recent_visit, group: board.group, board: board, user: user)
-
- list_boards
-
- expect(response).to redirect_to(group_board_path(id: board.id))
- end
-
- it 'renders template if visited board is not found' do
- temporary_board = create(:board, group: group)
- visited = create(:board_group_recent_visit, group: temporary_board.group, board: temporary_board, user: user)
- temporary_board.delete
-
- allow_any_instance_of(Boards::Visits::LatestService).to receive(:execute).and_return(visited)
-
- list_boards
-
- expect(response).to render_template :index
- expect(response.content_type).to eq 'text/html'
- end
-
context 'with unauthorized user' do
before do
allow(Ability).to receive(:allowed?).with(user, :read_cross_project, :global).and_return(true)
diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
index 40673d10b91..15eb0a442a6 100644
--- a/spec/controllers/groups/settings/ci_cd_controller_spec.rb
+++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb
@@ -66,4 +66,77 @@ describe Groups::Settings::CiCdController do
end
end
end
+
+ describe 'PATCH #update_auto_devops' do
+ let(:auto_devops_param) { '1' }
+
+ subject do
+ patch :update_auto_devops, params: {
+ group_id: group,
+ group: { auto_devops_enabled: auto_devops_param }
+ }
+ end
+
+ context 'when user does not have enough permission' do
+ before do
+ group.add_maintainer(user)
+ end
+
+ it { is_expected.to have_gitlab_http_status(404) }
+ end
+
+ context 'when user has enough privileges' do
+ before do
+ group.add_owner(user)
+ end
+
+ it { is_expected.to redirect_to(group_settings_ci_cd_path) }
+
+ context 'when service execution went wrong' do
+ before do
+ allow_any_instance_of(Groups::AutoDevopsService).to receive(:execute).and_return(false)
+ allow_any_instance_of(Group).to receive_message_chain(:errors, :full_messages)
+ .and_return(['Error 1'])
+
+ subject
+ end
+
+ it 'returns a flash alert' do
+ expect(response).to set_flash[:alert]
+ .to eq("There was a problem updating Auto DevOps pipeline: [\"Error 1\"].")
+ end
+ end
+
+ context 'when service execution was successful' do
+ it 'returns a flash notice' do
+ subject
+
+ expect(response).to set_flash[:notice]
+ .to eq('Auto DevOps pipeline was updated for the group')
+ end
+ end
+
+ context 'when changing auto devops value' do
+ before do
+ subject
+
+ group.reload
+ end
+
+ context 'when explicitly enabling auto devops' do
+ it 'should update group attribute' do
+ expect(group.auto_devops_enabled).to eq(true)
+ end
+ end
+
+ context 'when explicitly disabling auto devops' do
+ let(:auto_devops_param) { '0' }
+
+ it 'should update group attribute' do
+ expect(group.auto_devops_enabled).to eq(false)
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/controllers/groups_controller_spec.rb b/spec/controllers/groups_controller_spec.rb
index 21e5122c06b..b2e6df6060a 100644
--- a/spec/controllers/groups_controller_spec.rb
+++ b/spec/controllers/groups_controller_spec.rb
@@ -32,21 +32,46 @@ describe GroupsController do
end
end
+ shared_examples 'details view' do
+ it { is_expected.to render_template('groups/show') }
+
+ context 'as atom' do
+ let!(:event) { create(:event, project: project) }
+ let(:format) { :atom }
+
+ it { is_expected.to render_template('groups/show') }
+
+ it 'assigns events for all the projects in the group' do
+ subject
+ expect(assigns(:events)).to contain_exactly(event)
+ end
+ end
+ end
+
describe 'GET #show' do
before do
sign_in(user)
project
end
- context 'as atom' do
- it 'assigns events for all the projects in the group' do
- create(:event, project: project)
+ let(:format) { :html }
- get :show, params: { id: group.to_param }, format: :atom
+ subject { get :show, params: { id: group.to_param }, format: format }
- expect(assigns(:events)).not_to be_empty
- end
+ it_behaves_like 'details view'
+ end
+
+ describe 'GET #details' do
+ before do
+ sign_in(user)
+ project
end
+
+ let(:format) { :html }
+
+ subject { get :details, params: { id: group.to_param }, format: format }
+
+ it_behaves_like 'details view'
end
describe 'GET edit' do
diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb
index e0da23ca0b8..06c6f49f7cc 100644
--- a/spec/controllers/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/omniauth_callbacks_controller_spec.rb
@@ -113,6 +113,33 @@ describe OmniauthCallbacksController, type: :controller do
expect(request.env['warden']).to be_authenticated
end
+ context 'when user has no linked provider' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in user
+ end
+
+ it 'links identity' do
+ expect do
+ post provider
+ user.reload
+ end.to change { user.identities.count }.by(1)
+ end
+
+ context 'and is not allowed to link the provider' do
+ before do
+ allow_any_instance_of(IdentityProviderPolicy).to receive(:can?).with(:link).and_return(false)
+ end
+
+ it 'returns 403' do
+ post provider
+
+ expect(response).to have_gitlab_http_status(403)
+ end
+ end
+ end
+
shared_context 'sign_up' do
let(:user) { double(email: 'new@example.com') }
diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb
index 09199067024..1eeded06459 100644
--- a/spec/controllers/projects/boards_controller_spec.rb
+++ b/spec/controllers/projects/boards_controller_spec.rb
@@ -28,28 +28,6 @@ describe Projects::BoardsController do
expect(response.content_type).to eq 'text/html'
end
- it 'redirects to latest visited board' do
- board = create(:board, project: project)
- create(:board_project_recent_visit, project: board.project, board: board, user: user)
-
- list_boards
-
- expect(response).to redirect_to(namespace_project_board_path(id: board.id))
- end
-
- it 'renders template if visited board is not found' do
- temporary_board = create(:board, project: project)
- visited = create(:board_project_recent_visit, project: temporary_board.project, board: temporary_board, user: user)
- temporary_board.delete
-
- allow_any_instance_of(Boards::Visits::LatestService).to receive(:execute).and_return(visited)
-
- list_boards
-
- expect(response).to render_template :index
- expect(response.content_type).to eq 'text/html'
- end
-
context 'with unauthorized user' do
before do
allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true)
diff --git a/spec/controllers/projects/git_http_controller_spec.rb b/spec/controllers/projects/git_http_controller_spec.rb
new file mode 100644
index 00000000000..bf099e8deeb
--- /dev/null
+++ b/spec/controllers/projects/git_http_controller_spec.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::GitHttpController do
+ describe 'HEAD #info_refs' do
+ it 'returns 403' do
+ project = create(:project, :public, :repository)
+
+ head :info_refs, params: { namespace_id: project.namespace.to_param, project_id: project.path + '.git' }
+
+ expect(response.status).to eq(403)
+ end
+ end
+end
diff --git a/spec/controllers/projects/notes_controller_spec.rb b/spec/controllers/projects/notes_controller_spec.rb
index 0b0f5117784..deecb7fefe9 100644
--- a/spec/controllers/projects/notes_controller_spec.rb
+++ b/spec/controllers/projects/notes_controller_spec.rb
@@ -413,6 +413,37 @@ describe Projects::NotesController do
end
end
end
+
+ context 'when creating a note with quick actions' do
+ context 'with commands that return changes' do
+ let(:note_text) { "/award :thumbsup:\n/estimate 1d\n/spend 3h" }
+
+ it 'includes changes in commands_changes ' do
+ post :create, params: request_params.merge(note: { note: note_text }, format: :json)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['commands_changes']).to include('emoji_award', 'time_estimate', 'spend_time')
+ expect(json_response['commands_changes']).not_to include('target_project', 'title')
+ end
+ end
+
+ context 'with commands that do not return changes' do
+ let(:issue) { create(:issue, project: project) }
+ let(:other_project) { create(:project) }
+ let(:note_text) { "/move #{other_project.full_path}\n/title AAA" }
+
+ before do
+ other_project.add_developer(user)
+ end
+
+ it 'does not include changes in commands_changes' do
+ post :create, params: request_params.merge(note: { note: note_text }, target_type: 'issue', target_id: issue.id, format: :json)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['commands_changes']).not_to include('target_project', 'title')
+ end
+ end
+ end
end
describe 'PUT update' do
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index fd151e8a298..c1baf88778d 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -15,7 +15,7 @@ describe RegistrationsController do
context 'when send_user_confirmation_email is false' do
it 'signs the user in' do
- allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(false)
+ stub_application_setting(send_user_confirmation_email: false)
expect { post(:create, params: user_params) }.not_to change { ActionMailer::Base.deliveries.size }
expect(subject.current_user).not_to be_nil
@@ -24,7 +24,7 @@ describe RegistrationsController do
context 'when send_user_confirmation_email is true' do
it 'does not authenticate user and sends confirmation email' do
- allow_any_instance_of(ApplicationSetting).to receive(:send_user_confirmation_email).and_return(true)
+ stub_application_setting(send_user_confirmation_email: true)
post(:create, params: user_params)
@@ -35,7 +35,7 @@ describe RegistrationsController do
context 'when signup_enabled? is false' do
it 'redirects to sign_in' do
- allow_any_instance_of(ApplicationSetting).to receive(:signup_enabled?).and_return(false)
+ stub_application_setting(signup_enabled: false)
expect { post(:create, params: user_params) }.not_to change(User, :count)
expect(response).to redirect_to(new_user_session_path)
diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb
index 0b3e67b4987..067391c1179 100644
--- a/spec/factories/ci/builds.rb
+++ b/spec/factories/ci/builds.rb
@@ -75,6 +75,10 @@ FactoryBot.define do
status 'created'
end
+ trait :preparing do
+ status 'preparing'
+ end
+
trait :scheduled do
schedulable
status 'scheduled'
diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb
index 8a44ce52849..aa5ccbda6cd 100644
--- a/spec/factories/ci/pipelines.rb
+++ b/spec/factories/ci/pipelines.rb
@@ -50,6 +50,14 @@ FactoryBot.define do
failure_reason :config_error
end
+ trait :created do
+ status :created
+ end
+
+ trait :preparing do
+ status :preparing
+ end
+
trait :blocked do
status :manual
end
@@ -82,6 +90,12 @@ FactoryBot.define do
end
end
+ trait :with_job do
+ after(:build) do |pipeline, evaluator|
+ pipeline.builds << build(:ci_build, pipeline: pipeline, project: pipeline.project)
+ end
+ end
+
trait :auto_devops_source do
config_source { Ci::Pipeline.config_sources[:auto_devops_source] }
end
diff --git a/spec/factories/clusters/clusters.rb b/spec/factories/clusters/clusters.rb
index a2e5f4862db..1cc3c0e03d8 100644
--- a/spec/factories/clusters/clusters.rb
+++ b/spec/factories/clusters/clusters.rb
@@ -12,7 +12,7 @@ FactoryBot.define do
cluster_type { Clusters::Cluster.cluster_types[:project_type] }
before(:create) do |cluster, evaluator|
- cluster.projects << create(:project, :repository)
+ cluster.projects << create(:project, :repository) unless cluster.projects.present?
end
end
@@ -20,7 +20,7 @@ FactoryBot.define do
cluster_type { Clusters::Cluster.cluster_types[:group_type] }
before(:create) do |cluster, evalutor|
- cluster.groups << create(:group)
+ cluster.groups << create(:group) unless cluster.groups.present?
end
end
diff --git a/spec/factories/clusters/providers/gcp.rb b/spec/factories/clusters/providers/gcp.rb
index a002ab28519..186c7c8027c 100644
--- a/spec/factories/clusters/providers/gcp.rb
+++ b/spec/factories/clusters/providers/gcp.rb
@@ -28,5 +28,9 @@ FactoryBot.define do
gcp.make_errored('Something wrong')
end
end
+
+ trait :abac_enabled do
+ legacy_abac true
+ end
end
end
diff --git a/spec/factories/commit_statuses.rb b/spec/factories/commit_statuses.rb
index 381bf07f6a0..848a31e96c1 100644
--- a/spec/factories/commit_statuses.rb
+++ b/spec/factories/commit_statuses.rb
@@ -33,6 +33,10 @@ FactoryBot.define do
status 'pending'
end
+ trait :preparing do
+ status 'preparing'
+ end
+
trait :created do
status 'created'
end
diff --git a/spec/factories/groups.rb b/spec/factories/groups.rb
index 3b354c0d96b..dcef8571f41 100644
--- a/spec/factories/groups.rb
+++ b/spec/factories/groups.rb
@@ -36,5 +36,13 @@ FactoryBot.define do
trait :nested do
parent factory: :group
end
+
+ trait :auto_devops_enabled do
+ auto_devops_enabled true
+ end
+
+ trait :auto_devops_disabled do
+ auto_devops_enabled false
+ end
end
end
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 18f724770b5..a73f330a7a9 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -101,12 +101,33 @@ FactoryBot.define do
end
end
- trait :with_merge_request_pipeline do
+ trait :with_detached_merge_request_pipeline do
after(:build) do |merge_request|
merge_request.merge_request_pipelines << build(:ci_pipeline,
source: :merge_request_event,
merge_request: merge_request,
- project: merge_request.source_project)
+ project: merge_request.source_project,
+ ref: merge_request.ref_path,
+ sha: merge_request.source_branch_sha)
+ end
+ end
+
+ trait :with_merge_request_pipeline do
+ transient do
+ merge_sha { 'test-merge-sha' }
+ source_sha { source_branch_sha }
+ target_sha { target_branch_sha }
+ end
+
+ after(:build) do |merge_request, evaluator|
+ merge_request.merge_request_pipelines << create(:ci_pipeline,
+ source: :merge_request_event,
+ merge_request: merge_request,
+ project: merge_request.source_project,
+ ref: merge_request.merge_ref_path,
+ sha: evaluator.merge_sha,
+ source_sha: evaluator.source_sha,
+ target_sha: evaluator.target_sha)
end
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 30d3b22d868..ab185ab3972 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -271,6 +271,10 @@ FactoryBot.define do
trait :auto_devops do
association :auto_devops, factory: :project_auto_devops
end
+
+ trait :auto_devops_disabled do
+ association :auto_devops, factory: [:project_auto_devops, :disabled]
+ end
end
# Project with empty repository
diff --git a/spec/features/clusters/cluster_detail_page_spec.rb b/spec/features/clusters/cluster_detail_page_spec.rb
index 0a9c4bcaf12..b9fc52d0dce 100644
--- a/spec/features/clusters/cluster_detail_page_spec.rb
+++ b/spec/features/clusters/cluster_detail_page_spec.rb
@@ -4,6 +4,8 @@ require 'spec_helper'
describe 'Clusterable > Show page' do
let(:current_user) { create(:user) }
+ let(:cluster_ingress_help_text_selector) { '.js-ingress-domain-help-text' }
+ let(:hide_modifier_selector) { '.hide' }
before do
sign_in(current_user)
@@ -35,7 +37,7 @@ describe 'Clusterable > Show page' do
it 'shows help text with the domain as an alternative to custom domain' do
within '#cluster-integration' do
- expect(page).to have_content('Alternatively 192.168.1.100.nip.io can be used instead of a custom domain')
+ expect(find(cluster_ingress_help_text_selector)).not_to match_css(hide_modifier_selector)
end
end
end
@@ -45,7 +47,7 @@ describe 'Clusterable > Show page' do
visit cluster_path
within '#cluster-integration' do
- expect(page).not_to have_content('can be used instead of a custom domain.')
+ expect(find(cluster_ingress_help_text_selector)).to match_css(hide_modifier_selector)
end
end
end
diff --git a/spec/features/groups/settings/ci_cd_spec.rb b/spec/features/groups/settings/ci_cd_spec.rb
index d422fd18346..0f793dbab6e 100644
--- a/spec/features/groups/settings/ci_cd_spec.rb
+++ b/spec/features/groups/settings/ci_cd_spec.rb
@@ -5,8 +5,8 @@ require 'spec_helper'
describe 'Group CI/CD settings' do
include WaitForRequests
- let(:user) {create(:user)}
- let(:group) {create(:group)}
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
before do
group.add_owner(user)
@@ -36,4 +36,45 @@ describe 'Group CI/CD settings' do
end
end
end
+
+ describe 'Auto DevOps form' do
+ before do
+ stub_application_setting(auto_devops_enabled: true)
+ end
+
+ context 'as owner first visiting group settings' do
+ it 'should see instance enabled badge' do
+ visit group_settings_ci_cd_path(group)
+
+ page.within '#auto-devops-settings' do
+ expect(page).to have_content('instance enabled')
+ end
+ end
+ end
+
+ context 'when Auto DevOps group has been enabled' do
+ it 'should see group enabled badge' do
+ group.update!(auto_devops_enabled: true)
+
+ visit group_settings_ci_cd_path(group)
+
+ page.within '#auto-devops-settings' do
+ expect(page).to have_content('group enabled')
+ end
+ end
+ end
+
+ context 'when Auto DevOps group has been disabled' do
+ it 'should not see a badge' do
+ group.update!(auto_devops_enabled: false)
+
+ visit group_settings_ci_cd_path(group)
+
+ page.within '#auto-devops-settings' do
+ expect(page).not_to have_content('instance enabled')
+ expect(page).not_to have_content('group enabled')
+ end
+ end
+ end
+ end
end
diff --git a/spec/features/issues/gfm_autocomplete_spec.rb b/spec/features/issues/gfm_autocomplete_spec.rb
index 986f3823275..8eb413bdd8d 100644
--- a/spec/features/issues/gfm_autocomplete_spec.rb
+++ b/spec/features/issues/gfm_autocomplete_spec.rb
@@ -278,12 +278,7 @@ describe 'GFM autocomplete', :js do
end
end
- # This context has just one example in each contexts in order to improve spec performance.
- context 'labels', :quarantine do
- let!(:backend) { create(:label, project: project, title: 'backend') }
- let!(:bug) { create(:label, project: project, title: 'bug') }
- let!(:feature_proposal) { create(:label, project: project, title: 'feature proposal') }
-
+ context 'labels' do
it 'opens autocomplete menu for Labels when field starts with text with item escaping HTML characters' do
create(:label, project: project, title: label_xss_title)
@@ -298,83 +293,6 @@ describe 'GFM autocomplete', :js do
expect(find('.atwho-view-ul').text).to have_content('alert label')
end
end
-
- context 'when no labels are assigned' do
- it 'shows labels' do
- note = find('#note-body')
-
- # It should show all the labels on "~".
- type(note, '~')
- wait_for_requests
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show all the labels on "/label ~".
- type(note, '/label ~')
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show all the labels on "/relabel ~".
- type(note, '/relabel ~')
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show no labels on "/unlabel ~".
- type(note, '/unlabel ~')
- expect_labels(not_shown: [backend, bug, feature_proposal])
- end
- end
-
- context 'when some labels are assigned' do
- before do
- issue.labels << [backend]
- end
-
- it 'shows labels' do
- note = find('#note-body')
-
- # It should show all the labels on "~".
- type(note, '~')
- wait_for_requests
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show only unset labels on "/label ~".
- type(note, '/label ~')
- expect_labels(shown: [bug, feature_proposal], not_shown: [backend])
-
- # It should show all the labels on "/relabel ~".
- type(note, '/relabel ~')
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show only set labels on "/unlabel ~".
- type(note, '/unlabel ~')
- expect_labels(shown: [backend], not_shown: [bug, feature_proposal])
- end
- end
-
- context 'when all labels are assigned' do
- before do
- issue.labels << [backend, bug, feature_proposal]
- end
-
- it 'shows labels' do
- note = find('#note-body')
-
- # It should show all the labels on "~".
- type(note, '~')
- wait_for_requests
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show no labels on "/label ~".
- type(note, '/label ~')
- expect_labels(not_shown: [backend, bug, feature_proposal])
-
- # It should show all the labels on "/relabel ~".
- type(note, '/relabel ~')
- expect_labels(shown: [backend, bug, feature_proposal])
-
- # It should show all the labels on "/unlabel ~".
- type(note, '/unlabel ~')
- expect_labels(shown: [backend, bug, feature_proposal])
- end
- end
end
shared_examples 'autocomplete suggestions' do
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 9bc340ed4bb..51508b78649 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -497,12 +497,21 @@ describe 'Issues' do
it 'allows user to unselect themselves', :js do
issue2 = create(:issue, project: project, author: user)
+
visit project_issue_path(project, issue2)
+ def close_dropdown_menu_if_visible
+ find('.dropdown-menu-toggle', visible: :all).tap do |toggle|
+ toggle.click if toggle.visible?
+ end
+ end
+
page.within '.assignee' do
click_link 'Edit'
click_link user.name
+ close_dropdown_menu_if_visible
+
page.within '.value .author' do
expect(page).to have_content user.name
end
@@ -510,6 +519,8 @@ describe 'Issues' do
click_link 'Edit'
click_link user.name
+ close_dropdown_menu_if_visible
+
page.within '.value .assign-yourself' do
expect(page).to have_content "No assignee"
end
diff --git a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
index 97b2aa82fce..28f88718ec1 100644
--- a/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_request_pipelines_spec.rb
@@ -2,7 +2,7 @@
require 'rails_helper'
-describe 'Merge request > User sees merge request pipelines', :js do
+describe 'Merge request > User sees pipelines triggered by merge request', :js do
include ProjectForksHelper
include TestReportsHelper
@@ -47,7 +47,7 @@ describe 'Merge request > User sees merge request pipelines', :js do
.execute(:push)
end
- let!(:merge_request_pipeline) do
+ let!(:detached_merge_request_pipeline) do
Ci::CreatePipelineService.new(project, user, ref: 'feature')
.execute(:merge_request_event, merge_request: merge_request)
end
@@ -60,16 +60,16 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
- it 'sees branch pipelines and merge request pipelines in correct order' do
+ it 'sees branch pipelines and detached merge request pipelines in correct order' do
page.within('.ci-table') do
expect(page).to have_selector('.ci-pending', count: 2)
- expect(first('.js-pipeline-url-link')).to have_content("##{merge_request_pipeline.id}")
+ expect(first('.js-pipeline-url-link')).to have_content("##{detached_merge_request_pipeline.id}")
end
end
- it 'sees the latest merge request pipeline as the head pipeline' do
+ it 'sees the latest detached merge request pipeline as the head pipeline' do
page.within('.ci-widget-content') do
- expect(page).to have_content("##{merge_request_pipeline.id}")
+ expect(page).to have_content("##{detached_merge_request_pipeline.id}")
end
end
@@ -79,7 +79,7 @@ describe 'Merge request > User sees merge request pipelines', :js do
.execute(:push)
end
- let!(:merge_request_pipeline_2) do
+ let!(:detached_merge_request_pipeline_2) do
Ci::CreatePipelineService.new(project, user, ref: 'feature')
.execute(:merge_request_event, merge_request: merge_request)
end
@@ -92,15 +92,15 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
- it 'sees branch pipelines and merge request pipelines in correct order' do
+ it 'sees branch pipelines and detached merge request pipelines in correct order' do
page.within('.ci-table') do
expect(page).to have_selector('.ci-pending', count: 4)
expect(all('.js-pipeline-url-link')[0])
- .to have_content("##{merge_request_pipeline_2.id}")
+ .to have_content("##{detached_merge_request_pipeline_2.id}")
expect(all('.js-pipeline-url-link')[1])
- .to have_content("##{merge_request_pipeline.id}")
+ .to have_content("##{detached_merge_request_pipeline.id}")
expect(all('.js-pipeline-url-link')[2])
.to have_content("##{push_pipeline_2.id}")
@@ -110,25 +110,25 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
- it 'sees merge request tag for merge request pipelines' do
+ it 'sees detached tag for detached merge request pipelines' do
page.within('.ci-table') do
expect(all('.pipeline-tags')[0])
- .to have_content("merge request")
+ .to have_content("detached")
expect(all('.pipeline-tags')[1])
- .to have_content("merge request")
+ .to have_content("detached")
expect(all('.pipeline-tags')[2])
- .not_to have_content("merge request")
+ .not_to have_content("detached")
expect(all('.pipeline-tags')[3])
- .not_to have_content("merge request")
+ .not_to have_content("detached")
end
end
- it 'sees the latest merge request pipeline as the head pipeline' do
+ it 'sees the latest detached merge request pipeline as the head pipeline' do
page.within('.ci-widget-content') do
- expect(page).to have_content("##{merge_request_pipeline_2.id}")
+ expect(page).to have_content("##{detached_merge_request_pipeline_2.id}")
end
end
end
@@ -140,16 +140,16 @@ describe 'Merge request > User sees merge request pipelines', :js do
wait_for_requests
end
- context 'when merge request pipeline is pending' do
+ context 'when detached merge request pipeline is pending' do
it 'waits the head pipeline' do
expect(page).to have_content('to be merged automatically when the pipeline succeeds')
expect(page).to have_link('Cancel automatic merge')
end
end
- context 'when merge request pipeline succeeds' do
+ context 'when detached merge request pipeline succeeds' do
before do
- merge_request_pipeline.succeed!
+ detached_merge_request_pipeline.succeed!
wait_for_requests
end
@@ -218,7 +218,7 @@ describe 'Merge request > User sees merge request pipelines', :js do
.execute(:push)
end
- let!(:merge_request_pipeline) do
+ let!(:detached_merge_request_pipeline) do
Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
.execute(:merge_request_event, merge_request: merge_request)
end
@@ -236,16 +236,16 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
- it 'sees branch pipelines and merge request pipelines in correct order' do
+ it 'sees branch pipelines and detached merge request pipelines in correct order' do
page.within('.ci-table') do
expect(page).to have_selector('.ci-pending', count: 2)
- expect(first('.js-pipeline-url-link')).to have_content("##{merge_request_pipeline.id}")
+ expect(first('.js-pipeline-url-link')).to have_content("##{detached_merge_request_pipeline.id}")
end
end
- it 'sees the latest merge request pipeline as the head pipeline' do
+ it 'sees the latest detached merge request pipeline as the head pipeline' do
page.within('.ci-widget-content') do
- expect(page).to have_content("##{merge_request_pipeline.id}")
+ expect(page).to have_content("##{detached_merge_request_pipeline.id}")
end
end
@@ -261,7 +261,7 @@ describe 'Merge request > User sees merge request pipelines', :js do
.execute(:push)
end
- let!(:merge_request_pipeline_2) do
+ let!(:detached_merge_request_pipeline_2) do
Ci::CreatePipelineService.new(forked_project, user2, ref: 'feature')
.execute(:merge_request_event, merge_request: merge_request)
end
@@ -274,15 +274,15 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
- it 'sees branch pipelines and merge request pipelines in correct order' do
+ it 'sees branch pipelines and detached merge request pipelines in correct order' do
page.within('.ci-table') do
expect(page).to have_selector('.ci-pending', count: 4)
expect(all('.js-pipeline-url-link')[0])
- .to have_content("##{merge_request_pipeline_2.id}")
+ .to have_content("##{detached_merge_request_pipeline_2.id}")
expect(all('.js-pipeline-url-link')[1])
- .to have_content("##{merge_request_pipeline.id}")
+ .to have_content("##{detached_merge_request_pipeline.id}")
expect(all('.js-pipeline-url-link')[2])
.to have_content("##{push_pipeline_2.id}")
@@ -292,25 +292,25 @@ describe 'Merge request > User sees merge request pipelines', :js do
end
end
- it 'sees merge request tag for merge request pipelines' do
+ it 'sees detached tag for detached merge request pipelines' do
page.within('.ci-table') do
expect(all('.pipeline-tags')[0])
- .to have_content("merge request")
+ .to have_content("detached")
expect(all('.pipeline-tags')[1])
- .to have_content("merge request")
+ .to have_content("detached")
expect(all('.pipeline-tags')[2])
- .not_to have_content("merge request")
+ .not_to have_content("detached")
expect(all('.pipeline-tags')[3])
- .not_to have_content("merge request")
+ .not_to have_content("detached")
end
end
- it 'sees the latest merge request pipeline as the head pipeline' do
+ it 'sees the latest detached merge request pipeline as the head pipeline' do
page.within('.ci-widget-content') do
- expect(page).to have_content("##{merge_request_pipeline_2.id}")
+ expect(page).to have_content("##{detached_merge_request_pipeline_2.id}")
end
end
@@ -328,16 +328,16 @@ describe 'Merge request > User sees merge request pipelines', :js do
wait_for_requests
end
- context 'when merge request pipeline is pending' do
+ context 'when detached merge request pipeline is pending' do
it 'waits the head pipeline' do
expect(page).to have_content('to be merged automatically when the pipeline succeeds')
expect(page).to have_link('Cancel automatic merge')
end
end
- context 'when merge request pipeline succeeds' do
+ context 'when detached merge request pipeline succeeds' do
before do
- merge_request_pipeline.succeed!
+ detached_merge_request_pipeline.succeed!
wait_for_requests
end
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 afb978d7c45..2609546990d 100644
--- a/spec/features/merge_request/user_sees_merge_widget_spec.rb
+++ b/spec/features/merge_request/user_sees_merge_widget_spec.rb
@@ -145,6 +145,119 @@ describe 'Merge request > User sees merge widget', :js do
end
end
+ context 'when merge request has a branch pipeline as the head pipeline' do
+ let!(:pipeline) do
+ create(:ci_pipeline,
+ ref: merge_request.source_branch,
+ sha: merge_request.source_branch_sha,
+ project: merge_request.source_project)
+ end
+
+ before do
+ merge_request.update_head_pipeline
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'shows head pipeline information' do
+ within '.ci-widget-content' do
+ expect(page).to have_content("Pipeline ##{pipeline.id} pending " \
+ "for #{pipeline.short_sha} " \
+ "on #{pipeline.ref}")
+ end
+ end
+ end
+
+ context 'when merge request has a detached merge request pipeline as the head pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_detached_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project)
+ end
+
+ let!(:pipeline) do
+ merge_request.all_pipelines.last
+ end
+
+ let(:source_project) { project }
+ let(:target_project) { project }
+
+ before do
+ merge_request.update_head_pipeline
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'shows head pipeline information' do
+ within '.ci-widget-content' do
+ expect(page).to have_content("Pipeline ##{pipeline.id} pending " \
+ "for #{pipeline.short_sha} " \
+ "on #{merge_request.to_reference} " \
+ "with #{merge_request.source_branch}")
+ end
+ end
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+
+ it 'shows head pipeline information' do
+ within '.ci-widget-content' do
+ expect(page).to have_content("Pipeline ##{pipeline.id} pending " \
+ "for #{pipeline.short_sha} " \
+ "on #{merge_request.to_reference} " \
+ "with #{merge_request.source_branch}")
+ end
+ end
+ end
+ end
+
+ context 'when merge request has a merge request pipeline as the head pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project,
+ merge_sha: merge_sha)
+ end
+
+ let!(:pipeline) do
+ merge_request.all_pipelines.last
+ end
+
+ let(:source_project) { project }
+ let(:target_project) { project }
+ let(:merge_sha) { project.commit.sha }
+
+ before do
+ merge_request.update_head_pipeline
+ visit project_merge_request_path(project, merge_request)
+ end
+
+ it 'shows head pipeline information' do
+ within '.ci-widget-content' do
+ expect(page).to have_content("Pipeline ##{pipeline.id} pending " \
+ "for #{pipeline.short_sha} " \
+ "on #{merge_request.to_reference} " \
+ "with #{merge_request.source_branch} " \
+ "into #{merge_request.target_branch}")
+ end
+ end
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+ let(:merge_sha) { source_project.commit.sha }
+
+ it 'shows head pipeline information' do
+ within '.ci-widget-content' do
+ expect(page).to have_content("Pipeline ##{pipeline.id} pending " \
+ "for #{pipeline.short_sha} " \
+ "on #{merge_request.to_reference} " \
+ "with #{merge_request.source_branch} " \
+ "into #{merge_request.target_branch}")
+ end
+ end
+ end
+ end
+
context 'view merge request with MWBS button' do
before do
commit_status = create(:commit_status, project: project, status: 'pending')
diff --git a/spec/features/merge_request/user_views_diffs_spec.rb b/spec/features/merge_request/user_views_diffs_spec.rb
index 0434db04113..74342b16cb2 100644
--- a/spec/features/merge_request/user_views_diffs_spec.rb
+++ b/spec/features/merge_request/user_views_diffs_spec.rb
@@ -34,6 +34,16 @@ describe 'User views diffs', :js do
expect(page).not_to have_selector('.mr-loading-status .loading', visible: true)
end
+ it 'expands all diffs' do
+ first('#a5cc2925ca8258af241be7e5b0381edf30266302 .js-file-title').click
+
+ expect(page).to have_button('Expand all')
+
+ click_button 'Expand all'
+
+ expect(page).not_to have_button('Expand all')
+ end
+
context 'when in the inline view' do
include_examples 'unfold diffs'
end
diff --git a/spec/features/merge_requests/user_filters_by_target_branch_spec.rb b/spec/features/merge_requests/user_filters_by_target_branch_spec.rb
new file mode 100644
index 00000000000..ffbdacc68f6
--- /dev/null
+++ b/spec/features/merge_requests/user_filters_by_target_branch_spec.rb
@@ -0,0 +1,45 @@
+require 'rails_helper'
+
+describe 'Merge Requests > User filters by target branch', :js do
+ include FilteredSearchHelpers
+
+ let!(:project) { create(:project, :public, :repository) }
+ let!(:user) { project.creator }
+ let!(:mr1) { create(:merge_request, source_project: project, target_project: project, source_branch: 'feature', target_branch: 'master') }
+ let!(:mr2) { create(:merge_request, source_project: project, target_project: project, source_branch: 'feature', target_branch: 'merged-target') }
+
+ before do
+ sign_in(user)
+ visit project_merge_requests_path(project)
+ end
+
+ context 'filtering by target-branch:master' do
+ it 'applies the filter' do
+ input_filtered_search('target-branch:master')
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).to have_content mr1.title
+ expect(page).not_to have_content mr2.title
+ end
+ end
+
+ context 'filtering by target-branch:merged-target' do
+ it 'applies the filter' do
+ input_filtered_search('target-branch:merged-target')
+
+ expect(page).to have_issuable_counts(open: 1, closed: 0, all: 1)
+ expect(page).not_to have_content mr1.title
+ expect(page).to have_content mr2.title
+ end
+ end
+
+ context 'filtering by target-branch:feature' do
+ it 'applies the filter' do
+ input_filtered_search('target-branch:feature')
+
+ expect(page).to have_issuable_counts(open: 0, closed: 0, all: 0)
+ expect(page).not_to have_content mr1.title
+ expect(page).not_to have_content mr2.title
+ end
+ end
+end
diff --git a/spec/features/projects/badges/pipeline_badge_spec.rb b/spec/features/projects/badges/pipeline_badge_spec.rb
index dee81898928..4ac4e8f0fcb 100644
--- a/spec/features/projects/badges/pipeline_badge_spec.rb
+++ b/spec/features/projects/badges/pipeline_badge_spec.rb
@@ -41,6 +41,25 @@ describe 'Pipeline Badge' do
end
end
+ context 'when the pipeline is preparing' do
+ let!(:job) { create(:ci_build, status: 'created', pipeline: pipeline) }
+
+ before do
+ # Prevent skipping directly to 'pending'
+ allow(Ci::BuildPrepareWorker).to receive(:perform_async)
+ allow(job).to receive(:prerequisites).and_return([double])
+ end
+
+ it 'displays the preparing badge' do
+ job.enqueue
+
+ visit pipeline_project_badges_path(project, ref: ref, format: :svg)
+
+ expect(page.status_code).to eq(200)
+ expect_badge('preparing')
+ end
+ end
+
context 'when the pipeline is running' do
it 'shows displays so on the badge' do
create(:ci_build, pipeline: pipeline, name: 'second build', status_event: 'run')
diff --git a/spec/features/projects/clusters/applications_spec.rb b/spec/features/projects/clusters/applications_spec.rb
index 4981bf794d9..aa1c3902f0f 100644
--- a/spec/features/projects/clusters/applications_spec.rb
+++ b/spec/features/projects/clusters/applications_spec.rb
@@ -227,7 +227,7 @@ describe 'Clusters Applications', :js do
expect(page).to have_css('.js-cluster-application-install-button', exact_text: 'Installed')
expect(page).to have_css('.js-cluster-application-install-button[disabled]')
expect(page).to have_selector('.js-no-endpoint-message')
- expect(page.find('.js-endpoint').value).to eq('?')
+ expect(page).to have_selector('.js-ingress-ip-loading-icon')
# We receive the external IP address and display
Clusters::Cluster.last.application_ingress.update!(external_ip: '192.168.1.100')
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 65ce872363f..224375daf71 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -2,6 +2,9 @@ require 'spec_helper'
require 'tempfile'
describe 'Jobs', :clean_gitlab_redis_shared_state do
+ include Gitlab::Routing
+ include ProjectForksHelper
+
let(:user) { create(:user) }
let(:user_access_level) { :developer }
let(:project) { create(:project, :repository) }
@@ -121,6 +124,112 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end
end
+ context 'pipeline info block', :js do
+ it 'shows pipeline id and source branch' do
+ visit project_job_path(project, job)
+
+ within '.js-pipeline-info' do
+ expect(page).to have_content("Pipeline ##{pipeline.id} for #{pipeline.ref}")
+ end
+ end
+
+ context 'when pipeline is detached merge request pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_detached_merge_request_pipeline,
+ target_project: target_project,
+ source_project: source_project)
+ end
+
+ let(:source_project) { project }
+ let(:target_project) { project }
+ let(:pipeline) { merge_request.all_pipelines.last }
+ let(:job) { create(:ci_build, pipeline: pipeline) }
+
+ it 'shows merge request iid and source branch' do
+ visit project_job_path(project, job)
+
+ within '.js-pipeline-info' do
+ expect(page).to have_content("for !#{pipeline.merge_request.iid} " \
+ "with #{pipeline.merge_request.source_branch}")
+ expect(page).to have_link("!#{pipeline.merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(pipeline.merge_request.source_branch,
+ href: project_commits_path(project, merge_request.source_branch))
+ end
+ end
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+ let(:target_project) { project }
+
+ it 'shows merge request iid and source branch' do
+ visit project_job_path(source_project, job)
+
+ within '.js-pipeline-info' do
+ expect(page).to have_content("for !#{pipeline.merge_request.iid} " \
+ "with #{pipeline.merge_request.source_branch}")
+ expect(page).to have_link("!#{pipeline.merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(pipeline.merge_request.source_branch,
+ href: project_commits_path(source_project, merge_request.source_branch))
+ end
+ end
+ end
+ end
+
+ context 'when pipeline is merge request pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_merge_request_pipeline,
+ target_project: target_project,
+ source_project: source_project)
+ end
+
+ let(:source_project) { project }
+ let(:target_project) { project }
+ let(:pipeline) { merge_request.all_pipelines.last }
+ let(:job) { create(:ci_build, pipeline: pipeline) }
+
+ it 'shows merge request iid and source branch' do
+ visit project_job_path(project, job)
+
+ within '.js-pipeline-info' do
+ expect(page).to have_content("for !#{pipeline.merge_request.iid} " \
+ "with #{pipeline.merge_request.source_branch} " \
+ "into #{pipeline.merge_request.target_branch}")
+ expect(page).to have_link("!#{pipeline.merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(pipeline.merge_request.source_branch,
+ href: project_commits_path(project, merge_request.source_branch))
+ expect(page).to have_link(pipeline.merge_request.target_branch,
+ href: project_commits_path(project, merge_request.target_branch))
+ end
+ end
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+ let(:target_project) { project }
+
+ it 'shows merge request iid and source branch' do
+ visit project_job_path(source_project, job)
+
+ within '.js-pipeline-info' do
+ expect(page).to have_content("for !#{pipeline.merge_request.iid} " \
+ "with #{pipeline.merge_request.source_branch} " \
+ "into #{pipeline.merge_request.target_branch}")
+ expect(page).to have_link("!#{pipeline.merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(pipeline.merge_request.source_branch,
+ href: project_commits_path(source_project, merge_request.source_branch))
+ expect(page).to have_link(pipeline.merge_request.target_branch,
+ href: project_commits_path(project, merge_request.target_branch))
+ end
+ end
+ end
+ end
+ end
+
context 'sidebar', :js do
let(:job) { create(:ci_build, :success, :trace_live, pipeline: pipeline, name: '<img src=x onerror=alert(document.domain)>') }
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 36b8c15b8b6..9fdf78baa1e 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -1,6 +1,9 @@
require 'spec_helper'
describe 'Pipeline', :js do
+ include RoutesHelpers
+ include ProjectForksHelper
+
let(:project) { create(:project) }
let(:user) { create(:user) }
let(:role) { :developer }
@@ -21,6 +24,11 @@ describe 'Pipeline', :js do
pipeline: pipeline, stage: 'test', name: 'test')
end
+ let!(:build_preparing) do
+ create(:ci_build, :preparing,
+ pipeline: pipeline, stage: 'deploy', name: 'prepare')
+ end
+
let!(:build_running) do
create(:ci_build, :running,
pipeline: pipeline, stage: 'deploy', name: 'deploy')
@@ -72,6 +80,15 @@ describe 'Pipeline', :js do
expect(page).to have_link(pipeline.ref)
end
+ it 'shows the pipeline information' do
+ within '.pipeline-info' do
+ expect(page).to have_content("#{pipeline.statuses.count} jobs " \
+ "for #{pipeline.ref} ")
+ expect(page).to have_link(pipeline.ref,
+ href: project_commits_path(pipeline.project, pipeline.ref))
+ end
+ end
+
it_behaves_like 'showing user status' do
let(:user_with_status) { pipeline.user }
@@ -97,6 +114,24 @@ describe 'Pipeline', :js do
end
end
+ context 'when pipeline has preparing builds' do
+ it 'shows a preparing icon and a cancel action' do
+ page.within('#ci-badge-prepare') do
+ expect(page).to have_selector('.js-ci-status-icon-preparing')
+ expect(page).to have_selector('.js-icon-cancel')
+ expect(page).to have_content('prepare')
+ end
+ end
+
+ it 'cancels the preparing build and shows retry button' do
+ find('#ci-badge-deploy .ci-action-icon-container').click
+
+ page.within('#ci-badge-deploy') do
+ expect(page).to have_css('.js-icon-retry')
+ end
+ end
+ end
+
context 'when pipeline has successful builds' do
it 'shows the success icon and a retry action for the successful build' do
page.within('#ci-badge-build') do
@@ -254,6 +289,113 @@ describe 'Pipeline', :js do
expect(page).to have_content(pipeline.ref)
end
end
+
+ context 'when pipeline is detached merge request pipeline' do
+ let(:source_project) { project }
+ let(:target_project) { project }
+
+ let(:merge_request) do
+ create(:merge_request,
+ :with_detached_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project)
+ end
+
+ let(:pipeline) do
+ merge_request.all_pipelines.last
+ end
+
+ it 'shows the pipeline information' do
+ within '.pipeline-info' do
+ expect(page).to have_content("#{pipeline.statuses.count} jobs " \
+ "for !#{merge_request.iid} " \
+ "with #{merge_request.source_branch}")
+ expect(page).to have_link("!#{merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(merge_request.source_branch,
+ href: project_commits_path(merge_request.source_project, merge_request.source_branch))
+ end
+ end
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+
+ before do
+ visit project_pipeline_path(source_project, pipeline)
+ end
+
+ it 'shows the pipeline information' do
+ within '.pipeline-info' do
+ expect(page).to have_content("#{pipeline.statuses.count} jobs " \
+ "for !#{merge_request.iid} " \
+ "with #{merge_request.source_branch}")
+ expect(page).to have_link("!#{merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(merge_request.source_branch,
+ href: project_commits_path(merge_request.source_project, merge_request.source_branch))
+ end
+ end
+ end
+ end
+
+ context 'when pipeline is merge request pipeline' do
+ let(:source_project) { project }
+ let(:target_project) { project }
+
+ let(:merge_request) do
+ create(:merge_request,
+ :with_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project,
+ merge_sha: project.commit.id)
+ end
+
+ let(:pipeline) do
+ merge_request.all_pipelines.last
+ end
+
+ before do
+ pipeline.update(user: user)
+ end
+
+ it 'shows the pipeline information' do
+ within '.pipeline-info' do
+ expect(page).to have_content("#{pipeline.statuses.count} jobs " \
+ "for !#{merge_request.iid} " \
+ "with #{merge_request.source_branch} " \
+ "into #{merge_request.target_branch}")
+ expect(page).to have_link("!#{merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(merge_request.source_branch,
+ href: project_commits_path(merge_request.source_project, merge_request.source_branch))
+ expect(page).to have_link(merge_request.target_branch,
+ href: project_commits_path(merge_request.target_project, merge_request.target_branch))
+ end
+ end
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+
+ before do
+ visit project_pipeline_path(source_project, pipeline)
+ end
+
+ it 'shows the pipeline information' do
+ within '.pipeline-info' do
+ expect(page).to have_content("#{pipeline.statuses.count} jobs " \
+ "for !#{merge_request.iid} " \
+ "with #{merge_request.source_branch} " \
+ "into #{merge_request.target_branch}")
+ expect(page).to have_link("!#{merge_request.iid}",
+ href: project_merge_request_path(project, merge_request))
+ expect(page).to have_link(merge_request.source_branch,
+ href: project_commits_path(merge_request.source_project, merge_request.source_branch))
+ expect(page).to have_link(merge_request.target_branch,
+ href: project_commits_path(merge_request.target_project, merge_request.target_branch))
+ end
+ end
+ end
+ end
end
context 'when user does not have access to read jobs' do
@@ -686,9 +828,9 @@ describe 'Pipeline', :js do
visit project_pipeline_path(project, pipeline)
end
- it 'contains badge that indicates merge request pipeline' do
+ it 'contains badge that indicates detached merge request pipeline' do
page.within(all('.well-segment')[1]) do
- expect(page).to have_content 'merge request'
+ expect(page).to have_content 'detached'
end
end
end
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 88d7c9ef8bd..7ca3b3d8edd 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe 'Pipelines', :js do
+ include ProjectForksHelper
+
let(:project) { create(:project) }
context 'when user is logged in' do
@@ -165,6 +167,99 @@ describe 'Pipelines', :js do
end
end
+ context 'when pipeline is detached merge request pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_detached_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project)
+ end
+
+ let!(:pipeline) { merge_request.all_pipelines.first }
+ let(:source_project) { project }
+ let(:target_project) { project }
+
+ before do
+ visit project_pipelines_path(source_project)
+ end
+
+ shared_examples_for 'showing detached merge request pipeline information' do
+ it 'shows detached tag for the pipeline' do
+ within '.pipeline-tags' do
+ expect(page).to have_content('detached')
+ end
+ end
+
+ it 'shows the link of the merge request' do
+ within '.branch-commit' do
+ expect(page).to have_link(merge_request.iid,
+ href: project_merge_request_path(project, merge_request))
+ end
+ end
+
+ it 'does not show the ref of the pipeline' do
+ within '.branch-commit' do
+ expect(page).not_to have_link(pipeline.ref)
+ end
+ end
+ end
+
+ it_behaves_like 'showing detached merge request pipeline information'
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+
+ it_behaves_like 'showing detached merge request pipeline information'
+ end
+ end
+
+ context 'when pipeline is merge request pipeline' do
+ let(:merge_request) do
+ create(:merge_request,
+ :with_merge_request_pipeline,
+ source_project: source_project,
+ target_project: target_project,
+ merge_sha: target_project.commit.sha)
+ end
+
+ let!(:pipeline) { merge_request.all_pipelines.first }
+ let(:source_project) { project }
+ let(:target_project) { project }
+
+ before do
+ visit project_pipelines_path(source_project)
+ end
+
+ shared_examples_for 'Correct merge request pipeline information' do
+ it 'does not show detached tag for the pipeline' do
+ within '.pipeline-tags' do
+ expect(page).not_to have_content('detached')
+ end
+ end
+
+ it 'shows the link of the merge request' do
+ within '.branch-commit' do
+ expect(page).to have_link(merge_request.iid,
+ href: project_merge_request_path(project, merge_request))
+ end
+ end
+
+ it 'does not show the ref of the pipeline' do
+ within '.branch-commit' do
+ expect(page).not_to have_link(pipeline.ref)
+ end
+ end
+ end
+
+ it_behaves_like 'Correct merge request pipeline information'
+
+ context 'when source project is a forked project' do
+ let(:source_project) { fork_project(project, user, repository: true) }
+
+ it_behaves_like 'Correct merge request pipeline information'
+ end
+ end
+
context 'when pipeline has configuration errors' do
let(:pipeline) do
create(:ci_pipeline, :invalid, project: project)
@@ -282,6 +377,30 @@ describe 'Pipelines', :js do
end
context 'for generic statuses' do
+ context 'when preparing' do
+ let!(:pipeline) do
+ create(:ci_empty_pipeline,
+ status: 'preparing', project: project)
+ end
+
+ let!(:status) do
+ create(:generic_commit_status,
+ :preparing, pipeline: pipeline)
+ end
+
+ before do
+ visit_project_pipelines
+ end
+
+ it 'is cancelable' do
+ expect(page).to have_selector('.js-pipelines-cancel-button')
+ end
+
+ it 'shows the pipeline as preparing' do
+ expect(page).to have_selector('.ci-preparing')
+ end
+ end
+
context 'when running' do
let!(:running) do
create(:generic_commit_status,
diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb
index 4c85abe9971..bf0c0de89b2 100644
--- a/spec/features/projects/settings/pipelines_settings_spec.rb
+++ b/spec/features/projects/settings/pipelines_settings_spec.rb
@@ -110,6 +110,37 @@ describe "Projects > Settings > Pipelines settings" do
expect(page).not_to have_content('instance enabled')
end
end
+
+ context 'when auto devops is turned on group level' do
+ before do
+ project.update!(namespace: create(:group, :auto_devops_enabled))
+ end
+
+ it 'renders group enabled badge' do
+ visit project_settings_ci_cd_path(project)
+
+ page.within '#autodevops-settings' do
+ expect(page).to have_content('group enabled')
+ expect(find_field('project_auto_devops_attributes_enabled')).to be_checked
+ end
+ end
+ end
+
+ context 'when auto devops is turned on group parent level', :nested_groups do
+ before do
+ group = create(:group, parent: create(:group, :auto_devops_enabled))
+ project.update!(namespace: group)
+ end
+
+ it 'renders group enabled badge' do
+ visit project_settings_ci_cd_path(project)
+
+ page.within '#autodevops-settings' do
+ expect(page).to have_content('group enabled')
+ expect(find_field('project_auto_devops_attributes_enabled')).to be_checked
+ end
+ end
+ end
end
end
diff --git a/spec/features/projects/show/user_sees_git_instructions_spec.rb b/spec/features/projects/show/user_sees_git_instructions_spec.rb
index ffa80235083..0d59ef4a727 100644
--- a/spec/features/projects/show/user_sees_git_instructions_spec.rb
+++ b/spec/features/projects/show/user_sees_git_instructions_spec.rb
@@ -13,7 +13,7 @@ describe 'Projects > Show > User sees Git instructions' do
it 'shows Git command line instructions' do
click_link 'Create empty repository'
- page.within '.empty_wrapper' do
+ page.within '.empty-wrapper' do
expect(page).to have_content('Command line instructions')
end
end
diff --git a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
index dcca1d388c7..58bd20d7551 100644
--- a/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
+++ b/spec/features/projects/show/user_sees_setup_shortcut_buttons_spec.rb
@@ -20,18 +20,18 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
visit project_path(project)
end
- it 'no Auto DevOps button if can not manage pipelines' do
- page.within('.project-buttons') do
- expect(page).not_to have_link('Enable Auto DevOps')
- expect(page).not_to have_link('Auto DevOps enabled')
- end
- end
-
- it '"Auto DevOps enabled" button not linked' do
+ it 'Project buttons are not visible' do
visit project_path(project)
page.within('.project-buttons') do
- expect(page).to have_text('Auto DevOps enabled')
+ expect(page).not_to have_link('New file')
+ expect(page).not_to have_link('Add README')
+ expect(page).not_to have_link('Add CHANGELOG')
+ expect(page).not_to have_link('Add CONTRIBUTING')
+ expect(page).not_to have_link('Enable Auto DevOps')
+ expect(page).not_to have_link('Auto DevOps enabled')
+ expect(page).not_to have_link('Add Kubernetes cluster')
+ expect(page).not_to have_link('Kubernetes configured')
end
end
end
@@ -61,46 +61,6 @@ describe 'Projects > Show > User sees setup shortcut buttons' do
expect(page).to have_link('Add license', href: presenter.add_license_path)
end
end
-
- describe 'Auto DevOps button' do
- context 'when Auto DevOps is enabled' do
- it '"Auto DevOps enabled" anchor linked to settings page' do
- visit project_path(project)
-
- page.within('.project-buttons') do
- expect(page).to have_link('Auto DevOps enabled', href: project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
- end
- end
- end
-
- context 'when Auto DevOps is not enabled' do
- let(:project) { create(:project, :public, :empty_repo, auto_devops_attributes: { enabled: false }) }
-
- it '"Enable Auto DevOps" button linked to settings page' do
- page.within('.project-buttons') do
- expect(page).to have_link('Enable Auto DevOps', href: project_settings_ci_cd_path(project, anchor: 'autodevops-settings'))
- end
- end
- end
- end
-
- describe 'Kubernetes cluster button' do
- it '"Add Kubernetes cluster" button linked to clusters page' do
- page.within('.project-buttons') do
- expect(page).to have_link('Add Kubernetes cluster', href: new_project_cluster_path(project))
- end
- end
-
- it '"Kubernetes cluster" anchor linked to cluster page' do
- cluster = create(:cluster, :provided_by_gcp, projects: [project])
-
- visit project_path(project)
-
- page.within('.project-buttons') do
- expect(page).to have_link('Kubernetes configured', href: project_cluster_path(project, cluster))
- end
- end
- end
end
end
diff --git a/spec/features/search/user_searches_for_users_spec.rb b/spec/features/search/user_searches_for_users_spec.rb
new file mode 100644
index 00000000000..3725143291d
--- /dev/null
+++ b/spec/features/search/user_searches_for_users_spec.rb
@@ -0,0 +1,83 @@
+require 'spec_helper'
+
+describe 'User searches for users' do
+ context 'when on the dashboard' do
+ it 'finds the user' do
+ create(:user, username: 'gob_bluth', name: 'Gob Bluth')
+
+ sign_in(create(:user))
+
+ visit dashboard_projects_path
+
+ fill_in 'search', with: 'gob'
+ click_button 'Go'
+
+ expect(page).to have_content('Users 1')
+
+ click_on('Users 1')
+
+ expect(page).to have_content('Gob Bluth')
+ expect(page).to have_content('@gob_bluth')
+ end
+ end
+
+ context 'when on the project page' do
+ it 'finds the user belonging to the project' do
+ project = create(:project)
+
+ user1 = create(:user, username: 'gob_bluth', name: 'Gob Bluth')
+ create(:project_member, :developer, user: user1, project: project)
+
+ user2 = create(:user, username: 'michael_bluth', name: 'Michael Bluth')
+ create(:project_member, :developer, user: user2, project: project)
+
+ create(:user, username: 'gob_2018', name: 'George Oscar Bluth')
+
+ sign_in(user1)
+
+ visit projects_path(project)
+
+ fill_in 'search', with: 'gob'
+ click_button 'Go'
+
+ expect(page).to have_content('Gob Bluth')
+ expect(page).to have_content('@gob_bluth')
+
+ expect(page).not_to have_content('Michael Bluth')
+ expect(page).not_to have_content('@michael_bluth')
+
+ expect(page).not_to have_content('George Oscar Bluth')
+ expect(page).not_to have_content('@gob_2018')
+ end
+ end
+
+ context 'when on the group page' do
+ it 'finds the user belonging to the group' do
+ group = create(:group)
+
+ user1 = create(:user, username: 'gob_bluth', name: 'Gob Bluth')
+ create(:group_member, :developer, user: user1, group: group)
+
+ user2 = create(:user, username: 'michael_bluth', name: 'Michael Bluth')
+ create(:group_member, :developer, user: user2, group: group)
+
+ create(:user, username: 'gob_2018', name: 'George Oscar Bluth')
+
+ sign_in(user1)
+
+ visit group_path(group)
+
+ fill_in 'search', with: 'gob'
+ click_button 'Go'
+
+ expect(page).to have_content('Gob Bluth')
+ expect(page).to have_content('@gob_bluth')
+
+ expect(page).not_to have_content('Michael Bluth')
+ expect(page).not_to have_content('@michael_bluth')
+
+ expect(page).not_to have_content('George Oscar Bluth')
+ expect(page).not_to have_content('@gob_2018')
+ end
+ end
+end
diff --git a/spec/features/security/group/private_access_spec.rb b/spec/features/security/group/private_access_spec.rb
index 3238e07fe15..a776169a8e5 100644
--- a/spec/features/security/group/private_access_spec.rb
+++ b/spec/features/security/group/private_access_spec.rb
@@ -27,7 +27,7 @@ describe 'Private Group access' do
it { is_expected.to be_allowed_for(:developer).of(group) }
it { is_expected.to be_allowed_for(:reporter).of(group) }
it { is_expected.to be_allowed_for(:guest).of(group) }
- it { is_expected.to be_denied_for(project_guest) }
+ it { is_expected.to be_allowed_for(project_guest) }
it { is_expected.to be_denied_for(:user) }
it { is_expected.to be_denied_for(:external) }
it { is_expected.to be_denied_for(:visitor) }
@@ -42,7 +42,7 @@ describe 'Private Group access' do
it { is_expected.to be_allowed_for(:developer).of(group) }
it { is_expected.to be_allowed_for(:reporter).of(group) }
it { is_expected.to be_allowed_for(:guest).of(group) }
- it { is_expected.to be_denied_for(project_guest) }
+ it { is_expected.to be_allowed_for(project_guest) }
it { is_expected.to be_denied_for(:user) }
it { is_expected.to be_denied_for(:external) }
it { is_expected.to be_denied_for(:visitor) }
@@ -58,7 +58,7 @@ describe 'Private Group access' do
it { is_expected.to be_allowed_for(:developer).of(group) }
it { is_expected.to be_allowed_for(:reporter).of(group) }
it { is_expected.to be_allowed_for(:guest).of(group) }
- it { is_expected.to be_denied_for(project_guest) }
+ it { is_expected.to be_allowed_for(project_guest) }
it { is_expected.to be_denied_for(:user) }
it { is_expected.to be_denied_for(:external) }
it { is_expected.to be_denied_for(:visitor) }
@@ -73,7 +73,7 @@ describe 'Private Group access' do
it { is_expected.to be_allowed_for(:developer).of(group) }
it { is_expected.to be_allowed_for(:reporter).of(group) }
it { is_expected.to be_allowed_for(:guest).of(group) }
- it { is_expected.to be_denied_for(project_guest) }
+ it { is_expected.to be_allowed_for(project_guest) }
it { is_expected.to be_denied_for(:user) }
it { is_expected.to be_denied_for(:external) }
it { is_expected.to be_denied_for(:visitor) }
@@ -96,12 +96,9 @@ describe 'Private Group access' do
describe 'GET /groups/:path for shared projects' do
let(:project) { create(:project, :public) }
+
before do
- Projects::GroupLinks::CreateService.new(
- project,
- create(:user),
- link_group_access: ProjectGroupLink::DEVELOPER
- ).execute(group)
+ create(:project_group_link, project: project, group: group)
end
subject { group_path(group) }
diff --git a/spec/features/user_opens_link_to_comment.rb b/spec/features/user_opens_link_to_comment.rb
new file mode 100644
index 00000000000..f1e07e55799
--- /dev/null
+++ b/spec/features/user_opens_link_to_comment.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'User opens link to comment', :js do
+ let(:project) { create(:project, :public) }
+ let(:note) { create(:note_on_issue, project: project) }
+
+ context 'authenticated user' do
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+ end
+
+ it 'switches to all activity and does not show error message' do
+ create(:user_preference, user: user, issue_notes_filter: UserPreference::NOTES_FILTERS[:only_activity])
+
+ visit Gitlab::UrlBuilder.build(note)
+
+ expect(page.find('#discussion-filter-dropdown')).to have_content('Show all activity')
+ expect(page).not_to have_content('Something went wrong while fetching comments')
+ end
+ end
+
+ context 'anonymous user' do
+ it 'does not show error message' do
+ visit Gitlab::UrlBuilder.build(note)
+
+ expect(page).not_to have_content('Something went wrong while fetching comments')
+ end
+ end
+end
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index ad856bd062e..368a814874f 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -434,16 +434,22 @@ describe 'Login' do
context 'within the grace period' do
it 'redirects to two-factor configuration page' do
- expect(authentication_metrics)
- .to increment(:user_authenticated_counter)
-
- gitlab_sign_in(user)
-
- expect(current_path).to eq profile_two_factor_auth_path
- expect(page).to have_content(
- 'The group settings for Group 1 and Group 2 require you to enable ' \
- 'Two-Factor Authentication for your account. You need to do this ' \
- 'before ')
+ Timecop.freeze do
+ expect(authentication_metrics)
+ .to increment(:user_authenticated_counter)
+
+ gitlab_sign_in(user)
+
+ expect(current_path).to eq profile_two_factor_auth_path
+ expect(page).to have_content(
+ 'The group settings for Group 1 and Group 2 require you to enable '\
+ 'Two-Factor Authentication for your account. '\
+ 'You can leave Group 1 and leave Group 2. '\
+ 'You need to do this '\
+ 'before '\
+ "#{(Time.zone.now + 2.days).strftime("%a, %-d %b %Y %H:%M:%S %z")}"
+ )
+ end
end
it 'allows skipping two-factor configuration', :js do
@@ -500,7 +506,8 @@ describe 'Login' do
expect(current_path).to eq profile_two_factor_auth_path
expect(page).to have_content(
'The group settings for Group 1 and Group 2 require you to enable ' \
- 'Two-Factor Authentication for your account.'
+ 'Two-Factor Authentication for your account. '\
+ 'You can leave Group 1 and leave Group 2.'
)
end
end
diff --git a/spec/finders/group_projects_finder_spec.rb b/spec/finders/group_projects_finder_spec.rb
index d6d95906f5e..f8fcc2d0e40 100644
--- a/spec/finders/group_projects_finder_spec.rb
+++ b/spec/finders/group_projects_finder_spec.rb
@@ -1,26 +1,7 @@
require 'spec_helper'
describe GroupProjectsFinder do
- let(:group) { create(:group) }
- let(:subgroup) { create(:group, parent: group) }
- let(:current_user) { create(:user) }
- let(:options) { {} }
-
- let(:finder) { described_class.new(group: group, current_user: current_user, options: options) }
-
- let!(:public_project) { create(:project, :public, group: group, path: '1') }
- let!(:private_project) { create(:project, :private, group: group, path: '2') }
- let!(:shared_project_1) { create(:project, :public, path: '3') }
- let!(:shared_project_2) { create(:project, :private, path: '4') }
- let!(:shared_project_3) { create(:project, :internal, path: '5') }
- let!(:subgroup_project) { create(:project, :public, path: '6', group: subgroup) }
- let!(:subgroup_private_project) { create(:project, :private, path: '7', group: subgroup) }
-
- before do
- shared_project_1.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group)
- shared_project_2.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group)
- shared_project_3.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group)
- end
+ include_context 'GroupProjectsFinder context'
subject { finder.execute }
@@ -144,6 +125,24 @@ describe GroupProjectsFinder do
end
end
+ describe 'with an admin current user' do
+ let(:current_user) { create(:admin) }
+
+ context "only shared" do
+ let(:options) { { only_shared: true } }
+ it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1]) }
+ end
+
+ context "only owned" do
+ let(:options) { { only_owned: true } }
+ it { is_expected.to eq([private_project, public_project]) }
+ end
+
+ context "all" do
+ it { is_expected.to eq([shared_project_3, shared_project_2, shared_project_1, private_project, public_project]) }
+ end
+ end
+
describe "no user" do
context "only shared" do
let(:options) { { only_shared: true } }
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 55efab7dec3..00b6cad1a66 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -1,45 +1,10 @@
require 'spec_helper'
describe IssuesFinder do
- set(:user) { create(:user) }
- set(:user2) { create(:user) }
- set(:group) { create(:group) }
- set(:subgroup) { create(:group, parent: group) }
- set(:project1) { create(:project, group: group) }
- set(:project2) { create(:project) }
- set(:project3) { create(:project, group: subgroup) }
- set(:milestone) { create(:milestone, project: project1) }
- set(:label) { create(:label, project: project2) }
- set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago, updated_at: 1.week.ago) }
- set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab', created_at: 1.week.from_now, updated_at: 1.week.from_now) }
- set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 2.weeks.from_now, updated_at: 2.weeks.from_now) }
- set(:issue4) { create(:issue, project: project3) }
- set(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: issue1) }
- set(:award_emoji2) { create(:award_emoji, name: 'thumbsup', user: user2, awardable: issue2) }
- set(:award_emoji3) { create(:award_emoji, name: 'thumbsdown', user: user, awardable: issue3) }
+ include_context 'IssuesFinder context'
describe '#execute' do
- let!(:closed_issue) { create(:issue, author: user2, assignees: [user2], project: project2, state: 'closed') }
- let!(:label_link) { create(:label_link, label: label, target: issue2) }
- let(:search_user) { user }
- let(:params) { {} }
- let(:issues) { described_class.new(search_user, params.reverse_merge(scope: scope, state: 'opened')).execute }
-
- before(:context) do
- project1.add_maintainer(user)
- project2.add_developer(user)
- project2.add_developer(user2)
- project3.add_developer(user)
-
- issue1
- issue2
- issue3
- issue4
-
- award_emoji1
- award_emoji2
- award_emoji3
- end
+ include_context 'IssuesFinder#execute context'
context 'scope: all' do
let(:scope) { 'all' }
@@ -56,6 +21,21 @@ describe IssuesFinder do
end
end
+ context 'filtering by assignee usernames' do
+ set(:user3) { create(:user) }
+ let(:params) { { assignee_username: [user2.username, user3.username] } }
+
+ before do
+ project2.add_developer(user3)
+
+ issue3.assignees = [user2, user3]
+ end
+
+ it 'returns issues assigned to those users' do
+ expect(issues).to contain_exactly(issue3)
+ end
+ end
+
context 'filtering by no assignee' do
let(:params) { { assignee_id: 'None' } }
@@ -220,6 +200,7 @@ describe IssuesFinder do
let(:yesterday) { Date.today - 1.day }
let(:tomorrow) { Date.today + 1.day }
let(:two_days_ago) { Date.today - 2.days }
+ let(:three_days_ago) { Date.today - 3.days }
let(:milestones) do
[
@@ -227,6 +208,8 @@ describe IssuesFinder do
create(:milestone, project: project_started_1_and_2, title: '1.0', start_date: two_days_ago),
create(:milestone, project: project_started_1_and_2, title: '2.0', start_date: yesterday),
create(:milestone, project: project_started_1_and_2, title: '3.0', start_date: tomorrow),
+ create(:milestone, :closed, project: project_started_1_and_2, title: '4.0', start_date: three_days_ago),
+ create(:milestone, :closed, project: project_started_8, title: '6.0', start_date: three_days_ago),
create(:milestone, project: project_started_8, title: '7.0'),
create(:milestone, project: project_started_8, title: '8.0', start_date: yesterday),
create(:milestone, project: project_started_8, title: '9.0', start_date: tomorrow)
@@ -640,6 +623,16 @@ describe IssuesFinder do
expect(subject).to include(public_issue, confidential_issue)
end
end
+
+ context 'for an admin' do
+ let(:admin_user) { create(:user, :admin) }
+
+ subject { described_class.new(admin_user, params).with_confidentiality_access_check }
+
+ it 'returns all issues' do
+ expect(subject).to include(public_issue, confidential_issue)
+ end
+ end
end
context 'when searching within a specific project' do
@@ -707,6 +700,22 @@ describe IssuesFinder do
subject
end
end
+
+ context 'for an admin' do
+ let(:admin_user) { create(:user, :admin) }
+
+ subject { described_class.new(admin_user, params).with_confidentiality_access_check }
+
+ it 'returns all issues' do
+ expect(subject).to include(public_issue, confidential_issue)
+ end
+
+ it 'does not filter by confidentiality' do
+ expect(Issue).not_to receive(:where).with(a_string_matching('confidential'), anything)
+
+ subject
+ end
+ end
end
end
diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb
index 9abc52aa664..3f060ba0553 100644
--- a/spec/finders/labels_finder_spec.rb
+++ b/spec/finders/labels_finder_spec.rb
@@ -209,6 +209,12 @@ describe LabelsFinder do
expect(finder.execute).to eq [project_label_1]
end
+
+ it 'returns labels matching a single character' do
+ finder = described_class.new(user, search: '(')
+
+ expect(finder.execute).to eq [group_label_1]
+ end
end
context 'filter by subscription' do
diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb
index 503b88fcbad..56136eb84bc 100644
--- a/spec/finders/merge_requests_finder_spec.rb
+++ b/spec/finders/merge_requests_finder_spec.rb
@@ -1,72 +1,24 @@
require 'spec_helper'
describe MergeRequestsFinder do
- include ProjectForksHelper
-
- # We need to explicitly permit Gitaly N+1s because of the specs that use
- # :request_store. Gitaly N+1 detection is only enabled when :request_store is,
- # but we don't care about potential N+1s when we're just creating several
- # projects in the setup phase.
- def create_project_without_n_plus_1(*args)
- Gitlab::GitalyClient.allow_n_plus_1_calls do
- create(:project, :public, *args)
- end
- end
-
context "multiple projects with merge requests" do
- let(:user) { create :user }
- let(:user2) { create :user }
-
- let(:group) { create(:group) }
- let(:subgroup) { create(:group, parent: group) }
- let(:project1) { create_project_without_n_plus_1(group: group) }
- let(:project2) do
- Gitlab::GitalyClient.allow_n_plus_1_calls do
- fork_project(project1, user)
- end
- end
- let(:project3) do
- Gitlab::GitalyClient.allow_n_plus_1_calls do
- p = fork_project(project1, user)
- p.update!(archived: true)
- p
- end
- end
- let(:project4) { create_project_without_n_plus_1(:repository, group: subgroup) }
- let(:project5) { create_project_without_n_plus_1(group: subgroup) }
- let(:project6) { create_project_without_n_plus_1(group: subgroup) }
-
- let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) }
- let!(:merge_request2) { create(:merge_request, :conflict, author: user, source_project: project2, target_project: project1, state: 'closed') }
- let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2, state: 'locked', title: 'thing WIP thing') }
- let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3, title: 'WIP thing') }
- let!(:merge_request5) { create(:merge_request, :simple, author: user, source_project: project4, target_project: project4, title: '[WIP]') }
- let!(:merge_request6) { create(:merge_request, :simple, author: user, source_project: project5, target_project: project5, title: 'WIP: thing') }
- let!(:merge_request7) { create(:merge_request, :simple, author: user, source_project: project6, target_project: project6, title: 'wip thing') }
- let!(:merge_request8) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project1, title: '[wip] thing') }
- let!(:merge_request9) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project2, title: 'wip: thing') }
-
- before do
- project1.add_maintainer(user)
- project2.add_developer(user)
- project3.add_developer(user)
- project2.add_developer(user2)
- project4.add_developer(user)
- project5.add_developer(user)
- project6.add_developer(user)
- end
+ include_context 'MergeRequestsFinder multiple projects with merge requests context'
describe '#execute' do
it 'filters by scope' do
params = { scope: 'authored', state: 'opened' }
+
merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(7)
+
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request4, merge_request5)
end
it 'filters by project' do
params = { project_id: project1.id, scope: 'authored', state: 'opened' }
+
merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(2)
+
+ expect(merge_requests).to contain_exactly(merge_request1)
end
it 'filters by commit sha' do
@@ -79,24 +31,15 @@ describe MergeRequestsFinder do
end
context 'filtering by group' do
- it 'includes all merge requests when user has access' do
- params = { group_id: group.id }
-
- merge_requests = described_class.new(user, params).execute
-
- expect(merge_requests.size).to eq(3)
- end
-
- it 'excludes merge requests from projects the user does not have access to' do
- private_project = create_project_without_n_plus_1(:private, group: group)
- private_mr = create(:merge_request, :simple, author: user, source_project: private_project, target_project: private_project)
+ it 'includes all merge requests when user has access exceluding merge requests from projects the user does not have access to' do
+ private_project = allow_gitaly_n_plus_1 { create(:project, :private, group: group) }
+ private_project.add_guest(user)
+ create(:merge_request, :simple, author: user, source_project: private_project, target_project: private_project)
params = { group_id: group.id }
- private_project.add_guest(user)
merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(3)
- expect(merge_requests).not_to include(private_mr)
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request2)
end
it 'filters by group including subgroups', :nested_groups do
@@ -104,14 +47,16 @@ describe MergeRequestsFinder do
merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(6)
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request5)
end
end
it 'filters by non_archived' do
params = { non_archived: true }
+
merge_requests = described_class.new(user, params).execute
- expect(merge_requests.size).to eq(8)
+
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request5)
end
it 'filters by iid' do
@@ -146,41 +91,45 @@ describe MergeRequestsFinder do
expect(merge_requests).to contain_exactly(merge_request3)
end
- it 'filters by wip' do
- params = { wip: 'yes' }
+ describe 'WIP state' do
+ let!(:wip_merge_request1) { create(:merge_request, :simple, author: user, source_project: project5, target_project: project5, title: 'WIP: thing') }
+ let!(:wip_merge_request2) { create(:merge_request, :simple, author: user, source_project: project6, target_project: project6, title: 'wip thing') }
+ let!(:wip_merge_request3) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project1, title: '[wip] thing') }
+ let!(:wip_merge_request4) { create(:merge_request, :simple, author: user, source_project: project1, target_project: project2, title: 'wip: thing') }
- merge_requests = described_class.new(user, params).execute
+ it 'filters by wip' do
+ params = { wip: 'yes' }
- expect(merge_requests).to contain_exactly(merge_request4, merge_request5, merge_request6, merge_request7, merge_request8, merge_request9)
- end
+ merge_requests = described_class.new(user, params).execute
- it 'filters by not wip' do
- params = { wip: 'no' }
+ expect(merge_requests).to contain_exactly(merge_request4, merge_request5, wip_merge_request1, wip_merge_request2, wip_merge_request3, wip_merge_request4)
+ end
- merge_requests = described_class.new(user, params).execute
+ it 'filters by not wip' do
+ params = { wip: 'no' }
- expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3)
- end
+ merge_requests = described_class.new(user, params).execute
- it 'returns all items if no valid wip param exists' do
- params = { wip: '' }
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3)
+ end
- merge_requests = described_class.new(user, params).execute
+ it 'returns all items if no valid wip param exists' do
+ params = { wip: '' }
- expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request4, merge_request5, merge_request6, merge_request7, merge_request8, merge_request9)
- end
+ merge_requests = described_class.new(user, params).execute
- it 'adds wip to scalar params' do
- scalar_params = described_class.scalar_params
+ expect(merge_requests).to contain_exactly(merge_request1, merge_request2, merge_request3, merge_request4, merge_request5, wip_merge_request1, wip_merge_request2, wip_merge_request3, wip_merge_request4)
+ end
+
+ it 'adds wip to scalar params' do
+ scalar_params = described_class.scalar_params
- expect(scalar_params).to include(:wip, :assignee_id)
+ expect(scalar_params).to include(:wip, :assignee_id)
+ end
end
context 'filtering by group milestone' do
- let!(:group) { create(:group, :public) }
let(:group_milestone) { create(:milestone, group: group) }
- let!(:group_member) { create(:group_member, group: group, user: user) }
- let(:params) { { milestone_title: group_milestone.title } }
before do
project2.update(namespace: group)
@@ -188,7 +137,9 @@ describe MergeRequestsFinder do
merge_request3.update(milestone: group_milestone)
end
- it 'returns issues assigned to that group milestone' do
+ it 'returns merge requests assigned to that group milestone' do
+ params = { milestone_title: group_milestone.title }
+
merge_requests = described_class.new(user, params).execute
expect(merge_requests).to contain_exactly(merge_request2, merge_request3)
@@ -285,7 +236,7 @@ describe MergeRequestsFinder do
it 'returns the number of rows for the default state' do
finder = described_class.new(user)
- expect(finder.row_count).to eq(7)
+ expect(finder.row_count).to eq(3)
end
it 'returns the number of rows for a given state' do
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
index 134fb5f2c04..93287f3e9b8 100644
--- a/spec/finders/snippets_finder_spec.rb
+++ b/spec/finders/snippets_finder_spec.rb
@@ -2,7 +2,6 @@ require 'spec_helper'
describe SnippetsFinder do
include Gitlab::Allowable
- using RSpec::Parameterized::TableSyntax
describe '#initialize' do
it 'raises ArgumentError when a project and author are given' do
@@ -14,174 +13,142 @@ describe SnippetsFinder do
end
end
- context 'filter by scope' do
- let(:user) { create :user }
- let!(:snippet1) { create(:personal_snippet, :private, author: user) }
- let!(:snippet2) { create(:personal_snippet, :internal, author: user) }
- let!(:snippet3) { create(:personal_snippet, :public, author: user) }
-
- it "returns all snippets for 'all' scope" do
- snippets = described_class.new(user, scope: :all).execute
-
- expect(snippets).to include(snippet1, snippet2, snippet3)
- end
-
- it "returns all snippets for 'are_private' scope" do
- snippets = described_class.new(user, scope: :are_private).execute
+ describe '#execute' do
+ set(:user) { create(:user) }
+ set(:private_personal_snippet) { create(:personal_snippet, :private, author: user) }
+ set(:internal_personal_snippet) { create(:personal_snippet, :internal, author: user) }
+ set(:public_personal_snippet) { create(:personal_snippet, :public, author: user) }
- expect(snippets).to include(snippet1)
- expect(snippets).not_to include(snippet2, snippet3)
- end
+ context 'filter by scope' do
+ it "returns all snippets for 'all' scope" do
+ snippets = described_class.new(user, scope: :all).execute
- it "returns all snippets for 'are_internal' scope" do
- snippets = described_class.new(user, scope: :are_internal).execute
+ expect(snippets).to contain_exactly(private_personal_snippet, internal_personal_snippet, public_personal_snippet)
+ end
- expect(snippets).to include(snippet2)
- expect(snippets).not_to include(snippet1, snippet3)
- end
+ it "returns all snippets for 'are_private' scope" do
+ snippets = described_class.new(user, scope: :are_private).execute
- it "returns all snippets for 'are_private' scope" do
- snippets = described_class.new(user, scope: :are_public).execute
+ expect(snippets).to contain_exactly(private_personal_snippet)
+ end
- expect(snippets).to include(snippet3)
- expect(snippets).not_to include(snippet1, snippet2)
- end
- end
+ it "returns all snippets for 'are_internal' scope" do
+ snippets = described_class.new(user, scope: :are_internal).execute
- context 'filter by author' do
- let(:user) { create :user }
- let(:user1) { create :user }
- let!(:snippet1) { create(:personal_snippet, :private, author: user) }
- let!(:snippet2) { create(:personal_snippet, :internal, author: user) }
- let!(:snippet3) { create(:personal_snippet, :public, author: user) }
+ expect(snippets).to contain_exactly(internal_personal_snippet)
+ end
- it "returns all public and internal snippets" do
- snippets = described_class.new(user1, author: user).execute
+ it "returns all snippets for 'are_private' scope" do
+ snippets = described_class.new(user, scope: :are_public).execute
- expect(snippets).to include(snippet2, snippet3)
- expect(snippets).not_to include(snippet1)
+ expect(snippets).to contain_exactly(public_personal_snippet)
+ end
end
- it "returns internal snippets" do
- snippets = described_class.new(user, author: user, scope: :are_internal).execute
+ context 'filter by author' do
+ it 'returns all public and internal snippets' do
+ snippets = described_class.new(create(:user), author: user).execute
- expect(snippets).to include(snippet2)
- expect(snippets).not_to include(snippet1, snippet3)
- end
+ expect(snippets).to contain_exactly(internal_personal_snippet, public_personal_snippet)
+ end
- it "returns private snippets" do
- snippets = described_class.new(user, author: user, scope: :are_private).execute
+ it 'returns internal snippets' do
+ snippets = described_class.new(user, author: user, scope: :are_internal).execute
- expect(snippets).to include(snippet1)
- expect(snippets).not_to include(snippet2, snippet3)
- end
+ expect(snippets).to contain_exactly(internal_personal_snippet)
+ end
- it "returns public snippets" do
- snippets = described_class.new(user, author: user, scope: :are_public).execute
+ it 'returns private snippets' do
+ snippets = described_class.new(user, author: user, scope: :are_private).execute
- expect(snippets).to include(snippet3)
- expect(snippets).not_to include(snippet1, snippet2)
- end
+ expect(snippets).to contain_exactly(private_personal_snippet)
+ end
- it "returns all snippets" do
- snippets = described_class.new(user, author: user).execute
+ it 'returns public snippets' do
+ snippets = described_class.new(user, author: user, scope: :are_public).execute
- expect(snippets).to include(snippet1, snippet2, snippet3)
- end
+ expect(snippets).to contain_exactly(public_personal_snippet)
+ end
- it "returns only public snippets if unauthenticated user" do
- snippets = described_class.new(nil, author: user).execute
+ it 'returns all snippets' do
+ snippets = described_class.new(user, author: user).execute
- expect(snippets).to include(snippet3)
- expect(snippets).not_to include(snippet2, snippet1)
- end
+ expect(snippets).to contain_exactly(private_personal_snippet, internal_personal_snippet, public_personal_snippet)
+ end
- it 'returns all snippets for an admin' do
- admin = create(:user, :admin)
- snippets = described_class.new(admin, author: user).execute
+ it 'returns only public snippets if unauthenticated user' do
+ snippets = described_class.new(nil, author: user).execute
- expect(snippets).to include(snippet1, snippet2, snippet3)
- end
- end
+ expect(snippets).to contain_exactly(public_personal_snippet)
+ end
- context 'filter by project' do
- let(:user) { create :user }
- let(:group) { create :group, :public }
- let(:project1) { create(:project, :public, group: group) }
+ it 'returns all snippets for an admin' do
+ admin = create(:user, :admin)
+ snippets = described_class.new(admin, author: user).execute
- before do
- @snippet1 = create(:project_snippet, :private, project: project1)
- @snippet2 = create(:project_snippet, :internal, project: project1)
- @snippet3 = create(:project_snippet, :public, project: project1)
+ expect(snippets).to contain_exactly(private_personal_snippet, internal_personal_snippet, public_personal_snippet)
+ end
end
- it "returns public snippets for unauthorized user" do
- snippets = described_class.new(nil, project: project1).execute
+ context 'project snippets' do
+ let(:group) { create(:group, :public) }
+ let(:project) { create(:project, :public, group: group) }
+ let!(:private_project_snippet) { create(:project_snippet, :private, project: project) }
+ let!(:internal_project_snippet) { create(:project_snippet, :internal, project: project) }
+ let!(:public_project_snippet) { create(:project_snippet, :public, project: project) }
- expect(snippets).to include(@snippet3)
- expect(snippets).not_to include(@snippet1, @snippet2)
- end
+ it 'returns public personal and project snippets for unauthorized user' do
+ snippets = described_class.new(nil, project: project).execute
- it "returns public and internal snippets for non project members" do
- snippets = described_class.new(user, project: project1).execute
+ expect(snippets).to contain_exactly(public_project_snippet)
+ end
- expect(snippets).to include(@snippet2, @snippet3)
- expect(snippets).not_to include(@snippet1)
- end
+ it 'returns public and internal snippets for non project members' do
+ snippets = described_class.new(user, project: project).execute
- it "returns public snippets for non project members" do
- snippets = described_class.new(user, project: project1, scope: :are_public).execute
+ expect(snippets).to contain_exactly(internal_project_snippet, public_project_snippet)
+ end
- expect(snippets).to include(@snippet3)
- expect(snippets).not_to include(@snippet1, @snippet2)
- end
+ it 'returns public snippets for non project members' do
+ snippets = described_class.new(user, project: project, scope: :are_public).execute
- it "returns internal snippets for non project members" do
- snippets = described_class.new(user, project: project1, scope: :are_internal).execute
+ expect(snippets).to contain_exactly(public_project_snippet)
+ end
- expect(snippets).to include(@snippet2)
- expect(snippets).not_to include(@snippet1, @snippet3)
- end
+ it 'returns internal snippets for non project members' do
+ snippets = described_class.new(user, project: project, scope: :are_internal).execute
- it "does not return private snippets for non project members" do
- snippets = described_class.new(user, project: project1, scope: :are_private).execute
+ expect(snippets).to contain_exactly(internal_project_snippet)
+ end
- expect(snippets).not_to include(@snippet1, @snippet2, @snippet3)
- end
+ it 'does not return private snippets for non project members' do
+ snippets = described_class.new(user, project: project, scope: :are_private).execute
- it "returns all snippets for project members" do
- project1.add_developer(user)
+ expect(snippets).to be_empty
+ end
- snippets = described_class.new(user, project: project1).execute
+ it 'returns all snippets for project members' do
+ project.add_developer(user)
- expect(snippets).to include(@snippet1, @snippet2, @snippet3)
- end
+ snippets = described_class.new(user, project: project).execute
- it "returns private snippets for project members" do
- project1.add_developer(user)
+ expect(snippets).to contain_exactly(private_project_snippet, internal_project_snippet, public_project_snippet)
+ end
- snippets = described_class.new(user, project: project1, scope: :are_private).execute
+ it 'returns private snippets for project members' do
+ project.add_developer(user)
- expect(snippets).to include(@snippet1)
- end
+ snippets = described_class.new(user, project: project, scope: :are_private).execute
- it 'returns all snippets for an admin' do
- admin = create(:user, :admin)
- snippets = described_class.new(admin, project: project1).execute
+ expect(snippets).to contain_exactly(private_project_snippet)
+ end
- expect(snippets).to include(@snippet1, @snippet2, @snippet3)
- end
- end
+ it 'returns all snippets for an admin' do
+ admin = create(:user, :admin)
+ snippets = described_class.new(admin, project: project).execute
- describe '#execute' do
- let(:project) { create(:project, :public) }
- let!(:project_snippet) { create(:project_snippet, :public, project: project) }
- let!(:personal_snippet) { create(:personal_snippet, :public) }
- let(:user) { create(:user) }
- subject(:finder) { described_class.new(user) }
-
- it 'returns project- and personal snippets' do
- expect(finder.execute).to contain_exactly(project_snippet, personal_snippet)
+ expect(snippets).to contain_exactly(private_project_snippet, internal_project_snippet, public_project_snippet)
+ end
end
context 'when the user cannot read cross project' do
@@ -191,7 +158,7 @@ describe SnippetsFinder do
end
it 'returns only personal snippets when the user cannot read cross project' do
- expect(finder.execute).to contain_exactly(personal_snippet)
+ expect(described_class.new(user).execute).to contain_exactly(private_personal_snippet, internal_personal_snippet, public_personal_snippet)
end
end
end
diff --git a/spec/finders/users_finder_spec.rb b/spec/finders/users_finder_spec.rb
index fecf97dc641..d71d3c99272 100644
--- a/spec/finders/users_finder_spec.rb
+++ b/spec/finders/users_finder_spec.rb
@@ -2,10 +2,7 @@ require 'spec_helper'
describe UsersFinder do
describe '#execute' do
- let!(:user1) { create(:user, username: 'johndoe') }
- let!(:user2) { create(:user, :blocked, username: 'notsorandom') }
- let!(:external_user) { create(:user, :external) }
- let!(:omniauth_user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') }
+ include_context 'UsersFinder#execute filter by project context'
context 'with a normal user' do
let(:user) { create(:user) }
@@ -13,43 +10,43 @@ describe UsersFinder do
it 'returns all users' do
users = described_class.new(user).execute
- expect(users).to contain_exactly(user, user1, user2, omniauth_user)
+ expect(users).to contain_exactly(user, normal_user, blocked_user, omniauth_user)
end
it 'filters by username' do
users = described_class.new(user, username: 'johndoe').execute
- expect(users).to contain_exactly(user1)
+ expect(users).to contain_exactly(normal_user)
end
it 'filters by username (case insensitive)' do
users = described_class.new(user, username: 'joHNdoE').execute
- expect(users).to contain_exactly(user1)
+ expect(users).to contain_exactly(normal_user)
end
it 'filters by search' do
users = described_class.new(user, search: 'orando').execute
- expect(users).to contain_exactly(user2)
+ expect(users).to contain_exactly(blocked_user)
end
it 'filters by blocked users' do
users = described_class.new(user, blocked: true).execute
- expect(users).to contain_exactly(user2)
+ expect(users).to contain_exactly(blocked_user)
end
it 'filters by active users' do
users = described_class.new(user, active: true).execute
- expect(users).to contain_exactly(user, user1, omniauth_user)
+ expect(users).to contain_exactly(user, normal_user, omniauth_user)
end
it 'returns no external users' do
users = described_class.new(user, external: true).execute
- expect(users).to contain_exactly(user, user1, user2, omniauth_user)
+ expect(users).to contain_exactly(user, normal_user, blocked_user, omniauth_user)
end
it 'filters by created_at' do
@@ -69,7 +66,7 @@ describe UsersFinder do
custom_attributes: { foo: 'bar' }
).execute
- expect(users).to contain_exactly(user, user1, user2, omniauth_user)
+ expect(users).to contain_exactly(user, normal_user, blocked_user, omniauth_user)
end
end
@@ -85,20 +82,20 @@ describe UsersFinder do
it 'returns all users' do
users = described_class.new(admin).execute
- expect(users).to contain_exactly(admin, user1, user2, external_user, omniauth_user)
+ expect(users).to contain_exactly(admin, normal_user, blocked_user, external_user, omniauth_user)
end
it 'filters by custom attributes' do
- create :user_custom_attribute, user: user1, key: 'foo', value: 'foo'
- create :user_custom_attribute, user: user1, key: 'bar', value: 'bar'
- create :user_custom_attribute, user: user2, key: 'foo', value: 'foo'
+ create :user_custom_attribute, user: normal_user, key: 'foo', value: 'foo'
+ create :user_custom_attribute, user: normal_user, key: 'bar', value: 'bar'
+ create :user_custom_attribute, user: blocked_user, key: 'foo', value: 'foo'
users = described_class.new(
admin,
custom_attributes: { foo: 'foo', bar: 'bar' }
).execute
- expect(users).to contain_exactly(user1)
+ expect(users).to contain_exactly(normal_user)
end
end
end
diff --git a/spec/fixtures/api/schemas/board.json b/spec/fixtures/api/schemas/board.json
index 03aca4a3cc0..7c146647948 100644
--- a/spec/fixtures/api/schemas/board.json
+++ b/spec/fixtures/api/schemas/board.json
@@ -6,6 +6,5 @@
"properties" : {
"id": { "type": "integer" },
"name": { "type": "string" }
- },
- "additionalProperties": false
+ }
}
diff --git a/spec/fixtures/api/schemas/entities/issue.json b/spec/fixtures/api/schemas/entities/issue.json
index 00abe73ec8a..162fb9c8108 100644
--- a/spec/fixtures/api/schemas/entities/issue.json
+++ b/spec/fixtures/api/schemas/entities/issue.json
@@ -38,6 +38,5 @@
"items": { "$ref": "label.json" }
},
"assignees": { "type": ["array", "null"] }
- },
- "additionalProperties": false
+ }
}
diff --git a/spec/fixtures/api/schemas/entities/issue_boards.json b/spec/fixtures/api/schemas/entities/issue_boards.json
index 0ac1d9468c8..742f7be5485 100644
--- a/spec/fixtures/api/schemas/entities/issue_boards.json
+++ b/spec/fixtures/api/schemas/entities/issue_boards.json
@@ -10,6 +10,5 @@
"items": { "$ref": "issue_board.json" }
},
"size": { "type": "integer" }
- },
- "additionalProperties": false
+ }
}
diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json
index 67c209f3fc3..6b1cd60c25d 100644
--- a/spec/fixtures/api/schemas/entities/merge_request_widget.json
+++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json
@@ -52,6 +52,7 @@
"mergeable_discussions_state": { "type": "boolean" },
"conflicts_can_be_resolved_in_ui": { "type": "boolean" },
"branch_missing": { "type": "boolean" },
+ "commits_count": { "type": ["integer", "null"] },
"has_conflicts": { "type": "boolean" },
"can_be_merged": { "type": "boolean" },
"mergeable": { "type": "boolean" },
@@ -125,6 +126,5 @@
"can_receive_suggestion": { "type": "boolean" },
"source_branch_protected": { "type": "boolean" },
"conflicts_docs_path": { "type": ["string", "null"] }
- },
- "additionalProperties": false
+ }
}
diff --git a/spec/fixtures/api/schemas/issue.json b/spec/fixtures/api/schemas/issue.json
index a83ec55cede..77de9ae4f9f 100644
--- a/spec/fixtures/api/schemas/issue.json
+++ b/spec/fixtures/api/schemas/issue.json
@@ -28,7 +28,7 @@
"items": { "$ref": "entities/label.json" }
},
"assignee": {
- "id": { "type": "integet" },
+ "id": { "type": "integer" },
"name": { "type": "string" },
"username": { "type": "string" },
"avatar_url": { "type": "uri" }
@@ -52,6 +52,5 @@
}
},
"subscribed": { "type": ["boolean", "null"] }
- },
- "additionalProperties": false
+ }
}
diff --git a/spec/fixtures/api/schemas/issues.json b/spec/fixtures/api/schemas/issues.json
index 70771b21c96..fbcd9eea389 100644
--- a/spec/fixtures/api/schemas/issues.json
+++ b/spec/fixtures/api/schemas/issues.json
@@ -10,6 +10,5 @@
"items": { "$ref": "issue.json" }
},
"size": { "type": "integer" }
- },
- "additionalProperties": false
+ }
}
diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_request.json b/spec/fixtures/api/schemas/public_api/v4/merge_request.json
new file mode 100644
index 00000000000..cd50be00418
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/merge_request.json
@@ -0,0 +1,124 @@
+{
+ "type": "object",
+ "properties" : {
+ "properties" : {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "project_id": { "type": "integer" },
+ "title": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "state": { "type": "string" },
+ "merged_by": {
+ "type": ["object", "null"],
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ },
+ "merged_at": { "type": ["date", "null"] },
+ "closed_by": {
+ "type": ["object", "null"],
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ },
+ "closed_at": { "type": ["date", "null"] },
+ "created_at": { "type": "date" },
+ "updated_at": { "type": "date" },
+ "target_branch": { "type": "string" },
+ "source_branch": { "type": "string" },
+ "upvotes": { "type": "integer" },
+ "downvotes": { "type": "integer" },
+ "author": {
+ "type": "object",
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ },
+ "assignee": {
+ "type": ["object", "null"],
+ "properties": {
+ "name": { "type": "string" },
+ "username": { "type": "string" },
+ "id": { "type": "integer" },
+ "state": { "type": "string" },
+ "avatar_url": { "type": "uri" },
+ "web_url": { "type": "uri" }
+ },
+ "additionalProperties": false
+ },
+ "source_project_id": { "type": "integer" },
+ "target_project_id": { "type": "integer" },
+ "labels": {
+ "type": "array",
+ "items": {
+ "type": "string"
+ }
+ },
+ "work_in_progress": { "type": "boolean" },
+ "milestone": {
+ "type": ["object", "null"],
+ "properties": {
+ "id": { "type": "integer" },
+ "iid": { "type": "integer" },
+ "project_id": { "type": ["integer", "null"] },
+ "group_id": { "type": ["integer", "null"] },
+ "title": { "type": "string" },
+ "description": { "type": ["string", "null"] },
+ "state": { "type": "string" },
+ "created_at": { "type": "date" },
+ "updated_at": { "type": "date" },
+ "due_date": { "type": "date" },
+ "start_date": { "type": "date" }
+ },
+ "additionalProperties": false
+ },
+ "merge_when_pipeline_succeeds": { "type": "boolean" },
+ "merge_status": { "type": "string" },
+ "sha": { "type": "string" },
+ "merge_commit_sha": { "type": ["string", "null"] },
+ "user_notes_count": { "type": "integer" },
+ "changes_count": { "type": "string" },
+ "should_remove_source_branch": { "type": ["boolean", "null"] },
+ "force_remove_source_branch": { "type": ["boolean", "null"] },
+ "discussion_locked": { "type": ["boolean", "null"] },
+ "web_url": { "type": "uri" },
+ "squash": { "type": "boolean" },
+ "time_stats": {
+ "time_estimate": { "type": "integer" },
+ "total_time_spent": { "type": "integer" },
+ "human_time_estimate": { "type": ["string", "null"] },
+ "human_total_time_spent": { "type": ["string", "null"] }
+ },
+ "allow_collaboration": { "type": ["boolean", "null"] },
+ "allow_maintainer_to_push": { "type": ["boolean", "null"] }
+ },
+ "required": [
+ "id", "iid", "project_id", "title", "description",
+ "state", "created_at", "updated_at", "target_branch",
+ "source_branch", "upvotes", "downvotes", "author",
+ "assignee", "source_project_id", "target_project_id",
+ "labels", "work_in_progress", "milestone", "merge_when_pipeline_succeeds",
+ "merge_status", "sha", "merge_commit_sha", "user_notes_count",
+ "should_remove_source_branch", "force_remove_source_branch",
+ "web_url", "squash"
+ ]
+ }
+}
diff --git a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
index 6df27bf32b9..b35c83950e8 100644
--- a/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
+++ b/spec/fixtures/api/schemas/public_api/v4/merge_requests.json
@@ -1,126 +1,6 @@
{
"type": "array",
"items": {
- "type": "object",
- "properties" : {
- "id": { "type": "integer" },
- "iid": { "type": "integer" },
- "project_id": { "type": "integer" },
- "title": { "type": "string" },
- "description": { "type": ["string", "null"] },
- "state": { "type": "string" },
- "merged_by": {
- "type": ["object", "null"],
- "properties": {
- "name": { "type": "string" },
- "username": { "type": "string" },
- "id": { "type": "integer" },
- "state": { "type": "string" },
- "avatar_url": { "type": "uri" },
- "web_url": { "type": "uri" }
- },
- "additionalProperties": false
- },
- "merged_at": { "type": ["date", "null"] },
- "closed_by": {
- "type": ["object", "null"],
- "properties": {
- "name": { "type": "string" },
- "username": { "type": "string" },
- "id": { "type": "integer" },
- "state": { "type": "string" },
- "avatar_url": { "type": "uri" },
- "web_url": { "type": "uri" }
- },
- "additionalProperties": false
- },
- "closed_at": { "type": ["date", "null"] },
- "created_at": { "type": "date" },
- "updated_at": { "type": "date" },
- "target_branch": { "type": "string" },
- "source_branch": { "type": "string" },
- "upvotes": { "type": "integer" },
- "downvotes": { "type": "integer" },
- "author": {
- "type": "object",
- "properties": {
- "name": { "type": "string" },
- "username": { "type": "string" },
- "id": { "type": "integer" },
- "state": { "type": "string" },
- "avatar_url": { "type": "uri" },
- "web_url": { "type": "uri" }
- },
- "additionalProperties": false
- },
- "assignee": {
- "type": ["object", "null"],
- "properties": {
- "name": { "type": "string" },
- "username": { "type": "string" },
- "id": { "type": "integer" },
- "state": { "type": "string" },
- "avatar_url": { "type": "uri" },
- "web_url": { "type": "uri" }
- },
- "additionalProperties": false
- },
- "source_project_id": { "type": "integer" },
- "target_project_id": { "type": "integer" },
- "labels": {
- "type": "array",
- "items": {
- "type": "string"
- }
- },
- "work_in_progress": { "type": "boolean" },
- "milestone": {
- "type": ["object", "null"],
- "properties": {
- "id": { "type": "integer" },
- "iid": { "type": "integer" },
- "project_id": { "type": ["integer", "null"] },
- "group_id": { "type": ["integer", "null"] },
- "title": { "type": "string" },
- "description": { "type": ["string", "null"] },
- "state": { "type": "string" },
- "created_at": { "type": "date" },
- "updated_at": { "type": "date" },
- "due_date": { "type": "date" },
- "start_date": { "type": "date" }
- },
- "additionalProperties": false
- },
- "merge_when_pipeline_succeeds": { "type": "boolean" },
- "merge_status": { "type": "string" },
- "sha": { "type": "string" },
- "merge_commit_sha": { "type": ["string", "null"] },
- "user_notes_count": { "type": "integer" },
- "changes_count": { "type": "string" },
- "should_remove_source_branch": { "type": ["boolean", "null"] },
- "force_remove_source_branch": { "type": ["boolean", "null"] },
- "discussion_locked": { "type": ["boolean", "null"] },
- "web_url": { "type": "uri" },
- "squash": { "type": "boolean" },
- "time_stats": {
- "time_estimate": { "type": "integer" },
- "total_time_spent": { "type": "integer" },
- "human_time_estimate": { "type": ["string", "null"] },
- "human_total_time_spent": { "type": ["string", "null"] }
- },
- "allow_collaboration": { "type": ["boolean", "null"] },
- "allow_maintainer_to_push": { "type": ["boolean", "null"] }
- },
- "required": [
- "id", "iid", "project_id", "title", "description",
- "state", "created_at", "updated_at", "target_branch",
- "source_branch", "upvotes", "downvotes", "author",
- "assignee", "source_project_id", "target_project_id",
- "labels", "work_in_progress", "milestone", "merge_when_pipeline_succeeds",
- "merge_status", "sha", "merge_commit_sha", "user_notes_count",
- "should_remove_source_branch", "force_remove_source_branch",
- "web_url", "squash"
- ],
- "additionalProperties": false
+ "$ref": "./merge_request.json"
}
}
diff --git a/spec/javascripts/behaviors/secret_values_spec.js b/spec/frontend/behaviors/secret_values_spec.js
index 5aaab093c0c..5aaab093c0c 100644
--- a/spec/javascripts/behaviors/secret_values_spec.js
+++ b/spec/frontend/behaviors/secret_values_spec.js
diff --git a/spec/javascripts/blob/blob_fork_suggestion_spec.js b/spec/frontend/blob/blob_fork_suggestion_spec.js
index 9b81b7e6f92..9b81b7e6f92 100644
--- a/spec/javascripts/blob/blob_fork_suggestion_spec.js
+++ b/spec/frontend/blob/blob_fork_suggestion_spec.js
diff --git a/spec/javascripts/boards/modal_store_spec.js b/spec/frontend/boards/modal_store_spec.js
index 3257a3fb8a3..3257a3fb8a3 100644
--- a/spec/javascripts/boards/modal_store_spec.js
+++ b/spec/frontend/boards/modal_store_spec.js
diff --git a/spec/javascripts/cycle_analytics/limit_warning_component_spec.js b/spec/frontend/cycle_analytics/limit_warning_component_spec.js
index 13e9fe00a00..13e9fe00a00 100644
--- a/spec/javascripts/cycle_analytics/limit_warning_component_spec.js
+++ b/spec/frontend/cycle_analytics/limit_warning_component_spec.js
diff --git a/spec/javascripts/diffs/components/diff_stats_spec.js b/spec/frontend/diffs/components/diff_stats_spec.js
index 984b3026209..984b3026209 100644
--- a/spec/javascripts/diffs/components/diff_stats_spec.js
+++ b/spec/frontend/diffs/components/diff_stats_spec.js
diff --git a/spec/javascripts/diffs/components/edit_button_spec.js b/spec/frontend/diffs/components/edit_button_spec.js
index ccdae4cb312..ccdae4cb312 100644
--- a/spec/javascripts/diffs/components/edit_button_spec.js
+++ b/spec/frontend/diffs/components/edit_button_spec.js
diff --git a/spec/javascripts/diffs/components/hidden_files_warning_spec.js b/spec/frontend/diffs/components/hidden_files_warning_spec.js
index 5bf5ddd27bd..5bf5ddd27bd 100644
--- a/spec/javascripts/diffs/components/hidden_files_warning_spec.js
+++ b/spec/frontend/diffs/components/hidden_files_warning_spec.js
diff --git a/spec/javascripts/diffs/components/no_changes_spec.js b/spec/frontend/diffs/components/no_changes_spec.js
index e45d34bf9d5..e45d34bf9d5 100644
--- a/spec/javascripts/diffs/components/no_changes_spec.js
+++ b/spec/frontend/diffs/components/no_changes_spec.js
diff --git a/spec/javascripts/error_tracking/components/error_tracking_list_spec.js b/spec/frontend/error_tracking/components/error_tracking_list_spec.js
index 503af3920a8..503af3920a8 100644
--- a/spec/javascripts/error_tracking/components/error_tracking_list_spec.js
+++ b/spec/frontend/error_tracking/components/error_tracking_list_spec.js
diff --git a/spec/javascripts/error_tracking/store/mutation_spec.js b/spec/frontend/error_tracking/store/mutation_spec.js
index 8117104bdbc..8117104bdbc 100644
--- a/spec/javascripts/error_tracking/store/mutation_spec.js
+++ b/spec/frontend/error_tracking/store/mutation_spec.js
diff --git a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js b/spec/frontend/filtered_search/filtered_search_token_keys_spec.js
index d1fea18dea8..d1fea18dea8 100644
--- a/spec/javascripts/filtered_search/filtered_search_token_keys_spec.js
+++ b/spec/frontend/filtered_search/filtered_search_token_keys_spec.js
diff --git a/spec/javascripts/filtered_search/services/recent_searches_service_error_spec.js b/spec/frontend/filtered_search/services/recent_searches_service_error_spec.js
index ea7c146fa4f..ea7c146fa4f 100644
--- a/spec/javascripts/filtered_search/services/recent_searches_service_error_spec.js
+++ b/spec/frontend/filtered_search/services/recent_searches_service_error_spec.js
diff --git a/spec/javascripts/filtered_search/stores/recent_searches_store_spec.js b/spec/frontend/filtered_search/stores/recent_searches_store_spec.js
index 56bb82ae941..56bb82ae941 100644
--- a/spec/javascripts/filtered_search/stores/recent_searches_store_spec.js
+++ b/spec/frontend/filtered_search/stores/recent_searches_store_spec.js
diff --git a/spec/javascripts/frequent_items/store/getters_spec.js b/spec/frontend/frequent_items/store/getters_spec.js
index 1cd12eb6832..1cd12eb6832 100644
--- a/spec/javascripts/frequent_items/store/getters_spec.js
+++ b/spec/frontend/frequent_items/store/getters_spec.js
diff --git a/spec/frontend/gfm_auto_complete_spec.js b/spec/frontend/gfm_auto_complete_spec.js
index c7008c780d6..ed12af925f1 100644
--- a/spec/frontend/gfm_auto_complete_spec.js
+++ b/spec/frontend/gfm_auto_complete_spec.js
@@ -3,8 +3,16 @@
import $ from 'jquery';
import GfmAutoComplete from '~/gfm_auto_complete';
-import 'vendor/jquery.caret';
-import 'vendor/jquery.atwho';
+import 'jquery.caret';
+import 'at.js';
+
+import { TEST_HOST } from 'helpers/test_constants';
+import { setTestTimeout } from 'helpers/timeout';
+import { getJSONFixture } from 'helpers/fixtures';
+
+setTestTimeout(500);
+
+const labelsFixture = getJSONFixture('autocomplete_sources/labels.json');
describe('GfmAutoComplete', () => {
const gfmAutoCompleteCallbacks = GfmAutoComplete.prototype.getDefaultCallbacks.call({
@@ -12,11 +20,12 @@ describe('GfmAutoComplete', () => {
});
let atwhoInstance;
- let items;
let sorterValue;
describe('DefaultOptions.sorter', () => {
describe('assets loading', () => {
+ let items;
+
beforeEach(() => {
jest.spyOn(GfmAutoComplete, 'isLoading').mockReturnValue(true);
@@ -61,7 +70,7 @@ describe('GfmAutoComplete', () => {
atwhoInstance = { setting: {} };
const query = 'query';
- items = [];
+ const items = [];
const searchKey = 'searchKey';
gfmAutoCompleteCallbacks.sorter.call(atwhoInstance, query, items, searchKey);
@@ -250,4 +259,90 @@ describe('GfmAutoComplete', () => {
).toBe('<li><small>grp/proj#5</small> Some Issue</li>');
});
});
+
+ describe('labels', () => {
+ const dataSources = {
+ labels: `${TEST_HOST}/autocomplete_sources/labels`,
+ };
+
+ const allLabels = labelsFixture;
+ const assignedLabels = allLabels.filter(label => label.set);
+ const unassignedLabels = allLabels.filter(label => !label.set);
+
+ let autocomplete;
+ let $textarea;
+
+ beforeEach(() => {
+ autocomplete = new GfmAutoComplete(dataSources);
+ $textarea = $('<textarea></textarea>');
+ autocomplete.setup($textarea, { labels: true });
+ });
+
+ afterEach(() => {
+ autocomplete.destroy();
+ });
+
+ const triggerDropdown = text => {
+ $textarea
+ .trigger('focus')
+ .val(text)
+ .caret('pos', -1);
+ $textarea.trigger('keyup');
+
+ return new Promise(window.requestAnimationFrame);
+ };
+
+ const getDropdownItems = () => {
+ const dropdown = document.getElementById('at-view-labels');
+ const items = dropdown.getElementsByTagName('li');
+ return [].map.call(items, item => item.textContent.trim());
+ };
+
+ const expectLabels = ({ input, output }) =>
+ triggerDropdown(input).then(() => {
+ expect(getDropdownItems()).toEqual(output.map(label => label.title));
+ });
+
+ describe('with no labels assigned', () => {
+ beforeEach(() => {
+ autocomplete.cachedData['~'] = [...unassignedLabels];
+ });
+
+ it.each`
+ input | output
+ ${'~'} | ${unassignedLabels}
+ ${'/label ~'} | ${unassignedLabels}
+ ${'/relabel ~'} | ${unassignedLabels}
+ ${'/unlabel ~'} | ${[]}
+ `('$input shows $output.length labels', expectLabels);
+ });
+
+ describe('with some labels assigned', () => {
+ beforeEach(() => {
+ autocomplete.cachedData['~'] = allLabels;
+ });
+
+ it.each`
+ input | output
+ ${'~'} | ${allLabels}
+ ${'/label ~'} | ${unassignedLabels}
+ ${'/relabel ~'} | ${allLabels}
+ ${'/unlabel ~'} | ${assignedLabels}
+ `('$input shows $output.length labels', expectLabels);
+ });
+
+ describe('with all labels assigned', () => {
+ beforeEach(() => {
+ autocomplete.cachedData['~'] = [...assignedLabels];
+ });
+
+ it.each`
+ input | output
+ ${'~'} | ${assignedLabels}
+ ${'/label ~'} | ${[]}
+ ${'/relabel ~'} | ${assignedLabels}
+ ${'/unlabel ~'} | ${assignedLabels}
+ `('$input shows $output.length labels', expectLabels);
+ });
+ });
});
diff --git a/spec/frontend/helpers/fixtures.js b/spec/frontend/helpers/fixtures.js
new file mode 100644
index 00000000000..f96f27c4d80
--- /dev/null
+++ b/spec/frontend/helpers/fixtures.js
@@ -0,0 +1,24 @@
+/* eslint-disable import/prefer-default-export, global-require, import/no-dynamic-require */
+
+import fs from 'fs';
+import path from 'path';
+
+// jest-util is part of Jest
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { ErrorWithStack } from 'jest-util';
+
+const fixturesBasePath = path.join(process.cwd(), 'spec', 'javascripts', 'fixtures');
+
+export function getJSONFixture(relativePath, ee = false) {
+ const absolutePath = path.join(fixturesBasePath, ee ? 'ee' : '', relativePath);
+ if (!fs.existsSync(absolutePath)) {
+ throw new ErrorWithStack(
+ `Fixture file ${relativePath} does not exist.
+
+Did you run bin/rake karma:fixtures?`,
+ getJSONFixture,
+ );
+ }
+
+ return require(absolutePath);
+}
diff --git a/spec/frontend/helpers/timeout.js b/spec/frontend/helpers/timeout.js
new file mode 100644
index 00000000000..318593a48a4
--- /dev/null
+++ b/spec/frontend/helpers/timeout.js
@@ -0,0 +1,24 @@
+let testTimeoutInMs;
+
+export const setTestTimeout = newTimeoutInMs => {
+ testTimeoutInMs = newTimeoutInMs;
+ jest.setTimeout(newTimeoutInMs);
+};
+
+export const initializeTestTimeout = defaultTimeoutInMs => {
+ setTestTimeout(defaultTimeoutInMs);
+
+ let testStartTime;
+
+ // https://github.com/facebook/jest/issues/6947
+ beforeEach(() => {
+ testStartTime = Date.now();
+ });
+
+ afterEach(() => {
+ const elapsedTimeInMs = Date.now() - testStartTime;
+ if (elapsedTimeInMs > testTimeoutInMs) {
+ throw new Error(`Test took too long (${elapsedTimeInMs}ms > ${testTimeoutInMs}ms)!`);
+ }
+ });
+};
diff --git a/spec/frontend/helpers/vue_mount_component_helper.js b/spec/frontend/helpers/vue_mount_component_helper.js
new file mode 100644
index 00000000000..6848c95d95d
--- /dev/null
+++ b/spec/frontend/helpers/vue_mount_component_helper.js
@@ -0,0 +1,38 @@
+import Vue from 'vue';
+
+const mountComponent = (Component, props = {}, el = null) =>
+ new Component({
+ propsData: props,
+ }).$mount(el);
+
+export const createComponentWithStore = (Component, store, propsData = {}) =>
+ new Component({
+ store,
+ propsData,
+ });
+
+export const mountComponentWithStore = (Component, { el, props, store }) =>
+ new Component({
+ store,
+ propsData: props || {},
+ }).$mount(el);
+
+export const mountComponentWithSlots = (Component, { props, slots }) => {
+ const component = new Component({
+ propsData: props || {},
+ });
+
+ component.$slots = slots;
+
+ return component.$mount();
+};
+
+/**
+ * Mount a component with the given render method.
+ *
+ * This helps with inserting slots that need to be compiled.
+ */
+export const mountComponentWithRender = (render, el = null) =>
+ mountComponent(Vue.extend({ render }), {}, el);
+
+export default mountComponent;
diff --git a/spec/javascripts/ide/lib/common/disposable_spec.js b/spec/frontend/ide/lib/common/disposable_spec.js
index af12ca15369..af12ca15369 100644
--- a/spec/javascripts/ide/lib/common/disposable_spec.js
+++ b/spec/frontend/ide/lib/common/disposable_spec.js
diff --git a/spec/javascripts/ide/lib/diff/diff_spec.js b/spec/frontend/ide/lib/diff/diff_spec.js
index 57f3ac3d365..57f3ac3d365 100644
--- a/spec/javascripts/ide/lib/diff/diff_spec.js
+++ b/spec/frontend/ide/lib/diff/diff_spec.js
diff --git a/spec/javascripts/ide/lib/editor_options_spec.js b/spec/frontend/ide/lib/editor_options_spec.js
index d149a883166..d149a883166 100644
--- a/spec/javascripts/ide/lib/editor_options_spec.js
+++ b/spec/frontend/ide/lib/editor_options_spec.js
diff --git a/spec/javascripts/ide/lib/files_spec.js b/spec/frontend/ide/lib/files_spec.js
index fe791aa2b74..fe791aa2b74 100644
--- a/spec/javascripts/ide/lib/files_spec.js
+++ b/spec/frontend/ide/lib/files_spec.js
diff --git a/spec/javascripts/ide/stores/modules/commit/mutations_spec.js b/spec/frontend/ide/stores/modules/commit/mutations_spec.js
index 5de7a281d34..5de7a281d34 100644
--- a/spec/javascripts/ide/stores/modules/commit/mutations_spec.js
+++ b/spec/frontend/ide/stores/modules/commit/mutations_spec.js
diff --git a/spec/javascripts/ide/stores/modules/file_templates/getters_spec.js b/spec/frontend/ide/stores/modules/file_templates/getters_spec.js
index 17cb457881f..17cb457881f 100644
--- a/spec/javascripts/ide/stores/modules/file_templates/getters_spec.js
+++ b/spec/frontend/ide/stores/modules/file_templates/getters_spec.js
diff --git a/spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js b/spec/frontend/ide/stores/modules/file_templates/mutations_spec.js
index 8e0e3ae99a1..8e0e3ae99a1 100644
--- a/spec/javascripts/ide/stores/modules/file_templates/mutations_spec.js
+++ b/spec/frontend/ide/stores/modules/file_templates/mutations_spec.js
diff --git a/spec/javascripts/ide/stores/modules/pane/getters_spec.js b/spec/frontend/ide/stores/modules/pane/getters_spec.js
index 8a213323de0..8a213323de0 100644
--- a/spec/javascripts/ide/stores/modules/pane/getters_spec.js
+++ b/spec/frontend/ide/stores/modules/pane/getters_spec.js
diff --git a/spec/javascripts/ide/stores/modules/pane/mutations_spec.js b/spec/frontend/ide/stores/modules/pane/mutations_spec.js
index b5fcd35912e..b5fcd35912e 100644
--- a/spec/javascripts/ide/stores/modules/pane/mutations_spec.js
+++ b/spec/frontend/ide/stores/modules/pane/mutations_spec.js
diff --git a/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js b/spec/frontend/ide/stores/modules/pipelines/getters_spec.js
index 4514896b5ea..4514896b5ea 100644
--- a/spec/javascripts/ide/stores/modules/pipelines/getters_spec.js
+++ b/spec/frontend/ide/stores/modules/pipelines/getters_spec.js
diff --git a/spec/javascripts/ide/stores/mutations/branch_spec.js b/spec/frontend/ide/stores/mutations/branch_spec.js
index 29eb859ddaf..29eb859ddaf 100644
--- a/spec/javascripts/ide/stores/mutations/branch_spec.js
+++ b/spec/frontend/ide/stores/mutations/branch_spec.js
diff --git a/spec/javascripts/ide/stores/mutations/merge_request_spec.js b/spec/frontend/ide/stores/mutations/merge_request_spec.js
index e30ca22022f..e30ca22022f 100644
--- a/spec/javascripts/ide/stores/mutations/merge_request_spec.js
+++ b/spec/frontend/ide/stores/mutations/merge_request_spec.js
diff --git a/spec/javascripts/image_diff/view_types_spec.js b/spec/frontend/image_diff/view_types_spec.js
index e9639f46497..e9639f46497 100644
--- a/spec/javascripts/image_diff/view_types_spec.js
+++ b/spec/frontend/image_diff/view_types_spec.js
diff --git a/spec/javascripts/import_projects/store/getters_spec.js b/spec/frontend/import_projects/store/getters_spec.js
index e5e4a95f473..e5e4a95f473 100644
--- a/spec/javascripts/import_projects/store/getters_spec.js
+++ b/spec/frontend/import_projects/store/getters_spec.js
diff --git a/spec/javascripts/import_projects/store/mutations_spec.js b/spec/frontend/import_projects/store/mutations_spec.js
index 8db8e9819ba..8db8e9819ba 100644
--- a/spec/javascripts/import_projects/store/mutations_spec.js
+++ b/spec/frontend/import_projects/store/mutations_spec.js
diff --git a/spec/javascripts/jobs/components/empty_state_spec.js b/spec/frontend/jobs/components/empty_state_spec.js
index a2df79bdda0..a2df79bdda0 100644
--- a/spec/javascripts/jobs/components/empty_state_spec.js
+++ b/spec/frontend/jobs/components/empty_state_spec.js
diff --git a/spec/javascripts/jobs/components/erased_block_spec.js b/spec/frontend/jobs/components/erased_block_spec.js
index 8e0433d3fb7..8e0433d3fb7 100644
--- a/spec/javascripts/jobs/components/erased_block_spec.js
+++ b/spec/frontend/jobs/components/erased_block_spec.js
diff --git a/spec/javascripts/jobs/components/sidebar_detail_row_spec.js b/spec/frontend/jobs/components/sidebar_detail_row_spec.js
index 42d11266dad..42d11266dad 100644
--- a/spec/javascripts/jobs/components/sidebar_detail_row_spec.js
+++ b/spec/frontend/jobs/components/sidebar_detail_row_spec.js
diff --git a/spec/javascripts/jobs/components/stuck_block_spec.js b/spec/frontend/jobs/components/stuck_block_spec.js
index c320793b2be..c320793b2be 100644
--- a/spec/javascripts/jobs/components/stuck_block_spec.js
+++ b/spec/frontend/jobs/components/stuck_block_spec.js
diff --git a/spec/javascripts/jobs/store/getters_spec.js b/spec/frontend/jobs/store/getters_spec.js
index 379114c3737..379114c3737 100644
--- a/spec/javascripts/jobs/store/getters_spec.js
+++ b/spec/frontend/jobs/store/getters_spec.js
diff --git a/spec/javascripts/jobs/store/mutations_spec.js b/spec/frontend/jobs/store/mutations_spec.js
index d7908efcf13..d7908efcf13 100644
--- a/spec/javascripts/jobs/store/mutations_spec.js
+++ b/spec/frontend/jobs/store/mutations_spec.js
diff --git a/spec/javascripts/labels_select_spec.js b/spec/frontend/labels_select_spec.js
index acfdc885032..acfdc885032 100644
--- a/spec/javascripts/labels_select_spec.js
+++ b/spec/frontend/labels_select_spec.js
diff --git a/spec/frontend/lib/utils/autosave_spec.js b/spec/frontend/lib/utils/autosave_spec.js
new file mode 100644
index 00000000000..12e97f6cdec
--- /dev/null
+++ b/spec/frontend/lib/utils/autosave_spec.js
@@ -0,0 +1,64 @@
+import { clearDraft, getDraft, updateDraft } from '~/lib/utils/autosave';
+
+describe('autosave utils', () => {
+ const autosaveKey = 'dummy-autosave-key';
+ const text = 'some dummy text';
+
+ describe('clearDraft', () => {
+ beforeEach(() => {
+ localStorage.setItem(`autosave/${autosaveKey}`, text);
+ });
+
+ afterEach(() => {
+ localStorage.removeItem(`autosave/${autosaveKey}`);
+ });
+
+ it('removes the draft from localStorage', () => {
+ clearDraft(autosaveKey);
+
+ expect(localStorage.getItem(`autosave/${autosaveKey}`)).toBe(null);
+ });
+ });
+
+ describe('getDraft', () => {
+ beforeEach(() => {
+ localStorage.setItem(`autosave/${autosaveKey}`, text);
+ });
+
+ afterEach(() => {
+ localStorage.removeItem(`autosave/${autosaveKey}`);
+ });
+
+ it('returns the draft from localStorage', () => {
+ const result = getDraft(autosaveKey);
+
+ expect(result).toBe(text);
+ });
+
+ it('returns null if no entry exists in localStorage', () => {
+ localStorage.removeItem(`autosave/${autosaveKey}`);
+
+ const result = getDraft(autosaveKey);
+
+ expect(result).toBe(null);
+ });
+ });
+
+ describe('updateDraft', () => {
+ beforeEach(() => {
+ localStorage.setItem(`autosave/${autosaveKey}`, text);
+ });
+
+ afterEach(() => {
+ localStorage.removeItem(`autosave/${autosaveKey}`);
+ });
+
+ it('removes the draft from localStorage', () => {
+ const newText = 'new text';
+
+ updateDraft(autosaveKey, newText);
+
+ expect(localStorage.getItem(`autosave/${autosaveKey}`)).toBe(newText);
+ });
+ });
+});
diff --git a/spec/javascripts/lib/utils/cache_spec.js b/spec/frontend/lib/utils/cache_spec.js
index 2fe02a7592c..2fe02a7592c 100644
--- a/spec/javascripts/lib/utils/cache_spec.js
+++ b/spec/frontend/lib/utils/cache_spec.js
diff --git a/spec/javascripts/lib/utils/grammar_spec.js b/spec/frontend/lib/utils/grammar_spec.js
index 377b2ffb48c..377b2ffb48c 100644
--- a/spec/javascripts/lib/utils/grammar_spec.js
+++ b/spec/frontend/lib/utils/grammar_spec.js
diff --git a/spec/javascripts/lib/utils/image_utility_spec.js b/spec/frontend/lib/utils/image_utility_spec.js
index a7eff419fba..a7eff419fba 100644
--- a/spec/javascripts/lib/utils/image_utility_spec.js
+++ b/spec/frontend/lib/utils/image_utility_spec.js
diff --git a/spec/javascripts/lib/utils/number_utility_spec.js b/spec/frontend/lib/utils/number_utility_spec.js
index 818404bad81..818404bad81 100644
--- a/spec/javascripts/lib/utils/number_utility_spec.js
+++ b/spec/frontend/lib/utils/number_utility_spec.js
diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/frontend/lib/utils/text_utility_spec.js
index 0a266b19ea5..0a266b19ea5 100644
--- a/spec/javascripts/lib/utils/text_utility_spec.js
+++ b/spec/frontend/lib/utils/text_utility_spec.js
diff --git a/spec/javascripts/locale/ensure_single_line_spec.js b/spec/frontend/locale/ensure_single_line_spec.js
index 20b04cab9c8..20b04cab9c8 100644
--- a/spec/javascripts/locale/ensure_single_line_spec.js
+++ b/spec/frontend/locale/ensure_single_line_spec.js
diff --git a/spec/javascripts/locale/sprintf_spec.js b/spec/frontend/locale/sprintf_spec.js
index 52e903b819f..52e903b819f 100644
--- a/spec/javascripts/locale/sprintf_spec.js
+++ b/spec/frontend/locale/sprintf_spec.js
diff --git a/spec/javascripts/notebook/lib/highlight_spec.js b/spec/frontend/notebook/lib/highlight_spec.js
index d71c5718858..d71c5718858 100644
--- a/spec/javascripts/notebook/lib/highlight_spec.js
+++ b/spec/frontend/notebook/lib/highlight_spec.js
diff --git a/spec/javascripts/notes/components/discussion_reply_placeholder_spec.js b/spec/frontend/notes/components/discussion_reply_placeholder_spec.js
index 07a366cf339..07a366cf339 100644
--- a/spec/javascripts/notes/components/discussion_reply_placeholder_spec.js
+++ b/spec/frontend/notes/components/discussion_reply_placeholder_spec.js
diff --git a/spec/javascripts/notes/components/discussion_resolve_button_spec.js b/spec/frontend/notes/components/discussion_resolve_button_spec.js
index 5024f40ec5d..5024f40ec5d 100644
--- a/spec/javascripts/notes/components/discussion_resolve_button_spec.js
+++ b/spec/frontend/notes/components/discussion_resolve_button_spec.js
diff --git a/spec/javascripts/notes/components/note_attachment_spec.js b/spec/frontend/notes/components/note_attachment_spec.js
index b14a518b622..b14a518b622 100644
--- a/spec/javascripts/notes/components/note_attachment_spec.js
+++ b/spec/frontend/notes/components/note_attachment_spec.js
diff --git a/spec/javascripts/notes/components/note_edited_text_spec.js b/spec/frontend/notes/components/note_edited_text_spec.js
index e4c8d954d50..e4c8d954d50 100644
--- a/spec/javascripts/notes/components/note_edited_text_spec.js
+++ b/spec/frontend/notes/components/note_edited_text_spec.js
diff --git a/spec/javascripts/performance_bar/services/performance_bar_service_spec.js b/spec/frontend/performance_bar/services/performance_bar_service_spec.js
index cfec4b779e4..cfec4b779e4 100644
--- a/spec/javascripts/performance_bar/services/performance_bar_service_spec.js
+++ b/spec/frontend/performance_bar/services/performance_bar_service_spec.js
diff --git a/spec/javascripts/pipelines/blank_state_spec.js b/spec/frontend/pipelines/blank_state_spec.js
index 033bd5ccb73..033bd5ccb73 100644
--- a/spec/javascripts/pipelines/blank_state_spec.js
+++ b/spec/frontend/pipelines/blank_state_spec.js
diff --git a/spec/javascripts/pipelines/empty_state_spec.js b/spec/frontend/pipelines/empty_state_spec.js
index f12950b8fce..f12950b8fce 100644
--- a/spec/javascripts/pipelines/empty_state_spec.js
+++ b/spec/frontend/pipelines/empty_state_spec.js
diff --git a/spec/javascripts/pipelines/pipeline_store_spec.js b/spec/frontend/pipelines/pipeline_store_spec.js
index 1d5754d1f05..1d5754d1f05 100644
--- a/spec/javascripts/pipelines/pipeline_store_spec.js
+++ b/spec/frontend/pipelines/pipeline_store_spec.js
diff --git a/spec/javascripts/pipelines/pipelines_store_spec.js b/spec/frontend/pipelines/pipelines_store_spec.js
index ce21f788ed5..ce21f788ed5 100644
--- a/spec/javascripts/pipelines/pipelines_store_spec.js
+++ b/spec/frontend/pipelines/pipelines_store_spec.js
diff --git a/spec/javascripts/registry/getters_spec.js b/spec/frontend/registry/getters_spec.js
index 839aa718997..839aa718997 100644
--- a/spec/javascripts/registry/getters_spec.js
+++ b/spec/frontend/registry/getters_spec.js
diff --git a/spec/javascripts/reports/components/report_link_spec.js b/spec/frontend/reports/components/report_link_spec.js
index f879899e9c5..f879899e9c5 100644
--- a/spec/javascripts/reports/components/report_link_spec.js
+++ b/spec/frontend/reports/components/report_link_spec.js
diff --git a/spec/javascripts/reports/store/utils_spec.js b/spec/frontend/reports/store/utils_spec.js
index 1679d120db2..1679d120db2 100644
--- a/spec/javascripts/reports/store/utils_spec.js
+++ b/spec/frontend/reports/store/utils_spec.js
diff --git a/spec/javascripts/sidebar/confidential_edit_buttons_spec.js b/spec/frontend/sidebar/confidential_edit_buttons_spec.js
index 32da9f83112..32da9f83112 100644
--- a/spec/javascripts/sidebar/confidential_edit_buttons_spec.js
+++ b/spec/frontend/sidebar/confidential_edit_buttons_spec.js
diff --git a/spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js b/spec/frontend/sidebar/confidential_edit_form_buttons_spec.js
index 369088cb258..369088cb258 100644
--- a/spec/javascripts/sidebar/confidential_edit_form_buttons_spec.js
+++ b/spec/frontend/sidebar/confidential_edit_form_buttons_spec.js
diff --git a/spec/javascripts/sidebar/lock/edit_form_spec.js b/spec/frontend/sidebar/lock/edit_form_spec.js
index ec10a999a40..ec10a999a40 100644
--- a/spec/javascripts/sidebar/lock/edit_form_spec.js
+++ b/spec/frontend/sidebar/lock/edit_form_spec.js
diff --git a/spec/frontend/test_setup.js b/spec/frontend/test_setup.js
index d892889b98d..006fc60ef57 100644
--- a/spec/frontend/test_setup.js
+++ b/spec/frontend/test_setup.js
@@ -1,24 +1,15 @@
import Vue from 'vue';
import Translate from '~/vue_shared/translate';
import axios from '~/lib/utils/axios_utils';
+import { initializeTestTimeout } from './helpers/timeout';
-const testTimeoutInMs = 300;
-jest.setTimeout(testTimeoutInMs);
-
-let testStartTime;
-
-// https://github.com/facebook/jest/issues/6947
-beforeEach(() => {
- testStartTime = Date.now();
-});
-
+// wait for pending setTimeout()s
afterEach(() => {
- const elapsedTimeInMs = Date.now() - testStartTime;
- if (elapsedTimeInMs > testTimeoutInMs) {
- throw new Error(`Test took too long (${elapsedTimeInMs}ms > ${testTimeoutInMs}ms)!`);
- }
+ jest.runAllTimers();
});
+initializeTestTimeout(300);
+
// fail tests for unmocked requests
beforeEach(done => {
axios.defaults.adapter = config => {
diff --git a/spec/javascripts/u2f/util_spec.js b/spec/frontend/u2f/util_spec.js
index 32cd6891384..32cd6891384 100644
--- a/spec/javascripts/u2f/util_spec.js
+++ b/spec/frontend/u2f/util_spec.js
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_container_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_container_spec.js
index 16c8c939a6f..16c8c939a6f 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_container_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_container_spec.js
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_icon_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_icon_spec.js
index f7c2376eebf..f7c2376eebf 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_icon_spec.js
+++ b/spec/frontend/vue_mr_widget/components/mr_widget_icon_spec.js
diff --git a/spec/javascripts/vue_mr_widget/components/states/commit_edit_spec.js b/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js
index 994d6255324..994d6255324 100644
--- a/spec/javascripts/vue_mr_widget/components/states/commit_edit_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/commit_edit_spec.js
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js
index daf1cc2d98b..daf1cc2d98b 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_commits_header_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js
index 5cf6408cf34..9ee2f88c78d 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_commits_header_spec.js
+++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js
@@ -15,6 +15,7 @@ describe('Commits header component', () => {
isSquashEnabled: false,
targetBranch: 'master',
commitsCount: 5,
+ isFastForwardEnabled: false,
...props,
},
});
@@ -31,6 +32,27 @@ describe('Commits header component', () => {
const findTargetBranchMessage = () => wrapper.find('.label-branch');
const findModifyButton = () => wrapper.find('.modify-message-button');
+ describe('when fast-forward is enabled', () => {
+ beforeEach(() => {
+ createComponent({
+ isFastForwardEnabled: true,
+ isSquashEnabled: true,
+ });
+ });
+
+ it('has commits count message showing 1 commit', () => {
+ expect(findCommitsCountMessage().text()).toBe('1 commit');
+ });
+
+ it('has button with modify commit message', () => {
+ expect(findModifyButton().text()).toBe('Modify commit message');
+ });
+
+ it('does not have merge commit part of the message', () => {
+ expect(findHeaderWrapper().text()).not.toContain('1 merge commit');
+ });
+ });
+
describe('when collapsed', () => {
it('toggle has aria-label equal to Expand', () => {
createComponent();
@@ -78,6 +100,10 @@ describe('Commits header component', () => {
expect(findTargetBranchMessage().text()).toBe('master');
});
+
+ it('does has merge commit part of the message', () => {
+ expect(findHeaderWrapper().text()).toContain('1 merge commit');
+ });
});
describe('when expanded', () => {
diff --git a/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
index b356ea85cad..b356ea85cad 100644
--- a/spec/javascripts/vue_mr_widget/stores/get_state_key_spec.js
+++ b/spec/frontend/vue_mr_widget/stores/get_state_key_spec.js
diff --git a/spec/javascripts/vue_shared/components/callout_spec.js b/spec/frontend/vue_shared/components/callout_spec.js
index 91208dfb31a..91208dfb31a 100644
--- a/spec/javascripts/vue_shared/components/callout_spec.js
+++ b/spec/frontend/vue_shared/components/callout_spec.js
diff --git a/spec/javascripts/vue_shared/components/code_block_spec.js b/spec/frontend/vue_shared/components/code_block_spec.js
index 6b91a20ff76..6b91a20ff76 100644
--- a/spec/javascripts/vue_shared/components/code_block_spec.js
+++ b/spec/frontend/vue_shared/components/code_block_spec.js
diff --git a/spec/javascripts/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js b/spec/frontend/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js
index c4358f0d9cb..c4358f0d9cb 100644
--- a/spec/javascripts/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js
+++ b/spec/frontend/vue_shared/components/diff_viewer/viewers/mode_changed_spec.js
diff --git a/spec/javascripts/vue_shared/components/identicon_spec.js b/spec/frontend/vue_shared/components/identicon_spec.js
index 0b3dbb61c96..0b3dbb61c96 100644
--- a/spec/javascripts/vue_shared/components/identicon_spec.js
+++ b/spec/frontend/vue_shared/components/identicon_spec.js
diff --git a/spec/javascripts/vue_shared/components/lib/utils/dom_utils_spec.js b/spec/frontend/vue_shared/components/lib/utils/dom_utils_spec.js
index 2388660b0c2..2388660b0c2 100644
--- a/spec/javascripts/vue_shared/components/lib/utils/dom_utils_spec.js
+++ b/spec/frontend/vue_shared/components/lib/utils/dom_utils_spec.js
diff --git a/spec/javascripts/vue_shared/components/pagination_links_spec.js b/spec/frontend/vue_shared/components/pagination_links_spec.js
index d0cb3731050..d0cb3731050 100644
--- a/spec/javascripts/vue_shared/components/pagination_links_spec.js
+++ b/spec/frontend/vue_shared/components/pagination_links_spec.js
diff --git a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js
index 536bb57b946..536bb57b946 100644
--- a/spec/javascripts/vue_shared/components/time_ago_tooltip_spec.js
+++ b/spec/frontend/vue_shared/components/time_ago_tooltip_spec.js
diff --git a/spec/javascripts/vuex_shared/modules/modal/mutations_spec.js b/spec/frontend/vuex_shared/modules/modal/mutations_spec.js
index d07f8ba1e65..d07f8ba1e65 100644
--- a/spec/javascripts/vuex_shared/modules/modal/mutations_spec.js
+++ b/spec/frontend/vuex_shared/modules/modal/mutations_spec.js
diff --git a/spec/helpers/auth_helper_spec.rb b/spec/helpers/auth_helper_spec.rb
index f0c2e4768ec..2ba8b3dbf22 100644
--- a/spec/helpers/auth_helper_spec.rb
+++ b/spec/helpers/auth_helper_spec.rb
@@ -97,17 +97,37 @@ describe AuthHelper do
end
end
- describe 'unlink_allowed?' do
- [:saml, :cas3].each do |provider|
- it "returns true if the provider is #{provider}" do
- expect(helper.unlink_allowed?(provider)).to be false
- end
+ describe '#link_provider_allowed?' do
+ let(:policy) { instance_double('IdentityProviderPolicy') }
+ let(:current_user) { instance_double('User') }
+ let(:provider) { double }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(current_user)
+ allow(IdentityProviderPolicy).to receive(:new).with(current_user, provider).and_return(policy)
end
- [:twitter, :facebook, :google_oauth2, :gitlab, :github, :bitbucket, :crowd, :auth0, :authentiq].each do |provider|
- it "returns false if the provider is #{provider}" do
- expect(helper.unlink_allowed?(provider)).to be true
- end
+ it 'delegates to identity provider policy' do
+ allow(policy).to receive(:can?).with(:link).and_return('policy_link_result')
+
+ expect(helper.link_provider_allowed?(provider)).to eq 'policy_link_result'
+ end
+ end
+
+ describe '#unlink_provider_allowed?' do
+ let(:policy) { instance_double('IdentityProviderPolicy') }
+ let(:current_user) { instance_double('User') }
+ let(:provider) { double }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(current_user)
+ allow(IdentityProviderPolicy).to receive(:new).with(current_user, provider).and_return(policy)
+ end
+
+ it 'delegates to identity provider policy' do
+ allow(policy).to receive(:can?).with(:unlink).and_return('policy_unlink_result')
+
+ expect(helper.unlink_provider_allowed?(provider)).to eq 'policy_unlink_result'
end
end
end
diff --git a/spec/helpers/auto_devops_helper_spec.rb b/spec/helpers/auto_devops_helper_spec.rb
index 223e562238d..d2540696b17 100644
--- a/spec/helpers/auto_devops_helper_spec.rb
+++ b/spec/helpers/auto_devops_helper_spec.rb
@@ -29,11 +29,11 @@ describe AutoDevopsHelper do
end
context 'when the banner is disabled by feature flag' do
- it 'allows the feature flag to disable' do
+ before do
Feature.get(:auto_devops_banner_disabled).enable
-
- expect(subject).to be(false)
end
+
+ it { is_expected.to be_falsy }
end
context 'when dismissed' do
@@ -90,4 +90,136 @@ describe AutoDevopsHelper do
it { is_expected.to eq(false) }
end
end
+
+ describe '#badge_for_auto_devops_scope' do
+ subject { helper.badge_for_auto_devops_scope(receiver) }
+
+ context 'when receiver is a group' do
+ context 'when explicitly enabled' do
+ let(:receiver) { create(:group, :auto_devops_enabled) }
+
+ it { is_expected.to eq('group enabled') }
+ end
+
+ context 'when explicitly disabled' do
+ let(:receiver) { create(:group, :auto_devops_disabled) }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when auto devops is implicitly enabled' do
+ let(:receiver) { create(:group) }
+
+ context 'by instance' do
+ before do
+ stub_application_setting(auto_devops_enabled: true)
+ end
+
+ it { is_expected.to eq('instance enabled') }
+ end
+
+ context 'with groups', :nested_groups do
+ before do
+ receiver.update(parent: parent)
+ end
+
+ context 'when auto devops is enabled on parent' do
+ let(:parent) { create(:group, :auto_devops_enabled) }
+
+ it { is_expected.to eq('group enabled') }
+ end
+
+ context 'when auto devops is enabled on parent group' do
+ let(:root_parent) { create(:group, :auto_devops_enabled) }
+ let(:parent) { create(:group, parent: root_parent) }
+
+ it { is_expected.to eq('group enabled') }
+ end
+
+ context 'when auto devops disabled set on parent group' do
+ let(:root_parent) { create(:group, :auto_devops_disabled) }
+ let(:parent) { create(:group, parent: root_parent) }
+
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+ end
+
+ context 'when receiver is a project' do
+ context 'when auto devops is enabled at project level' do
+ let(:receiver) { create(:project, :auto_devops) }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when auto devops is disabled at project level' do
+ let(:receiver) { create(:project, :auto_devops_disabled) }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when auto devops is implicitly enabled' do
+ let(:receiver) { create(:project) }
+
+ context 'by instance' do
+ before do
+ stub_application_setting(auto_devops_enabled: true)
+ end
+
+ it { is_expected.to eq('instance enabled') }
+ end
+
+ context 'with groups', :nested_groups do
+ let(:receiver) { create(:project, :repository, namespace: group) }
+
+ before do
+ stub_application_setting(auto_devops_enabled: false)
+ end
+
+ context 'when auto devops is enabled on group level' do
+ let(:group) { create(:group, :auto_devops_enabled) }
+
+ it { is_expected.to eq('group enabled') }
+ end
+
+ context 'when auto devops is enabled on root group' do
+ let(:root_parent) { create(:group, :auto_devops_enabled) }
+ let(:group) { create(:group, parent: root_parent) }
+
+ it { is_expected.to eq('group enabled') }
+ end
+ end
+ end
+
+ context 'when auto devops is implicitly disabled' do
+ let(:receiver) { create(:project) }
+
+ context 'by instance' do
+ before do
+ stub_application_setting(auto_devops_enabled: false)
+ end
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'with groups', :nested_groups do
+ let(:receiver) { create(:project, :repository, namespace: group) }
+
+ context 'when auto devops is disabled on group level' do
+ let(:group) { create(:group, :auto_devops_disabled) }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when root group is enabled and parent disabled' do
+ let(:root_parent) { create(:group, :auto_devops_enabled) }
+ let(:group) { create(:group, :auto_devops_disabled, parent: root_parent) }
+
+ it { is_expected.to be_nil }
+ end
+ end
+ end
+ end
+ end
end
diff --git a/spec/helpers/clusters_helper_spec.rb b/spec/helpers/clusters_helper_spec.rb
new file mode 100644
index 00000000000..4ea0f76fc28
--- /dev/null
+++ b/spec/helpers/clusters_helper_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ClustersHelper do
+ describe '#has_rbac_enabled?' do
+ context 'when kubernetes platform has been created' do
+ let(:platform_kubernetes) { build_stubbed(:cluster_platform_kubernetes) }
+ let(:cluster) { build_stubbed(:cluster, :provided_by_gcp, platform_kubernetes: platform_kubernetes) }
+
+ it 'returns kubernetes platform value' do
+ expect(helper.has_rbac_enabled?(cluster)).to be_truthy
+ end
+ end
+
+ context 'when kubernetes platform has not been created yet' do
+ let(:cluster) { build_stubbed(:cluster, :providing_by_gcp) }
+
+ it 'delegates to cluster provider' do
+ expect(helper.has_rbac_enabled?(cluster)).to be_truthy
+ end
+
+ context 'when ABAC cluster is created' do
+ let(:provider) { build_stubbed(:cluster_provider_gcp, :abac_enabled) }
+ let(:cluster) { build_stubbed(:cluster, :providing_by_gcp, provider_gcp: provider) }
+
+ it 'delegates to cluster provider' do
+ expect(helper.has_rbac_enabled?(cluster)).to be_falsy
+ end
+ end
+ end
+ end
+end
diff --git a/spec/javascripts/badges/components/badge_list_spec.js b/spec/javascripts/badges/components/badge_list_spec.js
index 536671db377..2f72c9ed89d 100644
--- a/spec/javascripts/badges/components/badge_list_spec.js
+++ b/spec/javascripts/badges/components/badge_list_spec.js
@@ -60,7 +60,7 @@ describe('BadgeList component', () => {
Vue.nextTick()
.then(() => {
- const loadingIcon = vm.$el.querySelector('.fa-spinner');
+ const loadingIcon = vm.$el.querySelector('.spinner');
expect(loadingIcon).toBeVisible();
})
diff --git a/spec/javascripts/badges/components/badge_spec.js b/spec/javascripts/badges/components/badge_spec.js
index 29805408bcf..4e4d1ae2e99 100644
--- a/spec/javascripts/badges/components/badge_spec.js
+++ b/spec/javascripts/badges/components/badge_spec.js
@@ -15,7 +15,7 @@ describe('Badge component', () => {
const buttons = vm.$el.querySelectorAll('button');
return {
badgeImage: vm.$el.querySelector('img.project-badge'),
- loadingIcon: vm.$el.querySelector('.fa-spinner'),
+ loadingIcon: vm.$el.querySelector('.spinner'),
reloadButton: buttons[buttons.length - 1],
};
};
diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js
index 2642c8b1bdb..396fc823ef5 100644
--- a/spec/javascripts/boards/board_list_spec.js
+++ b/spec/javascripts/boards/board_list_spec.js
@@ -195,7 +195,7 @@ describe('Board list component', () => {
component.list.loadingMore = true;
Vue.nextTick(() => {
- expect(component.$el.querySelector('.board-list-count .fa-spinner')).not.toBeNull();
+ expect(component.$el.querySelector('.board-list-count .spinner')).not.toBeNull();
done();
});
diff --git a/spec/javascripts/clusters/clusters_bundle_spec.js b/spec/javascripts/clusters/clusters_bundle_spec.js
index 7928feeadfa..71f16dc259e 100644
--- a/spec/javascripts/clusters/clusters_bundle_spec.js
+++ b/spec/javascripts/clusters/clusters_bundle_spec.js
@@ -1,5 +1,10 @@
import Clusters from '~/clusters/clusters_bundle';
-import { REQUEST_SUBMITTED, REQUEST_FAILURE, APPLICATION_STATUS } from '~/clusters/constants';
+import {
+ REQUEST_SUBMITTED,
+ REQUEST_FAILURE,
+ APPLICATION_STATUS,
+ INGRESS_DOMAIN_SUFFIX,
+} from '~/clusters/constants';
import getSetTimeoutPromise from 'spec/helpers/set_timeout_promise_helper';
describe('Clusters', () => {
@@ -265,4 +270,77 @@ describe('Clusters', () => {
.catch(done.fail);
});
});
+
+ describe('handleSuccess', () => {
+ beforeEach(() => {
+ spyOn(cluster.store, 'updateStateFromServer');
+ spyOn(cluster, 'toggleIngressDomainHelpText');
+ spyOn(cluster, 'checkForNewInstalls');
+ spyOn(cluster, 'updateContainer');
+
+ cluster.handleSuccess({ data: {} });
+ });
+
+ it('updates clusters store', () => {
+ expect(cluster.store.updateStateFromServer).toHaveBeenCalled();
+ });
+
+ it('checks for new installable apps', () => {
+ expect(cluster.checkForNewInstalls).toHaveBeenCalled();
+ });
+
+ it('toggles ingress domain help text', () => {
+ expect(cluster.toggleIngressDomainHelpText).toHaveBeenCalled();
+ });
+
+ it('updates message containers', () => {
+ expect(cluster.updateContainer).toHaveBeenCalled();
+ });
+ });
+
+ describe('toggleIngressDomainHelpText', () => {
+ const { INSTALLED, INSTALLABLE, NOT_INSTALLABLE } = APPLICATION_STATUS;
+
+ const ingressPreviousState = { status: INSTALLABLE };
+ const ingressNewState = { status: INSTALLED, externalIp: '127.0.0.1' };
+
+ describe(`when ingress application new status is ${INSTALLED}`, () => {
+ beforeEach(() => {
+ ingressNewState.status = INSTALLED;
+ cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState);
+ });
+
+ it('displays custom domain help text', () => {
+ expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(false);
+ });
+
+ it('updates ingress external ip address', () => {
+ expect(cluster.ingressDomainSnippet.textContent).toEqual(
+ `${ingressNewState.externalIp}${INGRESS_DOMAIN_SUFFIX}`,
+ );
+ });
+ });
+
+ describe(`when ingress application new status is different from ${INSTALLED}`, () => {
+ it('hides custom domain help text', () => {
+ ingressNewState.status = NOT_INSTALLABLE;
+ cluster.ingressDomainHelpText.classList.remove('hide');
+
+ cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState);
+
+ expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true);
+ });
+ });
+
+ describe('when ingress application new status and old status are the same', () => {
+ it('does not modify custom domain help text', () => {
+ ingressPreviousState.status = INSTALLED;
+ ingressNewState.status = ingressPreviousState.status;
+
+ cluster.toggleIngressDomainHelpText(ingressPreviousState, ingressNewState);
+
+ expect(cluster.ingressDomainHelpText.classList.contains('hide')).toEqual(true);
+ });
+ });
+ });
});
diff --git a/spec/javascripts/clusters/components/applications_spec.js b/spec/javascripts/clusters/components/applications_spec.js
index e2466bf326c..790e4b9602c 100644
--- a/spec/javascripts/clusters/components/applications_spec.js
+++ b/spec/javascripts/clusters/components/applications_spec.js
@@ -141,7 +141,7 @@ describe('Applications', () => {
});
describe('without ip address', () => {
- it('renders an input text with a question mark and an alert text', () => {
+ it('renders an input text with a loading icon and an alert text', () => {
vm = mountComponent(Applications, {
applications: {
...APPLICATIONS_MOCK_STATE,
@@ -152,8 +152,7 @@ describe('Applications', () => {
},
});
- expect(vm.$el.querySelector('.js-endpoint').value).toEqual('?');
-
+ expect(vm.$el.querySelector('.js-ingress-ip-loading-icon')).not.toBe(null);
expect(vm.$el.querySelector('.js-no-endpoint-message')).not.toBe(null);
});
});
@@ -330,7 +329,7 @@ describe('Applications', () => {
});
describe('without ip address', () => {
- it('renders an input text with a question mark and an alert text', () => {
+ it('renders an input text with a loading icon and an alert text', () => {
vm = mountComponent(Applications, {
applications: {
...APPLICATIONS_MOCK_STATE,
@@ -342,8 +341,7 @@ describe('Applications', () => {
},
});
- expect(vm.$el.querySelector('.js-knative-endpoint').value).toEqual('?');
-
+ expect(vm.$el.querySelector('.js-knative-ip-loading-icon')).not.toBe(null);
expect(vm.$el.querySelector('.js-no-knative-endpoint-message')).not.toBe(null);
});
});
diff --git a/spec/javascripts/diffs/components/app_spec.js b/spec/javascripts/diffs/components/app_spec.js
index d81c433cca6..8d7c52a2876 100644
--- a/spec/javascripts/diffs/components/app_spec.js
+++ b/spec/javascripts/diffs/components/app_spec.js
@@ -397,4 +397,61 @@ describe('diffs/components/app', () => {
expect(wrapper.find(TreeList).exists()).toBe(true);
});
});
+
+ describe('hideTreeListIfJustOneFile', () => {
+ let toggleShowTreeList;
+
+ beforeEach(() => {
+ toggleShowTreeList = jasmine.createSpy('toggleShowTreeList');
+ });
+
+ afterEach(() => {
+ localStorage.removeItem('mr_tree_show');
+ });
+
+ it('calls toggleShowTreeList when only 1 file', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.diffFiles.push({ sha: '123' });
+ });
+
+ wrapper.setMethods({
+ toggleShowTreeList,
+ });
+
+ wrapper.vm.hideTreeListIfJustOneFile();
+
+ expect(toggleShowTreeList).toHaveBeenCalledWith(false);
+ });
+
+ it('does not call toggleShowTreeList when more than 1 file', () => {
+ createComponent({}, ({ state }) => {
+ state.diffs.diffFiles.push({ sha: '123' });
+ state.diffs.diffFiles.push({ sha: '124' });
+ });
+
+ wrapper.setMethods({
+ toggleShowTreeList,
+ });
+
+ wrapper.vm.hideTreeListIfJustOneFile();
+
+ expect(toggleShowTreeList).not.toHaveBeenCalled();
+ });
+
+ it('does not call toggleShowTreeList when localStorage is set', () => {
+ localStorage.setItem('mr_tree_show', 'true');
+
+ createComponent({}, ({ state }) => {
+ state.diffs.diffFiles.push({ sha: '123' });
+ });
+
+ wrapper.setMethods({
+ toggleShowTreeList,
+ });
+
+ wrapper.vm.hideTreeListIfJustOneFile();
+
+ expect(toggleShowTreeList).not.toHaveBeenCalled();
+ });
+ });
});
diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js
index ba04c8c4a4c..d9b298e84da 100644
--- a/spec/javascripts/diffs/components/diff_file_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_spec.js
@@ -109,6 +109,31 @@ describe('DiffFile', () => {
done();
});
});
+
+ it('should update store state', done => {
+ spyOn(vm.$store, 'dispatch');
+
+ vm.isCollapsed = true;
+
+ vm.$nextTick(() => {
+ expect(vm.$store.dispatch).toHaveBeenCalledWith('diffs/setFileCollapsed', {
+ filePath: vm.file.file_path,
+ collapsed: true,
+ });
+
+ done();
+ });
+ });
+
+ it('updates local state when changing file state', done => {
+ vm.file.viewer.collapsed = true;
+
+ vm.$nextTick(() => {
+ expect(vm.isCollapsed).toBe(true);
+
+ done();
+ });
+ });
});
});
diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js
index 070bfb2ccd0..bca99caa920 100644
--- a/spec/javascripts/diffs/store/actions_spec.js
+++ b/spec/javascripts/diffs/store/actions_spec.js
@@ -35,6 +35,7 @@ import actions, {
receiveFullDiffError,
fetchFullDiff,
toggleFullDiff,
+ setFileCollapsed,
} from '~/diffs/store/actions';
import eventHub from '~/notes/event_hub';
import * as types from '~/diffs/store/mutation_types';
@@ -733,6 +734,14 @@ describe('DiffsStoreActions', () => {
expect(localStorage.setItem).toHaveBeenCalledWith('mr_tree_show', true);
});
+
+ it('does not update localStorage', () => {
+ spyOn(localStorage, 'setItem');
+
+ toggleShowTreeList({ commit() {}, state: { showTreeList: true } }, false);
+
+ expect(localStorage.setItem).not.toHaveBeenCalled();
+ });
});
describe('renderFileForDiscussionId', () => {
@@ -977,4 +986,17 @@ describe('DiffsStoreActions', () => {
);
});
});
+
+ describe('setFileCollapsed', () => {
+ it('commits SET_FILE_COLLAPSED', done => {
+ testAction(
+ setFileCollapsed,
+ { filePath: 'test', collapsed: true },
+ null,
+ [{ type: types.SET_FILE_COLLAPSED, payload: { filePath: 'test', collapsed: true } }],
+ [],
+ done,
+ );
+ });
+ });
});
diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js
index 270e7d75666..5556a994a14 100644
--- a/spec/javascripts/diffs/store/mutations_spec.js
+++ b/spec/javascripts/diffs/store/mutations_spec.js
@@ -58,13 +58,15 @@ describe('DiffsStoreMutations', () => {
describe('EXPAND_ALL_FILES', () => {
it('should change the collapsed prop from diffFiles', () => {
const diffFile = {
- collapsed: true,
+ viewer: {
+ collapsed: true,
+ },
};
const state = { expandAllFiles: true, diffFiles: [diffFile] };
mutations[types.EXPAND_ALL_FILES](state);
- expect(state.diffFiles[0].collapsed).toEqual(false);
+ expect(state.diffFiles[0].viewer.collapsed).toEqual(false);
});
});
@@ -742,4 +744,16 @@ describe('DiffsStoreMutations', () => {
expect(state.diffFiles[0].isShowingFullFile).toBe(true);
});
});
+
+ describe('SET_FILE_COLLAPSED', () => {
+ it('sets collapsed', () => {
+ const state = {
+ diffFiles: [{ file_path: 'test', viewer: { collapsed: false } }],
+ };
+
+ mutations[types.SET_FILE_COLLAPSED](state, { filePath: 'test', collapsed: true });
+
+ expect(state.diffFiles[0].viewer.collapsed).toBe(true);
+ });
+ });
});
diff --git a/spec/javascripts/environments/environment_item_spec.js b/spec/javascripts/environments/environment_item_spec.js
index a89e50045da..388d7063d13 100644
--- a/spec/javascripts/environments/environment_item_spec.js
+++ b/spec/javascripts/environments/environment_item_spec.js
@@ -20,23 +20,23 @@ describe('Environment item', () => {
size: 3,
isFolder: true,
environment_path: 'url',
+ log_path: 'url',
};
component = new EnvironmentItem({
propsData: {
model: mockItem,
canReadEnvironment: true,
- service: {},
},
}).$mount();
});
- it('Should render folder icon and name', () => {
+ it('should render folder icon and name', () => {
expect(component.$el.querySelector('.folder-name').textContent).toContain(mockItem.name);
expect(component.$el.querySelector('.folder-icon')).toBeDefined();
});
- it('Should render the number of children in a badge', () => {
+ it('should render the number of children in a badge', () => {
expect(component.$el.querySelector('.folder-name .badge').textContent).toContain(
mockItem.size,
);
@@ -60,7 +60,7 @@ describe('Environment item', () => {
sha: '500aabcb17c97bdcf2d0c410b70cb8556f0362dd',
ref: {
name: 'master',
- ref_path: 'root/ci-folders/tree/master',
+ ref_url: 'root/ci-folders/tree/master',
},
tag: true,
'last?': true,
@@ -109,6 +109,7 @@ describe('Environment item', () => {
},
has_stop_action: true,
environment_path: 'root/ci-folders/environments/31',
+ log_path: 'root/ci-folders/environments/31/logs',
created_at: '2016-11-07T11:11:16.525Z',
updated_at: '2016-11-10T15:55:58.778Z',
};
@@ -117,7 +118,6 @@ describe('Environment item', () => {
propsData: {
model: environment,
canReadEnvironment: true,
- service: {},
},
}).$mount();
});
@@ -157,13 +157,13 @@ describe('Environment item', () => {
});
describe('With build url', () => {
- it('Should link to build url provided', () => {
+ it('should link to build url provided', () => {
expect(component.$el.querySelector('.build-link').getAttribute('href')).toEqual(
environment.last_deployment.deployable.build_path,
);
});
- it('Should render deployable name and id', () => {
+ it('should render deployable name and id', () => {
expect(component.$el.querySelector('.build-link').getAttribute('href')).toEqual(
environment.last_deployment.deployable.build_path,
);
@@ -178,7 +178,7 @@ describe('Environment item', () => {
});
describe('With manual actions', () => {
- it('Should render actions component', () => {
+ it('should render actions component', () => {
expect(component.$el.querySelector('.js-manual-actions-container')).toBeDefined();
});
});
@@ -190,13 +190,13 @@ describe('Environment item', () => {
});
describe('With stop action', () => {
- it('Should render stop action component', () => {
+ it('should render stop action component', () => {
expect(component.$el.querySelector('.js-stop-component-container')).toBeDefined();
});
});
describe('With retry action', () => {
- it('Should render rollback component', () => {
+ it('should render rollback component', () => {
expect(component.$el.querySelector('.js-rollback-component-container')).toBeDefined();
});
});
diff --git a/spec/javascripts/environments/environment_table_spec.js b/spec/javascripts/environments/environment_table_spec.js
index ecd28594873..a3f34232a85 100644
--- a/spec/javascripts/environments/environment_table_spec.js
+++ b/spec/javascripts/environments/environment_table_spec.js
@@ -6,6 +6,14 @@ describe('Environment table', () => {
let Component;
let vm;
+ const eeOnlyProps = {
+ canaryDeploymentFeatureId: 'canary_deployment',
+ showCanaryDeploymentCallout: true,
+ userCalloutsPath: '/callouts',
+ lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
+ helpCanaryDeploymentsPath: 'help/canary-deployments',
+ };
+
beforeEach(() => {
Component = Vue.extend(environmentTableComp);
});
@@ -27,6 +35,7 @@ describe('Environment table', () => {
vm = mountComponent(Component, {
environments: [mockItem],
canReadEnvironment: true,
+ ...eeOnlyProps,
});
expect(vm.$el.getAttribute('class')).toContain('ci-table');
@@ -67,6 +76,7 @@ describe('Environment table', () => {
vm = mountComponent(Component, {
environments: mockItems,
canReadEnvironment: true,
+ ...eeOnlyProps,
});
const [old, newer, older, noDeploy] = mockItems;
@@ -130,6 +140,7 @@ describe('Environment table', () => {
vm = mountComponent(Component, {
environments: mockItems,
canReadEnvironment: true,
+ ...eeOnlyProps,
});
const [prod, review, staging] = mockItems;
@@ -166,6 +177,7 @@ describe('Environment table', () => {
vm = mountComponent(Component, {
environments: mockItems,
canReadEnvironment: true,
+ ...eeOnlyProps,
});
const [old, newer, older] = mockItems;
@@ -192,6 +204,7 @@ describe('Environment table', () => {
vm = mountComponent(Component, {
environments: mockItems,
canReadEnvironment: true,
+ ...eeOnlyProps,
});
const [old, newer, older] = mockItems;
@@ -240,6 +253,7 @@ describe('Environment table', () => {
vm = mountComponent(Component, {
environments: mockItems,
canReadEnvironment: true,
+ ...eeOnlyProps,
});
expect(vm.sortedEnvironments.map(env => env.name)).toEqual([
diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js
index b6a244f7cd3..0dcd8868aba 100644
--- a/spec/javascripts/environments/environments_app_spec.js
+++ b/spec/javascripts/environments/environments_app_spec.js
@@ -13,6 +13,11 @@ describe('Environment', () => {
cssContainerClass: 'container',
newEnvironmentPath: 'environments/new',
helpPagePath: 'help',
+ canaryDeploymentFeatureId: 'canary_deployment',
+ showCanaryDeploymentCallout: true,
+ userCalloutsPath: '/callouts',
+ lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
+ helpCanaryDeploymentsPath: 'help/canary-deployments',
};
let EnvironmentsComponent;
diff --git a/spec/javascripts/environments/environments_store_spec.js b/spec/javascripts/environments/environments_store_spec.js
index c3d16f10d72..8abdbcbbe54 100644
--- a/spec/javascripts/environments/environments_store_spec.js
+++ b/spec/javascripts/environments/environments_store_spec.js
@@ -34,54 +34,46 @@ describe('Store', () => {
expect(store.state.stoppedCounter).toEqual(2);
});
- describe('store environments', () => {
- it('should store environments', () => {
- store.storeEnvironments(serverData);
-
- expect(store.state.environments.length).toEqual(serverData.length);
- });
-
- it('should add folder keys when environment is a folder', () => {
- const environment = {
- name: 'bar',
- size: 3,
- id: 2,
- };
+ it('should add folder keys when environment is a folder', () => {
+ const environment = {
+ name: 'bar',
+ size: 3,
+ id: 2,
+ };
- store.storeEnvironments([environment]);
+ store.storeEnvironments([environment]);
- expect(store.state.environments[0].isFolder).toEqual(true);
- expect(store.state.environments[0].folderName).toEqual('bar');
- });
-
- it('should extract content of `latest` key when provided', () => {
- const environment = {
- name: 'bar',
- size: 3,
- id: 2,
- latest: {
- last_deployment: {},
- isStoppable: true,
- },
- };
-
- store.storeEnvironments([environment]);
+ expect(store.state.environments[0].isFolder).toEqual(true);
+ expect(store.state.environments[0].folderName).toEqual('bar');
+ });
- expect(store.state.environments[0].last_deployment).toEqual({});
- expect(store.state.environments[0].isStoppable).toEqual(true);
- });
+ it('should extract content of `latest` key when provided', () => {
+ const environment = {
+ name: 'bar',
+ size: 3,
+ id: 2,
+ latest: {
+ last_deployment: {},
+ isStoppable: true,
+ },
+ };
+
+ store.storeEnvironments([environment]);
+
+ expect(store.state.environments[0].last_deployment).toEqual({});
+ expect(store.state.environments[0].isStoppable).toEqual(true);
+ });
- it('should store latest.name when the environment is not a folder', () => {
- store.storeEnvironments(serverData);
+ it('should store latest.name when the environment is not a folder', () => {
+ store.storeEnvironments(serverData);
- expect(store.state.environments[0].name).toEqual(serverData[0].latest.name);
- });
+ expect(store.state.environments[0].name).toEqual(serverData[0].latest.name);
+ });
- it('should store root level name when environment is a folder', () => {
- store.storeEnvironments(serverData);
+ it('should store root level name when environment is a folder', () => {
+ store.storeEnvironments(serverData);
- expect(store.state.environments[1].folderName).toEqual(serverData[1].name);
- });
+ expect(store.state.environments[1].folderName).toEqual(serverData[1].name);
});
describe('toggleFolder', () => {
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js
index 69ddd26eef1..ff15067aeac 100644
--- a/spec/javascripts/environments/folder/environments_folder_view_spec.js
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js
@@ -3,6 +3,7 @@ import MockAdapter from 'axios-mock-adapter';
import axios from '~/lib/utils/axios_utils';
import environmentsFolderViewComponent from '~/environments/folder/environments_folder_view.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { removeBreakLine, removeWhitespace } from 'spec/helpers/vue_component_helper';
import { environmentsList } from '../mock_data';
describe('Environments Folder View', () => {
@@ -15,6 +16,11 @@ describe('Environments Folder View', () => {
folderName: 'review',
canReadEnvironment: true,
cssContainerClass: 'container',
+ canaryDeploymentFeatureId: 'canary_deployment',
+ showCanaryDeploymentCallout: true,
+ userCalloutsPath: '/callouts',
+ lockPromotionSvgPath: '/assets/illustrations/lock-promotion.svg',
+ helpCanaryDeploymentsPath: 'help/canary-deployments',
};
beforeEach(() => {
@@ -89,9 +95,11 @@ describe('Environments Folder View', () => {
it('should render parent folder name', done => {
setTimeout(() => {
- expect(component.$el.querySelector('.js-folder-name').textContent.trim()).toContain(
- 'Environments / review',
- );
+ expect(
+ removeBreakLine(
+ removeWhitespace(component.$el.querySelector('.js-folder-name').textContent.trim()),
+ ),
+ ).toContain('Environments / review');
done();
}, 0);
});
diff --git a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
index f3dc35552d5..a72ea6ab547 100644
--- a/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
+++ b/spec/javascripts/filtered_search/filtered_search_visual_tokens_spec.js
@@ -293,6 +293,7 @@ describe('Filtered Search Visual Tokens', () => {
subject.addVisualTokenElement('milestone');
const token = tokensContainer.querySelector('.js-visual-token');
+ expect(token.classList.contains('search-token-milestone')).toEqual(true);
expect(token.classList.contains('filtered-search-token')).toEqual(true);
expect(token.querySelector('.name').innerText).toEqual('milestone');
expect(token.querySelector('.value')).toEqual(null);
@@ -302,6 +303,7 @@ describe('Filtered Search Visual Tokens', () => {
subject.addVisualTokenElement('label', 'Frontend');
const token = tokensContainer.querySelector('.js-visual-token');
+ expect(token.classList.contains('search-token-label')).toEqual(true);
expect(token.classList.contains('filtered-search-token')).toEqual(true);
expect(token.querySelector('.name').innerText).toEqual('label');
expect(token.querySelector('.value').innerText).toEqual('Frontend');
@@ -317,10 +319,12 @@ describe('Filtered Search Visual Tokens', () => {
const labelToken = tokens[0];
const assigneeToken = tokens[1];
+ expect(labelToken.classList.contains('search-token-label')).toEqual(true);
expect(labelToken.classList.contains('filtered-search-token')).toEqual(true);
expect(labelToken.querySelector('.name').innerText).toEqual('label');
expect(labelToken.querySelector('.value').innerText).toEqual('Frontend');
+ expect(assigneeToken.classList.contains('search-token-assignee')).toEqual(true);
expect(assigneeToken.classList.contains('filtered-search-token')).toEqual(true);
expect(assigneeToken.querySelector('.name').innerText).toEqual('assignee');
expect(assigneeToken.querySelector('.value').innerText).toEqual('@root');
diff --git a/spec/javascripts/filtered_search/visual_token_value_spec.js b/spec/javascripts/filtered_search/visual_token_value_spec.js
index f52dc26a7bb..14217d460cc 100644
--- a/spec/javascripts/filtered_search/visual_token_value_spec.js
+++ b/spec/javascripts/filtered_search/visual_token_value_spec.js
@@ -317,7 +317,18 @@ describe('Filtered Search Visual Tokens', () => {
it('does not update user token appearance for `none` filter', () => {
const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
- subject.tokenType = 'none';
+ subject.tokenValue = 'none';
+
+ const { updateUserTokenAppearanceSpy } = setupSpies(subject);
+ subject.render(tokenValueContainer, tokenValueElement);
+
+ expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0);
+ });
+
+ it('does not update user token appearance for `None` filter', () => {
+ const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
+
+ subject.tokenValue = 'None';
const { updateUserTokenAppearanceSpy } = setupSpies(subject);
subject.render(tokenValueContainer, tokenValueElement);
@@ -328,7 +339,7 @@ describe('Filtered Search Visual Tokens', () => {
it('does not update user token appearance for `any` filter', () => {
const { subject, tokenValueContainer, tokenValueElement } = findElements(authorToken);
- subject.tokenType = 'any';
+ subject.tokenValue = 'any';
const { updateUserTokenAppearanceSpy } = setupSpies(subject);
subject.render(tokenValueContainer, tokenValueElement);
@@ -336,10 +347,21 @@ describe('Filtered Search Visual Tokens', () => {
expect(updateUserTokenAppearanceSpy.calls.count()).toBe(0);
});
+ it('does not update label token color for `None` filter', () => {
+ const { subject, tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
+
+ subject.tokenValue = 'None';
+
+ const { updateLabelTokenColorSpy } = setupSpies(subject);
+ subject.render(tokenValueContainer, tokenValueElement);
+
+ expect(updateLabelTokenColorSpy.calls.count()).toBe(0);
+ });
+
it('does not update label token color for `none` filter', () => {
const { subject, tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
- subject.tokenType = 'none';
+ subject.tokenValue = 'none';
const { updateLabelTokenColorSpy } = setupSpies(subject);
subject.render(tokenValueContainer, tokenValueElement);
@@ -350,7 +372,7 @@ describe('Filtered Search Visual Tokens', () => {
it('does not update label token color for `any` filter', () => {
const { subject, tokenValueContainer, tokenValueElement } = findElements(bugLabelToken);
- subject.tokenType = 'any';
+ subject.tokenValue = 'any';
const { updateLabelTokenColorSpy } = setupSpies(subject);
subject.render(tokenValueContainer, tokenValueElement);
diff --git a/spec/javascripts/fixtures/ajax_loading_spinner.html.haml b/spec/javascripts/fixtures/ajax_loading_spinner.html.haml
deleted file mode 100644
index 09d8c9df3b2..00000000000
--- a/spec/javascripts/fixtures/ajax_loading_spinner.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-%a.js-ajax-loading-spinner{href: "http://goesnowhere.nothing/whereami", data: {remote: true}}
- %i.fa.fa-trash-o
diff --git a/spec/javascripts/fixtures/autocomplete_sources.rb b/spec/javascripts/fixtures/autocomplete_sources.rb
new file mode 100644
index 00000000000..c117fb7cd24
--- /dev/null
+++ b/spec/javascripts/fixtures/autocomplete_sources.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::AutocompleteSourcesController, '(JavaScript fixtures)', type: :controller do
+ include JavaScriptFixturesHelpers
+
+ set(:admin) { create(:admin) }
+ set(:group) { create(:group, name: 'frontend-fixtures') }
+ set(:project) { create(:project, namespace: group, path: 'autocomplete-sources-project') }
+ set(:issue) { create(:issue, project: project) }
+
+ before(:all) do
+ clean_frontend_fixtures('autocomplete_sources/')
+ end
+
+ before do
+ sign_in(admin)
+ end
+
+ it 'autocomplete_sources/labels.json' do |example|
+ issue.labels << create(:label, project: project, title: 'bug')
+ issue.labels << create(:label, project: project, title: 'critical')
+
+ create(:label, project: project, title: 'feature')
+ create(:label, project: project, title: 'documentation')
+
+ get :labels,
+ format: :json,
+ params: {
+ namespace_id: group.path,
+ project_id: project.path,
+ type: issue.class.name,
+ type_id: issue.id
+ }
+
+ expect(response).to be_success
+ store_frontend_fixture(response, example.description)
+ end
+end
diff --git a/spec/javascripts/fixtures/balsamiq_viewer.html.haml b/spec/javascripts/fixtures/balsamiq_viewer.html.haml
deleted file mode 100644
index 18166ba4901..00000000000
--- a/spec/javascripts/fixtures/balsamiq_viewer.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-.file-content.balsamiq-viewer#js-balsamiq-viewer{ data: { endpoint: '/test' } }
diff --git a/spec/javascripts/fixtures/create_item_dropdown.html.haml b/spec/javascripts/fixtures/create_item_dropdown.html.haml
deleted file mode 100644
index d4d91b93caf..00000000000
--- a/spec/javascripts/fixtures/create_item_dropdown.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-.js-create-item-dropdown-fixture-root
- %input{ name: 'variable[environment]', type: 'hidden' }
- = dropdown_tag('some label',
- options: { toggle_class: 'js-dropdown-menu-toggle',
- content_class: 'js-dropdown-content',
- filter: true,
- dropdown_class: "dropdown-menu-selectable",
- footer_content: true }) do
- %ul.dropdown-footer-list
- %li
- %button{ class: "dropdown-create-new-item-button js-dropdown-create-new-item" }
- Create wildcard
- %code
diff --git a/spec/javascripts/fixtures/emojis.rb b/spec/javascripts/fixtures/emojis.rb
deleted file mode 100644
index f5fb008c7b3..00000000000
--- a/spec/javascripts/fixtures/emojis.rb
+++ /dev/null
@@ -1,18 +0,0 @@
-require 'spec_helper'
-
-describe 'Emojis (JavaScript fixtures)' do
- include JavaScriptFixturesHelpers
-
- before(:all) do
- clean_frontend_fixtures('emojis/')
- end
-
- it 'emojis/emojis.json' do |example|
- JavaScriptFixturesHelpers::FIXTURE_PATHS.each do |fixture_path|
- # Copying the emojis.json from the public folder
- fixture_file_name = File.expand_path('emojis/emojis.json', fixture_path)
- FileUtils.mkdir_p(File.dirname(fixture_file_name))
- FileUtils.cp(Rails.root.join('public/-/emojis/1/emojis.json'), fixture_file_name)
- end
- end
-end
diff --git a/spec/javascripts/fixtures/event_filter.html.haml b/spec/javascripts/fixtures/event_filter.html.haml
deleted file mode 100644
index aa7af61c7eb..00000000000
--- a/spec/javascripts/fixtures/event_filter.html.haml
+++ /dev/null
@@ -1,25 +0,0 @@
-%ul.nav-links.event-filter.scrolling-tabs.nav.nav-tabs
- %li.active
- %a.event-filter-link{ id: "all_event_filter", title: "Filter by all", href: "/dashboard/activity"}
- %span
- All
- %li
- %a.event-filter-link{ id: "push_event_filter", title: "Filter by push events", href: "/dashboard/activity"}
- %span
- Push events
- %li
- %a.event-filter-link{ id: "merged_event_filter", title: "Filter by merge events", href: "/dashboard/activity"}
- %span
- Merge events
- %li
- %a.event-filter-link{ id: "issue_event_filter", title: "Filter by issue events", href: "/dashboard/activity"}
- %span
- Issue events
- %li
- %a.event-filter-link{ id: "comments_event_filter", title: "Filter by comments", href: "/dashboard/activity"}
- %span
- Comments
- %li
- %a.event-filter-link{ id: "team_event_filter", title: "Filter by team", href: "/dashboard/activity"}
- %span
- Team \ No newline at end of file
diff --git a/spec/javascripts/fixtures/gl_dropdown.html.haml b/spec/javascripts/fixtures/gl_dropdown.html.haml
deleted file mode 100644
index 43d57c2c4dc..00000000000
--- a/spec/javascripts/fixtures/gl_dropdown.html.haml
+++ /dev/null
@@ -1,17 +0,0 @@
-%div
- .dropdown.inline
- %button#js-project-dropdown.dropdown-menu-toggle{type: 'button', data: {toggle: 'dropdown'}}
- .dropdown-toggle-text
- Projects
- %i.fa.fa-chevron-down.dropdown-toggle-caret.js-projects-dropdown-toggle
- .dropdown-menu.dropdown-select.dropdown-menu-selectable
- .dropdown-title
- %span Go to project
- %button.dropdown-title-button.dropdown-menu-close{aria: {label: 'Close'}}
- %i.fa.fa-times.dropdown-menu-close-icon
- .dropdown-input
- %input.dropdown-input-field{type: 'search', placeholder: 'Filter results'}
- %i.fa.fa-search.dropdown-input-search
- .dropdown-content
- .dropdown-loading
- %i.fa.fa-spinner.fa-spin
diff --git a/spec/javascripts/fixtures/gl_field_errors.html.haml b/spec/javascripts/fixtures/gl_field_errors.html.haml
deleted file mode 100644
index 69445b61367..00000000000
--- a/spec/javascripts/fixtures/gl_field_errors.html.haml
+++ /dev/null
@@ -1,15 +0,0 @@
-%form.gl-show-field-errors{action: 'submit', method: 'post'}
- .form-group
- %input.required-text{required: true, type: 'text'} Text
- .form-group
- %input.email{type: 'email', title: 'Please provide a valid email address.', required: true } Email
- .form-group
- %input.password{type: 'password', required: true} Password
- .form-group
- %input.alphanumeric{type: 'text', pattern: '[a-zA-Z0-9]', required: true} Alphanumeric
- .form-group
- %input.hidden{ type:'hidden' }
- .form-group
- %input.custom.gl-field-error-ignore{ type:'text' } Custom, do not validate
- .form-group
- %input.submit{type: 'submit'} Submit
diff --git a/spec/javascripts/fixtures/issuable_filter.html.haml b/spec/javascripts/fixtures/issuable_filter.html.haml
deleted file mode 100644
index 84fa5395cb8..00000000000
--- a/spec/javascripts/fixtures/issuable_filter.html.haml
+++ /dev/null
@@ -1,8 +0,0 @@
-%form.js-filter-form{action: '/user/project/issues?scope=all&state=closed'}
- %input{id: 'utf8', name: 'utf8', value: '✓'}
- %input{id: 'check-all-issues', name: 'check-all-issues'}
- %input{id: 'search', name: 'search'}
- %input{id: 'author_id', name: 'author_id'}
- %input{id: 'assignee_id', name: 'assignee_id'}
- %input{id: 'milestone_title', name: 'milestone_title'}
- %input{id: 'label_name', name: 'label_name'}
diff --git a/spec/javascripts/fixtures/issue_sidebar_label.html.haml b/spec/javascripts/fixtures/issue_sidebar_label.html.haml
deleted file mode 100644
index 06ce248dc9c..00000000000
--- a/spec/javascripts/fixtures/issue_sidebar_label.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-.block.labels
- .sidebar-collapsed-icon.js-sidebar-labels-tooltip
- .title.hide-collapsed
- %a.edit-link.float-right{ href: "#" }
- Edit
- .selectbox.hide-collapsed{ style: "display: none;" }
- .dropdown
- %button.dropdown-menu-toggle.js-label-select.js-multiselect{ type: "button", data: { ability_name: "issue", field_name: "issue[label_names][]", issue_update: "/root/test/issues/2.json", labels: "/root/test/labels.json", project_id: "12", show_any: "true", show_no: "true", toggle: "dropdown" } }
- %span.dropdown-toggle-text
- Label
- %i.fa.fa-chevron-down
- .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable
- .dropdown-page-one
- .dropdown-content
- .dropdown-loading
- %i.fa.fa-spinner.fa-spin
diff --git a/spec/javascripts/fixtures/line_highlighter.html.haml b/spec/javascripts/fixtures/line_highlighter.html.haml
deleted file mode 100644
index 2782c50e298..00000000000
--- a/spec/javascripts/fixtures/line_highlighter.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-.file-holder
- .file-content
- .line-numbers
- - 1.upto(25) do |i|
- %a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
- %i.fa.fa-link
- = i
- %pre.code.highlight
- %code
- - 1.upto(25) do |i|
- %span.line{id: "LC#{i}"}= "Line #{i}"
diff --git a/spec/javascripts/fixtures/linked_tabs.html.haml b/spec/javascripts/fixtures/linked_tabs.html.haml
deleted file mode 100644
index 632606e0536..00000000000
--- a/spec/javascripts/fixtures/linked_tabs.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-%ul.nav.nav-tabs.new-session-tabs.linked-tabs
- %li.nav-item
- %a.nav-link{ href: 'foo/bar/1', data: { target: 'div#tab1', action: 'tab1', toggle: 'tab' } }
- Tab 1
- %li.nav-item
- %a.nav-link{ href: 'foo/bar/1/context', data: { target: 'div#tab2', action: 'tab2', toggle: 'tab' } }
- Tab 2
-
-.tab-content
- #tab1.tab-pane
- Tab 1 Content
- #tab2.tab-pane
- Tab 2 Content
diff --git a/spec/javascripts/fixtures/merge_requests_show.html.haml b/spec/javascripts/fixtures/merge_requests_show.html.haml
deleted file mode 100644
index 8447dfdda32..00000000000
--- a/spec/javascripts/fixtures/merge_requests_show.html.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-%a.btn-close
-
-.detail-page-description
- .description.js-task-list-container
- .wiki
- %ul.task-list
- %li.task-list-item
- %input.task-list-item-checkbox{type: 'checkbox'}
- Task List Item
- %textarea.js-task-list-field
- \- [ ] Task List Item
-
-%form.js-issuable-update{action: '/foo'}
diff --git a/spec/javascripts/fixtures/mini_dropdown_graph.html.haml b/spec/javascripts/fixtures/mini_dropdown_graph.html.haml
deleted file mode 100644
index 74584993739..00000000000
--- a/spec/javascripts/fixtures/mini_dropdown_graph.html.haml
+++ /dev/null
@@ -1,10 +0,0 @@
-%div.js-builds-dropdown-tests.dropdown.dropdown.js-mini-pipeline-graph
- %button.js-builds-dropdown-button{'data-stage-endpoint' => 'foobar', data: { toggle: 'dropdown'} }
- Dropdown
-
- %ul.dropdown-menu.mini-pipeline-graph-dropdown-menu.js-builds-dropdown-container
- %li.js-builds-dropdown-list.scrollable-menu
- %ul
-
- %li.js-builds-dropdown-loading.hidden
- %span.fa.fa-spinner
diff --git a/spec/javascripts/fixtures/notebook_viewer.html.haml b/spec/javascripts/fixtures/notebook_viewer.html.haml
deleted file mode 100644
index 17a7a9d8f31..00000000000
--- a/spec/javascripts/fixtures/notebook_viewer.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-.file-content#js-notebook-viewer{ data: { endpoint: '/test' } }
diff --git a/spec/javascripts/fixtures/oauth_remember_me.html.haml b/spec/javascripts/fixtures/oauth_remember_me.html.haml
deleted file mode 100644
index a5d7c4e816a..00000000000
--- a/spec/javascripts/fixtures/oauth_remember_me.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-#oauth-container
- %input#remember_me{ type: "checkbox" }
-
- %a.oauth-login.twitter{ href: "http://example.com/" }
- %a.oauth-login.github{ href: "http://example.com/" }
- %a.oauth-login.facebook{ href: "http://example.com/?redirect_fragment=L1" }
diff --git a/spec/javascripts/fixtures/pdf_viewer.html.haml b/spec/javascripts/fixtures/pdf_viewer.html.haml
deleted file mode 100644
index 2e57beae54b..00000000000
--- a/spec/javascripts/fixtures/pdf_viewer.html.haml
+++ /dev/null
@@ -1 +0,0 @@
-.file-content#js-pdf-viewer{ data: { endpoint: '/test' } }
diff --git a/spec/javascripts/fixtures/pipeline_graph.html.haml b/spec/javascripts/fixtures/pipeline_graph.html.haml
deleted file mode 100644
index c0b5ab4411e..00000000000
--- a/spec/javascripts/fixtures/pipeline_graph.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-%div.pipeline-visualization.js-pipeline-graph
- %ul.stage-column-list
- %li.stage-column
- .stage-name
- %a{:href => "/"}
- Test
- .builds-container
- %ul
- %li.build
- .curve
- %a
- %svg
- .ci-status-text
- stop_review
diff --git a/spec/javascripts/fixtures/pipelines.html.haml b/spec/javascripts/fixtures/pipelines.html.haml
deleted file mode 100644
index 0161c0550d1..00000000000
--- a/spec/javascripts/fixtures/pipelines.html.haml
+++ /dev/null
@@ -1,12 +0,0 @@
-%div
- #pipelines-list-vue{ data: { endpoint: 'foo',
- "help-page-path" => 'foo',
- "help-auto-devops-path" => 'foo',
- "empty-state-svg-path" => 'foo',
- "error-state-svg-path" => 'foo',
- "new-pipeline-path" => 'foo',
- "can-create-pipeline" => 'true',
- "has-ci" => 'foo',
- "ci-lint-path" => 'foo',
- "reset-cache-path" => 'foo' } }
-
diff --git a/spec/javascripts/fixtures/project_select_combo_button.html.haml b/spec/javascripts/fixtures/project_select_combo_button.html.haml
deleted file mode 100644
index 432cd5fcc74..00000000000
--- a/spec/javascripts/fixtures/project_select_combo_button.html.haml
+++ /dev/null
@@ -1,6 +0,0 @@
-.project-item-select-holder
- %input.project-item-select{ data: { group_id: '12345' , relative_path: 'issues/new' } }
- %a.new-project-item-link{ data: { label: 'New issue', type: 'issues' }, href: ''}
- %i.fa.fa-spinner.spin
- %a.new-project-item-select-button
- %i.fa.fa-caret-down
diff --git a/spec/javascripts/fixtures/search_autocomplete.html.haml b/spec/javascripts/fixtures/search_autocomplete.html.haml
deleted file mode 100644
index 4aa54da9411..00000000000
--- a/spec/javascripts/fixtures/search_autocomplete.html.haml
+++ /dev/null
@@ -1,9 +0,0 @@
-.search.search-form
- %form.form-inline
- .search-input-container
- .search-input-wrap
- .dropdown
- %input#search.search-input.dropdown-menu-toggle
- .dropdown-menu.dropdown-select
- .dropdown-content
- %input{ type: "hidden", class: "js-search-project-options" }
diff --git a/spec/javascripts/fixtures/signin_tabs.html.haml b/spec/javascripts/fixtures/signin_tabs.html.haml
deleted file mode 100644
index 2e00fe7865e..00000000000
--- a/spec/javascripts/fixtures/signin_tabs.html.haml
+++ /dev/null
@@ -1,5 +0,0 @@
-%ul.nav-links.new-session-tabs
- %li.active
- %a{ href: '#ldap' } LDAP
- %li
- %a{ href: '#login-pane'} Standard
diff --git a/spec/javascripts/fixtures/sketch_viewer.html.haml b/spec/javascripts/fixtures/sketch_viewer.html.haml
deleted file mode 100644
index f01bd00925a..00000000000
--- a/spec/javascripts/fixtures/sketch_viewer.html.haml
+++ /dev/null
@@ -1,2 +0,0 @@
-.file-content#js-sketch-viewer{ data: { endpoint: '/test_sketch_file.sketch' } }
- .js-loading-icon
diff --git a/spec/javascripts/fixtures/static/README.md b/spec/javascripts/fixtures/static/README.md
new file mode 100644
index 00000000000..b5c2f8233bf
--- /dev/null
+++ b/spec/javascripts/fixtures/static/README.md
@@ -0,0 +1,3 @@
+# Please do not add new files here!
+
+Instead use a Ruby file in the fixtures root directory (`spec/javascripts/fixtures/`).
diff --git a/spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw b/spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw
new file mode 100644
index 00000000000..0e1ebb32b1c
--- /dev/null
+++ b/spec/javascripts/fixtures/static/ajax_loading_spinner.html.raw
@@ -0,0 +1,3 @@
+<a class="js-ajax-loading-spinner" data-remote href="http://goesnowhere.nothing/whereami">
+<i class="fa fa-trash-o"></i>
+</a>
diff --git a/spec/javascripts/fixtures/static/balsamiq_viewer.html.raw b/spec/javascripts/fixtures/static/balsamiq_viewer.html.raw
new file mode 100644
index 00000000000..cdd723d1a84
--- /dev/null
+++ b/spec/javascripts/fixtures/static/balsamiq_viewer.html.raw
@@ -0,0 +1 @@
+<div class="file-content balsamiq-viewer" data-endpoint="/test" id="js-balsamiq-viewer"></div>
diff --git a/spec/javascripts/fixtures/static/create_item_dropdown.html.raw b/spec/javascripts/fixtures/static/create_item_dropdown.html.raw
new file mode 100644
index 00000000000..d2d38370092
--- /dev/null
+++ b/spec/javascripts/fixtures/static/create_item_dropdown.html.raw
@@ -0,0 +1,11 @@
+<div class="js-create-item-dropdown-fixture-root">
+<input name="variable[environment]" type="hidden">
+<div class="dropdown "><button class="dropdown-menu-toggle js-dropdown-menu-toggle" type="button" data-toggle="dropdown"><span class="dropdown-toggle-text ">some label</span><i aria-hidden="true" data-hidden="true" class="fa fa-chevron-down"></i></button><div class="dropdown-menu dropdown-select dropdown-menu-selectable"><div class="dropdown-input"><input type="search" id="" class="dropdown-input-field" autocomplete="off" /><i aria-hidden="true" data-hidden="true" class="fa fa-search dropdown-input-search"></i><i aria-hidden="true" data-hidden="true" role="button" class="fa fa-times dropdown-input-clear js-dropdown-input-clear"></i></div><div class="dropdown-content js-dropdown-content"></div><div class="dropdown-footer"><ul class="dropdown-footer-list">
+<li>
+<button class="dropdown-create-new-item-button js-dropdown-create-new-item">
+Create wildcard
+<code></code>
+</button>
+</li>
+</ul>
+</div><div class="dropdown-loading"><i aria-hidden="true" data-hidden="true" class="fa fa-spinner fa-spin"></i></div></div></div></div>
diff --git a/spec/javascripts/fixtures/static/event_filter.html.raw b/spec/javascripts/fixtures/static/event_filter.html.raw
new file mode 100644
index 00000000000..8e9b6fb1b5c
--- /dev/null
+++ b/spec/javascripts/fixtures/static/event_filter.html.raw
@@ -0,0 +1,44 @@
+<ul class="nav-links event-filter scrolling-tabs nav nav-tabs">
+<li class="active">
+<a class="event-filter-link" href="/dashboard/activity" id="all_event_filter" title="Filter by all">
+<span>
+All
+</span>
+</a>
+</li>
+<li>
+<a class="event-filter-link" href="/dashboard/activity" id="push_event_filter" title="Filter by push events">
+<span>
+Push events
+</span>
+</a>
+</li>
+<li>
+<a class="event-filter-link" href="/dashboard/activity" id="merged_event_filter" title="Filter by merge events">
+<span>
+Merge events
+</span>
+</a>
+</li>
+<li>
+<a class="event-filter-link" href="/dashboard/activity" id="issue_event_filter" title="Filter by issue events">
+<span>
+Issue events
+</span>
+</a>
+</li>
+<li>
+<a class="event-filter-link" href="/dashboard/activity" id="comments_event_filter" title="Filter by comments">
+<span>
+Comments
+</span>
+</a>
+</li>
+<li>
+<a class="event-filter-link" href="/dashboard/activity" id="team_event_filter" title="Filter by team">
+<span>
+Team
+</span>
+</a>
+</li>
+</ul>
diff --git a/spec/javascripts/fixtures/static/gl_dropdown.html.raw b/spec/javascripts/fixtures/static/gl_dropdown.html.raw
new file mode 100644
index 00000000000..08f6738414e
--- /dev/null
+++ b/spec/javascripts/fixtures/static/gl_dropdown.html.raw
@@ -0,0 +1,26 @@
+<div>
+<div class="dropdown inline">
+<button class="dropdown-menu-toggle" data-toggle="dropdown" id="js-project-dropdown" type="button">
+<div class="dropdown-toggle-text">
+Projects
+</div>
+<i class="fa fa-chevron-down dropdown-toggle-caret js-projects-dropdown-toggle"></i>
+</button>
+<div class="dropdown-menu dropdown-select dropdown-menu-selectable">
+<div class="dropdown-title">
+<span>Go to project</span>
+<button aria="{:label=&gt;&quot;Close&quot;}" class="dropdown-title-button dropdown-menu-close">
+<i class="fa fa-times dropdown-menu-close-icon"></i>
+</button>
+</div>
+<div class="dropdown-input">
+<input class="dropdown-input-field" placeholder="Filter results" type="search">
+<i class="fa fa-search dropdown-input-search"></i>
+</div>
+<div class="dropdown-content"></div>
+<div class="dropdown-loading">
+<i class="fa fa-spinner fa-spin"></i>
+</div>
+</div>
+</div>
+</div>
diff --git a/spec/javascripts/fixtures/static/gl_field_errors.html.raw b/spec/javascripts/fixtures/static/gl_field_errors.html.raw
new file mode 100644
index 00000000000..f8470e02b7c
--- /dev/null
+++ b/spec/javascripts/fixtures/static/gl_field_errors.html.raw
@@ -0,0 +1,22 @@
+<form action="submit" class="gl-show-field-errors" method="post">
+<div class="form-group">
+<input class="required-text" required type="text">Text</input>
+</div>
+<div class="form-group">
+<input class="email" required title="Please provide a valid email address." type="email">Email</input>
+</div>
+<div class="form-group">
+<input class="password" required type="password">Password</input>
+</div>
+<div class="form-group">
+<input class="alphanumeric" pattern="[a-zA-Z0-9]" required type="text">Alphanumeric</input>
+</div>
+<div class="form-group">
+<input class="hidden" type="hidden">
+</div>
+<div class="form-group">
+<input class="custom gl-field-error-ignore" type="text">Custom, do not validate</input>
+</div>
+<div class="form-group"></div>
+<input class="submit" type="submit">Submit</input>
+</form>
diff --git a/spec/javascripts/fixtures/static/issuable_filter.html.raw b/spec/javascripts/fixtures/static/issuable_filter.html.raw
new file mode 100644
index 00000000000..06b70fb43f1
--- /dev/null
+++ b/spec/javascripts/fixtures/static/issuable_filter.html.raw
@@ -0,0 +1,9 @@
+<form action="/user/project/issues?scope=all&amp;state=closed" class="js-filter-form">
+<input id="utf8" name="utf8" value="✓">
+<input id="check-all-issues" name="check-all-issues">
+<input id="search" name="search">
+<input id="author_id" name="author_id">
+<input id="assignee_id" name="assignee_id">
+<input id="milestone_title" name="milestone_title">
+<input id="label_name" name="label_name">
+</form>
diff --git a/spec/javascripts/fixtures/static/issue_sidebar_label.html.raw b/spec/javascripts/fixtures/static/issue_sidebar_label.html.raw
new file mode 100644
index 00000000000..ec8fb30f219
--- /dev/null
+++ b/spec/javascripts/fixtures/static/issue_sidebar_label.html.raw
@@ -0,0 +1,26 @@
+<div class="block labels">
+<div class="sidebar-collapsed-icon js-sidebar-labels-tooltip"></div>
+<div class="title hide-collapsed">
+<a class="edit-link float-right" href="#">
+Edit
+</a>
+</div>
+<div class="selectbox hide-collapsed" style="display: none;">
+<div class="dropdown">
+<button class="dropdown-menu-toggle js-label-select js-multiselect" data-ability-name="issue" data-field-name="issue[label_names][]" data-issue-update="/root/test/issues/2.json" data-labels="/root/test/labels.json" data-project-id="12" data-show-any="true" data-show-no="true" data-toggle="dropdown" type="button">
+<span class="dropdown-toggle-text">
+Label
+</span>
+<i class="fa fa-chevron-down"></i>
+</button>
+<div class="dropdown-menu dropdown-select dropdown-menu-paging dropdown-menu-labels dropdown-menu-selectable">
+<div class="dropdown-page-one">
+<div class="dropdown-content"></div>
+<div class="dropdown-loading">
+<i class="fa fa-spinner fa-spin"></i>
+</div>
+</div>
+</div>
+</div>
+</div>
+</div>
diff --git a/spec/javascripts/fixtures/static/line_highlighter.html.raw b/spec/javascripts/fixtures/static/line_highlighter.html.raw
new file mode 100644
index 00000000000..897a25d6760
--- /dev/null
+++ b/spec/javascripts/fixtures/static/line_highlighter.html.raw
@@ -0,0 +1,107 @@
+<div class="file-holder">
+<div class="file-content">
+<div class="line-numbers">
+<a data-line-number="1" href="#L1" id="L1">
+<i class="fa fa-link"></i>
+1
+</a>
+<a data-line-number="2" href="#L2" id="L2">
+<i class="fa fa-link"></i>
+2
+</a>
+<a data-line-number="3" href="#L3" id="L3">
+<i class="fa fa-link"></i>
+3
+</a>
+<a data-line-number="4" href="#L4" id="L4">
+<i class="fa fa-link"></i>
+4
+</a>
+<a data-line-number="5" href="#L5" id="L5">
+<i class="fa fa-link"></i>
+5
+</a>
+<a data-line-number="6" href="#L6" id="L6">
+<i class="fa fa-link"></i>
+6
+</a>
+<a data-line-number="7" href="#L7" id="L7">
+<i class="fa fa-link"></i>
+7
+</a>
+<a data-line-number="8" href="#L8" id="L8">
+<i class="fa fa-link"></i>
+8
+</a>
+<a data-line-number="9" href="#L9" id="L9">
+<i class="fa fa-link"></i>
+9
+</a>
+<a data-line-number="10" href="#L10" id="L10">
+<i class="fa fa-link"></i>
+10
+</a>
+<a data-line-number="11" href="#L11" id="L11">
+<i class="fa fa-link"></i>
+11
+</a>
+<a data-line-number="12" href="#L12" id="L12">
+<i class="fa fa-link"></i>
+12
+</a>
+<a data-line-number="13" href="#L13" id="L13">
+<i class="fa fa-link"></i>
+13
+</a>
+<a data-line-number="14" href="#L14" id="L14">
+<i class="fa fa-link"></i>
+14
+</a>
+<a data-line-number="15" href="#L15" id="L15">
+<i class="fa fa-link"></i>
+15
+</a>
+<a data-line-number="16" href="#L16" id="L16">
+<i class="fa fa-link"></i>
+16
+</a>
+<a data-line-number="17" href="#L17" id="L17">
+<i class="fa fa-link"></i>
+17
+</a>
+<a data-line-number="18" href="#L18" id="L18">
+<i class="fa fa-link"></i>
+18
+</a>
+<a data-line-number="19" href="#L19" id="L19">
+<i class="fa fa-link"></i>
+19
+</a>
+<a data-line-number="20" href="#L20" id="L20">
+<i class="fa fa-link"></i>
+20
+</a>
+<a data-line-number="21" href="#L21" id="L21">
+<i class="fa fa-link"></i>
+21
+</a>
+<a data-line-number="22" href="#L22" id="L22">
+<i class="fa fa-link"></i>
+22
+</a>
+<a data-line-number="23" href="#L23" id="L23">
+<i class="fa fa-link"></i>
+23
+</a>
+<a data-line-number="24" href="#L24" id="L24">
+<i class="fa fa-link"></i>
+24
+</a>
+<a data-line-number="25" href="#L25" id="L25">
+<i class="fa fa-link"></i>
+25
+</a>
+</div>
+<pre class="code highlight"><code><span class="line" id="LC1">Line 1</span><span class="line" id="LC2">Line 2</span><span class="line" id="LC3">Line 3</span><span class="line" id="LC4">Line 4</span><span class="line" id="LC5">Line 5</span><span class="line" id="LC6">Line 6</span><span class="line" id="LC7">Line 7</span><span class="line" id="LC8">Line 8</span><span class="line" id="LC9">Line 9</span><span class="line" id="LC10">Line 10</span><span class="line" id="LC11">Line 11</span><span class="line" id="LC12">Line 12</span><span class="line" id="LC13">Line 13</span><span class="line" id="LC14">Line 14</span><span class="line" id="LC15">Line 15</span><span class="line" id="LC16">Line 16</span><span class="line" id="LC17">Line 17</span><span class="line" id="LC18">Line 18</span><span class="line" id="LC19">Line 19</span><span class="line" id="LC20">Line 20</span><span class="line" id="LC21">Line 21</span><span class="line" id="LC22">Line 22</span><span class="line" id="LC23">Line 23</span><span class="line" id="LC24">Line 24</span><span class="line" id="LC25">Line 25</span></code></pre>
+</div>
+</div>
diff --git a/spec/javascripts/fixtures/static/linked_tabs.html.raw b/spec/javascripts/fixtures/static/linked_tabs.html.raw
new file mode 100644
index 00000000000..c25463bf1db
--- /dev/null
+++ b/spec/javascripts/fixtures/static/linked_tabs.html.raw
@@ -0,0 +1,20 @@
+<ul class="nav nav-tabs new-session-tabs linked-tabs">
+<li class="nav-item">
+<a class="nav-link" data-action="tab1" data-target="div#tab1" data-toggle="tab" href="foo/bar/1">
+Tab 1
+</a>
+</li>
+<li class="nav-item">
+<a class="nav-link" data-action="tab2" data-target="div#tab2" data-toggle="tab" href="foo/bar/1/context">
+Tab 2
+</a>
+</li>
+</ul>
+<div class="tab-content">
+<div class="tab-pane" id="tab1">
+Tab 1 Content
+</div>
+<div class="tab-pane" id="tab2">
+Tab 2 Content
+</div>
+</div>
diff --git a/spec/javascripts/fixtures/static/merge_requests_show.html.raw b/spec/javascripts/fixtures/static/merge_requests_show.html.raw
new file mode 100644
index 00000000000..e219d9462aa
--- /dev/null
+++ b/spec/javascripts/fixtures/static/merge_requests_show.html.raw
@@ -0,0 +1,15 @@
+<a class="btn-close"></a>
+<div class="detail-page-description">
+<div class="description js-task-list-container">
+<div class="wiki">
+<ul class="task-list">
+<li class="task-list-item">
+<input class="task-list-item-checkbox" type="checkbox">
+Task List Item
+</li>
+</ul>
+<textarea class="js-task-list-field">- [ ] Task List Item</textarea>
+</div>
+</div>
+</div>
+<form action="/foo" class="js-issuable-update"></form>
diff --git a/spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw b/spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw
new file mode 100644
index 00000000000..cd0b8dec3fc
--- /dev/null
+++ b/spec/javascripts/fixtures/static/mini_dropdown_graph.html.raw
@@ -0,0 +1,13 @@
+<div class="js-builds-dropdown-tests dropdown dropdown js-mini-pipeline-graph">
+<button class="js-builds-dropdown-button" data-toggle="dropdown" data-stage-endpoint="foobar">
+Dropdown
+</button>
+<ul class="dropdown-menu mini-pipeline-graph-dropdown-menu js-builds-dropdown-container">
+<li class="js-builds-dropdown-list scrollable-menu">
+<ul></ul>
+</li>
+<li class="js-builds-dropdown-loading hidden">
+<span class="fa fa-spinner"></span>
+</li>
+</ul>
+</div>
diff --git a/spec/javascripts/fixtures/static/notebook_viewer.html.raw b/spec/javascripts/fixtures/static/notebook_viewer.html.raw
new file mode 100644
index 00000000000..4bbb7bf1094
--- /dev/null
+++ b/spec/javascripts/fixtures/static/notebook_viewer.html.raw
@@ -0,0 +1 @@
+<div class="file-content" data-endpoint="/test" id="js-notebook-viewer"></div>
diff --git a/spec/javascripts/fixtures/static/oauth_remember_me.html.raw b/spec/javascripts/fixtures/static/oauth_remember_me.html.raw
new file mode 100644
index 00000000000..9ba1ffc72fe
--- /dev/null
+++ b/spec/javascripts/fixtures/static/oauth_remember_me.html.raw
@@ -0,0 +1,6 @@
+<div id="oauth-container">
+<input id="remember_me" type="checkbox">
+<a class="oauth-login twitter" href="http://example.com/"></a>
+<a class="oauth-login github" href="http://example.com/"></a>
+<a class="oauth-login facebook" href="http://example.com/?redirect_fragment=L1"></a>
+</div>
diff --git a/spec/javascripts/fixtures/static/pdf_viewer.html.raw b/spec/javascripts/fixtures/static/pdf_viewer.html.raw
new file mode 100644
index 00000000000..350d35a262f
--- /dev/null
+++ b/spec/javascripts/fixtures/static/pdf_viewer.html.raw
@@ -0,0 +1 @@
+<div class="file-content" data-endpoint="/test" id="js-pdf-viewer"></div>
diff --git a/spec/javascripts/fixtures/static/pipeline_graph.html.raw b/spec/javascripts/fixtures/static/pipeline_graph.html.raw
new file mode 100644
index 00000000000..422372bb7d5
--- /dev/null
+++ b/spec/javascripts/fixtures/static/pipeline_graph.html.raw
@@ -0,0 +1,24 @@
+<div class="pipeline-visualization js-pipeline-graph">
+<ul class="stage-column-list">
+<li class="stage-column">
+<div class="stage-name">
+<a href="/">
+Test
+<div class="builds-container">
+<ul>
+<li class="build">
+<div class="curve"></div>
+<a>
+<svg></svg>
+<div class="ci-status-text">
+stop_review
+</div>
+</a>
+</li>
+</ul>
+</div>
+</a>
+</div>
+</li>
+</ul>
+</div>
diff --git a/spec/javascripts/fixtures/static/pipelines.html.raw b/spec/javascripts/fixtures/static/pipelines.html.raw
new file mode 100644
index 00000000000..42333f94f2f
--- /dev/null
+++ b/spec/javascripts/fixtures/static/pipelines.html.raw
@@ -0,0 +1,3 @@
+<div>
+<div data-can-create-pipeline="true" data-ci-lint-path="foo" data-empty-state-svg-path="foo" data-endpoint="foo" data-error-state-svg-path="foo" data-has-ci="foo" data-help-auto-devops-path="foo" data-help-page-path="foo" data-new-pipeline-path="foo" data-reset-cache-path="foo" id="pipelines-list-vue"></div>
+</div>
diff --git a/spec/javascripts/fixtures/static/project_select_combo_button.html.raw b/spec/javascripts/fixtures/static/project_select_combo_button.html.raw
new file mode 100644
index 00000000000..50c826051c0
--- /dev/null
+++ b/spec/javascripts/fixtures/static/project_select_combo_button.html.raw
@@ -0,0 +1,9 @@
+<div class="project-item-select-holder">
+<input class="project-item-select" data-group-id="12345" data-relative-path="issues/new">
+<a class="new-project-item-link" data-label="New issue" data-type="issues" href="">
+<i class="fa fa-spinner spin"></i>
+</a>
+<a class="new-project-item-select-button">
+<i class="fa fa-caret-down"></i>
+</a>
+</div>
diff --git a/spec/javascripts/fixtures/static/search_autocomplete.html.raw b/spec/javascripts/fixtures/static/search_autocomplete.html.raw
new file mode 100644
index 00000000000..29db9020424
--- /dev/null
+++ b/spec/javascripts/fixtures/static/search_autocomplete.html.raw
@@ -0,0 +1,15 @@
+<div class="search search-form">
+<form class="form-inline">
+<div class="search-input-container">
+<div class="search-input-wrap">
+<div class="dropdown">
+<input class="search-input dropdown-menu-toggle" id="search">
+<div class="dropdown-menu dropdown-select">
+<div class="dropdown-content"></div>
+</div>
+</div>
+</div>
+</div>
+<input class="js-search-project-options" type="hidden">
+</form>
+</div>
diff --git a/spec/javascripts/fixtures/static/signin_tabs.html.raw b/spec/javascripts/fixtures/static/signin_tabs.html.raw
new file mode 100644
index 00000000000..7e66ab9394b
--- /dev/null
+++ b/spec/javascripts/fixtures/static/signin_tabs.html.raw
@@ -0,0 +1,8 @@
+<ul class="nav-links new-session-tabs">
+<li class="active">
+<a href="#ldap">LDAP</a>
+</li>
+<li>
+<a href="#login-pane">Standard</a>
+</li>
+</ul>
diff --git a/spec/javascripts/fixtures/static/sketch_viewer.html.raw b/spec/javascripts/fixtures/static/sketch_viewer.html.raw
new file mode 100644
index 00000000000..e25e554e568
--- /dev/null
+++ b/spec/javascripts/fixtures/static/sketch_viewer.html.raw
@@ -0,0 +1,3 @@
+<div class="file-content" data-endpoint="/test_sketch_file.sketch" id="js-sketch-viewer">
+<div class="js-loading-icon"></div>
+</div>
diff --git a/spec/javascripts/fixtures/static_fixtures.rb b/spec/javascripts/fixtures/static_fixtures.rb
index 852a82587b9..b5188eeb994 100644
--- a/spec/javascripts/fixtures/static_fixtures.rb
+++ b/spec/javascripts/fixtures/static_fixtures.rb
@@ -3,29 +3,17 @@ require 'spec_helper'
describe ApplicationController, '(Static JavaScript fixtures)', type: :controller do
include JavaScriptFixturesHelpers
- before(:all) do
- clean_frontend_fixtures('static/')
- end
-
- JavaScriptFixturesHelpers::FIXTURE_PATHS.each do |fixture_path|
- fixtures_path = File.expand_path(fixture_path, Rails.root)
-
- Dir.glob(File.expand_path('**/*.haml', fixtures_path)).map do |file_path|
- template_file_name = file_path.sub(/\A#{fixtures_path}#{File::SEPARATOR}/, '')
-
- it "static/#{template_file_name.sub(/\.haml\z/, '.raw')}" do |example|
- fixture_file_name = example.description
- rendered = render_template(fixture_path, template_file_name)
- store_frontend_fixture(rendered, fixture_file_name)
- end
+ Dir.glob('{,ee/}spec/javascripts/fixtures/**/*.haml').map do |file_path|
+ it "static/#{file_path.sub(%r{\A(ee/)?spec/javascripts/fixtures/}, '').sub(/\.haml\z/, '.raw')}" do |example|
+ store_frontend_fixture(render_template(file_path), example.description)
end
end
private
- def render_template(fixture_path, template_file_name)
+ def render_template(template_file_name)
controller = ApplicationController.new
- controller.prepend_view_path(fixture_path)
- controller.render_to_string(template: template_file_name, layout: false)
+ controller.prepend_view_path(File.dirname(template_file_name))
+ controller.render_to_string(template: File.basename(template_file_name), layout: false)
end
end
diff --git a/spec/javascripts/frequent_items/components/app_spec.js b/spec/javascripts/frequent_items/components/app_spec.js
index b1cc4d8dc8d..6814f656f5d 100644
--- a/spec/javascripts/frequent_items/components/app_spec.js
+++ b/spec/javascripts/frequent_items/components/app_spec.js
@@ -194,7 +194,7 @@ describe('Frequent Items App Component', () => {
expect(loadingEl).toBeDefined();
expect(loadingEl.classList.contains('prepend-top-20')).toBe(true);
- expect(loadingEl.querySelector('i').getAttribute('aria-label')).toBe('Loading projects');
+ expect(loadingEl.querySelector('span').getAttribute('aria-label')).toBe('Loading projects');
done();
});
});
diff --git a/spec/javascripts/groups/components/app_spec.js b/spec/javascripts/groups/components/app_spec.js
index d832441dc93..31873311e16 100644
--- a/spec/javascripts/groups/components/app_spec.js
+++ b/spec/javascripts/groups/components/app_spec.js
@@ -502,7 +502,7 @@ describe('AppComponent', () => {
vm.isLoading = true;
Vue.nextTick(() => {
expect(vm.$el.querySelector('.loading-animation')).toBeDefined();
- expect(vm.$el.querySelector('i.fa').getAttribute('aria-label')).toBe('Loading groups');
+ expect(vm.$el.querySelector('span').getAttribute('aria-label')).toBe('Loading groups');
done();
});
});
diff --git a/spec/javascripts/helpers/filtered_search_spec_helper.js b/spec/javascripts/helpers/filtered_search_spec_helper.js
index 8933dd5def4..fd06bb1f324 100644
--- a/spec/javascripts/helpers/filtered_search_spec_helper.js
+++ b/spec/javascripts/helpers/filtered_search_spec_helper.js
@@ -5,7 +5,7 @@ export default class FilteredSearchSpecHelper {
static createFilterVisualToken(name, value, isSelected = false) {
const li = document.createElement('li');
- li.classList.add('js-visual-token', 'filtered-search-token');
+ li.classList.add('js-visual-token', 'filtered-search-token', `search-token-${name}`);
li.innerHTML = `
<div class="selectable ${isSelected ? 'selected' : ''}" role="button">
diff --git a/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js b/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js
index ffc2a4c9ddb..db1988be3e1 100644
--- a/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js
+++ b/spec/javascripts/ide/components/commit_sidebar/radio_group_spec.js
@@ -76,6 +76,7 @@ describe('IDE commit sidebar radio group', () => {
const Component = Vue.extend(radioGroup);
store.state.commit.commitAction = '1';
+ store.state.commit.newBranchName = 'test-123';
vm = createComponentWithStore(Component, store, {
value: '1',
@@ -113,6 +114,12 @@ describe('IDE commit sidebar radio group', () => {
done();
});
});
+
+ it('renders newBranchName if present', () => {
+ const input = vm.$el.querySelector('.form-control');
+
+ expect(input.value).toBe('test-123');
+ });
});
describe('tooltipTitle', () => {
diff --git a/spec/javascripts/ide/components/new_dropdown/modal_spec.js b/spec/javascripts/ide/components/new_dropdown/modal_spec.js
index d1a0964ccdd..0556feae46a 100644
--- a/spec/javascripts/ide/components/new_dropdown/modal_spec.js
+++ b/spec/javascripts/ide/components/new_dropdown/modal_spec.js
@@ -109,6 +109,18 @@ describe('new file modal component', () => {
expect(vm.entryName).toBe('index.js');
});
+
+ it('removes leading/trailing spaces when found in the new name', () => {
+ vm.entryName = ' index.js ';
+
+ expect(vm.entryName).toBe('index.js');
+ });
+
+ it('does not remove internal spaces in the file name', () => {
+ vm.entryName = ' In Praise of Idleness.txt ';
+
+ expect(vm.entryName).toBe('In Praise of Idleness.txt');
+ });
});
});
diff --git a/spec/javascripts/ide/stores/modules/commit/actions_spec.js b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
index 06b8b452319..34d97347438 100644
--- a/spec/javascripts/ide/stores/modules/commit/actions_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/actions_spec.js
@@ -396,7 +396,7 @@ describe('IDE commit module actions', () => {
.then(() => {
expect(visitUrl).toHaveBeenCalledWith(
`webUrl/merge_requests/new?merge_request[source_branch]=${
- store.getters['commit/newBranchName']
+ store.getters['commit/placeholderBranchName']
}&merge_request[target_branch]=master`,
);
diff --git a/spec/javascripts/ide/stores/modules/commit/getters_spec.js b/spec/javascripts/ide/stores/modules/commit/getters_spec.js
index 3f4bf407a1f..702e78ef773 100644
--- a/spec/javascripts/ide/stores/modules/commit/getters_spec.js
+++ b/spec/javascripts/ide/stores/modules/commit/getters_spec.js
@@ -29,11 +29,11 @@ describe('IDE commit module getters', () => {
});
});
- describe('newBranchName', () => {
+ describe('placeholderBranchName', () => {
it('includes username, currentBranchId, patch & random number', () => {
gon.current_username = 'username';
- const branch = getters.newBranchName(state, null, {
+ const branch = getters.placeholderBranchName(state, null, {
currentBranchId: 'testing',
});
@@ -46,7 +46,7 @@ describe('IDE commit module getters', () => {
currentBranchId: 'master',
};
const localGetters = {
- newBranchName: 'newBranchName',
+ placeholderBranchName: 'newBranchName',
};
beforeEach(() => {
@@ -71,7 +71,7 @@ describe('IDE commit module getters', () => {
expect(getters.branchName(state, localGetters, rootState)).toBe('state-newBranchName');
});
- it('uses getters newBranchName when state newBranchName is empty', () => {
+ it('uses placeholderBranchName when state newBranchName is empty', () => {
Object.assign(state, {
newBranchName: '',
});
diff --git a/spec/javascripts/jobs/components/stages_dropdown_spec.js b/spec/javascripts/jobs/components/stages_dropdown_spec.js
index 9c731ae2f68..eccb4e13d67 100644
--- a/spec/javascripts/jobs/components/stages_dropdown_spec.js
+++ b/spec/javascripts/jobs/components/stages_dropdown_spec.js
@@ -1,59 +1,167 @@
import Vue from 'vue';
import component from '~/jobs/components/stages_dropdown.vue';
+import { trimText } from 'spec/helpers/vue_component_helper';
import mountComponent from '../../helpers/vue_mount_component_helper';
describe('Stages Dropdown', () => {
const Component = Vue.extend(component);
let vm;
- beforeEach(() => {
- vm = mountComponent(Component, {
- pipeline: {
- id: 28029444,
- details: {
- status: {
- details_path: '/gitlab-org/gitlab-ce/pipelines/28029444',
- group: 'success',
- has_details: true,
- icon: 'status_success',
- label: 'passed',
- text: 'passed',
- tooltip: 'passed',
- },
- },
- path: 'pipeline/28029444',
+ const mockPipelineData = {
+ id: 28029444,
+ details: {
+ status: {
+ details_path: '/gitlab-org/gitlab-ce/pipelines/28029444',
+ group: 'success',
+ has_details: true,
+ icon: 'status_success',
+ label: 'passed',
+ text: 'passed',
+ tooltip: 'passed',
},
- stages: [
- {
- name: 'build',
- },
- {
- name: 'test',
- },
- ],
- selectedStage: 'deploy',
+ },
+ path: 'pipeline/28029444',
+ flags: {
+ merge_request_pipeline: true,
+ detached_merge_request_pipeline: false,
+ },
+ merge_request: {
+ iid: 1234,
+ path: '/root/detached-merge-request-pipelines/merge_requests/1',
+ title: 'Update README.md',
+ source_branch: 'feature-1234',
+ source_branch_path: '/root/detached-merge-request-pipelines/branches/feature-1234',
+ target_branch: 'master',
+ target_branch_path: '/root/detached-merge-request-pipelines/branches/master',
+ },
+ ref: {
+ name: 'test-branch',
+ },
+ };
+
+ describe('without a merge request pipeline', () => {
+ let pipeline;
+
+ beforeEach(() => {
+ pipeline = JSON.parse(JSON.stringify(mockPipelineData));
+ delete pipeline.merge_request;
+ delete pipeline.flags.merge_request_pipeline;
+ delete pipeline.flags.detached_merge_request_pipeline;
+
+ vm = mountComponent(Component, {
+ pipeline,
+ stages: [{ name: 'build' }, { name: 'test' }],
+ selectedStage: 'deploy',
+ });
});
- });
- afterEach(() => {
- vm.$destroy();
- });
+ afterEach(() => {
+ vm.$destroy();
+ });
- it('renders pipeline status', () => {
- expect(vm.$el.querySelector('.js-ci-status-icon-success')).not.toBeNull();
- });
+ it('renders pipeline status', () => {
+ expect(vm.$el.querySelector('.js-ci-status-icon-success')).not.toBeNull();
+ });
+
+ it('renders pipeline link', () => {
+ expect(vm.$el.querySelector('.js-pipeline-path').getAttribute('href')).toEqual(
+ 'pipeline/28029444',
+ );
+ });
+
+ it('renders dropdown with stages', () => {
+ expect(vm.$el.querySelector('.dropdown .js-stage-item').textContent).toContain('build');
+ });
+
+ it('rendes selected stage', () => {
+ expect(vm.$el.querySelector('.dropdown .js-selected-stage').textContent).toContain('deploy');
+ });
- it('renders pipeline link', () => {
- expect(vm.$el.querySelector('.js-pipeline-path').getAttribute('href')).toEqual(
- 'pipeline/28029444',
- );
+ 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);
+ });
});
- it('renders dropdown with stages', () => {
- expect(vm.$el.querySelector('.dropdown .js-stage-item').textContent).toContain('build');
+ describe('with an "attached" merge request pipeline', () => {
+ let pipeline;
+
+ beforeEach(() => {
+ pipeline = JSON.parse(JSON.stringify(mockPipelineData));
+ pipeline.flags.merge_request_pipeline = true;
+ pipeline.flags.detached_merge_request_pipeline = false;
+
+ vm = mountComponent(Component, {
+ pipeline,
+ stages: [],
+ selectedStage: 'deploy',
+ });
+ });
+
+ 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);
+ });
+
+ it(`renders the correct merge request link`, () => {
+ const actual = vm.$el.querySelector('.js-mr-link').href;
+
+ expect(actual).toContain(pipeline.merge_request.path);
+ });
+
+ it(`renders the correct source branch link`, () => {
+ const actual = vm.$el.querySelector('.js-source-branch-link').href;
+
+ expect(actual).toContain(pipeline.merge_request.source_branch_path);
+ });
+
+ it(`renders the correct target branch link`, () => {
+ const actual = vm.$el.querySelector('.js-target-branch-link').href;
+
+ expect(actual).toContain(pipeline.merge_request.target_branch_path);
+ });
});
- it('rendes selected stage', () => {
- expect(vm.$el.querySelector('.dropdown .js-selected-stage').textContent).toContain('deploy');
+ describe('with a detached merge request pipeline', () => {
+ let pipeline;
+
+ beforeEach(() => {
+ pipeline = JSON.parse(JSON.stringify(mockPipelineData));
+ pipeline.flags.merge_request_pipeline = false;
+ pipeline.flags.detached_merge_request_pipeline = true;
+
+ vm = mountComponent(Component, {
+ pipeline,
+ stages: [],
+ selectedStage: 'deploy',
+ });
+ });
+
+ 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);
+ });
+
+ it(`renders the correct merge request link`, () => {
+ const actual = vm.$el.querySelector('.js-mr-link').href;
+
+ expect(actual).toContain(pipeline.merge_request.path);
+ });
+
+ it(`renders the correct source branch link`, () => {
+ const actual = vm.$el.querySelector('.js-source-branch-link').href;
+
+ expect(actual).toContain(pipeline.merge_request.source_branch_path);
+ });
});
});
diff --git a/spec/javascripts/notes/components/note_form_spec.js b/spec/javascripts/notes/components/note_form_spec.js
index 7cc324cfe44..c48f8188105 100644
--- a/spec/javascripts/notes/components/note_form_spec.js
+++ b/spec/javascripts/notes/components/note_form_spec.js
@@ -5,11 +5,33 @@ import MarkdownField from '~/vue_shared/components/markdown/field.vue';
import { noteableDataMock, notesDataMock } from '../mock_data';
describe('issue_note_form component', () => {
+ const dummyAutosaveKey = 'some-autosave-key';
+ const dummyDraft = 'dummy draft content';
+
let store;
let wrapper;
let props;
+ const createComponentWrapper = () => {
+ const localVue = createLocalVue();
+ return shallowMount(NoteForm, {
+ store,
+ propsData: props,
+ // see https://gitlab.com/gitlab-org/gitlab-ce/issues/56317 for the following
+ localVue,
+ sync: false,
+ });
+ };
+
beforeEach(() => {
+ spyOnDependency(NoteForm, 'getDraft').and.callFake(key => {
+ if (key === dummyAutosaveKey) {
+ return dummyDraft;
+ }
+
+ return null;
+ });
+
store = createStore();
store.dispatch('setNoteableData', noteableDataMock);
store.dispatch('setNotesData', notesDataMock);
@@ -20,14 +42,7 @@ describe('issue_note_form component', () => {
noteId: '545',
};
- const localVue = createLocalVue();
- wrapper = shallowMount(NoteForm, {
- store,
- propsData: props,
- // see https://gitlab.com/gitlab-org/gitlab-ce/issues/56317 for the following
- localVue,
- sync: false,
- });
+ wrapper = createComponentWrapper();
});
afterEach(() => {
@@ -181,4 +196,67 @@ describe('issue_note_form component', () => {
});
});
});
+
+ describe('with autosaveKey', () => {
+ beforeEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('with draft', () => {
+ beforeEach(done => {
+ Object.assign(props, {
+ noteBody: '',
+ autosaveKey: dummyAutosaveKey,
+ });
+ wrapper = createComponentWrapper();
+
+ wrapper.vm
+ .$nextTick()
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('displays the draft in textarea', () => {
+ const textarea = wrapper.find('textarea');
+
+ expect(textarea.element.value).toBe(dummyDraft);
+ });
+ });
+
+ describe('without draft', () => {
+ beforeEach(done => {
+ Object.assign(props, {
+ noteBody: '',
+ autosaveKey: 'some key without draft',
+ });
+ wrapper = createComponentWrapper();
+
+ wrapper.vm
+ .$nextTick()
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('leaves the textarea empty', () => {
+ const textarea = wrapper.find('textarea');
+
+ expect(textarea.element.value).toBe('');
+ });
+ });
+
+ it('updates the draft if textarea content changes', () => {
+ const updateDraftSpy = spyOnDependency(NoteForm, 'updateDraft').and.stub();
+ Object.assign(props, {
+ noteBody: '',
+ autosaveKey: dummyAutosaveKey,
+ });
+ wrapper = createComponentWrapper();
+ const textarea = wrapper.find('textarea');
+ const dummyContent = 'some new content';
+
+ textarea.setValue(dummyContent);
+
+ expect(updateDraftSpy).toHaveBeenCalledWith(dummyAutosaveKey, dummyContent);
+ });
+ });
});
diff --git a/spec/javascripts/notes/components/noteable_discussion_spec.js b/spec/javascripts/notes/components/noteable_discussion_spec.js
index 2b93fb9fb45..3304c79cdb7 100644
--- a/spec/javascripts/notes/components/noteable_discussion_spec.js
+++ b/spec/javascripts/notes/components/noteable_discussion_spec.js
@@ -3,6 +3,7 @@ import createStore from '~/notes/stores';
import noteableDiscussion from '~/notes/components/noteable_discussion.vue';
import ReplyPlaceholder from '~/notes/components/discussion_reply_placeholder.vue';
import ResolveWithIssueButton from '~/notes/components/discussion_resolve_with_issue_button.vue';
+import NoteForm from '~/notes/components/note_form.vue';
import '~/behaviors/markdown/render_gfm';
import { noteableDataMock, discussionMock, notesDataMock } from '../mock_data';
import mockDiffFile from '../../diffs/mock_data/diff_file';
@@ -72,7 +73,18 @@ describe('noteable_discussion component', () => {
.then(() => wrapper.vm.$nextTick())
.then(() => {
expect(wrapper.vm.isReplying).toEqual(true);
- expect(wrapper.vm.$refs.noteForm).not.toBeNull();
+
+ const noteForm = wrapper.find(NoteForm);
+
+ expect(noteForm.exists()).toBe(true);
+
+ const noteFormProps = noteForm.props();
+
+ expect(noteFormProps.discussion).toBe(discussionMock);
+ expect(noteFormProps.isEditing).toBe(false);
+ expect(noteFormProps.line).toBe(null);
+ expect(noteFormProps.saveButtonTitle).toBe('Comment');
+ expect(noteFormProps.autosaveKey).toBe(`Note/Issue/${discussionMock.id}/Reply`);
})
.then(done)
.catch(done.fail);
diff --git a/spec/javascripts/pipelines/graph/stage_column_component_spec.js b/spec/javascripts/pipelines/graph/stage_column_component_spec.js
index d0b8f877d6f..dafb892da43 100644
--- a/spec/javascripts/pipelines/graph/stage_column_component_spec.js
+++ b/spec/javascripts/pipelines/graph/stage_column_component_spec.js
@@ -35,6 +35,7 @@ describe('stage column component', () => {
component = mountComponent(StageColumnComponent, {
title: 'foo',
groups: mockGroups,
+ hasTriggeredBy: false,
});
});
@@ -61,6 +62,7 @@ describe('stage column component', () => {
},
],
title: 'test',
+ hasTriggeredBy: false,
});
expect(component.$el.querySelector('.builds-container li').getAttribute('id')).toEqual(
diff --git a/spec/javascripts/pipelines/pipeline_url_spec.js b/spec/javascripts/pipelines/pipeline_url_spec.js
index ea917b36526..faad49a78b0 100644
--- a/spec/javascripts/pipelines/pipeline_url_spec.js
+++ b/spec/javascripts/pipelines/pipeline_url_spec.js
@@ -100,7 +100,8 @@ describe('Pipeline Url Component', () => {
latest: true,
yaml_errors: true,
stuck: true,
- merge_request: true,
+ merge_request_pipeline: true,
+ detached_merge_request_pipeline: true,
},
},
autoDevopsHelpPath: 'foo',
@@ -108,15 +109,16 @@ describe('Pipeline Url Component', () => {
}).$mount();
expect(component.$el.querySelector('.js-pipeline-url-latest').textContent).toContain('latest');
+
expect(component.$el.querySelector('.js-pipeline-url-yaml').textContent).toContain(
'yaml invalid',
);
- expect(component.$el.querySelector('.js-pipeline-url-mergerequest').textContent).toContain(
- 'merge request',
- );
-
expect(component.$el.querySelector('.js-pipeline-url-stuck').textContent).toContain('stuck');
+
+ expect(component.$el.querySelector('.js-pipeline-url-detached').textContent).toContain(
+ 'detached',
+ );
});
it('should render a badge for autodevops', () => {
diff --git a/spec/javascripts/registry/components/app_spec.js b/spec/javascripts/registry/components/app_spec.js
index 67118ac03a5..76a17e6fb31 100644
--- a/spec/javascripts/registry/components/app_spec.js
+++ b/spec/javascripts/registry/components/app_spec.js
@@ -99,7 +99,7 @@ describe('Registry List', () => {
it('should render a loading spinner', done => {
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.fa-spinner')).not.toBe(null);
+ expect(vm.$el.querySelector('.spinner')).not.toBe(null);
done();
});
});
diff --git a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
index 69767d9cf1c..a17494966a3 100644
--- a/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
+++ b/spec/javascripts/reports/components/grouped_test_reports_app_spec.js
@@ -61,7 +61,7 @@ describe('Grouped Test Reports App', () => {
it('renders success summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.fa-spinner')).not.toBeNull();
+ expect(vm.$el.querySelector('.spinner')).not.toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary results are being parsed',
);
@@ -81,7 +81,7 @@ describe('Grouped Test Reports App', () => {
it('renders failed summary text + new badge', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.fa-spinner')).toBeNull();
+ expect(vm.$el.querySelector('.spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained 2 failed test results out of 11 total tests',
);
@@ -109,7 +109,7 @@ describe('Grouped Test Reports App', () => {
it('renders summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.fa-spinner')).toBeNull();
+ expect(vm.$el.querySelector('.spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained 2 failed test results and 2 fixed test results out of 11 total tests',
);
@@ -137,7 +137,7 @@ describe('Grouped Test Reports App', () => {
it('renders summary text', done => {
setTimeout(() => {
- expect(vm.$el.querySelector('.fa-spinner')).toBeNull();
+ expect(vm.$el.querySelector('.spinner')).toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary contained 2 fixed test results out of 11 total tests',
);
@@ -190,7 +190,7 @@ describe('Grouped Test Reports App', () => {
});
it('renders loading summary text with loading icon', done => {
- expect(vm.$el.querySelector('.fa-spinner')).not.toBeNull();
+ expect(vm.$el.querySelector('.spinner')).not.toBeNull();
expect(vm.$el.querySelector('.js-code-text').textContent.trim()).toEqual(
'Test summary results are being parsed',
);
diff --git a/spec/javascripts/test_bundle.js b/spec/javascripts/test_bundle.js
index 5eef5682bbd..235a17d13b0 100644
--- a/spec/javascripts/test_bundle.js
+++ b/spec/javascripts/test_bundle.js
@@ -69,7 +69,7 @@ window.gl = window.gl || {};
window.gl.TEST_HOST = TEST_HOST;
window.gon = window.gon || {};
window.gon.test_env = true;
-window.gon.ee = false;
+window.gon.ee = process.env.EE;
gon.relative_url_root = '';
let hasUnhandledPromiseRejections = false;
@@ -122,19 +122,26 @@ afterEach(() => {
const axiosDefaultAdapter = getDefaultAdapter();
// render all of our tests
-const testsContext = require.context('.', true, /_spec$/);
-testsContext.keys().forEach(function(path) {
- try {
- testsContext(path);
- } catch (err) {
- console.log(err);
- console.error('[GL SPEC RUNNER ERROR] Unable to load spec: ', path);
- describe('Test bundle', function() {
- it(`includes '${path}'`, function() {
- expect(err).toBeNull();
+const testContexts = [require.context('spec', true, /_spec$/)];
+
+if (process.env.EE) {
+ testContexts.push(require.context('ee_spec', true, /_spec$/));
+}
+
+testContexts.forEach(context => {
+ context.keys().forEach(path => {
+ try {
+ context(path);
+ } catch (err) {
+ console.log(err);
+ console.error('[GL SPEC RUNNER ERROR] Unable to load spec: ', path);
+ describe('Test bundle', function() {
+ it(`includes '${path}'`, function() {
+ expect(err).toBeNull();
+ });
});
- });
- }
+ }
+ });
});
describe('test errors', () => {
@@ -204,24 +211,35 @@ if (process.env.BABEL_ENV === 'coverage') {
];
describe('Uncovered files', function() {
- const sourceFiles = require.context('~', true, /\.(js|vue)$/);
+ const sourceFilesContexts = [require.context('~', true, /\.(js|vue)$/)];
+
+ if (process.env.EE) {
+ sourceFilesContexts.push(require.context('ee', true, /\.(js|vue)$/));
+ }
+
+ const allTestFiles = testContexts.reduce(
+ (accumulator, context) => accumulator.concat(context.keys()),
+ [],
+ );
$.holdReady(true);
- sourceFiles.keys().forEach(function(path) {
- // ignore if there is a matching spec file
- if (testsContext.keys().indexOf(`${path.replace(/\.(js|vue)$/, '')}_spec`) > -1) {
- return;
- }
-
- it(`includes '${path}'`, function() {
- try {
- sourceFiles(path);
- } catch (err) {
- if (troubleMakers.indexOf(path) === -1) {
- expect(err).toBeNull();
- }
+ sourceFilesContexts.forEach(context => {
+ context.keys().forEach(path => {
+ // ignore if there is a matching spec file
+ if (allTestFiles.indexOf(`${path.replace(/\.(js|vue)$/, '')}_spec`) > -1) {
+ return;
}
+
+ it(`includes '${path}'`, function() {
+ try {
+ context(path);
+ } catch (err) {
+ if (troubleMakers.indexOf(path) === -1) {
+ expect(err).toBeNull();
+ }
+ }
+ });
});
});
});
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 d905bbe4040..de213210cfc 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
@@ -1,6 +1,7 @@
import Vue from 'vue';
import pipelineComponent from '~/vue_merge_request_widget/components/mr_widget_pipeline.vue';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
+import { trimText } from 'spec/helpers/vue_component_helper';
import mockData from '../mock_data';
describe('MRWidgetPipeline', () => {
@@ -123,7 +124,7 @@ describe('MRWidgetPipeline', () => {
describe('without commit path', () => {
beforeEach(() => {
- const mockCopy = Object.assign({}, mockData);
+ const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.commit;
vm = mountComponent(Component, {
@@ -164,7 +165,7 @@ describe('MRWidgetPipeline', () => {
describe('without coverage', () => {
it('should not render a coverage', () => {
- const mockCopy = Object.assign({}, mockData);
+ const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.coverage;
vm = mountComponent(Component, {
@@ -180,7 +181,7 @@ describe('MRWidgetPipeline', () => {
describe('without a pipeline graph', () => {
it('should not render a pipeline graph', () => {
- const mockCopy = Object.assign({}, mockData);
+ const mockCopy = JSON.parse(JSON.stringify(mockData));
delete mockCopy.pipeline.details.stages;
vm = mountComponent(Component, {
@@ -193,5 +194,81 @@ describe('MRWidgetPipeline', () => {
expect(vm.$el.querySelector('.js-mini-pipeline-graph')).toEqual(null);
});
});
+
+ describe('without pipeline.merge_request', () => {
+ it('should render info that includes the commit and branch details', () => {
+ const mockCopy = JSON.parse(JSON.stringify(mockData));
+ delete mockCopy.pipeline.merge_request;
+ const { pipeline } = mockCopy;
+
+ vm = mountComponent(Component, {
+ pipeline,
+ hasCi: true,
+ ciStatus: 'success',
+ troubleshootingDocsPath: 'help',
+ sourceBranchLink: 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);
+
+ expect(actual).toBe(expected);
+ });
+ });
+
+ describe('with pipeline.merge_request and flags.merge_request_pipeline', () => {
+ it('should render info that includes the commit, MR, source branch, and target branch details', () => {
+ const mockCopy = JSON.parse(JSON.stringify(mockData));
+ const { pipeline } = mockCopy;
+ pipeline.flags.merge_request_pipeline = true;
+ pipeline.flags.detached_merge_request_pipeline = false;
+
+ vm = mountComponent(Component, {
+ pipeline,
+ hasCi: true,
+ ciStatus: 'success',
+ troubleshootingDocsPath: 'help',
+ sourceBranchLink: mockCopy.source_branch_link,
+ });
+
+ 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);
+
+ expect(actual).toBe(expected);
+ });
+ });
+
+ describe('with pipeline.merge_request and flags.detached_merge_request_pipeline', () => {
+ it('should render info that includes the commit, MR, and source branch details', () => {
+ const mockCopy = JSON.parse(JSON.stringify(mockData));
+ const { pipeline } = mockCopy;
+ pipeline.flags.merge_request_pipeline = false;
+ pipeline.flags.detached_merge_request_pipeline = true;
+
+ vm = mountComponent(Component, {
+ pipeline,
+ hasCi: true,
+ ciStatus: 'success',
+ troubleshootingDocsPath: 'help',
+ sourceBranchLink: mockCopy.source_branch_link,
+ });
+
+ 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);
+
+ expect(actual).toBe(expected);
+ });
+ });
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js b/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
index a0a336ae604..f622f52a7b9 100644
--- a/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/mr_widget_status_icon_spec.js
@@ -18,7 +18,7 @@ describe('MR widget status icon component', () => {
it('renders loading icon', () => {
vm = mountComponent(Component, { status: 'loading' });
- expect(vm.$el.querySelector('.mr-widget-icon i').classList).toContain('fa-spinner');
+ expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('spinner');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
index eb4fa0df727..d93badf8cd3 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_auto_merge_failed_spec.js
@@ -38,7 +38,7 @@ describe('MRWidgetAutoMergeFailed', () => {
Vue.nextTick(() => {
expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
- expect(vm.$el.querySelector('button i').classList).toContain('fa-spinner');
+ expect(vm.$el.querySelector('button .loading-container span').classList).toContain('spinner');
done();
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
index 7da27bb8890..96e512d222a 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
@@ -20,7 +20,7 @@ describe('MRWidgetChecking', () => {
});
it('renders loading icon', () => {
- expect(vm.$el.querySelector('.mr-widget-icon i').classList).toContain('fa-spinner');
+ expect(vm.$el.querySelector('.mr-widget-icon span').classList).toContain('spinner');
});
it('renders information about merging', () => {
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index 08e173b0a10..30659ad16f3 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -18,6 +18,7 @@ const createTestMr = customConfig => {
isPipelinePassing: false,
isMergeAllowed: true,
onlyAllowMergeIfPipelineSucceeds: false,
+ ffOnlyEnabled: false,
hasCI: false,
ciStatus: null,
sha: '12345678',
@@ -376,11 +377,29 @@ describe('ReadyToMerge', () => {
});
describe('initiateMergePolling', () => {
+ beforeEach(() => {
+ jasmine.clock().install();
+ });
+
+ afterEach(() => {
+ jasmine.clock().uninstall();
+ });
+
it('should call simplePoll', () => {
const simplePoll = spyOnDependency(ReadyToMerge, 'simplePoll');
vm.initiateMergePolling();
- expect(simplePoll).toHaveBeenCalled();
+ expect(simplePoll).toHaveBeenCalledWith(jasmine.any(Function), { timeout: 0 });
+ });
+
+ it('should call handleMergePolling', () => {
+ spyOn(vm, 'handleMergePolling');
+
+ vm.initiateMergePolling();
+
+ jasmine.clock().tick(2000);
+
+ expect(vm.handleMergePolling).toHaveBeenCalled();
});
});
@@ -624,6 +643,10 @@ describe('ReadyToMerge', () => {
const findCommitsHeaderElement = () => wrapper.find(CommitsHeader);
const findCommitEditElements = () => wrapper.findAll(CommitEdit);
const findCommitDropdownElement = () => wrapper.find(CommitMessageDropdown);
+ const findFirstCommitEditLabel = () =>
+ findCommitEditElements()
+ .at(0)
+ .props('label');
describe('squash checkbox', () => {
it('should be rendered when squash before merge is enabled and there is more than 1 commit', () => {
@@ -648,31 +671,129 @@ describe('ReadyToMerge', () => {
});
describe('commits count collapsible header', () => {
- it('should be rendered if fast-forward is disabled', () => {
+ it('should be rendered when fast-forward is disabled', () => {
createLocalComponent();
expect(findCommitsHeaderElement().exists()).toBeTruthy();
});
- it('should not be rendered if fast-forward is enabled', () => {
- createLocalComponent({ mr: { ffOnlyEnabled: true } });
+ describe('when fast-forward is enabled', () => {
+ it('should be rendered if squash and squash before are enabled and there is more than 1 commit', () => {
+ createLocalComponent({
+ mr: {
+ ffOnlyEnabled: true,
+ enableSquashBeforeMerge: true,
+ squash: true,
+ commitsCount: 2,
+ },
+ });
+
+ expect(findCommitsHeaderElement().exists()).toBeTruthy();
+ });
+
+ it('should not be rendered if squash before merge is disabled', () => {
+ createLocalComponent({
+ mr: {
+ ffOnlyEnabled: true,
+ enableSquashBeforeMerge: false,
+ squash: true,
+ commitsCount: 2,
+ },
+ });
+
+ expect(findCommitsHeaderElement().exists()).toBeFalsy();
+ });
+
+ it('should not be rendered if squash is disabled', () => {
+ createLocalComponent({
+ mr: {
+ ffOnlyEnabled: true,
+ squash: false,
+ enableSquashBeforeMerge: true,
+ commitsCount: 2,
+ },
+ });
+
+ expect(findCommitsHeaderElement().exists()).toBeFalsy();
+ });
+
+ it('should not be rendered if commits count is 1', () => {
+ createLocalComponent({
+ mr: {
+ ffOnlyEnabled: true,
+ squash: true,
+ enableSquashBeforeMerge: true,
+ commitsCount: 1,
+ },
+ });
- expect(findCommitsHeaderElement().exists()).toBeFalsy();
+ expect(findCommitsHeaderElement().exists()).toBeFalsy();
+ });
});
});
describe('commits edit components', () => {
+ describe('when fast-forward merge is enabled', () => {
+ it('should not be rendered if squash is disabled', () => {
+ createLocalComponent({
+ mr: {
+ ffOnlyEnabled: true,
+ squash: false,
+ enableSquashBeforeMerge: true,
+ commitsCount: 2,
+ },
+ });
+
+ expect(findCommitEditElements().length).toBe(0);
+ });
+
+ it('should not be rendered if squash before merge is disabled', () => {
+ createLocalComponent({
+ mr: {
+ ffOnlyEnabled: true,
+ squash: true,
+ enableSquashBeforeMerge: false,
+ commitsCount: 2,
+ },
+ });
+
+ expect(findCommitEditElements().length).toBe(0);
+ });
+
+ it('should not be rendered if there is only one commit', () => {
+ createLocalComponent({
+ mr: {
+ ffOnlyEnabled: true,
+ squash: true,
+ enableSquashBeforeMerge: true,
+ commitsCount: 1,
+ },
+ });
+
+ expect(findCommitEditElements().length).toBe(0);
+ });
+
+ it('should have one edit component if squash is enabled and there is more than 1 commit', () => {
+ createLocalComponent({
+ mr: {
+ ffOnlyEnabled: true,
+ squash: true,
+ enableSquashBeforeMerge: true,
+ commitsCount: 2,
+ },
+ });
+
+ expect(findCommitEditElements().length).toBe(1);
+ expect(findFirstCommitEditLabel()).toBe('Squash commit message');
+ });
+ });
+
it('should have one edit component when squash is disabled', () => {
createLocalComponent();
expect(findCommitEditElements().length).toBe(1);
});
- const findFirstCommitEditLabel = () =>
- findCommitEditElements()
- .at(0)
- .props('label');
-
it('should have two edit components when squash is enabled and there is more than 1 commit', () => {
createLocalComponent({
mr: {
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index 6ef07f81705..7ab203a6011 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -134,6 +134,8 @@ export default {
yaml_errors: false,
retryable: true,
cancelable: false,
+ merge_request_pipeline: false,
+ detached_merge_request_pipeline: true,
},
ref: {
name: 'daaaa',
@@ -141,6 +143,15 @@ export default {
tag: false,
branch: true,
},
+ merge_request: {
+ iid: 1,
+ path: '/root/detached-merge-request-pipelines/merge_requests/1',
+ title: 'Update README.md',
+ source_branch: 'feature-1',
+ source_branch_path: '/root/detached-merge-request-pipelines/branches/feature-1',
+ target_branch: 'master',
+ target_branch_path: '/root/detached-merge-request-pipelines/branches/master',
+ },
commit: {
id: '104096c51715e12e7ae41f9333e9fa35b73f385d',
short_id: '104096c5',
diff --git a/spec/javascripts/vue_shared/components/commit_spec.js b/spec/javascripts/vue_shared/components/commit_spec.js
index 18fcdf7ede1..f2e20f626b5 100644
--- a/spec/javascripts/vue_shared/components/commit_spec.js
+++ b/spec/javascripts/vue_shared/components/commit_spec.js
@@ -61,7 +61,7 @@ describe('Commit component', () => {
});
it('should render a tag icon if it represents a tag', () => {
- expect(component.$el.querySelector('.icon-container i').classList).toContain('fa-tag');
+ expect(component.$el.querySelector('.icon-container svg.ic-tag')).not.toBeNull();
});
it('should render a link to the ref url', () => {
@@ -143,4 +143,92 @@ describe('Commit component', () => {
);
});
});
+
+ describe('When commit ref is provided, but merge ref is not', () => {
+ it('should render the commit ref', () => {
+ props = {
+ tag: false,
+ commitRef: {
+ name: 'master',
+ ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
+ },
+ commitUrl:
+ 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
+ shortSha: 'b7836edd',
+ title: null,
+ author: {},
+ };
+
+ component = mountComponent(CommitComponent, props);
+ const refEl = component.$el.querySelector('.ref-name');
+
+ expect(refEl.textContent).toContain('master');
+
+ expect(refEl.href).toBe(props.commitRef.ref_url);
+
+ expect(refEl.getAttribute('data-original-title')).toBe(props.commitRef.name);
+
+ expect(component.$el.querySelector('.icon-container .ic-branch')).not.toBeNull();
+ });
+ });
+
+ describe('When both commit and merge ref are provided', () => {
+ it('should render the merge ref', () => {
+ props = {
+ tag: false,
+ commitRef: {
+ name: 'master',
+ ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
+ },
+ commitUrl:
+ 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
+ mergeRequestRef: {
+ iid: 1234,
+ path: 'https://example.com/path/to/mr',
+ title: 'Test MR',
+ },
+ shortSha: 'b7836edd',
+ title: null,
+ author: {},
+ };
+
+ component = mountComponent(CommitComponent, props);
+ const refEl = component.$el.querySelector('.ref-name');
+
+ expect(refEl.textContent).toContain('1234');
+
+ expect(refEl.href).toBe(props.mergeRequestRef.path);
+
+ expect(refEl.getAttribute('data-original-title')).toBe(props.mergeRequestRef.title);
+
+ expect(component.$el.querySelector('.icon-container .ic-git-merge')).not.toBeNull();
+ });
+ });
+
+ describe('When showRefInfo === false', () => {
+ it('should not render any ref info', () => {
+ props = {
+ tag: false,
+ commitRef: {
+ name: 'master',
+ ref_url: 'http://localhost/namespace2/gitlabhq/tree/master',
+ },
+ commitUrl:
+ 'https://gitlab.com/gitlab-org/gitlab-ce/commit/b7836eddf62d663c665769e1b0960197fd215067',
+ mergeRequestRef: {
+ iid: 1234,
+ path: '/path/to/mr',
+ title: 'Test MR',
+ },
+ shortSha: 'b7836edd',
+ title: null,
+ author: {},
+ showRefInfo: false,
+ };
+
+ component = mountComponent(CommitComponent, props);
+
+ expect(component.$el.querySelector('.ref-name')).toBeNull();
+ });
+ });
});
diff --git a/spec/javascripts/vue_shared/components/file_icon_spec.js b/spec/javascripts/vue_shared/components/file_icon_spec.js
index 34c9b35e02a..5bea8c43da3 100644
--- a/spec/javascripts/vue_shared/components/file_icon_spec.js
+++ b/spec/javascripts/vue_shared/components/file_icon_spec.js
@@ -70,12 +70,9 @@ describe('File Icon component', () => {
loading: true,
});
- const { classList } = vm.$el.querySelector('i');
+ const { classList } = vm.$el.querySelector('.loading-container span');
- expect(classList.contains('fa')).toEqual(true);
- expect(classList.contains('fa-spin')).toEqual(true);
- expect(classList.contains('fa-spinner')).toEqual(true);
- expect(classList.contains('fa-1x')).toEqual(true);
+ expect(classList.contains('spinner')).toEqual(true);
});
it('should add a special class and a size class', () => {
diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
index 7a741bdc067..a9c1a67b39b 100644
--- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js
+++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js
@@ -88,7 +88,7 @@ describe('Header CI Component', () => {
vm.actions[0].isLoading = true;
Vue.nextTick(() => {
- expect(vm.$el.querySelector('.btn .fa-spinner').getAttribute('style')).toBeFalsy();
+ expect(vm.$el.querySelector('.btn .spinner').getAttribute('style')).toBeFalsy();
done();
});
});
diff --git a/spec/lib/backup/uploads_spec.rb b/spec/lib/backup/uploads_spec.rb
new file mode 100644
index 00000000000..544d3754c0f
--- /dev/null
+++ b/spec/lib/backup/uploads_spec.rb
@@ -0,0 +1,18 @@
+require 'spec_helper'
+
+describe Backup::Uploads do
+ let(:progress) { StringIO.new }
+ subject(:backup) { described_class.new(progress) }
+
+ describe '#initialize' do
+ it 'uses the correct upload dir' do
+ Dir.mktmpdir do |tmpdir|
+ FileUtils.mkdir_p("#{tmpdir}/uploads")
+
+ allow(Gitlab.config.uploads).to receive(:storage_path) { tmpdir }
+
+ expect(backup.app_files_dir).to eq("#{tmpdir}/uploads")
+ end
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/output_safety_spec.rb b/spec/lib/banzai/filter/output_safety_spec.rb
new file mode 100644
index 00000000000..5ffe591c9a4
--- /dev/null
+++ b/spec/lib/banzai/filter/output_safety_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Banzai::Filter::OutputSafety do
+ subject do
+ Class.new do
+ include Banzai::Filter::OutputSafety
+ end.new
+ end
+
+ let(:content) { '<pre><code>foo</code></pre>' }
+
+ context 'when given HTML is safe' do
+ let(:html) { content.html_safe }
+
+ it 'returns safe HTML' do
+ expect(subject.escape_once(html)).to eq(html)
+ end
+ end
+
+ context 'when given HTML is not safe' do
+ let(:html) { content }
+
+ it 'returns escaped HTML' do
+ expect(subject.escape_once(html)).to eq(ERB::Util.html_escape_once(html))
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/suggestion_filter_spec.rb b/spec/lib/banzai/filter/suggestion_filter_spec.rb
index b13c90b54bd..af6f002fa30 100644
--- a/spec/lib/banzai/filter/suggestion_filter_spec.rb
+++ b/spec/lib/banzai/filter/suggestion_filter_spec.rb
@@ -5,7 +5,7 @@ require 'spec_helper'
describe Banzai::Filter::SuggestionFilter do
include FilterSpecHelper
- let(:input) { "<pre class='code highlight js-syntax-highlight suggestion'><code>foo\n</code></pre>" }
+ let(:input) { %(<pre class="code highlight js-syntax-highlight suggestion"><code>foo\n</code></pre>) }
let(:default_context) do
{ suggestions_filter_enabled: true }
end
@@ -23,4 +23,35 @@ describe Banzai::Filter::SuggestionFilter do
expect(result[:class]).to be_nil
end
+
+ context 'multi-line suggestions' do
+ let(:data_attr) { Banzai::Filter::SyntaxHighlightFilter::LANG_PARAMS_ATTR }
+ let(:input) { %(<pre class="code highlight js-syntax-highlight suggestion" #{data_attr}="-3+2"><code>foo\n</code></pre>) }
+
+ context 'feature disabled' do
+ before do
+ stub_feature_flags(multi_line_suggestions: false)
+ end
+
+ it 'removes data-lang-params if it matches a multi-line suggestion param' do
+ doc = filter(input, default_context)
+ pre = doc.css('pre').first
+
+ expect(pre[data_attr]).to be_nil
+ end
+ end
+
+ context 'feature enabled' do
+ before do
+ stub_feature_flags(multi_line_suggestions: true)
+ end
+
+ it 'keeps data-lang-params' do
+ doc = filter(input, default_context)
+ pre = doc.css('pre').first
+
+ expect(pre[data_attr]).to eq('-3+2')
+ end
+ end
+ end
end
diff --git a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
index ef52c572898..05057789cc1 100644
--- a/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
+++ b/spec/lib/banzai/filter/syntax_highlight_filter_spec.rb
@@ -45,7 +45,10 @@ describe Banzai::Filter::SyntaxHighlightFilter do
end
context "languages that should be passed through" do
- %w(math mermaid plantuml).each do |lang|
+ let(:delimiter) { described_class::PARAMS_DELIMITER }
+ let(:data_attr) { described_class::LANG_PARAMS_ATTR }
+
+ %w(math mermaid plantuml suggestion).each do |lang|
context "when #{lang} is specified" do
it "highlights as plaintext but with the correct language attribute and class" do
result = filter(%{<pre><code lang="#{lang}">This is a test</code></pre>})
@@ -55,6 +58,33 @@ describe Banzai::Filter::SyntaxHighlightFilter do
include_examples "XSS prevention", lang
end
+
+ context "when #{lang} has extra params" do
+ let(:lang_params) { 'foo-bar-kux' }
+
+ it "includes data-lang-params tag with extra information" do
+ result = filter(%{<pre><code lang="#{lang}#{delimiter}#{lang_params}">This is a test</code></pre>})
+
+ expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight #{lang}" lang="#{lang}" #{data_attr}="#{lang_params}" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>})
+ end
+
+ include_examples "XSS prevention", lang
+ include_examples "XSS prevention",
+ "#{lang}#{described_class::PARAMS_DELIMITER}&lt;script&gt;alert(1)&lt;/script&gt;"
+ include_examples "XSS prevention",
+ "#{lang}#{described_class::PARAMS_DELIMITER}<script>alert(1)</script>"
+ end
+ end
+
+ context 'when multiple param delimiters are used' do
+ let(:lang) { 'suggestion' }
+ let(:lang_params) { '-1+10' }
+
+ it "delimits on the first appearence" do
+ result = filter(%{<pre><code lang="#{lang}#{delimiter}#{lang_params}#{delimiter}more-things">This is a test</code></pre>})
+
+ expect(result.to_html).to eq(%{<pre class="code highlight js-syntax-highlight #{lang}" lang="#{lang}" #{data_attr}="#{lang_params}#{delimiter}more-things" v-pre="true"><code><span id="LC1" class="line" lang="#{lang}">This is a test</span></code></pre>})
+ end
end
end
diff --git a/spec/lib/gitlab/auth/o_auth/user_spec.rb b/spec/lib/gitlab/auth/o_auth/user_spec.rb
index dcbd12fe190..b765c265e69 100644
--- a/spec/lib/gitlab/auth/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/auth/o_auth/user_spec.rb
@@ -207,6 +207,7 @@ describe Gitlab::Auth::OAuth::User do
before do
allow(ldap_user).to receive(:uid) { uid }
allow(ldap_user).to receive(:username) { uid }
+ allow(ldap_user).to receive(:name) { 'John Doe' }
allow(ldap_user).to receive(:email) { ['johndoe@example.com', 'john2@example.com'] }
allow(ldap_user).to receive(:dn) { dn }
end
@@ -221,6 +222,7 @@ describe Gitlab::Auth::OAuth::User do
it "creates a user with dual LDAP and omniauth identities" do
expect(gl_user).to be_valid
expect(gl_user.username).to eql uid
+ expect(gl_user.name).to eql 'John Doe'
expect(gl_user.email).to eql 'johndoe@example.com'
expect(gl_user.identities.length).to be 2
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
@@ -232,11 +234,13 @@ describe Gitlab::Auth::OAuth::User do
)
end
- it "has email set as synced" do
+ it "has name and email set as synced" do
+ expect(gl_user.user_synced_attributes_metadata.name_synced).to be_truthy
expect(gl_user.user_synced_attributes_metadata.email_synced).to be_truthy
end
- it "has email set as read-only" do
+ it "has name and email set as read-only" do
+ expect(gl_user.read_only_attribute?(:name)).to be_truthy
expect(gl_user.read_only_attribute?(:email)).to be_truthy
end
@@ -246,7 +250,7 @@ describe Gitlab::Auth::OAuth::User do
end
context "and LDAP user has an account already" do
- let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: dn, provider: 'ldapmain', username: 'john') }
+ let!(:existing_user) { create(:omniauth_user, name: 'John Doe', email: 'john@example.com', extern_uid: dn, provider: 'ldapmain', username: 'john') }
it "adds the omniauth identity to the LDAP account" do
allow(Gitlab::Auth::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
@@ -254,6 +258,7 @@ describe Gitlab::Auth::OAuth::User do
expect(gl_user).to be_valid
expect(gl_user.username).to eql 'john'
+ expect(gl_user.name).to eql 'John Doe'
expect(gl_user.email).to eql 'john@example.com'
expect(gl_user.identities.length).to be 2
identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
diff --git a/spec/lib/gitlab/authorized_keys_spec.rb b/spec/lib/gitlab/authorized_keys_spec.rb
new file mode 100644
index 00000000000..42bc509eeef
--- /dev/null
+++ b/spec/lib/gitlab/authorized_keys_spec.rb
@@ -0,0 +1,194 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::AuthorizedKeys do
+ let(:logger) { double('logger').as_null_object }
+
+ subject { described_class.new(logger) }
+
+ describe '#add_key' do
+ context 'authorized_keys file exists' do
+ before do
+ create_authorized_keys_fixture
+ end
+
+ after do
+ delete_authorized_keys_file
+ end
+
+ it "adds a line at the end of the file and strips trailing garbage" do
+ auth_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa AAAAB3NzaDAxx2E"
+
+ expect(logger).to receive(:info).with('Adding key (key-741): ssh-rsa AAAAB3NzaDAxx2E')
+ expect(subject.add_key('key-741', 'ssh-rsa AAAAB3NzaDAxx2E trailing garbage'))
+ .to be_truthy
+ expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{auth_line}\n")
+ end
+ end
+
+ context 'authorized_keys file does not exist' do
+ before do
+ delete_authorized_keys_file
+ end
+
+ it 'creates the file' do
+ expect(subject.add_key('key-741', 'ssh-rsa AAAAB3NzaDAxx2E')).to be_truthy
+ expect(File.exist?(tmp_authorized_keys_path)).to be_truthy
+ end
+ end
+ end
+
+ describe '#batch_add_keys' do
+ let(:keys) do
+ [
+ double(shell_id: 'key-12', key: 'ssh-dsa ASDFASGADG trailing garbage'),
+ double(shell_id: 'key-123', key: 'ssh-rsa GFDGDFSGSDFG')
+ ]
+ end
+
+ context 'authorized_keys file exists' do
+ before do
+ create_authorized_keys_fixture
+ end
+
+ after do
+ delete_authorized_keys_file
+ end
+
+ it "adds lines at the end of the file" do
+ auth_line1 = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-12\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-dsa ASDFASGADG"
+ auth_line2 = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-123\",no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty ssh-rsa GFDGDFSGSDFG"
+
+ expect(logger).to receive(:info).with('Adding key (key-12): ssh-dsa ASDFASGADG')
+ expect(logger).to receive(:info).with('Adding key (key-123): ssh-rsa GFDGDFSGSDFG')
+ expect(subject.batch_add_keys(keys)).to be_truthy
+ expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{auth_line1}\n#{auth_line2}\n")
+ end
+
+ context "invalid key" do
+ let(:keys) { [double(shell_id: 'key-123', key: "ssh-rsa A\tSDFA\nSGADG")] }
+
+ it "doesn't add keys" do
+ expect(subject.batch_add_keys(keys)).to be_falsey
+ expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n")
+ end
+ end
+ end
+
+ context 'authorized_keys file does not exist' do
+ before do
+ delete_authorized_keys_file
+ end
+
+ it 'creates the file' do
+ expect(subject.batch_add_keys(keys)).to be_truthy
+ expect(File.exist?(tmp_authorized_keys_path)).to be_truthy
+ end
+ end
+ end
+
+ describe '#rm_key' do
+ context 'authorized_keys file exists' do
+ before do
+ create_authorized_keys_fixture
+ end
+
+ after do
+ delete_authorized_keys_file
+ end
+
+ it "removes the right line" do
+ other_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-742\",options ssh-rsa AAAAB3NzaDAxx2E"
+ delete_line = "command=\"#{Gitlab.config.gitlab_shell.path}/bin/gitlab-shell key-741\",options ssh-rsa AAAAB3NzaDAxx2E"
+ erased_line = delete_line.gsub(/./, '#')
+ File.open(tmp_authorized_keys_path, 'a') do |auth_file|
+ auth_file.puts delete_line
+ auth_file.puts other_line
+ end
+
+ expect(logger).to receive(:info).with('Removing key (key-741)')
+ expect(subject.rm_key('key-741')).to be_truthy
+ expect(File.read(tmp_authorized_keys_path)).to eq("existing content\n#{erased_line}\n#{other_line}\n")
+ end
+ end
+
+ context 'authorized_keys file does not exist' do
+ before do
+ delete_authorized_keys_file
+ end
+
+ it 'returns false' do
+ expect(subject.rm_key('key-741')).to be_falsey
+ end
+ end
+ end
+
+ describe '#clear' do
+ context 'authorized_keys file exists' do
+ before do
+ create_authorized_keys_fixture
+ end
+
+ after do
+ delete_authorized_keys_file
+ end
+
+ it "returns true" do
+ expect(subject.clear).to be_truthy
+ end
+ end
+
+ context 'authorized_keys file does not exist' do
+ before do
+ delete_authorized_keys_file
+ end
+
+ it "still returns true" do
+ expect(subject.clear).to be_truthy
+ end
+ end
+ end
+
+ describe '#list_key_ids' do
+ context 'authorized_keys file exists' do
+ before do
+ create_authorized_keys_fixture(
+ existing_content:
+ "key-1\tssh-dsa AAA\nkey-2\tssh-rsa BBB\nkey-3\tssh-rsa CCC\nkey-9000\tssh-rsa DDD\n"
+ )
+ end
+
+ after do
+ delete_authorized_keys_file
+ end
+
+ it 'returns array of key IDs' do
+ expect(subject.list_key_ids).to eq([1, 2, 3, 9000])
+ end
+ end
+
+ context 'authorized_keys file does not exist' do
+ before do
+ delete_authorized_keys_file
+ end
+
+ it 'returns an empty array' do
+ expect(subject.list_key_ids).to be_empty
+ end
+ end
+ end
+
+ def create_authorized_keys_fixture(existing_content: 'existing content')
+ FileUtils.mkdir_p(File.dirname(tmp_authorized_keys_path))
+ File.open(tmp_authorized_keys_path, 'w') { |file| file.puts(existing_content) }
+ end
+
+ def delete_authorized_keys_file
+ File.delete(tmp_authorized_keys_path) if File.exist?(tmp_authorized_keys_path)
+ end
+
+ def tmp_authorized_keys_path
+ Gitlab.config.gitlab_shell.authorized_keys_file
+ end
+end
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
index bc71a90605a..e7ff9169f1b 100644
--- 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
@@ -172,10 +172,8 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :m
let(:exception) { ActiveRecord::RecordNotFound }
let(:perform_ignoring_exceptions) do
- begin
- subject.perform(start_id, stop_id)
- rescue described_class::Error
- end
+ subject.perform(start_id, stop_id)
+ rescue described_class::Error
end
before do
diff --git a/spec/lib/gitlab/badge/pipeline/template_spec.rb b/spec/lib/gitlab/badge/pipeline/template_spec.rb
index 20fa4f879c3..bcef0b7e120 100644
--- a/spec/lib/gitlab/badge/pipeline/template_spec.rb
+++ b/spec/lib/gitlab/badge/pipeline/template_spec.rb
@@ -59,6 +59,16 @@ describe Gitlab::Badge::Pipeline::Template do
end
end
+ context 'when status is preparing' do
+ before do
+ allow(badge).to receive(:status).and_return('preparing')
+ end
+
+ it 'has expected color' do
+ expect(template.value_color).to eq '#dfb317'
+ end
+ end
+
context 'when status is unknown' do
before do
allow(badge).to receive(:status).and_return('unknown')
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index c432cc223b9..e1a2bae5fe8 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -95,6 +95,9 @@ describe Gitlab::BitbucketImport::Importer do
subject { described_class.new(project) }
describe '#import_pull_requests' do
+ let(:source_branch_sha) { sample.commits.last }
+ let(:target_branch_sha) { sample.commits.first }
+
before do
allow(subject).to receive(:import_wiki)
allow(subject).to receive(:import_issues)
@@ -102,9 +105,9 @@ describe Gitlab::BitbucketImport::Importer do
pull_request = instance_double(
Bitbucket::Representation::PullRequest,
iid: 10,
- source_branch_sha: sample.commits.last,
+ source_branch_sha: source_branch_sha,
source_branch_name: Gitlab::Git::BRANCH_REF_PREFIX + sample.source_branch,
- target_branch_sha: sample.commits.first,
+ target_branch_sha: target_branch_sha,
target_branch_name: Gitlab::Git::BRANCH_REF_PREFIX + sample.target_branch,
title: 'This is a title',
description: 'This is a test pull request',
@@ -162,6 +165,19 @@ describe Gitlab::BitbucketImport::Importer do
expect(reply_note).to be_a(DiffNote)
expect(reply_note.note).to eq(@reply.note)
end
+
+ context "when branches' sha is not found in the repository" do
+ let(:source_branch_sha) { 'a' * Commit::MIN_SHA_LENGTH }
+ let(:target_branch_sha) { 'b' * Commit::MIN_SHA_LENGTH }
+
+ it 'uses the pull request sha references' do
+ expect { subject.execute }.to change { MergeRequest.count }.by(1)
+
+ merge_request_diff = MergeRequest.first.merge_request_diff
+ expect(merge_request_diff.head_commit_sha).to eq source_branch_sha
+ expect(merge_request_diff.start_commit_sha).to eq target_branch_sha
+ end
+ end
end
context 'issues statuses' do
diff --git a/spec/lib/gitlab/checks/branch_check_spec.rb b/spec/lib/gitlab/checks/branch_check_spec.rb
index 12beeecd470..8d5ab27a17c 100644
--- a/spec/lib/gitlab/checks/branch_check_spec.rb
+++ b/spec/lib/gitlab/checks/branch_check_spec.rb
@@ -108,64 +108,86 @@ describe Gitlab::Checks::BranchCheck do
end
context 'protected branch creation feature is enabled' do
- context 'user is not allowed to create protected branches' do
+ context 'user can push to branch' do
before do
allow(user_access)
- .to receive(:can_merge_to_branch?)
+ .to receive(:can_push_to_branch?)
.with('feature')
- .and_return(false)
+ .and_return(true)
end
- it 'raises an error' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to create protected branches on this project.')
+ it 'does not raise an error' do
+ expect { subject.validate! }.not_to raise_error
end
end
- context 'user is allowed to create protected branches' do
+ context 'user cannot push to branch' do
before do
allow(user_access)
- .to receive(:can_merge_to_branch?)
+ .to receive(:can_push_to_branch?)
.with('feature')
- .and_return(true)
-
- allow(project.repository)
- .to receive(:branch_names_contains_sha)
- .with(newrev)
- .and_return(['branch'])
+ .and_return(false)
end
- context "newrev isn't in any protected branches" do
+ context 'user cannot merge to branch' do
before do
- allow(ProtectedBranch)
- .to receive(:any_protected?)
- .with(project, ['branch'])
+ allow(user_access)
+ .to receive(:can_merge_to_branch?)
+ .with('feature')
.and_return(false)
end
it 'raises an error' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only use an existing protected branch ref as the basis of a new protected branch.')
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to create protected branches on this project.')
end
end
- context 'newrev is included in a protected branch' do
+ context 'user can merge to branch' do
before do
- allow(ProtectedBranch)
- .to receive(:any_protected?)
- .with(project, ['branch'])
+ allow(user_access)
+ .to receive(:can_merge_to_branch?)
+ .with('feature')
.and_return(true)
+
+ allow(project.repository)
+ .to receive(:branch_names_contains_sha)
+ .with(newrev)
+ .and_return(['branch'])
end
- context 'via web interface' do
- let(:protocol) { 'web' }
+ context "newrev isn't in any protected branches" do
+ before do
+ allow(ProtectedBranch)
+ .to receive(:any_protected?)
+ .with(project, ['branch'])
+ .and_return(false)
+ end
- it 'allows branch creation' do
- expect { subject.validate! }.not_to raise_error
+ it 'raises an error' do
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only use an existing protected branch ref as the basis of a new protected branch.')
end
end
- context 'via SSH' do
- it 'raises an error' do
- expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only create protected branches using the web interface and API.')
+ context 'newrev is included in a protected branch' do
+ before do
+ allow(ProtectedBranch)
+ .to receive(:any_protected?)
+ .with(project, ['branch'])
+ .and_return(true)
+ end
+
+ context 'via web interface' do
+ let(:protocol) { 'web' }
+
+ it 'allows branch creation' do
+ expect { subject.validate! }.not_to raise_error
+ end
+ end
+
+ context 'via SSH' do
+ it 'raises an error' do
+ expect { subject.validate! }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only create protected branches using the web interface and API.')
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb b/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb
new file mode 100644
index 00000000000..5187f99a441
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/prerequisite/factory_spec.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Prerequisite::Factory do
+ let(:build) { create(:ci_build) }
+
+ describe '.for_build' do
+ let(:kubernetes_namespace) do
+ instance_double(
+ Gitlab::Ci::Build::Prerequisite::KubernetesNamespace,
+ unmet?: unmet)
+ end
+
+ subject { described_class.new(build).unmet }
+
+ before do
+ expect(Gitlab::Ci::Build::Prerequisite::KubernetesNamespace)
+ .to receive(:new).with(build).and_return(kubernetes_namespace)
+ end
+
+ context 'prerequisite is unmet' do
+ let(:unmet) { true }
+
+ it { is_expected.to eq [kubernetes_namespace] }
+ end
+
+ context 'prerequisite is met' do
+ let(:unmet) { false }
+
+ it { is_expected.to be_empty }
+ 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
new file mode 100644
index 00000000000..62dcd80fad7
--- /dev/null
+++ b/spec/lib/gitlab/ci/build/prerequisite/kubernetes_namespace_spec.rb
@@ -0,0 +1,85 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Build::Prerequisite::KubernetesNamespace do
+ let(:build) { create(:ci_build) }
+
+ describe '#unmet?' do
+ subject { described_class.new(build).unmet? }
+
+ context 'build has no deployment' do
+ before do
+ expect(build.deployment).to be_nil
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'build has a deployment' do
+ let!(:deployment) { create(:deployment, deployable: build) }
+
+ context 'and a cluster to deploy to' do
+ let(:cluster) { create(:cluster, projects: [build.project]) }
+
+ before do
+ allow(build.deployment).to receive(:cluster).and_return(cluster)
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'and a namespace is already created for this project' do
+ let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster, project: build.project) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'and no cluster to deploy to' do
+ before do
+ expect(deployment.cluster).to be_nil
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+ end
+
+ describe '#complete!' do
+ let!(:deployment) { create(:deployment, deployable: build) }
+ let(:service) { double(execute: true) }
+
+ subject { described_class.new(build).complete! }
+
+ context 'completion is required' do
+ let(:cluster) { create(:cluster, projects: [build.project]) }
+
+ before do
+ allow(build.deployment).to receive(:cluster).and_return(cluster)
+ end
+
+ it 'creates a kubernetes namespace' do
+ expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService)
+ .to receive(:new)
+ .with(cluster: cluster, kubernetes_namespace: instance_of(Clusters::KubernetesNamespace))
+ .and_return(service)
+
+ expect(service).to receive(:execute).once
+
+ subject
+ end
+ end
+
+ context 'completion is not required' do
+ before do
+ expect(deployment.cluster).to be_nil
+ end
+
+ it 'does not create a namespace' do
+ expect(Clusters::Gcp::Kubernetes::CreateOrUpdateNamespaceService).not_to receive(:new)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/build/preparing_spec.rb b/spec/lib/gitlab/ci/status/build/preparing_spec.rb
new file mode 100644
index 00000000000..4d8945845ba
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/build/preparing_spec.rb
@@ -0,0 +1,33 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Build::Preparing do
+ subject do
+ described_class.new(double('subject'))
+ end
+
+ describe '#illustration' do
+ it { expect(subject.illustration).to include(:image, :size, :title, :content) }
+ end
+
+ describe '.matches?' do
+ subject { described_class.matches?(build, nil) }
+
+ context 'when build is preparing' do
+ let(:build) { create(:ci_build, :preparing) }
+
+ it 'is a correct match' do
+ expect(subject).to be true
+ end
+ end
+
+ context 'when build is not preparing' do
+ let(:build) { create(:ci_build, :success) }
+
+ it 'does not match' do
+ expect(subject).to be false
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/ci/status/preparing_spec.rb b/spec/lib/gitlab/ci/status/preparing_spec.rb
new file mode 100644
index 00000000000..7211c0e506d
--- /dev/null
+++ b/spec/lib/gitlab/ci/status/preparing_spec.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Ci::Status::Preparing do
+ subject do
+ described_class.new(double('subject'), nil)
+ end
+
+ describe '#text' do
+ it { expect(subject.text).to eq 'preparing' }
+ end
+
+ describe '#label' do
+ it { expect(subject.label).to eq 'preparing' }
+ end
+
+ describe '#icon' do
+ it { expect(subject.icon).to eq 'status_created' }
+ end
+
+ describe '#favicon' do
+ it { expect(subject.favicon).to eq 'favicon_status_created' }
+ end
+
+ describe '#group' do
+ it { expect(subject.group).to eq 'preparing' }
+ end
+end
diff --git a/spec/lib/gitlab/danger/teammate_spec.rb b/spec/lib/gitlab/danger/teammate_spec.rb
new file mode 100644
index 00000000000..4bc0a4c1398
--- /dev/null
+++ b/spec/lib/gitlab/danger/teammate_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+describe Gitlab::Danger::Teammate do
+ subject { described_class.new({ 'projects' => projects }) }
+ let(:projects) { { project => capabilities } }
+ let(:project) { double }
+
+ describe 'multiple roles project project' do
+ let(:capabilities) { ['reviewer backend', 'maintainer frontend', 'trainee_maintainer database'] }
+
+ it '#reviewer? supports multiple roles per project' do
+ expect(subject.reviewer?(project, 'backend')).to be_truthy
+ end
+
+ it '#traintainer? supports multiple roles per project' do
+ expect(subject.traintainer?(project, 'database')).to be_truthy
+ end
+
+ it '#maintainer? supports multiple roles per project' do
+ expect(subject.maintainer?(project, 'frontend')).to be_truthy
+ end
+ end
+
+ describe 'one role project project' do
+ let(:capabilities) { 'reviewer backend' }
+
+ it '#reviewer? supports one role per project' do
+ expect(subject.reviewer?(project, 'backend')).to be_truthy
+ end
+
+ it '#traintainer? supports one role per project' do
+ expect(subject.traintainer?(project, 'database')).to be_falsey
+ end
+
+ it '#maintainer? supports one role per project' do
+ expect(subject.maintainer?(project, 'frontend')).to be_falsey
+ end
+ end
+end
diff --git a/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb b/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
index b44e8c5a110..bd3c66d0548 100644
--- a/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/reltuples_count_strategy_spec.rb
@@ -6,10 +6,11 @@ describe Gitlab::Database::Count::ReltuplesCountStrategy do
create(:identity)
end
- let(:models) { [Project, Identity] }
subject { described_class.new(models).count }
describe '#count', :postgresql do
+ let(:models) { [Project, Identity] }
+
context 'when reltuples is up to date' do
before do
ActiveRecord::Base.connection.execute('ANALYZE projects')
@@ -23,6 +24,22 @@ describe Gitlab::Database::Count::ReltuplesCountStrategy do
end
end
+ context 'when models using single-type inheritance are used' do
+ let(:models) { [Group, CiService, Namespace] }
+
+ before do
+ models.each do |model|
+ ActiveRecord::Base.connection.execute("ANALYZE #{model.table_name}")
+ end
+ end
+
+ it 'returns nil counts for inherited tables' do
+ models.each { |model| expect(model).not_to receive(:count) }
+
+ expect(subject).to eq({ Namespace => 3 })
+ end
+ end
+
context 'insufficient permissions' do
it 'returns an empty hash' do
allow(ActiveRecord::Base).to receive(:transaction).and_raise(PG::InsufficientPrivilege)
diff --git a/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb b/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
index 203f9344a41..40d810b195b 100644
--- a/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
+++ b/spec/lib/gitlab/database/count/tablesample_count_strategy_spec.rb
@@ -4,15 +4,23 @@ describe Gitlab::Database::Count::TablesampleCountStrategy do
before do
create_list(:project, 3)
create(:identity)
+ create(:group)
end
- let(:models) { [Project, Identity] }
+ let(:models) { [Project, Identity, Group, Namespace] }
let(:strategy) { described_class.new(models) }
subject { strategy.count }
describe '#count', :postgresql do
- let(:estimates) { { Project => threshold + 1, Identity => threshold - 1 } }
+ let(:estimates) do
+ {
+ Project => threshold + 1,
+ Identity => threshold - 1,
+ Group => threshold + 1,
+ Namespace => threshold + 1
+ }
+ end
let(:threshold) { Gitlab::Database::Count::TablesampleCountStrategy::EXACT_COUNT_THRESHOLD }
before do
@@ -30,9 +38,13 @@ describe Gitlab::Database::Count::TablesampleCountStrategy do
context 'for tables with an estimated large size' do
it 'performs a tablesample count' do
expect(Project).not_to receive(:count)
+ expect(Group).not_to receive(:count)
+ expect(Namespace).not_to receive(:count)
result = subject
expect(result[Project]).to eq(3)
+ expect(result[Group]).to eq(1)
+ expect(result[Namespace]).to eq(4)
end
end
diff --git a/spec/lib/gitlab/database_spec.rb b/spec/lib/gitlab/database_spec.rb
index 60106ee3c0b..5f57cd6b825 100644
--- a/spec/lib/gitlab/database_spec.rb
+++ b/spec/lib/gitlab/database_spec.rb
@@ -17,6 +17,20 @@ describe Gitlab::Database do
end
end
+ describe '.human_adapter_name' do
+ it 'returns PostgreSQL when using PostgreSQL' do
+ allow(described_class).to receive(:postgresql?).and_return(true)
+
+ expect(described_class.human_adapter_name).to eq('PostgreSQL')
+ end
+
+ it 'returns MySQL when using MySQL' do
+ allow(described_class).to receive(:postgresql?).and_return(false)
+
+ expect(described_class.human_adapter_name).to eq('MySQL')
+ end
+ end
+
# These are just simple smoke tests to check if the methods work (regardless
# of what they may return).
describe '.mysql?' do
@@ -87,6 +101,38 @@ describe Gitlab::Database do
end
end
+ describe '.postgresql_minimum_supported_version?' do
+ it 'returns false when not using PostgreSQL' do
+ allow(described_class).to receive(:postgresql?).and_return(false)
+
+ expect(described_class.postgresql_minimum_supported_version?).to eq(false)
+ end
+
+ context 'when using PostgreSQL' do
+ before do
+ allow(described_class).to receive(:postgresql?).and_return(true)
+ end
+
+ it 'returns false when using PostgreSQL 9.5' do
+ allow(described_class).to receive(:version).and_return('9.5')
+
+ expect(described_class.postgresql_minimum_supported_version?).to eq(false)
+ end
+
+ it 'returns true when using PostgreSQL 9.6' do
+ allow(described_class).to receive(:version).and_return('9.6')
+
+ expect(described_class.postgresql_minimum_supported_version?).to eq(true)
+ end
+
+ it 'returns true when using PostgreSQL 10 or newer' do
+ allow(described_class).to receive(:version).and_return('10')
+
+ expect(described_class.postgresql_minimum_supported_version?).to eq(true)
+ end
+ end
+ end
+
describe '.join_lateral_supported?' do
it 'returns false when using MySQL' do
allow(described_class).to receive(:postgresql?).and_return(false)
@@ -195,6 +241,12 @@ describe Gitlab::Database do
end
end
+ describe '.pg_last_xact_replay_timestamp' do
+ it 'returns pg_last_xact_replay_timestamp' do
+ expect(described_class.pg_last_xact_replay_timestamp).to eq('pg_last_xact_replay_timestamp')
+ end
+ end
+
describe '.nulls_last_order' do
context 'when using PostgreSQL' do
before do
diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb
index 611c3e946ed..cc36060f864 100644
--- a/spec/lib/gitlab/diff/file_spec.rb
+++ b/spec/lib/gitlab/diff/file_spec.rb
@@ -72,6 +72,13 @@ describe Gitlab::Diff::File do
expect(diff_file.diff_lines_for_serializer.last.type).to eq('match')
end
+ context 'when called multiple times' do
+ it 'only adds bottom match line once' do
+ expect(diff_file.diff_lines_for_serializer.size).to eq(31)
+ expect(diff_file.diff_lines_for_serializer.size).to eq(31)
+ end
+ end
+
context 'when deleted' do
let(:commit) { project.commit('d59c60028b053793cecfb4022de34602e1a9218e') }
let(:diff_file) { commit.diffs.diff_file_with_old_path('files/js/commit.js.coffee') }
diff --git a/spec/lib/gitlab/diff/suggestion_diff_spec.rb b/spec/lib/gitlab/diff/suggestion_diff_spec.rb
new file mode 100644
index 00000000000..5a32c2bea37
--- /dev/null
+++ b/spec/lib/gitlab/diff/suggestion_diff_spec.rb
@@ -0,0 +1,55 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Diff::SuggestionDiff do
+ describe '#diff_lines' do
+ let(:from_content) do
+ <<-BLOB.strip_heredoc
+ "tags": ["devel", "development", "nightly"],
+ "desktop-file-name-prefix": "(Development) ",
+ "finish-args": "foo",
+ BLOB
+ end
+
+ let(:to_content) do
+ <<-BLOB.strip_heredoc
+ "buildsystem": "meson",
+ "builddir": true,
+ "name": "nautilus",
+ "bar": "bar",
+ BLOB
+ end
+
+ let(:suggestion) do
+ instance_double(Suggestion, from_line: 12,
+ from_content: from_content,
+ to_content: to_content)
+ end
+
+ subject { described_class.new(suggestion).diff_lines }
+
+ let(:expected_diff_lines) do
+ [
+ { old_pos: 12, new_pos: 12, type: "match", text: "@@ -12 +12" },
+ { old_pos: 12, new_pos: 12, type: "old", text: "-\"tags\": [\"devel\", \"development\", \"nightly\"]," },
+ { old_pos: 13, new_pos: 12, type: "old", text: "-\"desktop-file-name-prefix\": \"(Development) \"," },
+ { old_pos: 14, new_pos: 12, type: "old", text: "-\"finish-args\": \"foo\"," },
+ { old_pos: 15, new_pos: 12, type: "new", text: "+\"buildsystem\": \"meson\"," },
+ { old_pos: 15, new_pos: 13, type: "new", text: "+\"builddir\": true," },
+ { old_pos: 15, new_pos: 14, type: "new", text: "+\"name\": \"nautilus\"," },
+ { old_pos: 15, new_pos: 15, type: "new", text: "+\"bar\": \"bar\"," }
+ ]
+ end
+
+ it 'returns diff lines with correct line numbers' do
+ diff_lines = subject
+
+ expect(diff_lines).to all(be_a(Gitlab::Diff::Line))
+
+ expected_diff_lines.each_with_index do |expected_line, index|
+ expect(diff_lines[index].to_hash).to include(expected_line)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/fake_application_settings_spec.rb b/spec/lib/gitlab/fake_application_settings_spec.rb
index af12e13d36d..c81cb83d9f4 100644
--- a/spec/lib/gitlab/fake_application_settings_spec.rb
+++ b/spec/lib/gitlab/fake_application_settings_spec.rb
@@ -1,32 +1,33 @@
require 'spec_helper'
describe Gitlab::FakeApplicationSettings do
- let(:defaults) { { password_authentication_enabled_for_web: false, foobar: 'asdf', signup_enabled: true, 'test?' => 123 } }
+ let(:defaults) do
+ described_class.defaults.merge(
+ foobar: 'asdf',
+ 'test?' => 123
+ )
+ end
- subject { described_class.new(defaults) }
+ let(:setting) { described_class.new(defaults) }
it 'wraps OpenStruct variables properly' do
- expect(subject.password_authentication_enabled_for_web).to be_falsey
- expect(subject.signup_enabled).to be_truthy
- expect(subject.foobar).to eq('asdf')
+ expect(setting.password_authentication_enabled_for_web).to be_truthy
+ expect(setting.signup_enabled).to be_truthy
+ expect(setting.foobar).to eq('asdf')
end
it 'defines predicate methods' do
- expect(subject.password_authentication_enabled_for_web?).to be_falsey
- expect(subject.signup_enabled?).to be_truthy
- end
-
- it 'predicate method changes when value is updated' do
- subject.password_authentication_enabled_for_web = true
-
- expect(subject.password_authentication_enabled_for_web?).to be_truthy
+ expect(setting.password_authentication_enabled_for_web?).to be_truthy
+ expect(setting.signup_enabled?).to be_truthy
end
it 'does not define a predicate method' do
- expect(subject.foobar?).to be_nil
+ expect(setting.foobar?).to be_nil
end
it 'does not override an existing predicate method' do
- expect(subject.test?).to eq(123)
+ expect(setting.test?).to eq(123)
end
+
+ it_behaves_like 'application settings examples'
end
diff --git a/spec/lib/gitlab/git/repository_cleaner_spec.rb b/spec/lib/gitlab/git/repository_cleaner_spec.rb
index 7f9cc2bc9ec..6602f22843f 100644
--- a/spec/lib/gitlab/git/repository_cleaner_spec.rb
+++ b/spec/lib/gitlab/git/repository_cleaner_spec.rb
@@ -37,14 +37,12 @@ describe Gitlab::Git::RepositoryCleaner do
let(:object_map) { Gitlab::HttpIO.new(url, object_map_data.size) }
around do |example|
- begin
- tempfile.write(object_map_data)
- tempfile.close
+ tempfile.write(object_map_data)
+ tempfile.close
- example.run
- ensure
- tempfile.unlink
- end
+ example.run
+ ensure
+ tempfile.unlink
end
it 'removes internal references' do
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 7e6dfa30e37..8ba6862392c 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1688,6 +1688,11 @@ describe Gitlab::Git::Repository, :seed_helper do
expect(repository.delete_config(*%w[does.not.exist test.foo1 test.foo2])).to be_nil
+ # Workaround for https://github.com/libgit2/rugged/issues/785: If
+ # Gitaly changes .gitconfig while Rugged has the file loaded
+ # Rugged::Repository#each_key will report stale values unless a
+ # lookup is done first.
+ expect(repository_rugged.config['test.foo1']).to be_nil
config_keys = repository_rugged.config.each_key.to_a
expect(config_keys).not_to include('test.foo1')
expect(config_keys).not_to include('test.foo2')
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index cf12baf1a93..f1acb1d9bc4 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -149,11 +149,21 @@ describe Gitlab::GitalyClient do
end
end
- context 'when RequestStore is enabled', :request_store do
+ context 'when RequestStore is enabled and the maximum number of calls is not enforced by a feature flag', :request_store do
+ before do
+ stub_feature_flags(gitaly_enforce_requests_limits: false)
+ end
+
it 'allows up the maximum number of allowed calls' do
expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS) }.not_to raise_error
end
+ it 'allows the maximum number of calls to be exceeded if GITALY_DISABLE_REQUEST_LIMITS is set' do
+ stub_env('GITALY_DISABLE_REQUEST_LIMITS', 'true')
+
+ expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 1) }.not_to raise_error
+ end
+
context 'when the maximum number of calls has been reached' do
before do
call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS)
@@ -189,6 +199,32 @@ describe Gitlab::GitalyClient do
end
end
+ context 'in production and when RequestStore is enabled', :request_store do
+ before do
+ allow(Rails.env).to receive(:production?).and_return(true)
+ end
+
+ context 'when the maximum number of calls is enforced by a feature flag' do
+ before do
+ stub_feature_flags(gitaly_enforce_requests_limits: true)
+ end
+
+ it 'does not allow the maximum number of calls to be exceeded' do
+ expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 1) }.to raise_error(Gitlab::GitalyClient::TooManyInvocationsError)
+ end
+ end
+
+ context 'when the maximum number of calls is not enforced by a feature flag' do
+ before do
+ stub_feature_flags(gitaly_enforce_requests_limits: false)
+ end
+
+ it 'allows the maximum number of calls to be exceeded' do
+ expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 1) }.not_to raise_error
+ end
+ end
+ end
+
context 'when RequestStore is not active' do
it 'does not raise errors when the maximum number of allowed calls is exceeded' do
expect { call_gitaly(Gitlab::GitalyClient::MAXIMUM_GITALY_CALLS + 2) }.not_to raise_error
diff --git a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
index 15e59718dce..680de47de2b 100644
--- a/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/pull_request_importer_spec.rb
@@ -273,6 +273,11 @@ describe Gitlab::GithubImport::Importer::PullRequestImporter, :clean_gitlab_redi
mr.state = 'opened'
mr.save
+ # Ensure the project creator is creating the branches because the
+ # merge request author may not have access to push to this
+ # repository. The project owner may also be a group.
+ allow(project.repository).to receive(:add_branch).with(project.creator, anything, anything).and_call_original
+
importer.insert_git_data(mr, exists)
expect(project.repository.branch_exists?(mr.source_branch)).to be_truthy
diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
index 47233ea6ee2..41810a8ec03 100644
--- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
@@ -179,6 +179,17 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do
describe '#import_repository' do
it 'imports the repository' do
+ repo = double(:repo, default_branch: 'develop')
+
+ expect(client)
+ .to receive(:repository)
+ .with('foo/bar')
+ .and_return(repo)
+
+ expect(project)
+ .to receive(:change_head)
+ .with('develop')
+
expect(project)
.to receive(:ensure_repository)
diff --git a/spec/lib/gitlab/group_search_results_spec.rb b/spec/lib/gitlab/group_search_results_spec.rb
new file mode 100644
index 00000000000..2734fcef0a0
--- /dev/null
+++ b/spec/lib/gitlab/group_search_results_spec.rb
@@ -0,0 +1,69 @@
+require 'spec_helper'
+
+describe Gitlab::GroupSearchResults do
+ let(:user) { create(:user) }
+
+ describe 'user search' do
+ let(:group) { create(:group) }
+
+ it 'returns the users belonging to the group matching the search query' do
+ user1 = create(:user, username: 'gob_bluth')
+ create(:group_member, :developer, user: user1, group: group)
+
+ user2 = create(:user, username: 'michael_bluth')
+ create(:group_member, :developer, user: user2, group: group)
+
+ create(:user, username: 'gob_2018')
+
+ result = described_class.new(user, anything, group, 'gob').objects('users')
+
+ expect(result).to eq [user1]
+ end
+
+ it 'returns the user belonging to the subgroup matching the search query', :nested_groups do
+ user1 = create(:user, username: 'gob_bluth')
+ subgroup = create(:group, parent: group)
+ create(:group_member, :developer, user: user1, group: subgroup)
+
+ create(:user, username: 'gob_2018')
+
+ result = described_class.new(user, anything, group, 'gob').objects('users')
+
+ expect(result).to eq [user1]
+ end
+
+ it 'returns the user belonging to the parent group matching the search query', :nested_groups do
+ user1 = create(:user, username: 'gob_bluth')
+ parent_group = create(:group, children: [group])
+ create(:group_member, :developer, user: user1, group: parent_group)
+
+ create(:user, username: 'gob_2018')
+
+ result = described_class.new(user, anything, group, 'gob').objects('users')
+
+ expect(result).to eq [user1]
+ end
+
+ it 'does not return the user belonging to the private subgroup', :nested_groups do
+ user1 = create(:user, username: 'gob_bluth')
+ subgroup = create(:group, :private, parent: group)
+ create(:group_member, :developer, user: user1, group: subgroup)
+
+ create(:user, username: 'gob_2018')
+
+ result = described_class.new(user, anything, group, 'gob').objects('users')
+
+ expect(result).to eq []
+ end
+
+ it 'does not return the user belonging to an unrelated group' do
+ user = create(:user, username: 'gob_bluth')
+ unrelated_group = create(:group)
+ create(:group_member, :developer, user: user, group: unrelated_group)
+
+ result = described_class.new(user, anything, group, 'gob').objects('users')
+
+ expect(result).to eq []
+ end
+ end
+end
diff --git a/spec/lib/gitlab/hashed_storage/migrator_spec.rb b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
index 6154b3e2f76..8e253b51597 100644
--- a/spec/lib/gitlab/hashed_storage/migrator_spec.rb
+++ b/spec/lib/gitlab/hashed_storage/migrator_spec.rb
@@ -1,6 +1,8 @@
+# frozen_string_literal: true
+
require 'spec_helper'
-describe Gitlab::HashedStorage::Migrator do
+describe Gitlab::HashedStorage::Migrator, :sidekiq, :redis do
describe '#bulk_schedule_migration' do
it 'schedules job to HashedStorage::MigratorWorker' do
Sidekiq::Testing.fake! do
@@ -182,4 +184,52 @@ describe Gitlab::HashedStorage::Migrator do
end
end
end
+
+ describe 'migration_pending?' do
+ set(:project) { create(:project, :empty_repo) }
+
+ it 'returns true when there are MigratorWorker jobs scheduled' do
+ Sidekiq::Testing.disable! do
+ ::HashedStorage::MigratorWorker.perform_async(1, 5)
+
+ expect(subject.migration_pending?).to be_truthy
+ end
+ end
+
+ it 'returns true when there are ProjectMigrateWorker jobs scheduled' do
+ Sidekiq::Testing.disable! do
+ ::HashedStorage::ProjectMigrateWorker.perform_async(1)
+
+ expect(subject.migration_pending?).to be_truthy
+ end
+ end
+
+ it 'returns false when queues are empty' do
+ expect(subject.migration_pending?).to be_falsey
+ end
+ end
+
+ describe 'rollback_pending?' do
+ set(:project) { create(:project, :empty_repo) }
+
+ it 'returns true when there are RollbackerWorker jobs scheduled' do
+ Sidekiq::Testing.disable! do
+ ::HashedStorage::RollbackerWorker.perform_async(1, 5)
+
+ expect(subject.rollback_pending?).to be_truthy
+ end
+ end
+
+ it 'returns true when there are jobs scheduled' do
+ Sidekiq::Testing.disable! do
+ ::HashedStorage::ProjectRollbackWorker.perform_async(1)
+
+ expect(subject.rollback_pending?).to be_truthy
+ end
+ end
+
+ it 'returns false when queues are empty' do
+ expect(subject.rollback_pending?).to be_falsey
+ end
+ end
end
diff --git a/spec/lib/gitlab/json_cache_spec.rb b/spec/lib/gitlab/json_cache_spec.rb
index 2cae8ec031a..b82c09af306 100644
--- a/spec/lib/gitlab/json_cache_spec.rb
+++ b/spec/lib/gitlab/json_cache_spec.rb
@@ -7,7 +7,7 @@ describe Gitlab::JsonCache do
let(:namespace) { 'geo' }
let(:key) { 'foo' }
let(:expanded_key) { "#{namespace}:#{key}:#{Rails.version}" }
- let(:broadcast_message) { create(:broadcast_message) }
+ set(:broadcast_message) { create(:broadcast_message) }
subject(:cache) { described_class.new(namespace: namespace, backend: backend) }
@@ -146,6 +146,18 @@ describe Gitlab::JsonCache do
expect(cache.read(key, BroadcastMessage)).to be_nil
end
+
+ it 'gracefully handles excluded fields from attributes during serialization' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return(broadcast_message.attributes.except("message_html").to_json)
+
+ result = cache.read(key, BroadcastMessage)
+
+ BroadcastMessage.cached_markdown_fields.html_fields.each do |field|
+ expect(result.public_send(field)).to be_nil
+ end
+ end
end
context 'when the cached value is an array' do
@@ -321,6 +333,46 @@ describe Gitlab::JsonCache do
expect(result).to be_new_record
end
+
+ it 'gracefully handles bad cached entry' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return('{')
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to eq 'block result'
+ end
+
+ it 'gracefully handles an empty hash' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return('{}')
+
+ expect(cache.fetch(key, as: BroadcastMessage)).to be_a(BroadcastMessage)
+ end
+
+ it 'gracefully handles unknown attributes' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return(broadcast_message.attributes.merge(unknown_attribute: 1).to_json)
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ expect(result).to eq 'block result'
+ end
+
+ it 'gracefully handles excluded fields from attributes during serialization' do
+ allow(backend).to receive(:read)
+ .with(expanded_key)
+ .and_return(broadcast_message.attributes.except("message_html").to_json)
+
+ result = cache.fetch(key, as: BroadcastMessage) { 'block result' }
+
+ BroadcastMessage.cached_markdown_fields.html_fields.each do |field|
+ expect(result.public_send(field)).to be_nil
+ end
+ end
end
it "returns the result of the block when 'as' option is nil" do
diff --git a/spec/lib/gitlab/kubernetes_spec.rb b/spec/lib/gitlab/kubernetes_spec.rb
index f326d57e9c6..57b570a9166 100644
--- a/spec/lib/gitlab/kubernetes_spec.rb
+++ b/spec/lib/gitlab/kubernetes_spec.rb
@@ -40,10 +40,40 @@ describe Gitlab::Kubernetes do
describe '#filter_by_label' do
it 'returns matching labels' do
- matching_items = [kube_pod(app: 'foo')]
+ matching_items = [kube_pod(track: 'foo'), kube_deployment(track: 'foo')]
+ items = matching_items + [kube_pod, kube_deployment]
+
+ expect(filter_by_label(items, 'track' => 'foo')).to eq(matching_items)
+ end
+ end
+
+ describe '#filter_by_annotation' do
+ it 'returns matching labels' do
+ matching_items = [kube_pod(environment_slug: 'foo'), kube_deployment(environment_slug: 'foo')]
+ items = matching_items + [kube_pod, kube_deployment]
+
+ expect(filter_by_annotation(items, 'app.gitlab.com/env' => 'foo')).to eq(matching_items)
+ end
+ end
+
+ describe '#filter_by_project_environment' do
+ let(:matching_pod) { kube_pod(environment_slug: 'production', project_slug: 'my-cool-app') }
+
+ it 'returns matching legacy env label' do
+ matching_pod['metadata']['annotations'].delete('app.gitlab.com/app')
+ matching_pod['metadata']['annotations'].delete('app.gitlab.com/env')
+ matching_pod['metadata']['labels']['app'] = 'production'
+ matching_items = [matching_pod]
+ items = matching_items + [kube_pod]
+
+ expect(filter_by_project_environment(items, 'my-cool-app', 'production')).to eq(matching_items)
+ end
+
+ it 'returns matching env label' do
+ matching_items = [matching_pod]
items = matching_items + [kube_pod]
- expect(filter_by_label(items, app: 'foo')).to eq(matching_items)
+ expect(filter_by_project_environment(items, 'my-cool-app', 'production')).to eq(matching_items)
end
end
diff --git a/spec/lib/gitlab/middleware/basic_health_check_spec.rb b/spec/lib/gitlab/middleware/basic_health_check_spec.rb
index 187d903a5e1..86bdc479b66 100644
--- a/spec/lib/gitlab/middleware/basic_health_check_spec.rb
+++ b/spec/lib/gitlab/middleware/basic_health_check_spec.rb
@@ -28,6 +28,35 @@ describe Gitlab::Middleware::BasicHealthCheck do
end
end
+ context 'with X-Forwarded-For headers' do
+ let(:load_balancer_ip) { '1.2.3.4' }
+
+ before do
+ env['HTTP_X_FORWARDED_FOR'] = "#{load_balancer_ip}, 127.0.0.1"
+ env['REMOTE_ADDR'] = '127.0.0.1'
+ env['PATH_INFO'] = described_class::HEALTH_PATH
+ end
+
+ it 'returns 200 response when endpoint is allowed' do
+ allow(Settings.monitoring).to receive(:ip_whitelist).and_return([load_balancer_ip])
+ expect(app).not_to receive(:call)
+
+ response = middleware.call(env)
+
+ expect(response[0]).to eq(200)
+ expect(response[1]).to eq({ 'Content-Type' => 'text/plain' })
+ expect(response[2]).to eq(['GitLab OK'])
+ end
+
+ it 'returns 404 when whitelist is not configured' do
+ allow(Settings.monitoring).to receive(:ip_whitelist).and_return([])
+
+ response = middleware.call(env)
+
+ expect(response[0]).to eq(404)
+ end
+ end
+
context 'whitelisted IP' do
before do
env['REMOTE_ADDR'] = '127.0.0.1'
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
index 6831274d37c..4a41d5cf51e 100644
--- a/spec/lib/gitlab/project_search_results_spec.rb
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -412,4 +412,36 @@ describe Gitlab::ProjectSearchResults do
end
end
end
+
+ describe 'user search' do
+ it 'returns the user belonging to the project matching the search query' do
+ project = create(:project)
+
+ user1 = create(:user, username: 'gob_bluth')
+ create(:project_member, :developer, user: user1, project: project)
+
+ user2 = create(:user, username: 'michael_bluth')
+ create(:project_member, :developer, user: user2, project: project)
+
+ create(:user, username: 'gob_2018')
+
+ result = described_class.new(user, project, 'gob').objects('users')
+
+ expect(result).to eq [user1]
+ end
+
+ it 'returns the user belonging to the group matching the search query' do
+ group = create(:group)
+ project = create(:project, namespace: group)
+
+ user1 = create(:user, username: 'gob_bluth')
+ create(:group_member, :developer, user: user1, group: group)
+
+ create(:user, username: 'gob_2018')
+
+ result = described_class.new(user, project, 'gob').objects('users')
+
+ expect(result).to eq [user1]
+ end
+ end
end
diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb
index c9b93a84aef..8c2fc048a54 100644
--- a/spec/lib/gitlab/project_template_spec.rb
+++ b/spec/lib/gitlab/project_template_spec.rb
@@ -9,6 +9,7 @@ describe Gitlab::ProjectTemplate do
described_class.new('express', 'NodeJS Express', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/express'),
described_class.new('iosswift', 'iOS (Swift)', 'A ready-to-go template for use with iOS Swift apps.', 'https://gitlab.com/gitlab-org/project-templates/iosswift'),
described_class.new('dotnetcore', '.NET Core', 'A .NET Core console application template, customizable for any .NET Core project', 'https://gitlab.com/gitlab-org/project-templates/dotnetcore'),
+ described_class.new('android', 'Android', 'A ready-to-go template for use with Android apps.', 'https://gitlab.com/gitlab-org/project-templates/android'),
described_class.new('gomicro', 'Go Micro', 'Go Micro is a framework for micro service development.', 'https://gitlab.com/gitlab-org/project-templates/go-micro'),
described_class.new('hugo', 'Pages/Hugo', 'Everything you need to get started using a Hugo Pages site.', 'https://gitlab.com/pages/hugo'),
described_class.new('jekyll', 'Pages/Jekyll', 'Everything you need to get started using a Jekyll Pages site.', 'https://gitlab.com/pages/jekyll'),
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 4139d1c650c..d982053d92e 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Gitlab::ReferenceExtractor do
diff --git a/spec/lib/gitlab/request_context_spec.rb b/spec/lib/gitlab/request_context_spec.rb
index fd443cc1f71..23e45aff1c5 100644
--- a/spec/lib/gitlab/request_context_spec.rb
+++ b/spec/lib/gitlab/request_context_spec.rb
@@ -6,6 +6,31 @@ describe Gitlab::RequestContext do
let(:app) { -> (env) {} }
let(:env) { Hash.new }
+ context 'with X-Forwarded-For headers', :request_store do
+ let(:load_balancer_ip) { '1.2.3.4' }
+ let(:headers) do
+ {
+ 'HTTP_X_FORWARDED_FOR' => "#{load_balancer_ip}, 127.0.0.1",
+ 'REMOTE_ADDR' => '127.0.0.1'
+ }
+ end
+
+ let(:env) { Rack::MockRequest.env_for("/").merge(headers) }
+
+ it 'returns the load balancer IP' do
+ client_ip = nil
+
+ endpoint = proc do
+ client_ip = Gitlab::SafeRequestStore[:client_ip]
+ [200, {}, ["Hello"]]
+ end
+
+ described_class.new(endpoint).call(env)
+
+ expect(client_ip).to eq(load_balancer_ip)
+ end
+ end
+
context 'when RequestStore::Middleware is used' do
around do |example|
RequestStore::Middleware.new(-> (env) { example.run }).call({})
@@ -15,7 +40,7 @@ describe Gitlab::RequestContext do
let(:ip) { '192.168.1.11' }
before do
- allow_any_instance_of(ActionDispatch::Request).to receive(:ip).and_return(ip)
+ allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip)
described_class.new(app).call(env)
end
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index 87288baedb0..4b57eecff93 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -121,6 +121,22 @@ describe Gitlab::SearchResults do
results.objects('issues')
end
end
+
+ describe '#users' do
+ it 'does not call the UsersFinder when the current_user is not allowed to read users list' do
+ allow(Ability).to receive(:allowed?).and_return(false)
+
+ expect(UsersFinder).not_to receive(:new).with(user, search: 'foo').and_call_original
+
+ results.objects('users')
+ end
+
+ it 'calls the UsersFinder' do
+ expect(UsersFinder).to receive(:new).with(user, search: 'foo').and_call_original
+
+ results.objects('users')
+ end
+ end
end
it 'does not list issues on private projects' do
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index d6aadf0f7de..e2f09de2808 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -8,6 +8,7 @@ describe Gitlab::Shell do
let(:gitlab_shell) { described_class.new }
let(:popen_vars) { { 'GIT_TERMINAL_PROMPT' => ENV['GIT_TERMINAL_PROMPT'] } }
let(:timeout) { Gitlab.config.gitlab_shell.git_timeout }
+ let(:gitlab_authorized_keys) { double }
before do
allow(Project).to receive(:find).and_return(project)
@@ -49,13 +50,38 @@ describe Gitlab::Shell do
describe '#add_key' do
context 'when authorized_keys_enabled is true' do
- it 'removes trailing garbage' do
- allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
- expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
- [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
- )
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ allow(gitlab_shell)
+ .to receive(:gitlab_shell_keys_path)
+ .and_return(:gitlab_shell_keys_path)
+ end
+
+ it 'calls #gitlab_shell_fast_execute with add-key command' do
+ expect(gitlab_shell)
+ .to receive(:gitlab_shell_fast_execute)
+ .with([
+ :gitlab_shell_keys_path,
+ 'add-key',
+ 'key-123',
+ 'ssh-rsa foobar'
+ ])
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ end
+ end
+
+ context 'authorized_keys_file set' do
+ it 'calls Gitlab::AuthorizedKeys#add_key with id and key' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ expect(gitlab_authorized_keys)
+ .to receive(:add_key)
+ .with('key-123', 'ssh-rsa foobar')
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar')
+ end
end
end
@@ -64,10 +90,24 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: false)
end
- it 'does nothing' do
- expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ end
+
+ it 'does nothing' do
+ expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ end
+ end
+
+ context 'authorized_keys_file set' do
+ it 'does nothing' do
+ expect(Gitlab::AuthorizedKeys).not_to receive(:new)
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ end
end
end
@@ -76,24 +116,89 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: nil)
end
- it 'removes trailing garbage' do
- allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
- expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
- [:gitlab_shell_keys_path, 'add-key', 'key-123', 'ssh-rsa foobar']
- )
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ allow(gitlab_shell)
+ .to receive(:gitlab_shell_keys_path)
+ .and_return(:gitlab_shell_keys_path)
+ end
+
+ it 'calls #gitlab_shell_fast_execute with add-key command' do
+ expect(gitlab_shell)
+ .to receive(:gitlab_shell_fast_execute)
+ .with([
+ :gitlab_shell_keys_path,
+ 'add-key',
+ 'key-123',
+ 'ssh-rsa foobar'
+ ])
+
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ end
+ end
+
+ context 'authorized_keys_file set' do
+ it 'calls Gitlab::AuthorizedKeys#add_key with id and key' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+
+ expect(gitlab_authorized_keys)
+ .to receive(:add_key)
+ .with('key-123', 'ssh-rsa foobar')
- gitlab_shell.add_key('key-123', 'ssh-rsa foobar trailing garbage')
+ gitlab_shell.add_key('key-123', 'ssh-rsa foobar')
+ end
end
end
end
describe '#batch_add_keys' do
+ let(:keys) { [double(shell_id: 'key-123', key: 'ssh-rsa foobar')] }
+
context 'when authorized_keys_enabled is true' do
- it 'instantiates KeyAdder' do
- expect_any_instance_of(Gitlab::Shell::KeyAdder).to receive(:add_key).with('key-123', 'ssh-rsa foobar')
+ context 'authorized_keys_file not set' do
+ let(:io) { double }
+
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ end
+
+ context 'valid keys' do
+ before do
+ allow(gitlab_shell)
+ .to receive(:gitlab_shell_keys_path)
+ .and_return(:gitlab_shell_keys_path)
+ end
+
+ it 'calls gitlab-keys with batch-add-keys command' do
+ expect(IO)
+ .to receive(:popen)
+ .with("gitlab_shell_keys_path batch-add-keys", 'w')
+ .and_yield(io)
+
+ expect(io).to receive(:puts).with("key-123\tssh-rsa foobar")
+ expect(gitlab_shell.batch_add_keys(keys)).to be_truthy
+ end
+ end
+
+ context 'invalid keys' do
+ let(:keys) { [double(shell_id: 'key-123', key: "ssh-rsa A\tSDFA\nSGADG")] }
+
+ it 'catches failure and returns false' do
+ expect(gitlab_shell.batch_add_keys(keys)).to be_falsey
+ end
+ end
+ end
- gitlab_shell.batch_add_keys do |adder|
- adder.add_key('key-123', 'ssh-rsa foobar')
+ context 'authorized_keys_file set' do
+ it 'calls Gitlab::AuthorizedKeys#batch_add_keys with keys to be added' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+
+ expect(gitlab_authorized_keys)
+ .to receive(:batch_add_keys)
+ .with(keys)
+
+ gitlab_shell.batch_add_keys(keys)
end
end
end
@@ -103,11 +208,23 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: false)
end
- it 'does nothing' do
- expect_any_instance_of(Gitlab::Shell::KeyAdder).not_to receive(:add_key)
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ end
+
+ it 'does nothing' do
+ expect(IO).not_to receive(:popen)
+
+ gitlab_shell.batch_add_keys(keys)
+ end
+ end
+
+ context 'authorized_keys_file set' do
+ it 'does nothing' do
+ expect(Gitlab::AuthorizedKeys).not_to receive(:new)
- gitlab_shell.batch_add_keys do |adder|
- adder.add_key('key-123', 'ssh-rsa foobar')
+ gitlab_shell.batch_add_keys(keys)
end
end
end
@@ -117,11 +234,37 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: nil)
end
- it 'instantiates KeyAdder' do
- expect_any_instance_of(Gitlab::Shell::KeyAdder).to receive(:add_key).with('key-123', 'ssh-rsa foobar')
+ context 'authorized_keys_file not set' do
+ let(:io) { double }
- gitlab_shell.batch_add_keys do |adder|
- adder.add_key('key-123', 'ssh-rsa foobar')
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ allow(gitlab_shell)
+ .to receive(:gitlab_shell_keys_path)
+ .and_return(:gitlab_shell_keys_path)
+ end
+
+ it 'calls gitlab-keys with batch-add-keys command' do
+ expect(IO)
+ .to receive(:popen)
+ .with("gitlab_shell_keys_path batch-add-keys", 'w')
+ .and_yield(io)
+
+ expect(io).to receive(:puts).with("key-123\tssh-rsa foobar")
+
+ gitlab_shell.batch_add_keys(keys)
+ end
+ end
+
+ context 'authorized_keys_file set' do
+ it 'calls Gitlab::AuthorizedKeys#batch_add_keys with keys to be added' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+
+ expect(gitlab_authorized_keys)
+ .to receive(:batch_add_keys)
+ .with(keys)
+
+ gitlab_shell.batch_add_keys(keys)
end
end
end
@@ -129,13 +272,34 @@ describe Gitlab::Shell do
describe '#remove_key' do
context 'when authorized_keys_enabled is true' do
- it 'removes trailing garbage' do
- allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
- expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
- [:gitlab_shell_keys_path, 'rm-key', 'key-123', 'ssh-rsa foobar']
- )
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ allow(gitlab_shell)
+ .to receive(:gitlab_shell_keys_path)
+ .and_return(:gitlab_shell_keys_path)
+ end
- gitlab_shell.remove_key('key-123', 'ssh-rsa foobar')
+ it 'calls #gitlab_shell_fast_execute with rm-key command' do
+ expect(gitlab_shell)
+ .to receive(:gitlab_shell_fast_execute)
+ .with([
+ :gitlab_shell_keys_path,
+ 'rm-key',
+ 'key-123'
+ ])
+
+ gitlab_shell.remove_key('key-123')
+ end
+ end
+
+ context 'authorized_keys_file not set' do
+ it 'calls Gitlab::AuthorizedKeys#rm_key with the key to be removed' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ expect(gitlab_authorized_keys).to receive(:rm_key).with('key-123')
+
+ gitlab_shell.remove_key('key-123')
+ end
end
end
@@ -144,10 +308,24 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: false)
end
- it 'does nothing' do
- expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ end
+
+ it 'does nothing' do
+ expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
- gitlab_shell.remove_key('key-123', 'ssh-rsa foobar')
+ gitlab_shell.remove_key('key-123')
+ end
+ end
+
+ context 'authorized_keys_file set' do
+ it 'does nothing' do
+ expect(Gitlab::AuthorizedKeys).not_to receive(:new)
+
+ gitlab_shell.remove_key('key-123')
+ end
end
end
@@ -156,232 +334,256 @@ describe Gitlab::Shell do
stub_application_setting(authorized_keys_enabled: nil)
end
- it 'removes trailing garbage' do
- allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
- expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
- [:gitlab_shell_keys_path, 'rm-key', 'key-123', 'ssh-rsa foobar']
- )
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ allow(gitlab_shell)
+ .to receive(:gitlab_shell_keys_path)
+ .and_return(:gitlab_shell_keys_path)
+ end
+
+ it 'calls #gitlab_shell_fast_execute with rm-key command' do
+ expect(gitlab_shell)
+ .to receive(:gitlab_shell_fast_execute)
+ .with([
+ :gitlab_shell_keys_path,
+ 'rm-key',
+ 'key-123'
+ ])
- gitlab_shell.remove_key('key-123', 'ssh-rsa foobar')
+ gitlab_shell.remove_key('key-123')
+ end
end
- end
- context 'when key content is not given' do
- it 'calls rm-key with only one argument' do
- allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
- expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
- [:gitlab_shell_keys_path, 'rm-key', 'key-123']
- )
+ context 'authorized_keys_file not set' do
+ it 'calls Gitlab::AuthorizedKeys#rm_key with the key to be removed' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ expect(gitlab_authorized_keys).to receive(:rm_key).with('key-123')
- gitlab_shell.remove_key('key-123')
+ gitlab_shell.remove_key('key-123')
+ end
end
end
end
describe '#remove_all_keys' do
context 'when authorized_keys_enabled is true' do
- it 'removes trailing garbage' do
- allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
- expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with([:gitlab_shell_keys_path, 'clear'])
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ allow(gitlab_shell)
+ .to receive(:gitlab_shell_keys_path)
+ .and_return(:gitlab_shell_keys_path)
+ end
- gitlab_shell.remove_all_keys
- end
- end
+ it 'calls #gitlab_shell_fast_execute with clear command' do
+ expect(gitlab_shell)
+ .to receive(:gitlab_shell_fast_execute)
+ .with([:gitlab_shell_keys_path, 'clear'])
- context 'when authorized_keys_enabled is false' do
- before do
- stub_application_setting(authorized_keys_enabled: false)
+ gitlab_shell.remove_all_keys
+ end
end
- it 'does nothing' do
- expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
+ context 'authorized_keys_file set' do
+ it 'calls Gitlab::AuthorizedKeys#clear' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ expect(gitlab_authorized_keys).to receive(:clear)
- gitlab_shell.remove_all_keys
+ gitlab_shell.remove_all_keys
+ end
end
end
- context 'when authorized_keys_enabled is nil' do
+ context 'when authorized_keys_enabled is false' do
before do
- stub_application_setting(authorized_keys_enabled: nil)
+ stub_application_setting(authorized_keys_enabled: false)
end
- it 'removes trailing garbage' do
- allow(gitlab_shell).to receive(:gitlab_shell_keys_path).and_return(:gitlab_shell_keys_path)
- expect(gitlab_shell).to receive(:gitlab_shell_fast_execute).with(
- [:gitlab_shell_keys_path, 'clear']
- )
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ end
- gitlab_shell.remove_all_keys
- end
- end
- end
+ it 'does nothing' do
+ expect(gitlab_shell).not_to receive(:gitlab_shell_fast_execute)
- describe '#remove_keys_not_found_in_db' do
- context 'when keys are in the file that are not in the DB' do
- before do
- gitlab_shell.remove_all_keys
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF')
- @another_key = create(:key) # this one IS in the DB
+ gitlab_shell.remove_all_keys
+ end
end
- it 'removes the keys' do
- expect(find_in_authorized_keys_file(1234)).to be_truthy
- expect(find_in_authorized_keys_file(9876)).to be_truthy
- expect(find_in_authorized_keys_file(@another_key.id)).to be_truthy
- gitlab_shell.remove_keys_not_found_in_db
- expect(find_in_authorized_keys_file(1234)).to be_falsey
- expect(find_in_authorized_keys_file(9876)).to be_falsey
- expect(find_in_authorized_keys_file(@another_key.id)).to be_truthy
+ context 'authorized_keys_file set' do
+ it 'does nothing' do
+ expect(Gitlab::AuthorizedKeys).not_to receive(:new)
+
+ gitlab_shell.remove_all_keys
+ end
end
end
- context 'when keys there are duplicate keys in the file that are not in the DB' do
+ context 'when authorized_keys_enabled is nil' do
before do
- gitlab_shell.remove_all_keys
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
- gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ stub_application_setting(authorized_keys_enabled: nil)
end
- it 'removes the keys' do
- expect(find_in_authorized_keys_file(1234)).to be_truthy
- gitlab_shell.remove_keys_not_found_in_db
- expect(find_in_authorized_keys_file(1234)).to be_falsey
- end
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ allow(gitlab_shell)
+ .to receive(:gitlab_shell_keys_path)
+ .and_return(:gitlab_shell_keys_path)
+ end
- it 'does not run remove more than once per key (in a batch)' do
- expect(gitlab_shell).to receive(:remove_key).with('key-1234').once
- gitlab_shell.remove_keys_not_found_in_db
- end
- end
+ it 'calls #gitlab_shell_fast_execute with clear command' do
+ expect(gitlab_shell)
+ .to receive(:gitlab_shell_fast_execute)
+ .with([:gitlab_shell_keys_path, 'clear'])
- context 'when keys there are duplicate keys in the file that ARE in the DB' do
- before do
- gitlab_shell.remove_all_keys
- @key = create(:key)
- gitlab_shell.add_key(@key.shell_id, @key.key)
+ gitlab_shell.remove_all_keys
+ end
end
- it 'does not remove the key' do
- gitlab_shell.remove_keys_not_found_in_db
- expect(find_in_authorized_keys_file(@key.id)).to be_truthy
- end
+ context 'authorized_keys_file set' do
+ it 'calls Gitlab::AuthorizedKeys#clear' do
+ expect(Gitlab::AuthorizedKeys).to receive(:new).and_return(gitlab_authorized_keys)
+ expect(gitlab_authorized_keys).to receive(:clear)
- it 'does not need to run a SELECT query for that batch, on account of that key' do
- expect_any_instance_of(ActiveRecord::Relation).not_to receive(:pluck)
- gitlab_shell.remove_keys_not_found_in_db
+ gitlab_shell.remove_all_keys
+ end
end
end
+ end
- unless ENV['CI'] # Skip in CI, it takes 1 minute
- context 'when the first batch can be skipped, but the next batch has keys that are not in the DB' do
+ describe '#remove_keys_not_found_in_db' do
+ context 'when keys are in the file that are not in the DB' do
+ context 'authorized_keys_file not set' do
before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
gitlab_shell.remove_all_keys
- 100.times { |i| create(:key) } # first batch is all in the DB
gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF')
+ @another_key = create(:key) # this one IS in the DB
end
- it 'removes the keys not in the DB' do
- expect(find_in_authorized_keys_file(1234)).to be_truthy
+ it 'removes the keys' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234')
+ expect(gitlab_shell).to receive(:remove_key).with('key-9876')
+ expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@another_key.id}")
+
gitlab_shell.remove_keys_not_found_in_db
- expect(find_in_authorized_keys_file(1234)).to be_falsey
end
end
- end
- end
- describe '#batch_read_key_ids' do
- context 'when there are keys in the authorized_keys file' do
- before do
- gitlab_shell.remove_all_keys
- (1..4).each do |i|
- gitlab_shell.add_key("key-#{i}", "ssh-rsa ASDFASDF#{i}")
+ context 'authorized_keys_file set' do
+ before do
+ gitlab_shell.remove_all_keys
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ gitlab_shell.add_key('key-9876', 'ssh-rsa ASDFASDF')
+ @another_key = create(:key) # this one IS in the DB
end
- end
- it 'iterates over the key IDs in the file, in batches' do
- loop_count = 0
- first_batch = [1, 2]
- second_batch = [3, 4]
+ it 'removes the keys' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234')
+ expect(gitlab_shell).to receive(:remove_key).with('key-9876')
+ expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@another_key.id}")
- gitlab_shell.batch_read_key_ids(batch_size: 2) do |batch|
- expected = (loop_count == 0 ? first_batch : second_batch)
- expect(batch).to eq(expected)
- loop_count += 1
+ gitlab_shell.remove_keys_not_found_in_db
end
end
end
- end
- describe '#list_key_ids' do
- context 'when there are keys in the authorized_keys file' do
- before do
- gitlab_shell.remove_all_keys
- (1..4).each do |i|
- gitlab_shell.add_key("key-#{i}", "ssh-rsa ASDFASDF#{i}")
+ context 'when keys there are duplicate keys in the file that are not in the DB' do
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ gitlab_shell.remove_all_keys
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ end
+
+ it 'removes the keys' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234')
+
+ gitlab_shell.remove_keys_not_found_in_db
end
end
- it 'outputs the key IDs in the file, separated by newlines' do
- ids = []
- gitlab_shell.list_key_ids do |io|
- io.each do |line|
- ids << line
- end
+ context 'authorized_keys_file set' do
+ before do
+ gitlab_shell.remove_all_keys
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
end
- expect(ids).to eq(%W{1\n 2\n 3\n 4\n})
- end
- end
+ it 'removes the keys' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234')
- context 'when there are no keys in the authorized_keys file' do
- before do
- gitlab_shell.remove_all_keys
+ gitlab_shell.remove_keys_not_found_in_db
+ end
end
+ end
- it 'outputs nothing, not even an empty string' do
- ids = []
- gitlab_shell.list_key_ids do |io|
- io.each do |line|
- ids << line
- end
+ context 'when keys there are duplicate keys in the file that ARE in the DB' do
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ gitlab_shell.remove_all_keys
+ @key = create(:key)
+ gitlab_shell.add_key(@key.shell_id, @key.key)
end
- expect(ids).to eq([])
+ it 'does not remove the key' do
+ expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@key.id}")
+
+ gitlab_shell.remove_keys_not_found_in_db
+ end
end
- end
- end
- describe Gitlab::Shell::KeyAdder do
- describe '#add_key' do
- it 'removes trailing garbage' do
- io = spy(:io)
- adder = described_class.new(io)
+ context 'authorized_keys_file set' do
+ before do
+ gitlab_shell.remove_all_keys
+ @key = create(:key)
+ gitlab_shell.add_key(@key.shell_id, @key.key)
+ end
- adder.add_key('key-42', "ssh-rsa foo bar\tbaz")
+ it 'does not remove the key' do
+ expect(gitlab_shell).not_to receive(:remove_key).with("key-#{@key.id}")
- expect(io).to have_received(:puts).with("key-42\tssh-rsa foo")
+ gitlab_shell.remove_keys_not_found_in_db
+ end
end
+ end
- it 'handles multiple spaces in the key' do
- io = spy(:io)
- adder = described_class.new(io)
+ unless ENV['CI'] # Skip in CI, it takes 1 minute
+ context 'when the first batch can be skipped, but the next batch has keys that are not in the DB' do
+ context 'authorized_keys_file not set' do
+ before do
+ stub_gitlab_shell_setting(authorized_keys_file: nil)
+ gitlab_shell.remove_all_keys
+ 100.times { |i| create(:key) } # first batch is all in the DB
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ end
- adder.add_key('key-42', "ssh-rsa foo")
+ it 'removes the keys not in the DB' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234')
- expect(io).to have_received(:puts).with("key-42\tssh-rsa foo")
- end
+ gitlab_shell.remove_keys_not_found_in_db
+ end
+ end
- it 'raises an exception if the key contains a tab' do
- expect do
- described_class.new(StringIO.new).add_key('key-42', "ssh-rsa\tfoobar")
- end.to raise_error(Gitlab::Shell::Error)
- end
+ context 'authorized_keys_file set' do
+ before do
+ gitlab_shell.remove_all_keys
+ 100.times { |i| create(:key) } # first batch is all in the DB
+ gitlab_shell.add_key('key-1234', 'ssh-rsa ASDFASDF')
+ end
+
+ it 'removes the keys not in the DB' do
+ expect(gitlab_shell).to receive(:remove_key).with('key-1234')
- it 'raises an exception if the key contains a newline' do
- expect do
- described_class.new(StringIO.new).add_key('key-42', "ssh-rsa foobar\nssh-rsa pawned")
- end.to raise_error(Gitlab::Shell::Error)
+ gitlab_shell.remove_keys_not_found_in_db
+ end
+ end
end
end
end
@@ -566,12 +768,4 @@ describe Gitlab::Shell do
end
end
end
-
- def find_in_authorized_keys_file(key_id)
- gitlab_shell.batch_read_key_ids do |ids|
- return true if ids.include?(key_id) # rubocop:disable Cop/AvoidReturnFromBlocks
- end
-
- false
- end
end
diff --git a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
index ff8c0825ee4..1a5a38b5d99 100644
--- a/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
+++ b/spec/lib/gitlab/sidekiq_middleware/memory_killer_spec.rb
@@ -54,7 +54,7 @@ describe Gitlab::SidekiqMiddleware::MemoryKiller do
expect(Process).to receive(:kill).with('SIGTSTP', pid).ordered
expect(Process).to receive(:kill).with('SIGTERM', pid).ordered
- expect(Process).to receive(:kill).with('SIGKILL', "-#{pid}").ordered
+ expect(Process).to receive(:kill).with('SIGKILL', 0).ordered
run
end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index cd9e4d48cd1..549cc5ac057 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -13,6 +13,8 @@ describe Gitlab::UsageData do
create(:service, project: projects[0], type: 'SlackSlashCommandsService', active: true)
create(:service, project: projects[1], type: 'SlackService', active: true)
create(:service, project: projects[2], type: 'SlackService', active: true)
+ create(:project_error_tracking_setting, project: projects[0])
+ create(:project_error_tracking_setting, project: projects[1], enabled: false)
gcp_cluster = create(:cluster, :provided_by_gcp)
create(:cluster, :provided_by_user)
@@ -117,6 +119,7 @@ describe Gitlab::UsageData do
projects_slack_slash_active
projects_prometheus_active
projects_with_repositories_enabled
+ projects_with_error_tracking_enabled
pages_domains
protected_branches
releases
@@ -146,6 +149,7 @@ describe Gitlab::UsageData do
expect(count_data[:projects_slack_notifications_active]).to eq(2)
expect(count_data[:projects_slack_slash_active]).to eq(1)
expect(count_data[:projects_with_repositories_enabled]).to eq(2)
+ expect(count_data[:projects_with_error_tracking_enabled]).to eq(1)
expect(count_data[:clusters_enabled]).to eq(7)
expect(count_data[:project_clusters_enabled]).to eq(6)
diff --git a/spec/lib/gitlab/user_extractor_spec.rb b/spec/lib/gitlab/user_extractor_spec.rb
index fcc05ab3a0c..b86ec5445b8 100644
--- a/spec/lib/gitlab/user_extractor_spec.rb
+++ b/spec/lib/gitlab/user_extractor_spec.rb
@@ -38,6 +38,18 @@ describe Gitlab::UserExtractor do
expect(extractor.users).to include(user)
end
+
+ context 'input as array of strings' do
+ it 'is treated as one string' do
+ extractor = described_class.new(text.lines)
+
+ user_1 = create(:user, username: "USER-1")
+ user_4 = create(:user, username: "USER-4")
+ user_email = create(:user, email: 'user@gitlab.org')
+
+ expect(extractor.users).to contain_exactly(user_1, user_4, user_email)
+ end
+ end
end
describe '#matches' do
@@ -48,6 +60,14 @@ describe Gitlab::UserExtractor do
it 'includes all mentioned usernames' do
expect(extractor.matches[:usernames]).to contain_exactly('user-1', 'user-2', 'user-4')
end
+
+ context 'input has no matching e-mail or usernames' do
+ it 'returns an empty list of users' do
+ extractor = described_class.new('My test')
+
+ expect(extractor.users).to be_empty
+ end
+ end
end
describe '#references' do
diff --git a/spec/lib/gitlab/utils_spec.rb b/spec/lib/gitlab/utils_spec.rb
index 8f5029b3565..4645339f439 100644
--- a/spec/lib/gitlab/utils_spec.rb
+++ b/spec/lib/gitlab/utils_spec.rb
@@ -213,4 +213,22 @@ describe Gitlab::Utils do
expect(subject[:variables].first[:key]).to eq('VAR1')
end
end
+
+ describe '.try_megabytes_to_bytes' do
+ context 'when the size can be converted to megabytes' do
+ it 'returns the size in megabytes' do
+ size = described_class.try_megabytes_to_bytes(1)
+
+ expect(size).to eq(1.megabytes)
+ end
+ end
+
+ context 'when the size can not be converted to megabytes' do
+ it 'returns the input size' do
+ size = described_class.try_megabytes_to_bytes('foo')
+
+ expect(size).to eq('foo')
+ end
+ end
+ end
end
diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb
index e2134dc279c..1fefc947636 100644
--- a/spec/lib/google_api/cloud_platform/client_spec.rb
+++ b/spec/lib/google_api/cloud_platform/client_spec.rb
@@ -97,6 +97,12 @@ describe GoogleApi::CloudPlatform::Client do
"node_config": {
"machine_type": machine_type
},
+ "master_auth": {
+ "username": "admin",
+ "client_certificate_config": {
+ issue_client_certificate: true
+ }
+ },
"legacy_abac": {
"enabled": true
}
@@ -122,6 +128,12 @@ describe GoogleApi::CloudPlatform::Client do
"node_config": {
"machine_type": machine_type
},
+ "master_auth": {
+ "username": "admin",
+ "client_certificate_config": {
+ issue_client_certificate: true
+ }
+ },
"legacy_abac": {
"enabled": false
}
diff --git a/spec/migrations/add_foreign_keys_to_todos_spec.rb b/spec/migrations/add_foreign_keys_to_todos_spec.rb
index efd87173b9c..2500e2f8333 100644
--- a/spec/migrations/add_foreign_keys_to_todos_spec.rb
+++ b/spec/migrations/add_foreign_keys_to_todos_spec.rb
@@ -36,7 +36,7 @@ describe AddForeignKeysToTodos, :migration do
end
context 'add foreign key on note_id' do
- let(:note) { create(:note) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let(:note) { table(:notes).create! }
let!(:todo_with_note) { create_todo(note_id: note.id) }
let!(:todo_with_invalid_note) { create_todo(note_id: 4711) }
let!(:todo_without_note) { create_todo(note_id: nil) }
diff --git a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
index d8dd7a2fb83..13dc62595b5 100644
--- a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
+++ b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
@@ -1,23 +1,21 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170508170547_add_head_pipeline_for_each_merge_request.rb')
-describe AddHeadPipelineForEachMergeRequest, :delete do
- include ProjectForksHelper
-
+describe AddHeadPipelineForEachMergeRequest, :migration do
let(:migration) { described_class.new }
- let!(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:other_project) { fork_project(project) }
+ let!(:project) { table(:projects).create! }
+ let!(:other_project) { table(:projects).create! }
- let!(:pipeline_1) { create(:ci_pipeline, project: project, ref: "branch_1") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:pipeline_2) { create(:ci_pipeline, project: other_project, ref: "branch_1") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:pipeline_3) { create(:ci_pipeline, project: other_project, ref: "branch_1") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:pipeline_4) { create(:ci_pipeline, project: project, ref: "branch_2") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:pipeline_1) { table(:ci_pipelines).create!(project_id: project.id, ref: "branch_1") }
+ let!(:pipeline_2) { table(:ci_pipelines).create!(project_id: other_project.id, ref: "branch_1") }
+ let!(:pipeline_3) { table(:ci_pipelines).create!(project_id: other_project.id, ref: "branch_1") }
+ let!(:pipeline_4) { table(:ci_pipelines).create!(project_id: project.id, ref: "branch_2") }
- let!(:mr_1) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_1", target_branch: "target_1") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:mr_2) { create(:merge_request, source_project: other_project, target_project: project, source_branch: "branch_1", target_branch: "target_2") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:mr_3) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_2", target_branch: "master") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:mr_4) { create(:merge_request, source_project: project, target_project: project, source_branch: "branch_3", target_branch: "master") } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:mr_1) { table(:merge_requests).create!(source_project_id: project.id, target_project_id: project.id, source_branch: "branch_1", target_branch: "target_1") }
+ let!(:mr_2) { table(:merge_requests).create!(source_project_id: other_project.id, target_project_id: project.id, source_branch: "branch_1", target_branch: "target_2") }
+ let!(:mr_3) { table(:merge_requests).create!(source_project_id: project.id, target_project_id: project.id, source_branch: "branch_2", target_branch: "master") }
+ let!(:mr_4) { table(:merge_requests).create!(source_project_id: project.id, target_project_id: project.id, source_branch: "branch_3", target_branch: "master") }
context "#up" do
context "when source_project and source_branch of pipeline are the same of merge request" do
diff --git a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
index 19f06810e54..09c78d02890 100644
--- a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
+++ b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
@@ -3,12 +3,30 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170803090603_calculate_conv_dev_index_percentages.rb')
-describe CalculateConvDevIndexPercentages, :delete do
+describe CalculateConvDevIndexPercentages, :migration do
let(:migration) { described_class.new }
let!(:conv_dev_index) do
- create(:conversational_development_index_metric, # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ table(:conversational_development_index_metrics).create!(
+ leader_issues: 9.256,
leader_notes: 0,
+ leader_milestones: 16.2456,
+ leader_boards: 5.2123,
+ leader_merge_requests: 1.2,
+ leader_ci_pipelines: 12.1234,
+ leader_environments: 3.3333,
+ leader_deployments: 1.200,
+ leader_projects_prometheus_active: 0.111,
+ leader_service_desk_issues: 15.891,
+ instance_issues: 1.234,
+ instance_notes: 28.123,
instance_milestones: 0,
+ instance_boards: 3.254,
+ instance_merge_requests: 0.6,
+ instance_ci_pipelines: 2.344,
+ instance_environments: 2.2222,
+ instance_deployments: 0.771,
+ instance_projects_prometheus_active: 0.109,
+ instance_service_desk_issues: 13.345,
percentage_issues: 0,
percentage_notes: 0,
percentage_milestones: 0,
diff --git a/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb b/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb
index 8f40ac3e38b..0e6bded29b4 100644
--- a/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb
+++ b/spec/migrations/cleanup_nonexisting_namespace_pending_delete_projects_spec.rb
@@ -1,20 +1,17 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170816102555_cleanup_nonexisting_namespace_pending_delete_projects.rb')
-describe CleanupNonexistingNamespacePendingDeleteProjects do
- before do
- # Stub after_save callbacks that will fail when Project has invalid namespace
- allow_any_instance_of(Project).to receive(:ensure_storage_path_exist).and_return(nil)
- allow_any_instance_of(Project).to receive(:update_project_statistics).and_return(nil)
- end
+describe CleanupNonexistingNamespacePendingDeleteProjects, :migration do
+ let(:projects) { table(:projects) }
+ let(:namespaces) { table(:namespaces) }
describe '#up' do
- set(:some_project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:some_project) { projects.create! }
+ let(:namespace) { namespaces.create!(name: 'test', path: 'test') }
it 'only cleans up when namespace does not exist' do
- create(:project, pending_delete: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs
- project = build(:project, pending_delete: true, namespace: nil, namespace_id: Namespace.maximum(:id).to_i.succ) # rubocop:disable RSpec/FactoriesInMigrationSpecs
- project.save(validate: false)
+ projects.create!(pending_delete: true, namespace_id: namespace.id)
+ project = projects.create!(pending_delete: true, namespace_id: 0)
expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id]])
@@ -22,7 +19,7 @@ describe CleanupNonexistingNamespacePendingDeleteProjects do
end
it 'does nothing when no pending delete projects without namespace found' do
- create(:project, pending_delete: true, namespace: create(:namespace)) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ projects.create!(pending_delete: true, namespace_id: namespace.id)
expect(NamespacelessProjectDestroyWorker).not_to receive(:bulk_perform_async)
diff --git a/spec/migrations/delete_inconsistent_internal_id_records_spec.rb b/spec/migrations/delete_inconsistent_internal_id_records_spec.rb
index e2ce69a7bb1..58b8b4a16f0 100644
--- a/spec/migrations/delete_inconsistent_internal_id_records_spec.rb
+++ b/spec/migrations/delete_inconsistent_internal_id_records_spec.rb
@@ -1,25 +1,36 @@
# frozen_string_literal: true
-# rubocop:disable RSpec/FactoriesInMigrationSpecs
+
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20180723130817_delete_inconsistent_internal_id_records.rb')
describe DeleteInconsistentInternalIdRecords, :migration do
- let!(:project1) { create(:project) }
- let!(:project2) { create(:project) }
- let!(:project3) { create(:project) }
+ 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_id_query) { ->(project) { InternalId.where(usage: InternalId.usages[scope.to_s.tableize], project: project) } }
+ 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
- 3.times { create(scope, project: project1) }
- 3.times { create(scope, project: project2) }
- 3.times { create(scope, project: project3) }
+ [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
@@ -33,11 +44,11 @@ describe DeleteInconsistentInternalIdRecords, :migration do
end
end
- it "deletes inconsistent issues" do
+ it "deletes inconsistent records" do
expect { migrate! }.to change { internal_id_query.call(project1).size }.from(1).to(0)
end
- it "retains consistent issues" do
+ it "retains consistent records" do
expect { migrate! }.not_to change { internal_id_query.call(project2).size }
end
@@ -48,6 +59,8 @@ describe DeleteInconsistentInternalIdRecords, :migration do
context 'for issues' do
let(:scope) { :issue }
+ let(:required_attributes) { {} }
+
it_behaves_like 'deleting inconsistent internal_id records'
end
@@ -55,9 +68,17 @@ describe DeleteInconsistentInternalIdRecords, :migration do
let(:scope) { :merge_request }
let(:create_models) do
- 3.times { |i| create(scope, target_project: project1, source_project: project1, source_branch: i.to_s) }
- 3.times { |i| create(scope, target_project: project2, source_project: project2, source_branch: i.to_s) }
- 3.times { |i| create(scope, target_project: project3, source_project: project3, source_branch: i.to_s) }
+ [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'
@@ -66,13 +87,6 @@ describe DeleteInconsistentInternalIdRecords, :migration do
context 'for deployments' do
let(:scope) { :deployment }
let(:deployments) { table(:deployments) }
- let(:internal_ids) { table(:internal_ids) }
-
- before do
- internal_ids.create!(project_id: project1.id, usage: 2, last_value: 2)
- internal_ids.create!(project_id: project2.id, usage: 2, last_value: 2)
- internal_ids.create!(project_id: project3.id, usage: 2, last_value: 2)
- end
let(:create_models) do
3.times { |i| deployments.create!(project_id: project1.id, iid: i, environment_id: 1, ref: 'master', sha: 'a', tag: false) }
@@ -85,17 +99,14 @@ describe DeleteInconsistentInternalIdRecords, :migration do
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(:create_models) do
- create_list(:ci_empty_pipeline, 3, project: project1)
- create_list(:ci_empty_pipeline, 3, project: project2)
- create_list(:ci_empty_pipeline, 3, project: project3)
- end
+ let(:required_attributes) { { ref: 'test' } }
it_behaves_like 'deleting inconsistent internal_id records'
end
@@ -107,12 +118,20 @@ describe DeleteInconsistentInternalIdRecords, :migration do
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: group) } }
+ let(:internal_id_query) { ->(group) { InternalId.where(usage: InternalId.usages['milestones'], namespace_id: group.id) } }
before do
- 3.times { create(:milestone, group_id: group1.id) }
- 3.times { create(:milestone, group_id: group2.id) }
- 3.times { create(:milestone, group_id: group3.id) }
+ [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
@@ -127,11 +146,11 @@ describe DeleteInconsistentInternalIdRecords, :migration do
end
end
- it "deletes inconsistent issues" do
+ it "deletes inconsistent records" do
expect { migrate! }.to change { internal_id_query.call(group1).size }.from(1).to(0)
end
- it "retains consistent issues" do
+ it "retains consistent records" do
expect { migrate! }.not_to change { internal_id_query.call(group2).size }
end
diff --git a/spec/migrations/issues_moved_to_id_foreign_key_spec.rb b/spec/migrations/issues_moved_to_id_foreign_key_spec.rb
index 495e86ee888..71a4e71ac8a 100644
--- a/spec/migrations/issues_moved_to_id_foreign_key_spec.rb
+++ b/spec/migrations/issues_moved_to_id_foreign_key_spec.rb
@@ -1,20 +1,19 @@
require 'spec_helper'
require Rails.root.join('db', 'migrate', '20171106151218_issues_moved_to_id_foreign_key.rb')
-# The schema version has to be far enough in advance to have the
-# only_mirror_protected_branches column in the projects table to create a
-# project via FactoryBot.
-describe IssuesMovedToIdForeignKey, :migration, schema: 20171114150259 do
- let!(:issue_first) { create(:issue, moved_to_id: issue_second.id) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:issue_second) { create(:issue, moved_to_id: issue_third.id) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:issue_third) { create(:issue) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+describe IssuesMovedToIdForeignKey, :migration do
+ let(:issues) { table(:issues) }
+
+ let!(:issue_third) { issues.create! }
+ let!(:issue_second) { issues.create!(moved_to_id: issue_third.id) }
+ let!(:issue_first) { issues.create!(moved_to_id: issue_second.id) }
subject { described_class.new }
it 'removes the orphaned moved_to_id' do
subject.down
- issue_third.update(moved_to_id: 100000)
+ issue_third.update!(moved_to_id: 0)
subject.up
diff --git a/spec/migrations/migrate_old_artifacts_spec.rb b/spec/migrations/migrate_old_artifacts_spec.rb
index af77d64fdbf..79e21514506 100644
--- a/spec/migrations/migrate_old_artifacts_spec.rb
+++ b/spec/migrations/migrate_old_artifacts_spec.rb
@@ -3,7 +3,9 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170523083112_migrate_old_artifacts.rb')
-describe MigrateOldArtifacts do
+# Adding the ci_job_artifacts table (from the 20170918072948 schema)
+# makes the use of model code below easier.
+describe MigrateOldArtifacts, :migration, schema: 20170918072948 do
let(:migration) { described_class.new }
let!(:directory) { Dir.mktmpdir }
@@ -16,18 +18,22 @@ describe MigrateOldArtifacts do
end
context 'with migratable data' do
- set(:project1) { create(:project, ci_id: 2) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- set(:project2) { create(:project, ci_id: 3) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- set(:project3) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
-
- set(:pipeline1) { create(:ci_empty_pipeline, project: project1) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- set(:pipeline2) { create(:ci_empty_pipeline, project: project2) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- set(:pipeline3) { create(:ci_empty_pipeline, project: project3) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
-
- let!(:build_with_legacy_artifacts) { create(:ci_build, pipeline: pipeline1) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:build_without_artifacts) { create(:ci_build, pipeline: pipeline1) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:build2) { create(:ci_build, pipeline: pipeline2) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:build3) { create(:ci_build, pipeline: pipeline3) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let(:projects) { table(:projects) }
+ let(:ci_pipelines) { table(:ci_pipelines) }
+ let(:ci_builds) { table(:ci_builds) }
+
+ let!(:project1) { projects.create!(ci_id: 2) }
+ let!(:project2) { projects.create!(ci_id: 3) }
+ let!(:project3) { projects.create! }
+
+ let!(:pipeline1) { ci_pipelines.create!(project_id: project1.id) }
+ let!(:pipeline2) { ci_pipelines.create!(project_id: project2.id) }
+ let!(:pipeline3) { ci_pipelines.create!(project_id: project3.id) }
+
+ let!(:build_with_legacy_artifacts) { ci_builds.create!(commit_id: pipeline1.id, project_id: project1.id, type: 'Ci::Build').becomes(Ci::Build) }
+ let!(:build_without_artifacts) { ci_builds.create!(commit_id: pipeline1.id, project_id: project1.id, type: 'Ci::Build').becomes(Ci::Build) }
+ let!(:build2) { ci_builds.create!(commit_id: pipeline2.id, project_id: project2.id, type: 'Ci::Build').becomes(Ci::Build) }
+ let!(:build3) { ci_builds.create!(commit_id: pipeline3.id, project_id: project3.id, type: 'Ci::Build').becomes(Ci::Build) }
before do
setup_builds(build2, build3)
diff --git a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
index 99173708190..88aef3b70b4 100644
--- a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
+++ b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
@@ -3,10 +3,10 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170324160416_migrate_user_activities_to_users_last_activity_on.rb')
-describe MigrateUserActivitiesToUsersLastActivityOn, :clean_gitlab_redis_shared_state, :delete do
+describe MigrateUserActivitiesToUsersLastActivityOn, :clean_gitlab_redis_shared_state, :migration do
let(:migration) { described_class.new }
- let!(:user_active_1) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let!(:user_active_2) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:user_active_1) { table(:users).create!(email: 'test1', username: 'test1') }
+ let!(:user_active_2) { table(:users).create!(email: 'test2', username: 'test2') }
def record_activity(user, time)
Gitlab::Redis::SharedState.with do |redis|
diff --git a/spec/migrations/migrate_user_project_view_spec.rb b/spec/migrations/migrate_user_project_view_spec.rb
index 80468b9d01e..a0179ab3ceb 100644
--- a/spec/migrations/migrate_user_project_view_spec.rb
+++ b/spec/migrations/migrate_user_project_view_spec.rb
@@ -3,15 +3,15 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170406142253_migrate_user_project_view.rb')
-describe MigrateUserProjectView, :delete do
+describe MigrateUserProjectView, :migration do
let(:migration) { described_class.new }
- let!(:user) { create(:user, project_view: 'readme') } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let!(:user) { table(:users).create!(project_view: User.project_views['readme']) }
describe '#up' do
it 'updates project view setting with new value' do
migration.up
- expect(user.reload.project_view).to eq('files')
+ expect(user.reload.project_view).to eq(User.project_views['files'])
end
end
end
diff --git a/spec/migrations/move_personal_snippets_files_spec.rb b/spec/migrations/move_personal_snippets_files_spec.rb
index 1f39ad98fb8..d94ae1e52f5 100644
--- a/spec/migrations/move_personal_snippets_files_spec.rb
+++ b/spec/migrations/move_personal_snippets_files_spec.rb
@@ -1,12 +1,19 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170612071012_move_personal_snippets_files.rb')
-describe MovePersonalSnippetsFiles do
+describe MovePersonalSnippetsFiles, :migration do
let(:migration) { described_class.new }
let(:test_dir) { File.join(Rails.root, "tmp", "tests", "move_snippet_files_test") }
let(:uploads_dir) { File.join(test_dir, 'uploads') }
let(:new_uploads_dir) { File.join(uploads_dir, '-', 'system') }
+ let(:notes) { table(:notes) }
+ let(:snippets) { table(:snippets) }
+ let(:uploads) { table(:uploads) }
+
+ let(:user) { table(:users).create!(email: 'user@example.com', projects_limit: 10) }
+ let(:project) { table(:projects).create!(name: 'gitlab', namespace_id: 1) }
+
before do
allow(CarrierWave).to receive(:root).and_return(test_dir)
allow(migration).to receive(:base_directory).and_return(test_dir)
@@ -16,14 +23,14 @@ describe MovePersonalSnippetsFiles do
describe "#up" do
let(:snippet) do
- snippet = create(:personal_snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ snippet = snippets.create!(author_id: user.id)
create_upload('picture.jpg', snippet)
snippet.update(description: markdown_linking_file('picture.jpg', snippet))
snippet
end
let(:snippet_with_missing_file) do
- snippet = create(:snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ snippet = snippets.create!(author_id: user.id, project_id: project.id)
create_upload('picture.jpg', snippet, create_file: false)
snippet.update(description: markdown_linking_file('picture.jpg', snippet))
snippet
@@ -62,7 +69,10 @@ describe MovePersonalSnippetsFiles do
secret = "secret#{snippet.id}"
file_location = "/uploads/-/system/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
markdown = markdown_linking_file('picture.jpg', snippet)
- note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown}") # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ note = notes.create!(noteable_id: snippet.id,
+ noteable_type: Snippet,
+ note: "with #{markdown}",
+ author_id: user.id)
migration.up
@@ -73,14 +83,14 @@ describe MovePersonalSnippetsFiles do
describe "#down" do
let(:snippet) do
- snippet = create(:personal_snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ snippet = snippets.create!(author_id: user.id)
create_upload('picture.jpg', snippet, in_new_path: true)
snippet.update(description: markdown_linking_file('picture.jpg', snippet, in_new_path: true))
snippet
end
let(:snippet_with_missing_file) do
- snippet = create(:personal_snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ snippet = snippets.create!(author_id: user.id)
create_upload('picture.jpg', snippet, create_file: false, in_new_path: true)
snippet.update(description: markdown_linking_file('picture.jpg', snippet, in_new_path: true))
snippet
@@ -119,7 +129,10 @@ describe MovePersonalSnippetsFiles do
markdown = markdown_linking_file('picture.jpg', snippet, in_new_path: true)
secret = "secret#{snippet.id}"
file_location = "/uploads/personal_snippet/#{snippet.id}/#{secret}/picture.jpg"
- note = create(:note_on_personal_snippet, noteable: snippet, note: "with #{markdown}") # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ note = notes.create!(noteable_id: snippet.id,
+ noteable_type: Snippet,
+ note: "with #{markdown}",
+ author_id: user.id)
migration.down
@@ -135,7 +148,7 @@ describe MovePersonalSnippetsFiles do
secret = '123456789'
filename = 'hello.jpg'
- snippet = create(:personal_snippet) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ snippet = snippets.create!(author_id: user.id)
path_before = "/uploads/personal_snippet/#{snippet.id}/#{secret}/#{filename}"
path_after = "/uploads/system/personal_snippet/#{snippet.id}/#{secret}/#{filename}"
@@ -161,7 +174,11 @@ describe MovePersonalSnippetsFiles do
FileUtils.touch(absolute_path)
end
- create(:upload, model: snippet, path: "#{secret}/#{filename}", uploader: PersonalFileUploader) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ uploads.create!(model_id: snippet.id,
+ model_type: snippet.class,
+ path: "#{secret}/#{filename}",
+ uploader: PersonalFileUploader,
+ size: 100.kilobytes)
end
def markdown_linking_file(filename, snippet, in_new_path: false)
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 789e14e8a20..314f0728b8e 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -117,14 +117,6 @@ describe ApplicationSetting do
it { expect(setting.repository_storages).to eq(['default']) }
end
- context '#commit_email_hostname' do
- it 'returns configured gitlab hostname if commit_email_hostname is not defined' do
- setting.update(commit_email_hostname: nil)
-
- expect(setting.commit_email_hostname).to eq("users.noreply.#{Gitlab.config.gitlab.host}")
- end
- end
-
context 'auto_devops_domain setting' do
context 'when auto_devops_enabled? is true' do
before do
@@ -182,15 +174,6 @@ describe ApplicationSetting do
it { is_expected.not_to allow_value("").for(:repository_storages) }
it { is_expected.not_to allow_value(nil).for(:repository_storages) }
end
-
- describe '.pick_repository_storage' do
- it 'uses Array#sample to pick a random storage' do
- array = double('array', sample: 'random')
- expect(setting).to receive(:repository_storages).and_return(array)
-
- expect(setting.pick_repository_storage).to eq('random')
- end
- end
end
context 'housekeeping settings' do
@@ -367,65 +350,6 @@ describe ApplicationSetting do
end
end
- context 'restricted signup domains' do
- it 'sets single domain' do
- setting.domain_whitelist_raw = 'example.com'
- expect(setting.domain_whitelist).to eq(['example.com'])
- end
-
- it 'sets multiple domains with spaces' do
- setting.domain_whitelist_raw = 'example.com *.example.com'
- expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
- end
-
- it 'sets multiple domains with newlines and a space' do
- setting.domain_whitelist_raw = "example.com\n *.example.com"
- expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
- end
-
- it 'sets multiple domains with commas' do
- setting.domain_whitelist_raw = "example.com, *.example.com"
- expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
- end
- end
-
- context 'blacklisted signup domains' do
- it 'sets single domain' do
- setting.domain_blacklist_raw = 'example.com'
- expect(setting.domain_blacklist).to contain_exactly('example.com')
- end
-
- it 'sets multiple domains with spaces' do
- setting.domain_blacklist_raw = 'example.com *.example.com'
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
- end
-
- it 'sets multiple domains with newlines and a space' do
- setting.domain_blacklist_raw = "example.com\n *.example.com"
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
- end
-
- it 'sets multiple domains with commas' do
- setting.domain_blacklist_raw = "example.com, *.example.com"
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
- end
-
- it 'sets multiple domains with semicolon' do
- setting.domain_blacklist_raw = "example.com; *.example.com"
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
- end
-
- it 'sets multiple domains with mixture of everything' do
- setting.domain_blacklist_raw = "example.com; *.example.com\n test.com\sblock.com yes.com"
- expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com', 'test.com', 'block.com', 'yes.com')
- end
-
- it 'sets multiple domain with file' do
- setting.domain_blacklist_file = File.open(Rails.root.join('spec/fixtures/', 'domain_blacklist.txt'))
- expect(setting.domain_blacklist).to contain_exactly('example.com', 'test.com', 'foo.bar')
- end
- end
-
describe 'performance bar settings' do
describe 'performance_bar_allowed_group' do
context 'with no performance_bar_allowed_group_id saved' do
@@ -462,142 +386,6 @@ describe ApplicationSetting do
end
end
- describe 'usage ping settings' do
- context 'when the usage ping is disabled in gitlab.yml' do
- before do
- allow(Settings.gitlab).to receive(:usage_ping_enabled).and_return(false)
- end
-
- it 'does not allow the usage ping to be configured' do
- expect(setting.usage_ping_can_be_configured?).to be_falsey
- end
-
- context 'when the usage ping is disabled in the DB' do
- before do
- setting.usage_ping_enabled = false
- end
-
- it 'returns false for usage_ping_enabled' do
- expect(setting.usage_ping_enabled).to be_falsey
- end
- end
-
- context 'when the usage ping is enabled in the DB' do
- before do
- setting.usage_ping_enabled = true
- end
-
- it 'returns false for usage_ping_enabled' do
- expect(setting.usage_ping_enabled).to be_falsey
- end
- end
- end
-
- context 'when the usage ping is enabled in gitlab.yml' do
- before do
- allow(Settings.gitlab).to receive(:usage_ping_enabled).and_return(true)
- end
-
- it 'allows the usage ping to be configured' do
- expect(setting.usage_ping_can_be_configured?).to be_truthy
- end
-
- context 'when the usage ping is disabled in the DB' do
- before do
- setting.usage_ping_enabled = false
- end
-
- it 'returns false for usage_ping_enabled' do
- expect(setting.usage_ping_enabled).to be_falsey
- end
- end
-
- context 'when the usage ping is enabled in the DB' do
- before do
- setting.usage_ping_enabled = true
- end
-
- it 'returns true for usage_ping_enabled' do
- expect(setting.usage_ping_enabled).to be_truthy
- end
- end
- end
- end
-
- describe '#allowed_key_types' do
- it 'includes all key types by default' do
- expect(setting.allowed_key_types).to contain_exactly(*described_class::SUPPORTED_KEY_TYPES)
- end
-
- it 'excludes disabled key types' do
- expect(setting.allowed_key_types).to include(:ed25519)
-
- setting.ed25519_key_restriction = described_class::FORBIDDEN_KEY_VALUE
-
- expect(setting.allowed_key_types).not_to include(:ed25519)
- end
- end
-
- describe '#key_restriction_for' do
- it 'returns the restriction value for recognised types' do
- setting.rsa_key_restriction = 1024
-
- expect(setting.key_restriction_for(:rsa)).to eq(1024)
- end
-
- it 'allows types to be passed as a string' do
- setting.rsa_key_restriction = 1024
-
- expect(setting.key_restriction_for('rsa')).to eq(1024)
- end
-
- it 'returns forbidden for unrecognised type' do
- expect(setting.key_restriction_for(:foo)).to eq(described_class::FORBIDDEN_KEY_VALUE)
- end
- end
-
- describe '#allow_signup?' do
- it 'returns true' do
- expect(setting.allow_signup?).to be_truthy
- end
-
- it 'returns false if signup is disabled' do
- allow(setting).to receive(:signup_enabled?).and_return(false)
-
- expect(setting.allow_signup?).to be_falsey
- end
-
- it 'returns false if password authentication is disabled for the web interface' do
- allow(setting).to receive(:password_authentication_enabled_for_web?).and_return(false)
-
- expect(setting.allow_signup?).to be_falsey
- end
- end
-
- describe '#user_default_internal_regex_enabled?' do
- using RSpec::Parameterized::TableSyntax
-
- where(:user_default_external, :user_default_internal_regex, :result) do
- false | nil | false
- false | '' | false
- false | '^(?:(?!\.ext@).)*$\r?\n?' | false
- true | '' | false
- true | nil | false
- true | '^(?:(?!\.ext@).)*$\r?\n?' | true
- end
-
- with_them do
- before do
- setting.update(user_default_external: user_default_external)
- setting.update(user_default_internal_regex: user_default_internal_regex)
- end
-
- subject { setting.user_default_internal_regex_enabled? }
-
- it { is_expected.to eq(result) }
- end
- end
-
context 'diff limit settings' do
describe '#diff_max_patch_bytes' do
context 'validations' do
@@ -613,23 +401,5 @@ describe ApplicationSetting do
end
end
- describe '#archive_builds_older_than' do
- subject { setting.archive_builds_older_than }
-
- context 'when the archive_builds_in_seconds is set' do
- before do
- setting.archive_builds_in_seconds = 3600
- end
-
- it { is_expected.to be_within(1.minute).of(1.hour.ago) }
- end
-
- context 'when the archive_builds_in_seconds is set' do
- before do
- setting.archive_builds_in_seconds = nil
- end
-
- it { is_expected.to be_nil }
- end
- end
+ it_behaves_like 'application settings examples'
end
diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb
index 89839709131..30ca07d5d2c 100644
--- a/spec/models/broadcast_message_spec.rb
+++ b/spec/models/broadcast_message_spec.rb
@@ -95,6 +95,12 @@ describe BroadcastMessage do
end
end
+ describe '#attributes' do
+ it 'includes message_html field' do
+ expect(subject.attributes.keys).to include("cached_markdown_version", "message_html")
+ end
+ end
+
describe '#active?' do
it 'is truthy when started and not ended' do
message = build(:broadcast_message)
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
index dd40b968bc1..7500e6ae5b1 100644
--- a/spec/models/ci/build_spec.rb
+++ b/spec/models/ci/build_spec.rb
@@ -186,6 +186,37 @@ describe Ci::Build do
end
end
+ describe '#enqueue' do
+ let(:build) { create(:ci_build, :created) }
+
+ subject { build.enqueue }
+
+ before do
+ allow(build).to receive(:any_unmet_prerequisites?).and_return(has_prerequisites)
+ allow(Ci::PrepareBuildService).to receive(:perform_async)
+ end
+
+ context 'build has unmet prerequisites' do
+ let(:has_prerequisites) { true }
+
+ it 'transitions to preparing' do
+ subject
+
+ expect(build).to be_preparing
+ end
+ end
+
+ context 'build has no prerequisites' do
+ let(:has_prerequisites) { false }
+
+ it 'transitions to pending' do
+ subject
+
+ expect(build).to be_pending
+ end
+ end
+ end
+
describe '#actionize' do
context 'when build is a created' do
before do
@@ -344,6 +375,18 @@ describe Ci::Build do
expect(build).to be_pending
end
+
+ context 'build has unmet prerequisites' do
+ before do
+ allow(build).to receive(:prerequisites).and_return([double])
+ end
+
+ it 'transits to preparing' do
+ subject
+
+ expect(build).to be_preparing
+ end
+ end
end
end
@@ -2772,7 +2815,7 @@ describe Ci::Build do
end
context 'when ref is merge request' do
- let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) }
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
let(:pipeline) { merge_request.merge_request_pipelines.first }
let(:build) { create(:ci_build, ref: merge_request.source_branch, tag: false, pipeline: pipeline, project: project) }
@@ -2830,7 +2873,7 @@ describe Ci::Build do
end
context 'when ref is merge request' do
- let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) }
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
let(:pipeline) { merge_request.merge_request_pipelines.first }
let(:build) { create(:ci_build, ref: merge_request.source_branch, tag: false, pipeline: pipeline, project: project) }
@@ -2876,6 +2919,36 @@ describe Ci::Build do
end
end
+ describe '#any_unmet_prerequisites?' do
+ let(:build) { create(:ci_build, :created) }
+
+ subject { build.any_unmet_prerequisites? }
+
+ context 'build has prerequisites' do
+ before do
+ allow(build).to receive(:prerequisites).and_return([double])
+ end
+
+ it { is_expected.to be_truthy }
+
+ context 'and the ci_preparing_state feature is disabled' do
+ before do
+ stub_feature_flags(ci_preparing_state: false)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ context 'build does not have prerequisites' do
+ before do
+ allow(build).to receive(:prerequisites).and_return([])
+ end
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
describe '#yaml_variables' do
let(:build) { create(:ci_build, pipeline: pipeline, yaml_variables: variables) }
@@ -2928,6 +3001,20 @@ describe Ci::Build do
end
end
+ describe 'state transition: any => [:preparing]' do
+ let(:build) { create(:ci_build, :created) }
+
+ before do
+ allow(build).to receive(:prerequisites).and_return([double])
+ end
+
+ it 'queues BuildPrepareWorker' do
+ expect(Ci::BuildPrepareWorker).to receive(:perform_async).with(build.id)
+
+ build.enqueue
+ end
+ end
+
describe 'state transition: any => [:pending]' do
let(:build) { create(:ci_build, :created) }
diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb
index d0b42d103a5..5b8097621e0 100644
--- a/spec/models/ci/pipeline_spec.rb
+++ b/spec/models/ci/pipeline_spec.rb
@@ -130,22 +130,118 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '.for_sha' do
+ subject { described_class.for_sha(sha) }
+
+ let(:sha) { 'abc' }
+ let!(:pipeline) { create(:ci_pipeline, sha: 'abc') }
+
+ it 'returns the pipeline' do
+ is_expected.to contain_exactly(pipeline)
+ end
+
+ context 'when argument is array' do
+ let(:sha) { %w[abc def] }
+ let!(:pipeline_2) { create(:ci_pipeline, sha: 'def') }
+
+ it 'returns the pipelines' do
+ is_expected.to contain_exactly(pipeline, pipeline_2)
+ end
+ end
+
+ context 'when sha is empty' do
+ let(:sha) { nil }
+
+ it 'does not return anything' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '.for_source_sha' do
+ subject { described_class.for_source_sha(source_sha) }
+
+ let(:source_sha) { 'abc' }
+ let!(:pipeline) { create(:ci_pipeline, source_sha: 'abc') }
+
+ it 'returns the pipeline' do
+ is_expected.to contain_exactly(pipeline)
+ end
+
+ context 'when argument is array' do
+ let(:source_sha) { %w[abc def] }
+ let!(:pipeline_2) { create(:ci_pipeline, source_sha: 'def') }
+
+ it 'returns the pipelines' do
+ is_expected.to contain_exactly(pipeline, pipeline_2)
+ end
+ end
+
+ context 'when source_sha is empty' do
+ let(:source_sha) { nil }
+
+ it 'does not return anything' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
+ describe '.for_sha_or_source_sha' do
+ subject { described_class.for_sha_or_source_sha(sha) }
+
+ let(:sha) { 'abc' }
+
+ context 'when sha is matched' do
+ let!(:pipeline) { create(:ci_pipeline, sha: sha) }
+
+ it 'returns the pipeline' do
+ is_expected.to contain_exactly(pipeline)
+ end
+ end
+
+ context 'when source sha is matched' do
+ let!(:pipeline) { create(:ci_pipeline, source_sha: sha) }
+
+ it 'returns the pipeline' do
+ is_expected.to contain_exactly(pipeline)
+ end
+ end
+
+ context 'when both sha and source sha are not matched' do
+ let!(:pipeline) { create(:ci_pipeline, sha: 'bcd', source_sha: 'bcd') }
+
+ it 'does not return anything' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
describe '.detached_merge_request_pipelines' do
- subject { described_class.detached_merge_request_pipelines(merge_request) }
+ subject { described_class.detached_merge_request_pipelines(merge_request, sha) }
let!(:pipeline) do
- create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, target_sha: target_sha)
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, sha: merge_request.diff_head_sha)
end
let(:merge_request) { create(:merge_request) }
- let(:target_sha) { nil }
+ let(:sha) { merge_request.diff_head_sha }
it 'returns detached merge request pipelines' do
is_expected.to eq([pipeline])
end
- context 'when target sha exists' do
- let(:target_sha) { merge_request.target_branch_sha }
+ context 'when sha does not exist' do
+ let(:sha) { 'abc' }
+
+ it 'returns empty array' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when pipeline is merge request pipeline' do
+ let!(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, source_sha: merge_request.diff_head_sha)
+ end
it 'returns empty array' do
is_expected.to be_empty
@@ -173,21 +269,31 @@ describe Ci::Pipeline, :mailer do
end
describe '.merge_request_pipelines' do
- subject { described_class.merge_request_pipelines(merge_request) }
+ subject { described_class.merge_request_pipelines(merge_request, source_sha) }
let!(:pipeline) do
- create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, target_sha: target_sha)
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, source_sha: merge_request.diff_head_sha)
end
let(:merge_request) { create(:merge_request) }
- let(:target_sha) { merge_request.target_branch_sha }
+ let(:source_sha) { merge_request.diff_head_sha }
it 'returns merge pipelines' do
is_expected.to eq([pipeline])
end
- context 'when target sha is empty' do
- let(:target_sha) { nil }
+ context 'when source sha is empty' do
+ let(:source_sha) { nil }
+
+ it 'returns empty array' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when pipeline is detached merge request pipeline' do
+ let!(:pipeline) do
+ create(:ci_pipeline, source: :merge_request_event, merge_request: merge_request, sha: merge_request.diff_head_sha)
+ end
it 'returns empty array' do
is_expected.to be_empty
@@ -256,6 +362,74 @@ describe Ci::Pipeline, :mailer do
end
end
+ describe '#matches_sha_or_source_sha?' do
+ subject { pipeline.matches_sha_or_source_sha?(sample_sha) }
+
+ let(:sample_sha) { Digest::SHA1.hexdigest(SecureRandom.hex) }
+
+ context 'when sha matches' do
+ let(:pipeline) { build(:ci_pipeline, sha: sample_sha) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when source_sha matches' do
+ let(:pipeline) { build(:ci_pipeline, source_sha: sample_sha) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when both sha and source_sha do not matche' do
+ let(:pipeline) { build(:ci_pipeline, sha: 'test', source_sha: 'test') }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ describe '.triggered_for_branch' do
+ subject { described_class.triggered_for_branch(ref) }
+
+ let(:project) { create(:project, :repository) }
+ let(:ref) { 'feature' }
+ let!(:pipeline) { create(:ci_pipeline, ref: ref) }
+
+ it 'returns the pipeline' do
+ is_expected.to eq([pipeline])
+ end
+
+ context 'when sha is not specified' do
+ it 'returns the pipeline' do
+ expect(described_class.triggered_for_branch(ref)).to eq([pipeline])
+ end
+ end
+
+ context 'when pipeline is triggered for tag' do
+ let(:ref) { 'v1.1.0' }
+ let!(:pipeline) { create(:ci_pipeline, ref: ref, tag: true) }
+
+ it 'does not return the pipeline' do
+ is_expected.to be_empty
+ end
+ end
+
+ context 'when pipeline is triggered for merge_request' do
+ let!(:merge_request) do
+ create(:merge_request,
+ :with_merge_request_pipeline,
+ source_project: project,
+ source_branch: ref,
+ target_project: project,
+ target_branch: 'master')
+ end
+
+ let(:pipeline) { merge_request.merge_request_pipelines.first }
+
+ it 'does not return the pipeline' do
+ is_expected.to be_empty
+ end
+ end
+ end
+
describe '.merge_request_event' do
subject { described_class.merge_request_event }
@@ -1051,16 +1225,28 @@ describe Ci::Pipeline, :mailer do
end
describe '#started_at' do
- it 'updates on transitioning to running' do
- build.run
+ let(:pipeline) { create(:ci_empty_pipeline, status: from_status) }
+
+ %i[created preparing pending].each do |status|
+ context "from #{status}" do
+ let(:from_status) { status }
- expect(pipeline.reload.started_at).not_to be_nil
+ it 'updates on transitioning to running' do
+ pipeline.run
+
+ expect(pipeline.started_at).not_to be_nil
+ end
+ end
end
- it 'does not update on transitioning to success' do
- build.success
+ context 'from created' do
+ let(:from_status) { :created }
- expect(pipeline.reload.started_at).to be_nil
+ it 'does not update on transitioning to success' do
+ pipeline.succeed
+
+ expect(pipeline.started_at).to be_nil
+ end
end
end
@@ -1079,27 +1265,49 @@ describe Ci::Pipeline, :mailer do
end
describe 'merge request metrics' do
- let(:project) { create(:project, :repository) }
- let(:pipeline) { FactoryBot.create(:ci_empty_pipeline, status: 'created', project: project, ref: 'master', sha: project.repository.commit('master').id) }
- let!(:merge_request) { create(:merge_request, source_project: project, source_branch: pipeline.ref) }
+ let(:pipeline) { create(:ci_empty_pipeline, status: from_status) }
before do
expect(PipelineMetricsWorker).to receive(:perform_async).with(pipeline.id)
end
context 'when transitioning to running' do
- it 'schedules metrics workers' do
- pipeline.run
+ %i[created preparing pending].each do |status|
+ context "from #{status}" do
+ let(:from_status) { status }
+
+ it 'schedules metrics workers' do
+ pipeline.run
+ end
+ end
end
end
context 'when transitioning to success' do
+ let(:from_status) { 'created' }
+
it 'schedules metrics workers' do
pipeline.succeed
end
end
end
+ describe 'merge on success' do
+ let(:pipeline) { create(:ci_empty_pipeline, status: from_status) }
+
+ %i[created preparing pending running].each do |status|
+ context "from #{status}" do
+ let(:from_status) { status }
+
+ it 'schedules pipeline success worker' do
+ expect(PipelineSuccessWorker).to receive(:perform_async).with(pipeline.id)
+
+ pipeline.succeed
+ end
+ end
+ end
+ end
+
describe 'pipeline caching' do
it 'performs ExpirePipelinesCacheWorker' do
expect(ExpirePipelineCacheWorker).to receive(:perform_async).with(pipeline.id)
@@ -1618,6 +1826,18 @@ describe Ci::Pipeline, :mailer do
subject { pipeline.reload.status }
+ context 'on prepare' do
+ before do
+ # Prevent skipping directly to 'pending'
+ allow(build).to receive(:prerequisites).and_return([double])
+ allow(Ci::BuildPrepareWorker).to receive(:perform_async)
+
+ build.enqueue
+ end
+
+ it { is_expected.to eq('preparing') }
+ end
+
context 'on queuing' do
before do
build.enqueue
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index 6972fc03415..06d9bc076cd 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -22,7 +22,7 @@ describe Clusters::Applications::Runner do
it 'should be initialized with 4 arguments' do
expect(subject.name).to eq('runner')
expect(subject.chart).to eq('runner/gitlab-runner')
- expect(subject.version).to eq('0.2.0')
+ expect(subject.version).to eq('0.3.0')
expect(subject).to be_rbac
expect(subject.repository).to eq('https://charts.gitlab.io')
expect(subject.files).to eq(gitlab_runner.files)
@@ -40,7 +40,7 @@ describe Clusters::Applications::Runner do
let(:gitlab_runner) { create(:clusters_applications_runner, :errored, runner: ci_runner, version: '0.1.13') }
it 'should be initialized with the locked version' do
- expect(subject.version).to eq('0.2.0')
+ expect(subject.version).to eq('0.3.0')
end
end
end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index cc93a1b4965..af65530e663 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -375,14 +375,14 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
end
context 'with valid pods' do
- let(:pod) { kube_pod(app: environment.slug) }
- let(:pod_with_no_terminal) { kube_pod(app: environment.slug, status: "Pending") }
+ let(:pod) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug) }
+ let(:pod_with_no_terminal) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: "Pending") }
let(:terminals) { kube_terminals(service, pod) }
before do
stub_reactive_cache(
service,
- pods: [pod, pod, pod_with_no_terminal, kube_pod(app: "should-be-filtered-out")]
+ pods: [pod, pod, pod_with_no_terminal, kube_pod(environment_slug: "should-be-filtered-out")]
)
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index baad8352185..9d4e18534ae 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -542,7 +542,7 @@ eos
end
end
- describe '#uri_type' do
+ shared_examples '#uri_type' do
it 'returns the URI type at the given path' do
expect(commit.uri_type('files/html')).to be(:tree)
expect(commit.uri_type('files/images/logo-black.png')).to be(:raw)
@@ -561,6 +561,20 @@ eos
end
end
+ describe '#uri_type with Gitaly enabled' do
+ it_behaves_like "#uri_type"
+ end
+
+ describe '#uri_type with Rugged enabled', :enable_rugged do
+ it 'calls out to the Rugged implementation' do
+ allow_any_instance_of(Rugged::Tree).to receive(:path).with('files/html').and_call_original
+
+ commit.uri_type('files/html')
+ end
+
+ it_behaves_like '#uri_type'
+ end
+
describe '.from_hash' do
let(:new_commit) { described_class.from_hash(commit.to_hash, project) }
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index 8b7c88805c1..e2b7f5c6ee2 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -49,6 +49,16 @@ describe CommitStatus do
commit_status.success!
end
+
+ describe 'transitioning to running' do
+ let(:commit_status) { create(:commit_status, :pending, started_at: nil) }
+
+ it 'records the started at time' do
+ commit_status.run!
+
+ expect(commit_status.started_at).to be_present
+ end
+ end
end
describe '#started?' do
@@ -479,6 +489,12 @@ describe CommitStatus do
it { is_expected.to be_script_failure }
end
+
+ context 'when failure_reason is unmet_prerequisites' do
+ let(:reason) { :unmet_prerequisites }
+
+ it { is_expected.to be_unmet_prerequisites }
+ end
end
describe 'ensure stage assignment' do
@@ -555,6 +571,7 @@ describe CommitStatus do
before do
allow(Time).to receive(:now).and_return(current_time)
+ expect(commit_status.any_unmet_prerequisites?).to eq false
end
shared_examples 'commit status enqueued' do
@@ -569,6 +586,12 @@ describe CommitStatus do
it_behaves_like 'commit status enqueued'
end
+ context 'when initial state is :preparing' do
+ let(:commit_status) { create(:commit_status, :preparing) }
+
+ it_behaves_like 'commit status enqueued'
+ end
+
context 'when initial state is :skipped' do
let(:commit_status) { create(:commit_status, :skipped) }
diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb
index 447279f19a8..7d555f15e39 100644
--- a/spec/models/concerns/cache_markdown_field_spec.rb
+++ b/spec/models/concerns/cache_markdown_field_spec.rb
@@ -23,6 +23,7 @@ describe CacheMarkdownField do
include CacheMarkdownField
cache_markdown_field :foo
cache_markdown_field :baz, pipeline: :single_line
+ cache_markdown_field :zoo, whitelisted: true
def self.add_attr(name)
self.attribute_names += [name]
@@ -35,7 +36,7 @@ describe CacheMarkdownField do
add_attr :cached_markdown_version
- [:foo, :foo_html, :bar, :baz, :baz_html].each do |name|
+ [:foo, :foo_html, :bar, :baz, :baz_html, :zoo, :zoo_html].each do |name|
add_attr(name)
end
@@ -84,8 +85,8 @@ describe CacheMarkdownField do
end
describe '.attributes' do
- it 'excludes cache attributes' do
- expect(thing.attributes.keys.sort).to eq(%w[bar baz foo])
+ it 'excludes cache attributes that is blacklisted by default' do
+ expect(thing.attributes.keys.sort).to eq(%w[bar baz cached_markdown_version foo zoo zoo_html])
end
end
@@ -297,7 +298,12 @@ describe CacheMarkdownField do
it 'saves the changes using #update_columns' do
expect(thing).to receive(:persisted?).and_return(true)
expect(thing).to receive(:update_columns)
- .with("foo_html" => updated_html, "baz_html" => "", "cached_markdown_version" => cache_version)
+ .with(
+ "foo_html" => updated_html,
+ "baz_html" => "",
+ "zoo_html" => "",
+ "cached_markdown_version" => cache_version
+ )
thing.refresh_markdown_cache!
end
diff --git a/spec/models/concerns/has_ref_spec.rb b/spec/models/concerns/has_ref_spec.rb
index 8aa2fecb18c..6805731fed3 100644
--- a/spec/models/concerns/has_ref_spec.rb
+++ b/spec/models/concerns/has_ref_spec.rb
@@ -18,7 +18,7 @@ describe HasRef do
end
context 'when it was triggered by merge request' do
- let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) }
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
let(:pipeline) { merge_request.merge_request_pipelines.first }
let(:build) { create(:ci_build, pipeline: pipeline) }
@@ -67,7 +67,7 @@ describe HasRef do
end
context 'when it is triggered by a merge request' do
- let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) }
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
let(:pipeline) { merge_request.merge_request_pipelines.first }
let(:build) { create(:ci_build, tag: false, pipeline: pipeline) }
diff --git a/spec/models/concerns/has_status_spec.rb b/spec/models/concerns/has_status_spec.rb
index 6b1038cb8fd..e8b1eba67cc 100644
--- a/spec/models/concerns/has_status_spec.rb
+++ b/spec/models/concerns/has_status_spec.rb
@@ -34,6 +34,22 @@ describe HasStatus do
it { is_expected.to eq 'running' }
end
+ context 'all preparing' do
+ let!(:statuses) do
+ [create(type, status: :preparing), create(type, status: :preparing)]
+ end
+
+ it { is_expected.to eq 'preparing' }
+ end
+
+ context 'at least one preparing' do
+ let!(:statuses) do
+ [create(type, status: :success), create(type, status: :preparing)]
+ end
+
+ it { is_expected.to eq 'preparing' }
+ end
+
context 'success and failed but allowed to fail' do
let!(:statuses) do
[create(type, status: :success),
@@ -188,7 +204,7 @@ describe HasStatus do
end
end
- %i[created running pending success
+ %i[created preparing running pending success
failed canceled skipped].each do |status|
it_behaves_like 'having a job', status
end
@@ -234,7 +250,7 @@ describe HasStatus do
describe '.alive' do
subject { CommitStatus.alive }
- %i[running pending created].each do |status|
+ %i[running pending preparing created].each do |status|
it_behaves_like 'containing the job', status
end
@@ -270,7 +286,7 @@ describe HasStatus do
describe '.cancelable' do
subject { CommitStatus.cancelable }
- %i[running pending created scheduled].each do |status|
+ %i[running pending preparing created scheduled].each do |status|
it_behaves_like 'containing the job', status
end
diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb
index a8d53cfcd7d..5fce9504334 100644
--- a/spec/models/deployment_spec.rb
+++ b/spec/models/deployment_spec.rb
@@ -356,4 +356,32 @@ describe Deployment do
end
end
end
+
+ describe '#cluster' do
+ let(:deployment) { create(:deployment) }
+ let(:project) { deployment.project }
+ let(:environment) { deployment.environment }
+
+ subject { deployment.cluster }
+
+ before do
+ expect(project).to receive(:deployment_platform)
+ .with(environment: environment.name).and_call_original
+ end
+
+ context 'project has no deployment platform' do
+ before do
+ expect(project.clusters).to be_empty
+ end
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'project has a deployment platform' do
+ let!(:cluster) { create(:cluster, projects: [project]) }
+ let!(:platform) { create(:cluster_platform_kubernetes, cluster: cluster) }
+
+ it { is_expected.to eq cluster }
+ end
+ end
end
diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb
index fda00a693f0..67e5f4f7e41 100644
--- a/spec/models/diff_note_spec.rb
+++ b/spec/models/diff_note_spec.rb
@@ -336,6 +336,16 @@ describe DiffNote do
end
end
+ describe '#banzai_render_context' do
+ let(:note) { create(:diff_note_on_merge_request) }
+
+ it 'includes expected context' do
+ context = note.banzai_render_context(:note)
+
+ expect(context).to include(suggestions_filter_enabled: true, noteable: note.noteable, project: note.project)
+ end
+ end
+
describe "image diff notes" do
subject { build(:image_diff_note_on_merge_request, project: project, noteable: merge_request) }
diff --git a/spec/models/environment_status_spec.rb b/spec/models/environment_status_spec.rb
index 9da16dea929..2576a9aba06 100644
--- a/spec/models/environment_status_spec.rb
+++ b/spec/models/environment_status_spec.rb
@@ -64,8 +64,8 @@ describe EnvironmentStatus do
end
describe '.for_merge_request' do
- let(:admin) { create(:admin) }
- let(:pipeline) { create(:ci_pipeline, sha: sha) }
+ let(:admin) { create(:admin) }
+ let!(:pipeline) { create(:ci_pipeline, sha: sha, merge_requests_as_head_pipeline: [merge_request]) }
it 'is based on merge_request.diff_head_sha' do
expect(merge_request).to receive(:diff_head_sha)
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 9dc32a815d8..16624ce47d0 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -810,4 +810,125 @@ describe Group do
it { is_expected.to be_truthy }
end
end
+
+ describe '#first_auto_devops_config' do
+ using RSpec::Parameterized::TableSyntax
+
+ let(:group) { create(:group) }
+
+ subject { group.first_auto_devops_config }
+
+ where(:instance_value, :group_value, :config) do
+ # Instance level enabled
+ true | nil | { status: true, scope: :instance }
+ true | true | { status: true, scope: :group }
+ true | false | { status: false, scope: :group }
+
+ # Instance level disabled
+ false | nil | { status: false, scope: :instance }
+ false | true | { status: true, scope: :group }
+ false | false | { status: false, scope: :group }
+ end
+
+ with_them do
+ before do
+ stub_application_setting(auto_devops_enabled: instance_value)
+
+ group.update_attribute(:auto_devops_enabled, group_value)
+ end
+
+ it { is_expected.to eq(config) }
+ end
+
+ context 'with parent groups', :nested_groups do
+ where(:instance_value, :parent_value, :group_value, :config) do
+ # Instance level enabled
+ true | nil | nil | { status: true, scope: :instance }
+ true | nil | true | { status: true, scope: :group }
+ true | nil | false | { status: false, scope: :group }
+
+ true | true | nil | { status: true, scope: :group }
+ true | true | true | { status: true, scope: :group }
+ true | true | false | { status: false, scope: :group }
+
+ true | false | nil | { status: false, scope: :group }
+ true | false | true | { status: true, scope: :group }
+ true | false | false | { status: false, scope: :group }
+
+ # Instance level disable
+ false | nil | nil | { status: false, scope: :instance }
+ false | nil | true | { status: true, scope: :group }
+ false | nil | false | { status: false, scope: :group }
+
+ false | true | nil | { status: true, scope: :group }
+ false | true | true | { status: true, scope: :group }
+ false | true | false | { status: false, scope: :group }
+
+ false | false | nil | { status: false, scope: :group }
+ false | false | true | { status: true, scope: :group }
+ false | false | false | { status: false, scope: :group }
+ end
+
+ with_them do
+ before do
+ stub_application_setting(auto_devops_enabled: instance_value)
+ parent = create(:group, auto_devops_enabled: parent_value)
+
+ group.update!(
+ auto_devops_enabled: group_value,
+ parent: parent
+ )
+ end
+
+ it { is_expected.to eq(config) }
+ end
+ end
+ end
+
+ describe '#auto_devops_enabled?' do
+ subject { group.auto_devops_enabled? }
+
+ context 'when auto devops is explicitly enabled on group' do
+ let(:group) { create(:group, :auto_devops_enabled) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when auto devops is explicitly disabled on group' do
+ let(:group) { create(:group, :auto_devops_disabled) }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when auto devops is implicitly enabled or disabled' do
+ before do
+ stub_application_setting(auto_devops_enabled: false)
+
+ group.update!(parent: parent_group)
+ end
+
+ context 'when auto devops is enabled on root group' do
+ let(:root_group) { create(:group, :auto_devops_enabled) }
+ let(:subgroup) { create(:group, parent: root_group) }
+ let(:parent_group) { create(:group, parent: subgroup) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when auto devops is disabled on root group' do
+ let(:root_group) { create(:group, :auto_devops_disabled) }
+ let(:subgroup) { create(:group, parent: root_group) }
+ let(:parent_group) { create(:group, parent: subgroup) }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when auto devops is disabled on parent group and enabled on root group' do
+ let(:root_group) { create(:group, :auto_devops_enabled) }
+ let(:parent_group) { create(:group, :auto_devops_disabled, parent: root_group) }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+ end
end
diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb
index e530e0302f5..53f5307ea0b 100644
--- a/spec/models/merge_request_diff_spec.rb
+++ b/spec/models/merge_request_diff_spec.rb
@@ -1,6 +1,8 @@
require 'spec_helper'
describe MergeRequestDiff do
+ include RepoHelpers
+
let(:diff_with_commits) { create(:merge_request).merge_request_diff }
describe 'validations' do
@@ -194,6 +196,25 @@ describe MergeRequestDiff do
expect(diff_file).to be_binary
expect(diff_file.diff).to eq(mr_diff.compare.diffs(paths: [path]).to_a.first.diff)
end
+
+ context 'with diffs that contain a null byte' do
+ let(:filename) { 'test-null.txt' }
+ let(:content) { "a" * 10000 + "\x00" }
+ let(:project) { create(:project, :repository) }
+ let(:branch) { 'null-data' }
+ let(:target_branch) { 'master' }
+
+ it 'saves diffs correctly' do
+ create_file_in_repo(project, target_branch, branch, filename, content)
+
+ mr_diff = create(:merge_request, target_project: project, source_project: project, source_branch: branch, target_branch: target_branch).merge_request_diff
+ diff_file = mr_diff.merge_request_diff_files.find_by(new_path: filename)
+
+ expect(diff_file).to be_binary
+ expect(diff_file.diff).to eq(mr_diff.compare.diffs(paths: [filename]).to_a.first.diff)
+ expect(diff_file.diff).to include(content)
+ end
+ end
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 07cb4c9c1e3..22998bc5b6a 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -270,6 +270,25 @@ describe MergeRequest do
end
end
+ describe '.recent_target_branches' do
+ let(:project) { create(:project) }
+ let!(:merge_request1) { create(:merge_request, :opened, source_project: project, target_branch: 'feature') }
+ let!(:merge_request2) { create(:merge_request, :closed, source_project: project, target_branch: 'merge-test') }
+ let!(:merge_request3) { create(:merge_request, :opened, source_project: project, target_branch: 'fix') }
+ let!(:merge_request4) { create(:merge_request, :closed, source_project: project, target_branch: 'feature') }
+
+ before do
+ merge_request1.update_columns(updated_at: 1.day.since)
+ merge_request2.update_columns(updated_at: 2.days.since)
+ merge_request3.update_columns(updated_at: 3.days.since)
+ merge_request4.update_columns(updated_at: 4.days.since)
+ end
+
+ it 'returns target branches sort by updated at desc' do
+ expect(described_class.recent_target_branches).to match_array(['feature', 'merge-test', 'fix'])
+ end
+ end
+
describe '#target_branch_sha' do
let(:project) { create(:project, :repository) }
@@ -1168,8 +1187,10 @@ describe MergeRequest do
end
context 'head pipeline' do
+ let(:diff_head_sha) { Digest::SHA1.hexdigest(SecureRandom.hex) }
+
before do
- allow(subject).to receive(:diff_head_sha).and_return('lastsha')
+ allow(subject).to receive(:diff_head_sha).and_return(diff_head_sha)
end
describe '#head_pipeline' do
@@ -1197,7 +1218,15 @@ describe MergeRequest do
end
it 'returns the pipeline for MR with recent pipeline' do
- pipeline = create(:ci_empty_pipeline, sha: 'lastsha')
+ pipeline = create(:ci_empty_pipeline, sha: diff_head_sha)
+ subject.update_attribute(:head_pipeline_id, pipeline.id)
+
+ expect(subject.actual_head_pipeline).to eq(subject.head_pipeline)
+ expect(subject.actual_head_pipeline).to eq(pipeline)
+ end
+
+ it 'returns the pipeline for MR with recent merge request pipeline' do
+ pipeline = create(:ci_empty_pipeline, sha: 'merge-sha', source_sha: diff_head_sha)
subject.update_attribute(:head_pipeline_id, pipeline.id)
expect(subject.actual_head_pipeline).to eq(subject.head_pipeline)
@@ -1331,7 +1360,7 @@ describe MergeRequest do
sha: shas.second)
end
- let!(:merge_request_pipeline) do
+ let!(:detached_merge_request_pipeline) do
create(:ci_pipeline,
source: :merge_request_event,
project: project,
@@ -1357,7 +1386,7 @@ describe MergeRequest do
it 'returns merge request pipeline first' do
expect(merge_request.all_pipelines)
- .to eq([merge_request_pipeline,
+ .to eq([detached_merge_request_pipeline,
branch_pipeline])
end
@@ -1370,7 +1399,7 @@ describe MergeRequest do
sha: shas.first)
end
- let!(:merge_request_pipeline_2) do
+ let!(:detached_merge_request_pipeline_2) do
create(:ci_pipeline,
source: :merge_request_event,
project: project,
@@ -1381,8 +1410,8 @@ describe MergeRequest do
it 'returns merge request pipelines first' do
expect(merge_request.all_pipelines)
- .to eq([merge_request_pipeline_2,
- merge_request_pipeline,
+ .to eq([detached_merge_request_pipeline_2,
+ detached_merge_request_pipeline,
branch_pipeline_2,
branch_pipeline])
end
@@ -1397,7 +1426,7 @@ describe MergeRequest do
sha: shas.first)
end
- let!(:merge_request_pipeline_2) do
+ let!(:detached_merge_request_pipeline_2) do
create(:ci_pipeline,
source: :merge_request_event,
project: project,
@@ -1420,16 +1449,35 @@ describe MergeRequest do
it 'returns only related merge request pipelines' do
expect(merge_request.all_pipelines)
- .to eq([merge_request_pipeline,
+ .to eq([detached_merge_request_pipeline,
branch_pipeline_2,
branch_pipeline])
expect(merge_request_2.all_pipelines)
- .to eq([merge_request_pipeline_2,
+ .to eq([detached_merge_request_pipeline_2,
branch_pipeline_2,
branch_pipeline])
end
end
+
+ context 'when detached merge request pipeline is run on head ref of the merge request' do
+ let!(:detached_merge_request_pipeline) do
+ create(:ci_pipeline,
+ source: :merge_request_event,
+ project: project,
+ ref: merge_request.ref_path,
+ sha: shas.second,
+ merge_request: merge_request)
+ end
+
+ it 'sets the head ref of the merge request to the pipeline ref' do
+ expect(detached_merge_request_pipeline.ref).to match(%r{refs/merge-requests/\d+/head})
+ end
+
+ it 'includes the detached merge request pipeline even though the ref is custom path' do
+ expect(merge_request.all_pipelines).to include(detached_merge_request_pipeline)
+ end
+ end
end
end
@@ -1470,6 +1518,37 @@ describe MergeRequest do
end
end
+ context 'when detached merge request pipeline is run on head ref of the merge request' do
+ let!(:pipeline) do
+ create(:ci_pipeline,
+ source: :merge_request_event,
+ project: merge_request.source_project,
+ ref: merge_request.ref_path,
+ sha: sha,
+ merge_request: merge_request)
+ end
+
+ let(:sha) { merge_request.diff_head_sha }
+
+ it 'sets the head ref of the merge request to the pipeline ref' do
+ expect(pipeline.ref).to match(%r{refs/merge-requests/\d+/head})
+ end
+
+ it 'updates correctly even though the target branch name of the merge request is different from the pipeline ref' do
+ expect { subject }
+ .to change { merge_request.reload.head_pipeline }
+ .from(nil).to(pipeline)
+ end
+
+ context 'when sha is not HEAD of the source branch' do
+ let(:sha) { merge_request.diff_base_sha }
+
+ it 'does not update head pipeline' do
+ expect { subject }.not_to change { merge_request.reload.head_pipeline }
+ end
+ end
+ end
+
context 'when there are no pipelines with the diff head sha' do
it 'does not update the head pipeline' do
expect { subject }
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 475fbe56e4d..aadc298ae0b 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -775,4 +775,28 @@ describe Namespace do
end
end
end
+
+ describe '#auto_devops_enabled' do
+ context 'with users' do
+ let(:user) { create(:user) }
+
+ subject { user.namespace.auto_devops_enabled? }
+
+ before do
+ user.namespace.update!(auto_devops_enabled: auto_devops_enabled)
+ end
+
+ context 'when auto devops is explicitly enabled' do
+ let(:auto_devops_enabled) { true }
+
+ it { is_expected.to eq(true) }
+ end
+
+ context 'when auto devops is explicitly disabled' do
+ let(:auto_devops_enabled) { false }
+
+ it { is_expected.to eq(false) }
+ end
+ end
+ end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 385b8a7959f..eb6f6ff5faf 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -208,6 +208,24 @@ describe Note do
end
end
+ describe "edited?" do
+ let(:note) { build(:note, updated_by_id: nil, created_at: Time.now, updated_at: Time.now + 5.hours) }
+
+ context "with updated_by" do
+ it "returns true" do
+ note.updated_by = build(:user)
+
+ expect(note.edited?).to be_truthy
+ end
+ end
+
+ context "without updated_by" do
+ it "returns false" do
+ expect(note.edited?).to be_falsy
+ end
+ end
+ end
+
describe "confidential?" do
it "delegates to noteable" do
issue_note = build(:note, :on_issue)
diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb
index 47f70e6648a..56e587262ef 100644
--- a/spec/models/project_services/kubernetes_service_spec.rb
+++ b/spec/models/project_services/kubernetes_service_spec.rb
@@ -323,13 +323,14 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
end
context 'with valid pods' do
- let(:pod) { kube_pod(app: environment.slug) }
+ let(:pod) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug) }
+ let(:pod_with_no_terminal) { kube_pod(environment_slug: environment.slug, project_slug: project.full_path_slug, status: "Pending") }
let(:terminals) { kube_terminals(service, pod) }
before do
stub_reactive_cache(
service,
- pods: [pod, pod, kube_pod(app: "should-be-filtered-out")]
+ pods: [pod, pod, pod_with_no_terminal, kube_pod(environment_slug: "should-be-filtered-out")]
)
end
@@ -360,14 +361,16 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
context 'when kubernetes responds with valid pods' do
before do
stub_kubeclient_pods
+ stub_kubeclient_deployments # Used by EE
end
- it { is_expected.to eq(pods: [kube_pod]) }
+ it { is_expected.to include(pods: [kube_pod]) }
end
context 'when kubernetes responds with 500s' do
before do
stub_kubeclient_pods(status: 500)
+ stub_kubeclient_deployments(status: 500) # Used by EE
end
it { expect { subject }.to raise_error(Kubeclient::HttpError) }
@@ -376,9 +379,10 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do
context 'when kubernetes responds with 404s' do
before do
stub_kubeclient_pods(status: 404)
+ stub_kubeclient_deployments(status: 404) # Used by EE
end
- it { is_expected.to eq(pods: []) }
+ it { is_expected.to include(pods: []) }
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 71bd7972436..1ea54eeb4f7 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -136,15 +136,6 @@ describe Project do
end
end
- describe '#boards' do
- it 'raises an error when attempting to add more than one board to the project' do
- subject.boards.build
-
- expect { subject.boards.build }.to raise_error(Project::BoardLimitExceeded, 'Number of permitted boards exceeded')
- expect(subject.boards.size).to eq 1
- end
- end
-
describe 'ci_pipelines association' do
it 'returns only pipelines from ci_sources' do
expect(Ci::Pipeline).to receive(:ci_sources).and_call_original
@@ -2388,6 +2379,12 @@ describe Project do
project.change_head(project.default_branch)
end
+ it 'updates commit count' do
+ expect(ProjectCacheWorker).to receive(:perform_async).with(project.id, [], [:commit_count])
+
+ project.change_head(project.default_branch)
+ end
+
it 'copies the gitattributes' do
expect(project.repository).to receive(:copy_gitattributes).with(project.default_branch)
project.change_head(project.default_branch)
@@ -3631,12 +3628,36 @@ describe Project do
subject { project.auto_devops_enabled? }
+ context 'when explicitly enabled' do
+ before do
+ create(:project_auto_devops, project: project)
+ end
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when explicitly disabled' do
+ before do
+ create(:project_auto_devops, project: project, enabled: false)
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
context 'when enabled in settings' do
before do
stub_application_setting(auto_devops_enabled: true)
end
it { is_expected.to be_truthy }
+ end
+
+ context 'when disabled in settings' do
+ before do
+ stub_application_setting(auto_devops_enabled: false)
+ end
+
+ it { is_expected.to be_falsey }
context 'when explicitly enabled' do
before do
@@ -3648,34 +3669,91 @@ describe Project do
context 'when explicitly disabled' do
before do
- create(:project_auto_devops, project: project, enabled: false)
+ create(:project_auto_devops, :disabled, project: project)
end
it { is_expected.to be_falsey }
end
end
- context 'when disabled in settings' do
+ context 'when force_autodevops_on_by_default is enabled for the project' do
+ it { is_expected.to be_truthy }
+ end
+
+ context 'with group parents' do
+ let(:instance_enabled) { true }
+
before do
- stub_application_setting(auto_devops_enabled: false)
+ stub_application_setting(auto_devops_enabled: instance_enabled)
+ project.update!(namespace: parent_group)
end
- it { is_expected.to be_falsey }
+ context 'when enabled on parent' do
+ let(:parent_group) { create(:group, :auto_devops_enabled) }
- context 'when explicitly enabled' do
- before do
- create(:project_auto_devops, project: project)
+ context 'when auto devops instance enabled' do
+ it { is_expected.to be_truthy }
end
- it { is_expected.to be_truthy }
+ context 'when auto devops instance disabled' do
+ let(:instance_disabled) { false }
+
+ it { is_expected.to be_truthy }
+ end
end
- context 'when force_autodevops_on_by_default is enabled for the project' do
- before do
- Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(100)
+ context 'when disabled on parent' do
+ let(:parent_group) { create(:group, :auto_devops_disabled) }
+
+ context 'when auto devops instance enabled' do
+ it { is_expected.to be_falsy }
end
- it { is_expected.to be_truthy }
+ context 'when auto devops instance disabled' do
+ let(:instance_disabled) { false }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ context 'when enabled on root parent', :nested_groups do
+ let(:parent_group) { create(:group, parent: create(:group, :auto_devops_enabled)) }
+
+ context 'when auto devops instance enabled' do
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when auto devops instance disabled' do
+ let(:instance_disabled) { false }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when explicitly disabled on parent' do
+ let(:parent_group) { create(:group, :auto_devops_disabled, parent: create(:group, :auto_devops_enabled)) }
+
+ it { is_expected.to be_falsy }
+ end
+ end
+
+ context 'when disabled on root parent', :nested_groups do
+ let(:parent_group) { create(:group, parent: create(:group, :auto_devops_disabled)) }
+
+ context 'when auto devops instance enabled' do
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when auto devops instance disabled' do
+ let(:instance_disabled) { false }
+
+ it { is_expected.to be_falsy }
+ end
+
+ context 'when explicitly disabled on parent' do
+ let(:parent_group) { create(:group, :auto_devops_disabled, parent: create(:group, :auto_devops_enabled)) }
+
+ it { is_expected.to be_falsy }
+ end
end
end
end
@@ -3722,15 +3800,52 @@ describe Project do
end
end
end
+
+ context 'when enabled on group' do
+ it 'has auto devops implicitly enabled' do
+ project.update(namespace: create(:group, :auto_devops_enabled))
+
+ expect(project).to have_auto_devops_implicitly_enabled
+ end
+ end
+
+ context 'when enabled on parent group' do
+ it 'has auto devops implicitly enabled' do
+ subgroup = create(:group, parent: create(:group, :auto_devops_enabled))
+ project.update(namespace: subgroup)
+
+ expect(project).to have_auto_devops_implicitly_enabled
+ end
+ end
end
describe '#has_auto_devops_implicitly_disabled?' do
+ set(:project) { create(:project) }
+
before do
allow(Feature).to receive(:enabled?).and_call_original
Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(0)
end
- set(:project) { create(:project) }
+ context 'when explicitly disabled' do
+ before do
+ create(:project_auto_devops, project: project, enabled: false)
+ end
+
+ it 'does not have auto devops implicitly disabled' do
+ expect(project).not_to have_auto_devops_implicitly_disabled
+ end
+ end
+
+ context 'when explicitly enabled' do
+ before do
+ create(:project_auto_devops, project: project, enabled: true)
+ end
+
+ it 'does not have auto devops implicitly disabled' do
+ expect(project).not_to have_auto_devops_implicitly_disabled
+ end
+ end
context 'when enabled in settings' do
before do
@@ -3753,6 +3868,8 @@ describe Project do
context 'when force_autodevops_on_by_default is enabled for the project' do
before do
+ create(:project_auto_devops, project: project, enabled: false)
+
Feature.get(:force_autodevops_on_by_default).enable_percentage_of_actors(100)
end
@@ -3761,23 +3878,20 @@ describe Project do
end
end
- context 'when explicitly disabled' do
- before do
- create(:project_auto_devops, project: project, enabled: false)
- end
+ context 'when disabled on group' do
+ it 'has auto devops implicitly disabled' do
+ project.update!(namespace: create(:group, :auto_devops_disabled))
- it 'does not have auto devops implicitly disabled' do
- expect(project).not_to have_auto_devops_implicitly_disabled
+ expect(project).to have_auto_devops_implicitly_disabled
end
end
- context 'when explicitly enabled' do
- before do
- create(:project_auto_devops, project: project, enabled: true)
- end
+ context 'when disabled on parent group' do
+ it 'has auto devops implicitly disabled' do
+ subgroup = create(:group, parent: create(:group, :auto_devops_disabled))
+ project.update!(namespace: subgroup)
- it 'does not have auto devops implicitly disabled' do
- expect(project).not_to have_auto_devops_implicitly_disabled
+ expect(project).to have_auto_devops_implicitly_disabled
end
end
end
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 3ccc706edf2..7be8d67ba9e 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -71,6 +71,14 @@ describe ProjectWiki do
expect(project_wiki.create_page("index", "test content")).to be_truthy
end
+ it "creates a new wiki repo with a default commit message" do
+ expect(project_wiki.create_page("index", "test content", :markdown, "")).to be_truthy
+
+ page = project_wiki.find_page('index')
+
+ expect(page.last_version.message).to eq("#{user.username} created page: index")
+ end
+
it "raises CouldNotCreateWikiError if it can't create the wiki repository" do
# Create a fresh project which will not have a wiki
project_wiki = described_class.new(create(:project), user)
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 70630467d24..6599b4e765a 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -1095,65 +1095,69 @@ describe Repository do
end
end
- describe '#exists?' do
- it 'returns true when a repository exists' do
- expect(repository.exists?).to be(true)
- end
-
- it 'returns false if no full path can be constructed' do
- allow(repository).to receive(:full_path).and_return(nil)
-
- expect(repository.exists?).to be(false)
- end
-
- context 'with broken storage', :broken_storage do
- it 'should raise a storage error' do
- expect_to_raise_storage_error { broken_repository.exists? }
- end
- end
-
+ shared_examples 'asymmetric cached method' do |method|
context 'asymmetric caching', :use_clean_rails_memory_store_caching, :request_store do
let(:cache) { repository.send(:cache) }
let(:request_store_cache) { repository.send(:request_store_cache) }
context 'when it returns true' do
before do
- expect(repository.raw_repository).to receive(:exists?).once.and_return(true)
+ expect(repository.raw_repository).to receive(method).once.and_return(true)
end
it 'caches the output in RequestStore' do
expect do
- repository.exists?
- end.to change { request_store_cache.read(:exists?) }.from(nil).to(true)
+ repository.send(method)
+ end.to change { request_store_cache.read(method) }.from(nil).to(true)
end
it 'caches the output in RepositoryCache' do
expect do
- repository.exists?
- end.to change { cache.read(:exists?) }.from(nil).to(true)
+ repository.send(method)
+ end.to change { cache.read(method) }.from(nil).to(true)
end
end
context 'when it returns false' do
before do
- expect(repository.raw_repository).to receive(:exists?).once.and_return(false)
+ expect(repository.raw_repository).to receive(method).once.and_return(false)
end
it 'caches the output in RequestStore' do
expect do
- repository.exists?
- end.to change { request_store_cache.read(:exists?) }.from(nil).to(false)
+ repository.send(method)
+ end.to change { request_store_cache.read(method) }.from(nil).to(false)
end
it 'does NOT cache the output in RepositoryCache' do
expect do
- repository.exists?
- end.not_to change { cache.read(:exists?) }.from(nil)
+ repository.send(method)
+ end.not_to change { cache.read(method) }.from(nil)
end
end
end
end
+ describe '#exists?' do
+ it 'returns true when a repository exists' do
+ expect(repository.exists?).to be(true)
+ end
+
+ it 'returns false if no full path can be constructed' do
+ allow(repository).to receive(:full_path).and_return(nil)
+
+ expect(repository.exists?).to be(false)
+ end
+
+ context 'with broken storage', :broken_storage do
+ it 'should raise a storage error' do
+ expect_to_raise_storage_error { broken_repository.exists? }
+ end
+ end
+
+ it_behaves_like 'asymmetric cached method', :exists?
+ end
+
describe '#has_visible_content?' do
before do
# If raw_repository.has_visible_content? gets called more than once then
@@ -1271,6 +1275,8 @@ describe Repository do
repository.root_ref
repository.root_ref
end
+
+ it_behaves_like 'asymmetric cached method', :root_ref
end
describe '#expire_root_ref_cache' do
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 85b157a9435..1be29d039a7 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -660,6 +660,68 @@ describe User do
end
end
+ describe '#highest_role' do
+ let(:user) { create(:user) }
+
+ let(:group) { create(:group) }
+
+ it 'returns NO_ACCESS if none has been set' do
+ expect(user.highest_role).to eq(Gitlab::Access::NO_ACCESS)
+ end
+
+ it 'returns MAINTAINER if user is maintainer of a project' do
+ create(:project, group: group) do |project|
+ project.add_maintainer(user)
+ end
+
+ expect(user.highest_role).to eq(Gitlab::Access::MAINTAINER)
+ end
+
+ it 'returns the highest role if user is member of multiple projects' do
+ create(:project, group: group) do |project|
+ project.add_maintainer(user)
+ end
+
+ create(:project, group: group) do |project|
+ project.add_developer(user)
+ end
+
+ expect(user.highest_role).to eq(Gitlab::Access::MAINTAINER)
+ end
+
+ it 'returns MAINTAINER if user is maintainer of a group' do
+ create(:group) do |group|
+ group.add_user(user, GroupMember::MAINTAINER)
+ end
+
+ expect(user.highest_role).to eq(Gitlab::Access::MAINTAINER)
+ end
+
+ it 'returns the highest role if user is member of multiple groups' do
+ create(:group) do |group|
+ group.add_user(user, GroupMember::MAINTAINER)
+ end
+
+ create(:group) do |group|
+ group.add_user(user, GroupMember::DEVELOPER)
+ end
+
+ expect(user.highest_role).to eq(Gitlab::Access::MAINTAINER)
+ end
+
+ it 'returns the highest role if user is member of multiple groups and projects' do
+ create(:group) do |group|
+ group.add_user(user, GroupMember::DEVELOPER)
+ end
+
+ create(:project, group: group) do |project|
+ project.add_maintainer(user)
+ end
+
+ expect(user.highest_role).to eq(Gitlab::Access::MAINTAINER)
+ end
+ end
+
describe '#update_tracked_fields!', :clean_gitlab_redis_shared_state do
let(:request) { OpenStruct.new(remote_ip: "127.0.0.1") }
let(:user) { create(:user) }
diff --git a/spec/policies/group_policy_spec.rb b/spec/policies/group_policy_spec.rb
index 0ad50c6f91f..92bdaa8b8b8 100644
--- a/spec/policies/group_policy_spec.rb
+++ b/spec/policies/group_policy_spec.rb
@@ -76,15 +76,10 @@ describe GroupPolicy do
context 'with no user and public project' do
let(:project) { create(:project, :public) }
- let(:user) { create(:user) }
let(:current_user) { nil }
before do
- Projects::GroupLinks::CreateService.new(
- project,
- user,
- link_group_access: ProjectGroupLink::DEVELOPER
- ).execute(group)
+ create(:project_group_link, project: project, group: group)
end
it { expect_disallowed(:read_group) }
@@ -96,11 +91,7 @@ describe GroupPolicy do
let(:current_user) { create(:user) }
before do
- Projects::GroupLinks::CreateService.new(
- project,
- user,
- link_group_access: ProjectGroupLink::DEVELOPER
- ).execute(group)
+ create(:project_group_link, project: project, group: group)
end
it { expect_disallowed(:read_group) }
diff --git a/spec/policies/identity_provider_policy_spec.rb b/spec/policies/identity_provider_policy_spec.rb
new file mode 100644
index 00000000000..2520469d4e7
--- /dev/null
+++ b/spec/policies/identity_provider_policy_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe IdentityProviderPolicy do
+ subject(:policy) { described_class.new(user, provider) }
+ let(:user) { User.new }
+ let(:provider) { :a_provider }
+
+ describe '#rules' do
+ it { is_expected.to be_allowed(:link) }
+ it { is_expected.to be_allowed(:unlink) }
+
+ context 'when user is anonymous' do
+ let(:user) { nil }
+
+ it { is_expected.not_to be_allowed(:link) }
+ it { is_expected.not_to be_allowed(:unlink) }
+ end
+
+ %w[saml cas3].each do |provider_name|
+ context "when provider is #{provider_name}" do
+ let(:provider) { provider_name }
+
+ it { is_expected.to be_allowed(:link) }
+ it { is_expected.not_to be_allowed(:unlink) }
+ end
+ end
+ end
+end
diff --git a/spec/policies/issuable_policy_spec.rb b/spec/policies/issuable_policy_spec.rb
index db3df760472..6d34b0a8b4b 100644
--- a/spec/policies/issuable_policy_spec.rb
+++ b/spec/policies/issuable_policy_spec.rb
@@ -13,7 +13,7 @@ describe IssuablePolicy, models: true do
context 'when user is able to read project' do
it 'enables user to read and update issuables' do
- expect(policies).to be_allowed(:read_issue, :update_issue, :reopen_issue, :read_merge_request, :update_merge_request)
+ expect(policies).to be_allowed(:read_issue, :update_issue, :reopen_issue, :read_merge_request, :update_merge_request, :reopen_merge_request)
end
end
@@ -24,12 +24,12 @@ describe IssuablePolicy, models: true do
it 'enables user to read and update issuables' do
project.add_maintainer(user)
- expect(policies).to be_allowed(:read_issue, :update_issue, :reopen_issue, :read_merge_request, :update_merge_request)
+ expect(policies).to be_allowed(:read_issue, :update_issue, :reopen_issue, :read_merge_request, :update_merge_request, :reopen_merge_request)
end
end
it 'disallows user from reading and updating issuables from that project' do
- expect(policies).to be_disallowed(:read_issue, :update_issue, :reopen_issue, :read_merge_request, :update_merge_request)
+ expect(policies).to be_disallowed(:read_issue, :update_issue, :reopen_issue, :read_merge_request, :update_merge_request, :reopen_merge_request)
end
end
end
diff --git a/spec/policies/merge_request_policy_spec.rb b/spec/policies/merge_request_policy_spec.rb
new file mode 100644
index 00000000000..1efa70addc2
--- /dev/null
+++ b/spec/policies/merge_request_policy_spec.rb
@@ -0,0 +1,50 @@
+require 'spec_helper'
+
+describe MergeRequestPolicy do
+ let(:guest) { create(:user) }
+ let(:author) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:project) { create(:project, :public) }
+
+ def permissions(user, merge_request)
+ described_class.new(user, merge_request)
+ end
+
+ before do
+ project.add_guest(guest)
+ project.add_guest(author)
+ project.add_developer(developer)
+ end
+
+ context 'when merge request is unlocked' do
+ let(:merge_request) { create(:merge_request, :closed, source_project: project, target_project: project, author: author) }
+
+ it 'allows author to reopen merge request' do
+ expect(permissions(author, merge_request)).to be_allowed(:reopen_merge_request)
+ end
+
+ it 'allows developer to reopen merge request' do
+ expect(permissions(developer, merge_request)).to be_allowed(:reopen_merge_request)
+ end
+
+ it 'prevents guest from reopening merge request' do
+ expect(permissions(guest, merge_request)).to be_disallowed(:reopen_merge_request)
+ end
+ end
+
+ context 'when merge request is locked' do
+ let(:merge_request_locked) { create(:merge_request, :closed, discussion_locked: true, source_project: project, target_project: project, author: author) }
+
+ it 'prevents author from reopening merge request' do
+ expect(permissions(author, merge_request_locked)).to be_disallowed(:reopen_merge_request)
+ end
+
+ it 'prevents developer from reopening merge request' do
+ expect(permissions(developer, merge_request_locked)).to be_disallowed(:reopen_merge_request)
+ end
+
+ it 'prevents guests from reopening merge request' do
+ expect(permissions(guest, merge_request_locked)).to be_disallowed(:reopen_merge_request)
+ end
+ end
+end
diff --git a/spec/presenters/ci/pipeline_presenter_spec.rb b/spec/presenters/ci/pipeline_presenter_spec.rb
index f7ceaf844be..cda07a0ae09 100644
--- a/spec/presenters/ci/pipeline_presenter_spec.rb
+++ b/spec/presenters/ci/pipeline_presenter_spec.rb
@@ -1,6 +1,9 @@
require 'spec_helper'
describe Ci::PipelinePresenter do
+ include Gitlab::Routing
+
+ let(:user) { create(:user) }
let(:project) { create(:project) }
let(:pipeline) { create(:ci_pipeline, project: project) }
@@ -8,6 +11,11 @@ describe Ci::PipelinePresenter do
described_class.new(pipeline)
end
+ before do
+ project.add_developer(user)
+ allow(presenter).to receive(:current_user) { user }
+ end
+
it 'inherits from Gitlab::View::Presenter::Delegated' do
expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated)
end
@@ -68,4 +76,130 @@ describe Ci::PipelinePresenter do
end
end
end
+
+ describe '#ref_text' do
+ subject { presenter.ref_text }
+
+ context 'when pipeline is detached merge request pipeline' do
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
+ let(:pipeline) { merge_request.all_pipelines.last }
+
+ it 'returns a correct ref text' do
+ is_expected.to eq("for <a class=\"mr-iid\" href=\"#{project_merge_request_path(merge_request.project, merge_request)}\">#{merge_request.to_reference}</a> " \
+ "with <a class=\"ref-name\" href=\"#{project_commits_path(merge_request.source_project, merge_request.source_branch)}\">#{merge_request.source_branch}</a>")
+ end
+ end
+
+ context 'when pipeline is merge request pipeline' do
+ let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) }
+ let(:pipeline) { merge_request.all_pipelines.last }
+
+ it 'returns a correct ref text' do
+ is_expected.to eq("for <a class=\"mr-iid\" href=\"#{project_merge_request_path(merge_request.project, merge_request)}\">#{merge_request.to_reference}</a> " \
+ "with <a class=\"ref-name\" href=\"#{project_commits_path(merge_request.source_project, merge_request.source_branch)}\">#{merge_request.source_branch}</a> " \
+ "into <a class=\"ref-name\" href=\"#{project_commits_path(merge_request.target_project, merge_request.target_branch)}\">#{merge_request.target_branch}</a>")
+ end
+ end
+
+ context 'when pipeline is branch pipeline' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ context 'when ref exists in the repository' do
+ before do
+ allow(pipeline).to receive(:ref_exists?) { true }
+ end
+
+ it 'returns a correct ref text' do
+ is_expected.to eq("for <a class=\"ref-name\" href=\"#{project_commits_path(pipeline.project, pipeline.ref)}\">#{pipeline.ref}</a>")
+ end
+
+ context 'when ref contains malicious script' do
+ let(:pipeline) { create(:ci_pipeline, ref: "<script>alter('1')</script>", project: project) }
+
+ it 'does not include the malicious script' do
+ is_expected.not_to include("<script>alter('1')</script>")
+ end
+ end
+ end
+
+ context 'when ref exists in the repository' do
+ before do
+ allow(pipeline).to receive(:ref_exists?) { false }
+ end
+
+ it 'returns a correct ref text' do
+ is_expected.to eq("for <span class=\"ref-name\">#{pipeline.ref}</span>")
+ end
+
+ context 'when ref contains malicious script' do
+ let(:pipeline) { create(:ci_pipeline, ref: "<script>alter('1')</script>", project: project) }
+
+ it 'does not include the malicious script' do
+ is_expected.not_to include("<script>alter('1')</script>")
+ end
+ end
+ end
+ end
+ end
+
+ describe '#link_to_merge_request' do
+ subject { presenter.link_to_merge_request }
+
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
+ let(:pipeline) { merge_request.all_pipelines.last }
+
+ it 'returns a correct link' do
+ is_expected
+ .to include(project_merge_request_path(merge_request.project, merge_request))
+ end
+
+ context 'when pipeline is branch pipeline' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ it 'returns nothing' do
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ describe '#link_to_merge_request_source_branch' do
+ subject { presenter.link_to_merge_request_source_branch }
+
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
+ let(:pipeline) { merge_request.all_pipelines.last }
+
+ it 'returns a correct link' do
+ is_expected
+ .to include(project_commits_path(merge_request.source_project,
+ merge_request.source_branch))
+ end
+
+ context 'when pipeline is branch pipeline' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ it 'returns nothing' do
+ is_expected.to be_nil
+ end
+ end
+ end
+
+ describe '#link_to_merge_request_target_branch' do
+ subject { presenter.link_to_merge_request_target_branch }
+
+ let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) }
+ let(:pipeline) { merge_request.all_pipelines.last }
+
+ it 'returns a correct link' do
+ is_expected
+ .to include(project_commits_path(merge_request.target_project, merge_request.target_branch))
+ end
+
+ context 'when pipeline is branch pipeline' do
+ let(:pipeline) { create(:ci_pipeline, project: project) }
+
+ it 'returns nothing' do
+ is_expected.to be_nil
+ end
+ end
+ end
end
diff --git a/spec/presenters/merge_request_presenter_spec.rb b/spec/presenters/merge_request_presenter_spec.rb
index 02cefcbc916..4a0f91c4c7a 100644
--- a/spec/presenters/merge_request_presenter_spec.rb
+++ b/spec/presenters/merge_request_presenter_spec.rb
@@ -1,8 +1,8 @@
require 'spec_helper'
describe MergeRequestPresenter do
- let(:resource) { create :merge_request, source_project: project }
- let(:project) { create :project }
+ let(:resource) { create(:merge_request, source_project: project) }
+ let(:project) { create(:project) }
let(:user) { create(:user) }
describe '#ci_status' do
@@ -345,6 +345,30 @@ describe MergeRequestPresenter do
end
end
+ describe '#source_branch_commits_path' do
+ subject do
+ described_class.new(resource, current_user: user)
+ .source_branch_commits_path
+ end
+
+ context 'when source branch exists' do
+ it 'returns path' do
+ allow(resource).to receive(:source_branch_exists?) { true }
+
+ is_expected
+ .to eq("/#{resource.source_project.full_path}/commits/#{resource.source_branch}")
+ end
+ end
+
+ context 'when source branch does not exist' do
+ it 'returns nil' do
+ allow(resource).to receive(:source_branch_exists?) { false }
+
+ is_expected.to be_nil
+ end
+ end
+ end
+
describe '#target_branch_tree_path' do
subject do
described_class.new(resource, current_user: user)
@@ -499,4 +523,46 @@ describe MergeRequestPresenter do
end
end
end
+
+ describe '#can_push_to_source_branch' do
+ before do
+ allow(resource).to receive(:source_branch_exists?) { source_branch_exists }
+
+ allow_any_instance_of(Gitlab::UserAccess::RequestCacheExtension)
+ .to receive(:can_push_to_branch?)
+ .with(resource.source_branch)
+ .and_return(can_push_to_branch)
+ end
+
+ subject do
+ described_class.new(resource, current_user: user).can_push_to_source_branch?
+ end
+
+ context 'when source branch exists AND user can push to source branch' do
+ let(:source_branch_exists) { true }
+ let(:can_push_to_branch) { true }
+
+ it 'returns true' do
+ is_expected.to eq(true)
+ end
+ end
+
+ context 'when source branch does not exists' do
+ let(:source_branch_exists) { false }
+ let(:can_push_to_branch) { true }
+
+ it 'returns false' do
+ is_expected.to eq(false)
+ end
+ end
+
+ context 'when user cannot push to source branch' do
+ let(:source_branch_exists) { true }
+ let(:can_push_to_branch) { false }
+
+ it 'returns false' do
+ is_expected.to eq(false)
+ end
+ end
+ end
end
diff --git a/spec/rack_servers/puma_spec.rb b/spec/rack_servers/puma_spec.rb
index 431fab87857..891df4f1a66 100644
--- a/spec/rack_servers/puma_spec.rb
+++ b/spec/rack_servers/puma_spec.rb
@@ -44,11 +44,9 @@ describe 'Puma' do
end
after(:all) do
- begin
- WebMock.disable_net_connect!(allow_localhost: true)
- Process.kill('TERM', @puma_master_pid)
- rescue Errno::ESRCH
- end
+ WebMock.disable_net_connect!(allow_localhost: true)
+ Process.kill('TERM', @puma_master_pid)
+ rescue Errno::ESRCH
end
def wait_puma_boot!(master_pid, ready_file)
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index cd85151ec1b..b184c92824a 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -26,6 +26,21 @@ describe API::Internal do
expect(json_response['redis']).to be(false)
end
+
+ context 'authenticating' do
+ it 'authenticates using a header' do
+ get api("/internal/check"),
+ headers: { API::Helpers::GITLAB_SHARED_SECRET_HEADER => Base64.encode64(secret_token) }
+
+ expect(response).to have_gitlab_http_status(200)
+ end
+
+ it 'returns 401 when no credentials provided' do
+ get(api("/internal/check"))
+
+ expect(response).to have_gitlab_http_status(401)
+ end
+ end
end
describe 'GET /internal/broadcast_message' do
@@ -237,6 +252,14 @@ describe API::Internal do
expect(json_response['name']).to eq(user.name)
end
+
+ it 'responds successfully when a user is not found' do
+ get(api("/internal/discover"), params: { username: 'noone', secret_token: secret_token })
+
+ expect(response).to have_gitlab_http_status(200)
+
+ expect(response.body).to eq('null')
+ end
end
describe "GET /internal/authorized_keys" do
@@ -324,7 +347,6 @@ describe API::Internal do
expect(response).to have_gitlab_http_status(200)
expect(json_response["status"]).to be_truthy
- expect(json_response["repository_path"]).to eq('/')
expect(json_response["gl_project_path"]).to eq(project.wiki.full_path)
expect(json_response["gl_repository"]).to eq("wiki-#{project.id}")
expect(user.reload.last_activity_on).to be_nil
@@ -337,7 +359,6 @@ describe API::Internal do
expect(response).to have_gitlab_http_status(200)
expect(json_response["status"]).to be_truthy
- expect(json_response["repository_path"]).to eq('/')
expect(json_response["gl_project_path"]).to eq(project.wiki.full_path)
expect(json_response["gl_repository"]).to eq("wiki-#{project.id}")
expect(user.reload.last_activity_on).to eql(Date.today)
@@ -350,7 +371,6 @@ describe API::Internal do
expect(response).to have_gitlab_http_status(200)
expect(json_response["status"]).to be_truthy
- expect(json_response["repository_path"]).to eq('/')
expect(json_response["gl_repository"]).to eq("project-#{project.id}")
expect(json_response["gl_project_path"]).to eq(project.full_path)
expect(json_response["gitaly"]).not_to be_nil
@@ -370,7 +390,6 @@ describe API::Internal do
expect(response).to have_gitlab_http_status(200)
expect(json_response["status"]).to be_truthy
- expect(json_response["repository_path"]).to eq('/')
expect(json_response["gl_repository"]).to eq("project-#{project.id}")
expect(json_response["gl_project_path"]).to eq(project.full_path)
expect(json_response["gitaly"]).not_to be_nil
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index fee6312a9c7..4259fda7f04 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -4,32 +4,406 @@ describe API::MergeRequests do
include ProjectForksHelper
let(:base_time) { Time.now }
- let(:user) { create(:user) }
- let(:admin) { create(:user, :admin) }
- let(:non_member) { create(:user) }
- let!(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace, only_allow_merge_if_pipeline_succeeds: false) }
+ set(:user) { create(:user) }
+ set(:admin) { create(:user, :admin) }
+ let(:project) { create(:project, :public, :repository, creator: user, namespace: user.namespace, only_allow_merge_if_pipeline_succeeds: false) }
let(:milestone) { create(:milestone, title: '1.0.0', project: project) }
- let(:pipeline) { create(:ci_empty_pipeline) }
- let(:milestone1) { create(:milestone, title: '0.9', project: project) }
+ let(:milestone1) { create(:milestone, title: '0.9', project: project) }
let!(:merge_request) { create(:merge_request, :simple, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Test", created_at: base_time) }
let!(:merge_request_closed) { create(:merge_request, state: "closed", milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Closed test", created_at: base_time + 1.second) }
let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test", created_at: base_time + 2.seconds, merge_commit_sha: '9999999999999999999999999999999999999999') }
let!(:merge_request_locked) { create(:merge_request, state: "locked", milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: "Locked test", created_at: base_time + 1.second) }
let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
let!(:note2) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "another comment on a MR") }
- let!(:label) do
- create(:label, title: 'label', color: '#FFAABB', project: project)
- end
- let!(:label2) { create(:label, title: 'a-test', color: '#FFFFFF', project: project) }
- let!(:label_link) { create(:label_link, label: label, target: merge_request) }
- let!(:label_link2) { create(:label_link, label: label2, target: merge_request) }
- let!(:downvote) { create(:award_emoji, :downvote, awardable: merge_request) }
- let!(:upvote) { create(:award_emoji, :upvote, awardable: merge_request) }
+ let(:label) { create(:label, title: 'label', color: '#FFAABB', project: project) }
+ let(:label2) { create(:label, title: 'a-test', color: '#FFFFFF', project: project) }
before do
project.add_reporter(user)
end
+ shared_context 'with labels' do
+ before do
+ create(:label_link, label: label, target: merge_request)
+ create(:label_link, label: label2, target: merge_request)
+ end
+ end
+
+ shared_examples 'merge requests list' do
+ context 'when unauthenticated' do
+ it 'returns merge requests for public projects' do
+ get api(endpoint_path)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ end
+ end
+
+ context 'when authenticated' do
+ it 'avoids N+1 queries' do
+ control = ActiveRecord::QueryRecorder.new do
+ get api(endpoint_path, user)
+ end
+
+ create(:merge_request, state: 'closed', milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: 'Test', created_at: base_time)
+
+ merge_request = create(:merge_request, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: 'Test', created_at: base_time)
+
+ merge_request.metrics.update!(merged_by: user,
+ latest_closed_by: user,
+ latest_closed_at: 1.hour.ago,
+ merged_at: 2.hours.ago)
+
+ expect do
+ get api(endpoint_path, user)
+ end.not_to exceed_query_limit(control)
+ end
+
+ context 'with labels' do
+ include_context 'with labels'
+
+ it 'returns an array of all merge_requests' do
+ get api(endpoint_path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(4)
+ expect(json_response.last['title']).to eq(merge_request.title)
+ expect(json_response.last).to have_key('web_url')
+ expect(json_response.last['sha']).to eq(merge_request.diff_head_sha)
+ expect(json_response.last['merge_commit_sha']).to be_nil
+ expect(json_response.last['merge_commit_sha']).to eq(merge_request.merge_commit_sha)
+ expect(json_response.last['downvotes']).to eq(0)
+ expect(json_response.last['upvotes']).to eq(0)
+ expect(json_response.last['labels']).to eq([label2.title, label.title])
+ expect(json_response.first['title']).to eq(merge_request_merged.title)
+ expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha)
+ expect(json_response.first['merge_commit_sha']).not_to be_nil
+ expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha)
+ end
+ end
+
+ it 'returns an array of all merge_requests using simple mode' do
+ path = endpoint_path + '?view=simple'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response.last.keys).to match_array(%w(id iid title web_url created_at description project_id state updated_at))
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(4)
+ expect(json_response.last['iid']).to eq(merge_request.iid)
+ expect(json_response.last['title']).to eq(merge_request.title)
+ expect(json_response.last).to have_key('web_url')
+ expect(json_response.first['iid']).to eq(merge_request_merged.iid)
+ expect(json_response.first['title']).to eq(merge_request_merged.title)
+ expect(json_response.first).to have_key('web_url')
+ end
+
+ it 'returns an array of all merge_requests' do
+ path = endpoint_path + '?state'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(4)
+ expect(json_response.last['title']).to eq(merge_request.title)
+ end
+
+ it 'returns an array of open merge_requests' do
+ path = endpoint_path + '?state=opened'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.last['title']).to eq(merge_request.title)
+ end
+
+ it 'returns an array of closed merge_requests' do
+ path = endpoint_path + '?state=closed'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['title']).to eq(merge_request_closed.title)
+ end
+
+ it 'returns an array of merged merge_requests' do
+ path = endpoint_path + '?state=merged'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['title']).to eq(merge_request_merged.title)
+ end
+
+ it 'matches V4 response schema' do
+ get api(endpoint_path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/merge_requests')
+ end
+
+ it 'returns an empty array if no issue matches milestone' do
+ get api(endpoint_path, user), params: { milestone: '1.0.0' }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+
+ it 'returns an empty array if milestone does not exist' do
+ get api(endpoint_path, user), params: { milestone: 'foo' }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+
+ it 'returns an array of merge requests in given milestone' do
+ get api(endpoint_path, user), params: { milestone: '0.9' }
+
+ closed_issues = json_response.select { |mr| mr['id'] == merge_request_closed.id }
+ expect(closed_issues.length).to eq(1)
+ expect(closed_issues.first['title']).to eq merge_request_closed.title
+ end
+
+ it 'returns an array of merge requests matching state in milestone' do
+ get api(endpoint_path, user), params: { milestone: '0.9', state: 'closed' }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(merge_request_closed.id)
+ end
+
+ context 'with labels' do
+ include_context 'with labels'
+
+ it 'returns an array of labeled merge requests' do
+ path = endpoint_path + "?labels=#{label.title}"
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['labels']).to eq([label2.title, label.title])
+ end
+
+ it 'returns an array of labeled merge requests where all labels match' do
+ path = endpoint_path + "?labels=#{label.title},foo,bar"
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+
+ it 'returns an empty array if no merge request matches labels' do
+ path = endpoint_path + '?labels=foo,bar'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(0)
+ end
+
+ it 'returns an array of labeled merge requests where all labels match' do
+ path = endpoint_path + "?labels[]=#{label.title}&labels[]=#{label2.title}"
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['labels']).to eq([label2.title, label.title])
+ end
+
+ it 'returns an array of merge requests with any label when filtering by any label' do
+ get api(endpoint_path, user), params: { labels: [" #{label.title} ", " #{label2.title} "] }
+
+ expect_paginated_array_response
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['labels']).to eq([label2.title, label.title])
+ expect(json_response.first['id']).to eq(merge_request.id)
+ end
+
+ it 'returns an array of merge requests with any label when filtering by any label' do
+ get api(endpoint_path, user), params: { labels: ["#{label.title} , #{label2.title}"] }
+
+ expect_paginated_array_response
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['labels']).to eq([label2.title, label.title])
+ expect(json_response.first['id']).to eq(merge_request.id)
+ end
+
+ it 'returns an array of merge requests with any label when filtering by any label' do
+ get api(endpoint_path, user), params: { labels: IssuesFinder::FILTER_ANY }
+
+ expect_paginated_array_response
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(merge_request.id)
+ end
+
+ it 'returns an array of merge requests without a label when filtering by no label' do
+ get api(endpoint_path, user), params: { labels: IssuesFinder::FILTER_NONE }
+
+ response_ids = json_response.map { |merge_request| merge_request['id'] }
+
+ expect_paginated_array_response
+ expect(response_ids).to contain_exactly(merge_request_closed.id, merge_request_merged.id, merge_request_locked.id)
+ end
+ end
+
+ it 'returns an array of labeled merge requests that are merged for a milestone' do
+ bug_label = create(:label, title: 'bug', color: '#FFAABB', project: project)
+
+ mr1 = create(:merge_request, state: 'merged', source_project: project, target_project: project, milestone: milestone)
+ mr2 = create(:merge_request, state: 'merged', source_project: project, target_project: project, milestone: milestone1)
+ mr3 = create(:merge_request, state: 'closed', source_project: project, target_project: project, milestone: milestone1)
+ _mr = create(:merge_request, state: 'merged', source_project: project, target_project: project, milestone: milestone1)
+
+ create(:label_link, label: bug_label, target: mr1)
+ create(:label_link, label: bug_label, target: mr2)
+ create(:label_link, label: bug_label, target: mr3)
+
+ path = endpoint_path + "?labels=#{bug_label.title}&milestone=#{milestone1.title}&state=merged"
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['id']).to eq(mr2.id)
+ end
+
+ context 'with ordering' do
+ before do
+ @mr_later = mr_with_later_created_and_updated_at_time
+ @mr_earlier = mr_with_earlier_created_and_updated_at_time
+ end
+
+ it 'returns an array of merge_requests in ascending order' do
+ path = endpoint_path + '?sort=asc'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(4)
+ response_dates = json_response.map { |merge_request| merge_request['created_at'] }
+ expect(response_dates).to eq(response_dates.sort)
+ end
+
+ it 'returns an array of merge_requests in descending order' do
+ path = endpoint_path + '?sort=desc'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(4)
+ response_dates = json_response.map { |merge_request| merge_request['created_at'] }
+ expect(response_dates).to eq(response_dates.sort.reverse)
+ end
+
+ context '2 merge requests with equal created_at' do
+ let!(:closed_mr2) do
+ create :merge_request,
+ state: 'closed',
+ milestone: milestone1,
+ author: user,
+ assignee: user,
+ source_project: project,
+ target_project: project,
+ title: "Test",
+ created_at: @mr_earlier.created_at
+ end
+
+ it 'page breaks first page correctly' do
+ get api("#{endpoint_path}?sort=desc&per_page=4", user)
+
+ response_ids = json_response.map { |merge_request| merge_request['id'] }
+
+ expect(response_ids).to include(closed_mr2.id)
+ expect(response_ids).not_to include(@mr_earlier.id)
+ end
+
+ it 'page breaks second page correctly' do
+ get api("#{endpoint_path}?sort=desc&per_page=4&page=2", user)
+
+ response_ids = json_response.map { |merge_request| merge_request['id'] }
+
+ expect(response_ids).not_to include(closed_mr2.id)
+ expect(response_ids).to include(@mr_earlier.id)
+ end
+ end
+
+ it 'returns an array of merge_requests ordered by updated_at' do
+ path = endpoint_path + '?order_by=updated_at'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(4)
+ response_dates = json_response.map { |merge_request| merge_request['updated_at'] }
+ expect(response_dates).to eq(response_dates.sort.reverse)
+ end
+
+ it 'returns an array of merge_requests ordered by created_at' do
+ path = endpoint_path + '?order_by=created_at&sort=asc'
+
+ get api(path, user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.length).to eq(4)
+ response_dates = json_response.map { |merge_request| merge_request['created_at'] }
+ expect(response_dates).to eq(response_dates.sort)
+ end
+ end
+
+ context 'source_branch param' do
+ it 'returns merge requests with the given source branch' do
+ get api(endpoint_path, user), params: { source_branch: merge_request_closed.source_branch, state: 'all' }
+
+ expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked)
+ end
+ end
+
+ context 'target_branch param' do
+ it 'returns merge requests with the given target branch' do
+ get api(endpoint_path, user), params: { target_branch: merge_request_closed.target_branch, state: 'all' }
+
+ expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked)
+ end
+ end
+ end
+ end
+
describe 'route shadowing' do
include GrapePathHelpers::NamedRouteMatcher
@@ -356,6 +730,9 @@ describe API::MergeRequests do
describe "GET /projects/:id/merge_requests/:merge_request_iid" do
it 'exposes known attributes' do
+ create(:award_emoji, :downvote, awardable: merge_request)
+ create(:award_emoji, :upvote, awardable: merge_request)
+
get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}", user)
expect(response).to have_gitlab_http_status(200)
@@ -405,6 +782,8 @@ describe API::MergeRequests do
end
context 'merge_request_metrics' do
+ let(:pipeline) { create(:ci_empty_pipeline) }
+
before do
merge_request.metrics.update!(merged_by: user,
latest_closed_by: user,
diff --git a/spec/requests/api/project_clusters_spec.rb b/spec/requests/api/project_clusters_spec.rb
index 9bab1f95150..4e42e233b4c 100644
--- a/spec/requests/api/project_clusters_spec.rb
+++ b/spec/requests/api/project_clusters_spec.rb
@@ -331,7 +331,6 @@ describe API::ProjectClusters do
it 'should update cluster attributes' do
expect(cluster.platform_kubernetes.namespace).to eq('new-namespace')
- expect(cluster.kubernetes_namespace.namespace).to eq('new-namespace')
end
end
diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb
index 9087cccb759..3ccedd8dd06 100644
--- a/spec/requests/api/runner_spec.rb
+++ b/spec/requests/api/runner_spec.rb
@@ -918,6 +918,15 @@ describe API::Runner, :clean_gitlab_redis_shared_state do
it { expect(job).to be_job_execution_timeout }
end
+
+ context 'when failure_reason is unmet_prerequisites' do
+ before do
+ update_job(state: 'failed', failure_reason: 'unmet_prerequisites')
+ job.reload
+ end
+
+ it { expect(job).to be_unmet_prerequisites }
+ end
end
context 'when trace is given' do
diff --git a/spec/requests/api/search_spec.rb b/spec/requests/api/search_spec.rb
index c48ca832c85..49672591b3b 100644
--- a/spec/requests/api/search_spec.rb
+++ b/spec/requests/api/search_spec.rb
@@ -77,6 +77,28 @@ describe API::Search do
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
end
+ context 'for users scope' do
+ before do
+ create(:user, name: 'billy')
+
+ get api('/search', user), params: { scope: 'users', search: 'billy' }
+ end
+
+ it_behaves_like 'response is correct', schema: 'public_api/v4/user/basics'
+
+ context 'when users search feature is disabled' do
+ before do
+ allow(Feature).to receive(:disabled?).with(:users_search, default_enabled: true).and_return(true)
+
+ get api('/search', user), params: { scope: 'users', search: 'billy' }
+ end
+
+ it 'returns 400 error' do
+ expect(response).to have_gitlab_http_status(400)
+ end
+ end
+ end
+
context 'for snippet_titles scope' do
before do
create(:snippet, :public, title: 'awesome snippet', content: 'snippet content')
@@ -192,6 +214,40 @@ describe API::Search do
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
end
+
+ context 'for users scope' do
+ before do
+ user = create(:user, name: 'billy')
+ create(:group_member, :developer, user: user, group: group)
+
+ get api("/groups/#{group.id}/search", user), params: { scope: 'users', search: 'billy' }
+ end
+
+ it_behaves_like 'response is correct', schema: 'public_api/v4/user/basics'
+
+ context 'when users search feature is disabled' do
+ before do
+ allow(Feature).to receive(:disabled?).with(:users_search, default_enabled: true).and_return(true)
+
+ get api("/groups/#{group.id}/search", user), params: { scope: 'users', search: 'billy' }
+ end
+
+ it 'returns 400 error' do
+ expect(response).to have_gitlab_http_status(400)
+ end
+ end
+ end
+
+ context 'for users scope with group path as id' do
+ before do
+ user1 = create(:user, name: 'billy')
+ create(:group_member, :developer, user: user1, group: group)
+
+ get api("/groups/#{CGI.escape(group.full_path)}/search", user), params: { scope: 'users', search: 'billy' }
+ end
+
+ it_behaves_like 'response is correct', schema: 'public_api/v4/user/basics'
+ end
end
end
@@ -269,6 +325,29 @@ describe API::Search do
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
end
+ context 'for users scope' do
+ before do
+ user1 = create(:user, name: 'billy')
+ create(:project_member, :developer, user: user1, project: project)
+
+ get api("/projects/#{project.id}/search", user), params: { scope: 'users', search: 'billy' }
+ end
+
+ it_behaves_like 'response is correct', schema: 'public_api/v4/user/basics'
+
+ context 'when users search feature is disabled' do
+ before do
+ allow(Feature).to receive(:disabled?).with(:users_search, default_enabled: true).and_return(true)
+
+ get api("/projects/#{project.id}/search", user), params: { scope: 'users', search: 'billy' }
+ end
+
+ it 'returns 400 error' do
+ expect(response).to have_gitlab_http_status(400)
+ end
+ end
+ end
+
context 'for notes scope' do
before do
create(:note_on_merge_request, project: project, note: 'awesome note')
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index a879426589d..b84202364e1 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -68,6 +68,13 @@ describe API::Users do
expect(json_response.size).to eq(0)
end
+ it "does not return the highest role" do
+ get api("/users"), params: { username: user.username }
+
+ expect(response).to match_response_schema('public_api/v4/user/basics')
+ expect(json_response.first.keys).not_to include 'highest_role'
+ end
+
context "when public level is restricted" do
before do
stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
@@ -286,6 +293,13 @@ describe API::Users do
expect(json_response.keys).not_to include 'is_admin'
end
+ it "does not return the user's `highest_role`" do
+ get api("/users/#{user.id}", user)
+
+ expect(response).to match_response_schema('public_api/v4/user/basic')
+ expect(json_response.keys).not_to include 'highest_role'
+ end
+
context 'when authenticated as admin' do
it 'includes the `is_admin` field' do
get api("/users/#{user.id}", admin)
@@ -300,6 +314,12 @@ describe API::Users do
expect(response).to match_response_schema('public_api/v4/user/admin')
expect(json_response.keys).to include 'created_at'
end
+ it 'includes the `highest_role` field' do
+ get api("/users/#{user.id}", admin)
+
+ expect(response).to match_response_schema('public_api/v4/user/admin')
+ expect(json_response['highest_role']).to be(0)
+ end
end
context 'for an anonymous user' do
diff --git a/spec/routing/api_routing_spec.rb b/spec/routing/api_routing_spec.rb
index 5fde4bd885b..3c48ead4ff2 100644
--- a/spec/routing/api_routing_spec.rb
+++ b/spec/routing/api_routing_spec.rb
@@ -7,25 +7,17 @@ describe 'api', 'routing' do
end
it 'does not route to the GraphqlController' do
- expect(get('/api/graphql')).not_to route_to('graphql#execute')
- end
-
- it 'does not expose graphiql' do
- expect(get('/-/graphql-explorer')).not_to route_to('graphiql/rails/editors#show')
+ expect(post('/api/graphql')).not_to route_to('graphql#execute')
end
end
- context 'when graphql is disabled' do
+ context 'when graphql is enabled' do
before do
stub_feature_flags(graphql: true)
end
it 'routes to the GraphqlController' do
- expect(get('/api/graphql')).not_to route_to('graphql#execute')
- end
-
- it 'exposes graphiql' do
- expect(get('/-/graphql-explorer')).not_to route_to('graphiql/rails/editors#show')
+ expect(post('/api/graphql')).to route_to('graphql#execute')
end
end
end
diff --git a/spec/routing/group_routing_spec.rb b/spec/routing/group_routing_spec.rb
index 71788028cbf..53271550e8b 100644
--- a/spec/routing/group_routing_spec.rb
+++ b/spec/routing/group_routing_spec.rb
@@ -17,6 +17,10 @@ describe "Groups", "routing" do
expect(get("/#{group_path}")).to route_to('groups#show', id: group_path)
end
+ it "to #details" do
+ expect(get("/groups/#{group_path}/-/details")).to route_to('groups#details', id: group_path)
+ end
+
it "to #activity" do
expect(get("/groups/#{group_path}/-/activity")).to route_to('groups#activity', id: group_path)
end
diff --git a/spec/serializers/pipeline_entity_spec.rb b/spec/serializers/pipeline_entity_spec.rb
index 11040862129..1d992e8a483 100644
--- a/spec/serializers/pipeline_entity_spec.rb
+++ b/spec/serializers/pipeline_entity_spec.rb
@@ -3,13 +3,16 @@ require 'spec_helper'
describe PipelineEntity do
include Gitlab::Routing
+ set(:project) { create(:project) }
set(:user) { create(:user) }
+ set(:project) { create(:project) }
let(:request) { double('request') }
before do
stub_not_protect_default_branch
allow(request).to receive(:current_user).and_return(user)
+ allow(request).to receive(:project).and_return(project)
end
let(:entity) do
@@ -132,12 +135,12 @@ describe PipelineEntity do
end
context 'when pipeline is detached merge request pipeline' do
- let(:merge_request) { create(:merge_request, :with_merge_request_pipeline) }
+ let(:merge_request) { create(:merge_request, :with_detached_merge_request_pipeline) }
let(:project) { merge_request.target_project }
let(:pipeline) { merge_request.merge_request_pipelines.first }
it 'makes detached flag true' do
- expect(subject[:flags][:detached]).to be_truthy
+ expect(subject[:flags][:detached_merge_request_pipeline]).to be_truthy
end
context 'when user is a developer' do
@@ -156,13 +159,13 @@ describe PipelineEntity do
expect(subject[:merge_request][:source_branch])
.to eq(merge_request.source_branch)
- expect(project_branch_path(project, merge_request.source_branch))
+ expect(project_commits_path(project, merge_request.source_branch))
.to include(subject[:merge_request][:source_branch_path])
expect(subject[:merge_request][:target_branch])
.to eq(merge_request.target_branch)
- expect(project_branch_path(project, merge_request.target_branch))
+ expect(project_commits_path(project, merge_request.target_branch))
.to include(subject[:merge_request][:target_branch_path])
end
end
@@ -173,5 +176,19 @@ describe PipelineEntity do
end
end
end
+
+ context 'when pipeline is merge request pipeline' do
+ let(:merge_request) { create(:merge_request, :with_merge_request_pipeline, merge_sha: 'abc') }
+ let(:project) { merge_request.target_project }
+ let(:pipeline) { merge_request.merge_request_pipelines.first }
+
+ it 'makes detached flag false' do
+ expect(subject[:flags][:detached_merge_request_pipeline]).to be_falsy
+ end
+
+ it 'makes atached flag true' do
+ expect(subject[:flags][:merge_request_pipeline]).to be_truthy
+ end
+ end
end
end
diff --git a/spec/serializers/pipeline_serializer_spec.rb b/spec/serializers/pipeline_serializer_spec.rb
index a21487938a0..0fdd675aa01 100644
--- a/spec/serializers/pipeline_serializer_spec.rb
+++ b/spec/serializers/pipeline_serializer_spec.rb
@@ -5,7 +5,7 @@ describe PipelineSerializer do
set(:user) { create(:user) }
let(:serializer) do
- described_class.new(current_user: user)
+ described_class.new(current_user: user, project: project)
end
before do
@@ -102,20 +102,20 @@ describe PipelineSerializer do
let!(:merge_request_1) do
create(:merge_request,
- :with_merge_request_pipeline,
+ :with_detached_merge_request_pipeline,
target_project: project,
target_branch: 'master',
source_project: project,
- source_branch: 'feature-1')
+ source_branch: 'feature')
end
let!(:merge_request_2) do
create(:merge_request,
- :with_merge_request_pipeline,
+ :with_detached_merge_request_pipeline,
target_project: project,
target_branch: 'master',
source_project: project,
- source_branch: 'feature-2')
+ source_branch: '2-mb-file')
end
before do
diff --git a/spec/services/auth/container_registry_authentication_service_spec.rb b/spec/services/auth/container_registry_authentication_service_spec.rb
index 8021bd338e0..c9d85e96750 100644
--- a/spec/services/auth/container_registry_authentication_service_spec.rb
+++ b/spec/services/auth/container_registry_authentication_service_spec.rb
@@ -88,6 +88,12 @@ describe Auth::ContainerRegistryAuthenticationService do
end
end
+ shared_examples 'a deletable since registry 2.7' do
+ it_behaves_like 'an accessible' do
+ let(:actions) { ['delete'] }
+ end
+ end
+
shared_examples 'a pullable' do
it_behaves_like 'an accessible' do
let(:actions) { ['pull'] }
@@ -184,6 +190,19 @@ describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'not a container repository factory'
end
+ context 'disallow developer to delete images since registry 2.7' do
+ before do
+ project.add_developer(current_user)
+ end
+
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
context 'allow reporter to pull images' do
before do
project.add_reporter(current_user)
@@ -212,6 +231,19 @@ describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'not a container repository factory'
end
+ context 'disallow reporter to delete images since registry 2.7' do
+ before do
+ project.add_reporter(current_user)
+ end
+
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
context 'return a least of privileges' do
before do
project.add_reporter(current_user)
@@ -250,6 +282,19 @@ describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
+
+ context 'disallow guest to delete images since regsitry 2.7' do
+ before do
+ project.add_guest(current_user)
+ end
+
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
end
context 'for public project' do
@@ -282,6 +327,15 @@ describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'not a container repository factory'
end
+ context 'disallow anyone to delete images since registry 2.7' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
+
context 'when repository name is invalid' do
let(:current_params) do
{ scopes: ['repository:invalid:push'] }
@@ -322,6 +376,15 @@ describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
+
+ context 'disallow anyone to delete images since registry 2.7' do
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
end
context 'for external user' do
@@ -344,6 +407,16 @@ describe Auth::ContainerRegistryAuthenticationService do
it_behaves_like 'an inaccessible'
it_behaves_like 'not a container repository factory'
end
+
+ context 'disallow anyone to delete images since registry 2.7' do
+ let(:current_user) { create(:user, external: true) }
+ let(:current_params) do
+ { scopes: ["repository:#{project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'an inaccessible'
+ it_behaves_like 'not a container repository factory'
+ end
end
end
end
@@ -371,6 +444,16 @@ describe Auth::ContainerRegistryAuthenticationService do
let(:project) { current_project }
end
end
+
+ context 'allow to delete images since registry 2.7' do
+ let(:current_params) do
+ { scopes: ["repository:#{current_project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'a deletable since registry 2.7' do
+ let(:project) { current_project }
+ end
+ end
end
context 'build authorized as user' do
@@ -419,6 +502,16 @@ describe Auth::ContainerRegistryAuthenticationService do
end
end
+ context 'disallow to delete images since registry 2.7' do
+ let(:current_params) do
+ { scopes: ["repository:#{current_project.full_path}:delete"] }
+ end
+
+ it_behaves_like 'an inaccessible' do
+ let(:project) { current_project }
+ end
+ end
+
context 'for other projects' do
context 'when pulling' do
let(:current_params) do
diff --git a/spec/services/ci/prepare_build_service_spec.rb b/spec/services/ci/prepare_build_service_spec.rb
new file mode 100644
index 00000000000..1797f8f964f
--- /dev/null
+++ b/spec/services/ci/prepare_build_service_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::PrepareBuildService do
+ describe '#execute' do
+ let(:build) { create(:ci_build, :preparing) }
+
+ subject { described_class.new(build).execute }
+
+ before do
+ allow(build).to receive(:prerequisites).and_return(prerequisites)
+ end
+
+ shared_examples 'build enqueueing' do
+ it 'enqueues the build' do
+ expect(build).to receive(:enqueue).once
+
+ subject
+ end
+ end
+
+ context 'build has unmet prerequisites' do
+ let(:prerequisite) { double(complete!: true) }
+ let(:prerequisites) { [prerequisite] }
+
+ it 'completes each prerequisite' do
+ expect(prerequisites).to all(receive(:complete!))
+
+ subject
+ end
+
+ include_examples 'build enqueueing'
+
+ context 'prerequisites fail to complete' do
+ before do
+ allow(build).to receive(:enqueue).and_return(false)
+ end
+
+ it 'drops the build' do
+ expect(build).to receive(:drop!).with(:unmet_prerequisites).once
+
+ subject
+ end
+ end
+ end
+
+ context 'build has no prerequisites' do
+ let(:prerequisites) { [] }
+
+ include_examples 'build enqueueing'
+ end
+ end
+end
diff --git a/spec/services/emails/create_service_spec.rb b/spec/services/emails/create_service_spec.rb
index 54692c88623..87f93ec97c9 100644
--- a/spec/services/emails/create_service_spec.rb
+++ b/spec/services/emails/create_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Emails::CreateService do
diff --git a/spec/services/emails/destroy_service_spec.rb b/spec/services/emails/destroy_service_spec.rb
index c3204fac3df..5abe8da2529 100644
--- a/spec/services/emails/destroy_service_spec.rb
+++ b/spec/services/emails/destroy_service_spec.rb
@@ -1,3 +1,5 @@
+# frozen_string_literal: true
+
require 'spec_helper'
describe Emails::DestroyService do
diff --git a/spec/services/groups/auto_devops_service_spec.rb b/spec/services/groups/auto_devops_service_spec.rb
new file mode 100644
index 00000000000..7f8ab517cef
--- /dev/null
+++ b/spec/services/groups/auto_devops_service_spec.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Groups::AutoDevopsService, '#execute' do
+ set(:group) { create(:group) }
+ set(:user) { create(:user) }
+ let(:group_params) { { auto_devops_enabled: '0' } }
+ let(:service) { described_class.new(group, user, group_params) }
+
+ context 'when user does not have enough privileges' do
+ it 'raises exception' do
+ group.add_developer(user)
+
+ expect do
+ service.execute
+ end.to raise_exception(Gitlab::Access::AccessDeniedError)
+ end
+ end
+
+ context 'when user has enough privileges' do
+ before do
+ group.add_owner(user)
+ end
+
+ it 'updates group auto devops enabled accordingly' do
+ service.execute
+
+ expect(group.auto_devops_enabled).to eq(false)
+ end
+
+ context 'when group has projects' do
+ it 'reflects changes on projects' do
+ project_1 = create(:project, namespace: group)
+
+ service.execute
+
+ expect(project_1).not_to have_auto_devops_implicitly_enabled
+ end
+ end
+
+ context 'when group has subgroups' do
+ it 'reflects changes on subgroups' do
+ subgroup_1 = create(:group, parent: group)
+
+ service.execute
+
+ expect(subgroup_1.auto_devops_enabled?).to eq(false)
+ end
+
+ context 'when subgroups have projects', :nested_groups do
+ it 'reflects changes on projects' do
+ subgroup_1 = create(:group, parent: group)
+ project_1 = create(:project, namespace: subgroup_1)
+
+ service.execute
+
+ expect(project_1).not_to have_auto_devops_implicitly_enabled
+ end
+ end
+ end
+ end
+end
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index d1b110b9806..e8418b09dc2 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -276,6 +276,7 @@ describe Projects::CreateService, '#execute' do
before do
group.add_owner(user)
+ stub_feature_flags(ci_preparing_state: false)
expect(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:namespace_creator).and_return(service_account_creator)
expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).to receive(:new).and_return(secrets_fetcher)
end
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
index dfbdfa2ab69..d3a8ee46f85 100644
--- a/spec/services/projects/destroy_service_spec.rb
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -128,10 +128,8 @@ describe Projects::DestroyService do
it 'keeps project team intact upon an error' do
perform_enqueued_jobs do
- begin
- destroy_project(project, user, {})
- rescue ::Redis::CannotConnectError
- end
+ destroy_project(project, user, {})
+ rescue ::Redis::CannotConnectError
end
expect(project.team.members.count).to eq 2
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index aae50d5307f..4efd360cb30 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -83,6 +83,7 @@ describe Projects::TransferService do
subject { transfer_project(project, user, group) }
before do
+ stub_feature_flags(ci_preparing_state: false)
expect(Clusters::Gcp::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:namespace_creator).and_return(service_account_creator)
expect(Clusters::Gcp::Kubernetes::FetchKubernetesTokenService).to receive(:new).and_return(secrets_fetcher)
end
diff --git a/spec/support/api/schema_matcher.rb b/spec/support/api/schema_matcher.rb
index f7d74df0656..4cf34d43117 100644
--- a/spec/support/api/schema_matcher.rb
+++ b/spec/support/api/schema_matcher.rb
@@ -1,10 +1,16 @@
module SchemaPath
- def self.expand(schema, dir = '')
- Rails.root.join(dir, 'spec', "fixtures/api/schemas/#{schema}.json").to_s
+ def self.expand(schema, dir = nil)
+ if Gitlab.ee? && dir.nil?
+ ee_path = expand(schema, 'ee')
+
+ return ee_path if File.exist?(ee_path)
+ end
+
+ Rails.root.join(dir.to_s, 'spec', "fixtures/api/schemas/#{schema}.json").to_s
end
end
-RSpec::Matchers.define :match_response_schema do |schema, dir: '', **options|
+RSpec::Matchers.define :match_response_schema do |schema, dir: nil, **options|
match do |response|
@errors = JSON::Validator.fully_validate(
SchemaPath.expand(schema, dir), response.body, options)
@@ -18,8 +24,16 @@ RSpec::Matchers.define :match_response_schema do |schema, dir: '', **options|
end
end
-RSpec::Matchers.define :match_schema do |schema, dir: '', **options|
+RSpec::Matchers.define :match_schema do |schema, dir: nil, **options|
match do |data|
- JSON::Validator.validate!(SchemaPath.expand(schema, dir), data, options)
+ @errors = JSON::Validator.fully_validate(
+ SchemaPath.expand(schema, dir), data, options)
+
+ @errors.empty?
+ end
+
+ failure_message do |response|
+ "didn't match the schema defined by #{SchemaPath.expand(schema, dir)}" \
+ " The validation errors were:\n#{@errors.join("\n")}"
end
end
diff --git a/spec/support/api/time_tracking_shared_examples.rb b/spec/support/api/time_tracking_shared_examples.rb
index e883d33f671..15037222630 100644
--- a/spec/support/api/time_tracking_shared_examples.rb
+++ b/spec/support/api/time_tracking_shared_examples.rb
@@ -3,6 +3,8 @@ shared_examples 'an unauthorized API user' do
end
shared_examples 'time tracking endpoints' do |issuable_name|
+ let(:non_member) { create(:user) }
+
issuable_collection_name = issuable_name.pluralize
describe "POST /projects/:id/#{issuable_collection_name}/:#{issuable_name}_id/time_estimate" do
diff --git a/spec/support/helpers/graphql_helpers.rb b/spec/support/helpers/graphql_helpers.rb
index 6cdc19ac2e5..ca28325eab9 100644
--- a/spec/support/helpers/graphql_helpers.rb
+++ b/spec/support/helpers/graphql_helpers.rb
@@ -18,12 +18,10 @@ module GraphqlHelpers
# Runs a block inside a BatchLoader::Executor wrapper
def batch(max_queries: nil, &blk)
wrapper = proc do
- begin
- BatchLoader::Executor.ensure_current
- yield
- ensure
- BatchLoader::Executor.clear_current
- end
+ BatchLoader::Executor.ensure_current
+ yield
+ ensure
+ BatchLoader::Executor.clear_current
end
if max_queries
diff --git a/spec/support/helpers/javascript_fixtures_helpers.rb b/spec/support/helpers/javascript_fixtures_helpers.rb
index f525b2f945e..cceb179d53e 100644
--- a/spec/support/helpers/javascript_fixtures_helpers.rb
+++ b/spec/support/helpers/javascript_fixtures_helpers.rb
@@ -5,7 +5,7 @@ module JavaScriptFixturesHelpers
extend ActiveSupport::Concern
include Gitlab::Popen
- FIXTURE_PATHS = %w[spec/javascripts/fixtures ee/spec/javascripts/fixtures].freeze
+ extend self
included do |base|
base.around do |example|
@@ -14,32 +14,32 @@ module JavaScriptFixturesHelpers
end
end
+ def fixture_root_path
+ 'spec/javascripts/fixtures'
+ end
+
# Public: Removes all fixture files from given directory
#
- # directory_name - directory of the fixtures (relative to FIXTURE_PATHS)
+ # directory_name - directory of the fixtures (relative to .fixture_root_path)
#
def clean_frontend_fixtures(directory_name)
- FIXTURE_PATHS.each do |fixture_path|
- directory_name = File.expand_path(directory_name, fixture_path)
- Dir[File.expand_path('*.html.raw', directory_name)].each do |file_name|
- FileUtils.rm(file_name)
- end
+ full_directory_name = File.expand_path(directory_name, fixture_root_path)
+ Dir[File.expand_path('*.html.raw', full_directory_name)].each do |file_name|
+ FileUtils.rm(file_name)
end
end
# Public: Store a response object as fixture file
#
# response - string or response object to store
- # fixture_file_name - file name to store the fixture in (relative to FIXTURE_PATHS)
+ # fixture_file_name - file name to store the fixture in (relative to .fixture_root_path)
#
def store_frontend_fixture(response, fixture_file_name)
- FIXTURE_PATHS.each do |fixture_path|
- fixture_file_name = File.expand_path(fixture_file_name, fixture_path)
- fixture = response.respond_to?(:body) ? parse_response(response) : response
+ full_fixture_path = File.expand_path(fixture_file_name, fixture_root_path)
+ fixture = response.respond_to?(:body) ? parse_response(response) : response
- FileUtils.mkdir_p(File.dirname(fixture_file_name))
- File.write(fixture_file_name, fixture)
- end
+ FileUtils.mkdir_p(File.dirname(full_fixture_path))
+ File.write(full_fixture_path, fixture)
end
def remove_repository(project)
diff --git a/spec/support/helpers/kubernetes_helpers.rb b/spec/support/helpers/kubernetes_helpers.rb
index cca11e112c9..ac52acb6570 100644
--- a/spec/support/helpers/kubernetes_helpers.rb
+++ b/spec/support/helpers/kubernetes_helpers.rb
@@ -250,16 +250,19 @@ module KubernetesHelpers
# This is a partial response, it will have many more elements in reality but
# these are the ones we care about at the moment
- def kube_pod(name: "kube-pod", app: "valid-pod-label", status: "Running", track: nil)
+ def kube_pod(name: "kube-pod", environment_slug: "production", project_slug: "project-path-slug", status: "Running", track: nil)
{
"metadata" => {
"name" => name,
"generate_name" => "generated-name-with-suffix",
"creationTimestamp" => "2016-11-25T19:55:19Z",
+ "annotations" => {
+ "app.gitlab.com/env" => environment_slug,
+ "app.gitlab.com/app" => project_slug
+ },
"labels" => {
- "app" => app,
"track" => track
- }
+ }.compact
},
"spec" => {
"containers" => [
@@ -293,13 +296,16 @@ module KubernetesHelpers
}
end
- def kube_deployment(name: "kube-deployment", app: "valid-deployment-label", track: nil)
+ def kube_deployment(name: "kube-deployment", environment_slug: "production", project_slug: "project-path-slug", track: nil)
{
"metadata" => {
"name" => name,
"generation" => 4,
+ "annotations" => {
+ "app.gitlab.com/env" => environment_slug,
+ "app.gitlab.com/app" => project_slug
+ },
"labels" => {
- "app" => app,
"track" => track
}.compact
},
diff --git a/spec/support/helpers/repo_helpers.rb b/spec/support/helpers/repo_helpers.rb
index 3c6956cf5e0..4af90f4af79 100644
--- a/spec/support/helpers/repo_helpers.rb
+++ b/spec/support/helpers/repo_helpers.rb
@@ -115,4 +115,18 @@ eos
commits: commits
)
end
+
+ def create_file_in_repo(
+ project, start_branch, branch_name, filename, content,
+ commit_message: 'Add new content')
+ Files::CreateService.new(
+ project,
+ project.owner,
+ commit_message: commit_message,
+ start_branch: start_branch,
+ branch_name: branch_name,
+ file_path: filename,
+ file_content: content
+ ).execute
+ end
end
diff --git a/spec/support/helpers/stub_configuration.rb b/spec/support/helpers/stub_configuration.rb
index ff21bbe28ca..cfa9151b2d7 100644
--- a/spec/support/helpers/stub_configuration.rb
+++ b/spec/support/helpers/stub_configuration.rb
@@ -84,6 +84,10 @@ module StubConfiguration
allow(Gitlab.config.kerberos).to receive_messages(to_settings(messages))
end
+ def stub_gitlab_shell_setting(messages)
+ allow(Gitlab.config.gitlab_shell).to receive_messages(to_settings(messages))
+ end
+
private
# Modifies stubbed messages to also stub possible predicate versions
diff --git a/spec/support/helpers/stub_object_storage.rb b/spec/support/helpers/stub_object_storage.rb
index e0c50e533a6..30c8477f16a 100644
--- a/spec/support/helpers/stub_object_storage.rb
+++ b/spec/support/helpers/stub_object_storage.rb
@@ -23,15 +23,13 @@ module StubObjectStorage
Fog.mock!
::Fog::Storage.new(connection_params).tap do |connection|
- begin
- connection.directories.create(key: remote_directory)
+ connection.directories.create(key: remote_directory)
- # Cleanup remaining files
- connection.directories.each do |directory|
- directory.files.map(&:destroy)
- end
- rescue Excon::Error::Conflict
+ # Cleanup remaining files
+ connection.directories.each do |directory|
+ directory.files.map(&:destroy)
end
+ rescue Excon::Error::Conflict
end
end
diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb
index 80a22134021..dc902d373b8 100644
--- a/spec/support/helpers/test_env.rb
+++ b/spec/support/helpers/test_env.rb
@@ -202,12 +202,10 @@ module TestEnv
socket = Gitlab::GitalyClient.address('default').sub('unix:', '')
Integer(sleep_time / sleep_interval).times do
- begin
- Socket.unix(socket)
- return
- rescue
- sleep sleep_interval
- end
+ Socket.unix(socket)
+ return
+ rescue
+ sleep sleep_interval
end
raise "could not connect to gitaly at #{socket.inspect} after #{sleep_time} seconds"
diff --git a/spec/support/pg_stat_activity.rb b/spec/support/pg_stat_activity.rb
deleted file mode 100644
index f93fba08a19..00000000000
--- a/spec/support/pg_stat_activity.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# frozen_string_literal: true
-
-RSpec.configure do |config|
- config.before do
- if Gitlab::Database.postgresql? && ENV['PG_STAT_WARNING_THRESHOLD']
- warning_threshold = ENV['PG_STAT_WARNING_THRESHOLD'].to_i
- results = ActiveRecord::Base.connection.execute('SELECT * FROM pg_stat_activity')
- ntuples = results.ntuples
-
- warn("pg_stat_activity count: #{ntuples}")
-
- if ntuples > warning_threshold
- results.each do |result|
- warn result.inspect
- end
- end
- end
- end
-end
diff --git a/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
new file mode 100644
index 00000000000..a0d994c4d8d
--- /dev/null
+++ b/spec/support/shared_contexts/finders/group_projects_finder_shared_contexts.rb
@@ -0,0 +1,24 @@
+require 'spec_helper'
+
+RSpec.shared_context 'GroupProjectsFinder context' do
+ let(:group) { create(:group) }
+ let(:subgroup) { create(:group, parent: group) }
+ let(:current_user) { create(:user) }
+ let(:options) { {} }
+
+ let(:finder) { described_class.new(group: group, current_user: current_user, options: options) }
+
+ let!(:public_project) { create(:project, :public, group: group, path: '1') }
+ let!(:private_project) { create(:project, :private, group: group, path: '2') }
+ let!(:shared_project_1) { create(:project, :public, path: '3') }
+ let!(:shared_project_2) { create(:project, :private, path: '4') }
+ let!(:shared_project_3) { create(:project, :internal, path: '5') }
+ let!(:subgroup_project) { create(:project, :public, path: '6', group: subgroup) }
+ let!(:subgroup_private_project) { create(:project, :private, path: '7', group: subgroup) }
+
+ before do
+ shared_project_1.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group)
+ shared_project_2.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group)
+ shared_project_3.project_group_links.create(group_access: Gitlab::Access::MAINTAINER, group: group)
+ end
+end
diff --git a/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb
new file mode 100644
index 00000000000..b8a9554f55f
--- /dev/null
+++ b/spec/support/shared_contexts/finders/issues_finder_shared_contexts.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+RSpec.shared_context 'IssuesFinder context' do
+ set(:user) { create(:user) }
+ set(:user2) { create(:user) }
+ set(:group) { create(:group) }
+ set(:subgroup) { create(:group, parent: group) }
+ set(:project1) { create(:project, group: group) }
+ set(:project2) { create(:project) }
+ set(:project3) { create(:project, group: subgroup) }
+ set(:milestone) { create(:milestone, project: project1) }
+ set(:label) { create(:label, project: project2) }
+ set(:issue1) { create(:issue, author: user, assignees: [user], project: project1, milestone: milestone, title: 'gitlab', created_at: 1.week.ago, updated_at: 1.week.ago) }
+ set(:issue2) { create(:issue, author: user, assignees: [user], project: project2, description: 'gitlab', created_at: 1.week.from_now, updated_at: 1.week.from_now) }
+ set(:issue3) { create(:issue, author: user2, assignees: [user2], project: project2, title: 'tanuki', description: 'tanuki', created_at: 2.weeks.from_now, updated_at: 2.weeks.from_now) }
+ set(:issue4) { create(:issue, project: project3) }
+ set(:award_emoji1) { create(:award_emoji, name: 'thumbsup', user: user, awardable: issue1) }
+ set(:award_emoji2) { create(:award_emoji, name: 'thumbsup', user: user2, awardable: issue2) }
+ set(:award_emoji3) { create(:award_emoji, name: 'thumbsdown', user: user, awardable: issue3) }
+end
+
+RSpec.shared_context 'IssuesFinder#execute context' do
+ let!(:closed_issue) { create(:issue, author: user2, assignees: [user2], project: project2, state: 'closed') }
+ let!(:label_link) { create(:label_link, label: label, target: issue2) }
+ let(:search_user) { user }
+ let(:params) { {} }
+ let(:issues) { described_class.new(search_user, params.reverse_merge(scope: scope, state: 'opened')).execute }
+
+ before(:context) do
+ project1.add_maintainer(user)
+ project2.add_developer(user)
+ project2.add_developer(user2)
+ project3.add_developer(user)
+
+ issue1
+ issue2
+ issue3
+ issue4
+
+ award_emoji1
+ award_emoji2
+ award_emoji3
+ end
+end
diff --git a/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
new file mode 100644
index 00000000000..4df80b4168a
--- /dev/null
+++ b/spec/support/shared_contexts/finders/merge_requests_finder_shared_contexts.rb
@@ -0,0 +1,65 @@
+require 'spec_helper'
+
+RSpec.shared_context 'MergeRequestsFinder multiple projects with merge requests context' do
+ include ProjectForksHelper
+
+ # We need to explicitly permit Gitaly N+1s because of the specs that use
+ # :request_store. Gitaly N+1 detection is only enabled when :request_store is,
+ # but we don't care about potential N+1s when we're just creating several
+ # projects in the setup phase.
+ def allow_gitaly_n_plus_1
+ Gitlab::GitalyClient.allow_n_plus_1_calls do
+ yield
+ end
+ end
+
+ set(:user) { create(:user) }
+ set(:user2) { create(:user) }
+
+ set(:group) { create(:group) }
+ set(:subgroup) { create(:group, parent: group) }
+ set(:project1) do
+ allow_gitaly_n_plus_1 { create(:project, :public, group: group) }
+ end
+ # We cannot use `set` here otherwise we get:
+ # Failure/Error: allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
+ # The use of doubles or partial doubles from rspec-mocks outside of the per-test lifecycle is not supported.
+ let(:project2) do
+ allow_gitaly_n_plus_1 do
+ fork_project(project1, user)
+ end
+ end
+ let(:project3) do
+ allow_gitaly_n_plus_1 do
+ fork_project(project1, user).tap do |project|
+ project.update!(archived: true)
+ end
+ end
+ end
+ set(:project4) do
+ allow_gitaly_n_plus_1 { create(:project, :repository, group: subgroup) }
+ end
+ set(:project5) do
+ allow_gitaly_n_plus_1 { create(:project, group: subgroup) }
+ end
+ set(:project6) do
+ allow_gitaly_n_plus_1 { create(:project, group: subgroup) }
+ end
+
+ let!(:merge_request1) { create(:merge_request, author: user, source_project: project2, target_project: project1, target_branch: 'merged-target') }
+ let!(:merge_request2) { create(:merge_request, :conflict, author: user, source_project: project2, target_project: project1, state: 'closed') }
+ let!(:merge_request3) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project2, state: 'locked', title: 'thing WIP thing') }
+ let!(:merge_request4) { create(:merge_request, :simple, author: user, source_project: project3, target_project: project3, title: 'WIP thing') }
+ let!(:merge_request5) { create(:merge_request, :simple, author: user, source_project: project4, target_project: project4, title: '[WIP]') }
+
+ before do
+ project1.add_maintainer(user)
+ project2.add_developer(user)
+ project3.add_developer(user)
+ project4.add_developer(user)
+ project5.add_developer(user)
+ project6.add_developer(user)
+
+ project2.add_developer(user2)
+ end
+end
diff --git a/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb b/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
new file mode 100644
index 00000000000..9e1f89ee0ed
--- /dev/null
+++ b/spec/support/shared_contexts/finders/users_finder_shared_contexts.rb
@@ -0,0 +1,8 @@
+require 'spec_helper'
+
+RSpec.shared_context 'UsersFinder#execute filter by project context' do
+ set(:normal_user) { create(:user, username: 'johndoe') }
+ set(:blocked_user) { create(:user, :blocked, username: 'notsorandom') }
+ set(:external_user) { create(:user, :external) }
+ set(:omniauth_user) { create(:omniauth_user, provider: 'twitter', extern_uid: '123456') }
+end
diff --git a/spec/support/shared_examples/application_setting_examples.rb b/spec/support/shared_examples/application_setting_examples.rb
new file mode 100644
index 00000000000..e7ec24c5b7e
--- /dev/null
+++ b/spec/support/shared_examples/application_setting_examples.rb
@@ -0,0 +1,252 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'application settings examples' do
+ context 'restricted signup domains' do
+ it 'sets single domain' do
+ setting.domain_whitelist_raw = 'example.com'
+ expect(setting.domain_whitelist).to eq(['example.com'])
+ end
+
+ it 'sets multiple domains with spaces' do
+ setting.domain_whitelist_raw = 'example.com *.example.com'
+ expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
+ end
+
+ it 'sets multiple domains with newlines and a space' do
+ setting.domain_whitelist_raw = "example.com\n *.example.com"
+ expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
+ end
+
+ it 'sets multiple domains with commas' do
+ setting.domain_whitelist_raw = "example.com, *.example.com"
+ expect(setting.domain_whitelist).to eq(['example.com', '*.example.com'])
+ end
+ end
+
+ context 'blacklisted signup domains' do
+ it 'sets single domain' do
+ setting.domain_blacklist_raw = 'example.com'
+ expect(setting.domain_blacklist).to contain_exactly('example.com')
+ end
+
+ it 'sets multiple domains with spaces' do
+ setting.domain_blacklist_raw = 'example.com *.example.com'
+ expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
+ end
+
+ it 'sets multiple domains with newlines and a space' do
+ setting.domain_blacklist_raw = "example.com\n *.example.com"
+ expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
+ end
+
+ it 'sets multiple domains with commas' do
+ setting.domain_blacklist_raw = "example.com, *.example.com"
+ expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
+ end
+
+ it 'sets multiple domains with semicolon' do
+ setting.domain_blacklist_raw = "example.com; *.example.com"
+ expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com')
+ end
+
+ it 'sets multiple domains with mixture of everything' do
+ setting.domain_blacklist_raw = "example.com; *.example.com\n test.com\sblock.com yes.com"
+ expect(setting.domain_blacklist).to contain_exactly('example.com', '*.example.com', 'test.com', 'block.com', 'yes.com')
+ end
+
+ it 'sets multiple domain with file' do
+ setting.domain_blacklist_file = File.open(Rails.root.join('spec/fixtures/', 'domain_blacklist.txt'))
+ expect(setting.domain_blacklist).to contain_exactly('example.com', 'test.com', 'foo.bar')
+ end
+ end
+
+ describe 'usage ping settings' do
+ context 'when the usage ping is disabled in gitlab.yml' do
+ before do
+ allow(Settings.gitlab).to receive(:usage_ping_enabled).and_return(false)
+ end
+
+ it 'does not allow the usage ping to be configured' do
+ expect(setting.usage_ping_can_be_configured?).to be_falsey
+ end
+
+ context 'when the usage ping is disabled in the DB' do
+ before do
+ setting.usage_ping_enabled = false
+ end
+
+ it 'returns false for usage_ping_enabled' do
+ expect(setting.usage_ping_enabled).to be_falsey
+ end
+ end
+
+ context 'when the usage ping is enabled in the DB' do
+ before do
+ setting.usage_ping_enabled = true
+ end
+
+ it 'returns false for usage_ping_enabled' do
+ expect(setting.usage_ping_enabled).to be_falsey
+ end
+ end
+ end
+
+ context 'when the usage ping is enabled in gitlab.yml' do
+ before do
+ allow(Settings.gitlab).to receive(:usage_ping_enabled).and_return(true)
+ end
+
+ it 'allows the usage ping to be configured' do
+ expect(setting.usage_ping_can_be_configured?).to be_truthy
+ end
+
+ context 'when the usage ping is disabled in the DB' do
+ before do
+ setting.usage_ping_enabled = false
+ end
+
+ it 'returns false for usage_ping_enabled' do
+ expect(setting.usage_ping_enabled).to be_falsey
+ end
+ end
+
+ context 'when the usage ping is enabled in the DB' do
+ before do
+ setting.usage_ping_enabled = true
+ end
+
+ it 'returns true for usage_ping_enabled' do
+ expect(setting.usage_ping_enabled).to be_truthy
+ end
+ end
+ end
+ end
+
+ describe '#allowed_key_types' do
+ it 'includes all key types by default' do
+ expect(setting.allowed_key_types).to contain_exactly(*described_class::SUPPORTED_KEY_TYPES)
+ end
+
+ it 'excludes disabled key types' do
+ expect(setting.allowed_key_types).to include(:ed25519)
+
+ setting.ed25519_key_restriction = described_class::FORBIDDEN_KEY_VALUE
+
+ expect(setting.allowed_key_types).not_to include(:ed25519)
+ end
+ end
+
+ describe '#key_restriction_for' do
+ it 'returns the restriction value for recognised types' do
+ setting.rsa_key_restriction = 1024
+
+ expect(setting.key_restriction_for(:rsa)).to eq(1024)
+ end
+
+ it 'allows types to be passed as a string' do
+ setting.rsa_key_restriction = 1024
+
+ expect(setting.key_restriction_for('rsa')).to eq(1024)
+ end
+
+ it 'returns forbidden for unrecognised type' do
+ expect(setting.key_restriction_for(:foo)).to eq(described_class::FORBIDDEN_KEY_VALUE)
+ end
+ end
+
+ describe '#allow_signup?' do
+ it 'returns true' do
+ expect(setting.allow_signup?).to be_truthy
+ end
+
+ it 'returns false if signup is disabled' do
+ allow(setting).to receive(:signup_enabled?).and_return(false)
+
+ expect(setting.allow_signup?).to be_falsey
+ end
+
+ it 'returns false if password authentication is disabled for the web interface' do
+ allow(setting).to receive(:password_authentication_enabled_for_web?).and_return(false)
+
+ expect(setting.allow_signup?).to be_falsey
+ end
+ end
+
+ describe '#pick_repository_storage' do
+ it 'uses Array#sample to pick a random storage' do
+ array = double('array', sample: 'random')
+ expect(setting).to receive(:repository_storages).and_return(array)
+
+ expect(setting.pick_repository_storage).to eq('random')
+ end
+ end
+
+ describe '#user_default_internal_regex_enabled?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:user_default_external, :user_default_internal_regex, :result) do
+ false | nil | false
+ false | '' | false
+ false | '^(?:(?!\.ext@).)*$\r?\n?' | false
+ true | '' | false
+ true | nil | false
+ true | '^(?:(?!\.ext@).)*$\r?\n?' | true
+ end
+
+ with_them do
+ before do
+ setting.user_default_external = user_default_external
+ setting.user_default_internal_regex = user_default_internal_regex
+ end
+
+ subject { setting.user_default_internal_regex_enabled? }
+
+ it { is_expected.to eq(result) }
+ end
+ end
+
+ describe '#archive_builds_older_than' do
+ subject { setting.archive_builds_older_than }
+
+ context 'when the archive_builds_in_seconds is set' do
+ before do
+ setting.archive_builds_in_seconds = 3600
+ end
+
+ it { is_expected.to be_within(1.minute).of(1.hour.ago) }
+ end
+
+ context 'when the archive_builds_in_seconds is set' do
+ before do
+ setting.archive_builds_in_seconds = nil
+ end
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#commit_email_hostname' do
+ context 'when the value is provided' do
+ before do
+ setting.commit_email_hostname = 'localhost'
+ end
+
+ it 'returns the provided value' do
+ expect(setting.commit_email_hostname).to eq('localhost')
+ end
+ end
+
+ context 'when the value is not provided' do
+ it 'returns the default from the class' do
+ expect(setting.commit_email_hostname)
+ .to eq(described_class.default_commit_email_hostname)
+ end
+ end
+ end
+
+ it 'predicate method changes when value is updated' do
+ setting.password_authentication_enabled_for_web = false
+
+ expect(setting.password_authentication_enabled_for_web?).to be_falsey
+ end
+end
diff --git a/spec/support/shared_examples/requests/api/merge_requests_list.rb b/spec/support/shared_examples/requests/api/merge_requests_list.rb
deleted file mode 100644
index 32e3b81c3c5..00000000000
--- a/spec/support/shared_examples/requests/api/merge_requests_list.rb
+++ /dev/null
@@ -1,366 +0,0 @@
-shared_examples 'merge requests list' do
- context 'when unauthenticated' do
- it 'returns merge requests for public projects' do
- get api(endpoint_path)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
- end
- end
-
- context 'when authenticated' do
- it 'avoids N+1 queries' do
- control = ActiveRecord::QueryRecorder.new do
- get api(endpoint_path, user)
- end
-
- create(:merge_request, state: 'closed', milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: 'Test', created_at: base_time)
-
- merge_request = create(:merge_request, milestone: milestone1, author: user, assignee: user, source_project: project, target_project: project, title: 'Test', created_at: base_time)
-
- merge_request.metrics.update!(merged_by: user,
- latest_closed_by: user,
- latest_closed_at: 1.hour.ago,
- merged_at: 2.hours.ago)
-
- expect do
- get api(endpoint_path, user)
- end.not_to exceed_query_limit(control)
- end
-
- it 'returns an array of all merge_requests' do
- get api(endpoint_path, user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(4)
- expect(json_response.last['title']).to eq(merge_request.title)
- expect(json_response.last).to have_key('web_url')
- expect(json_response.last['sha']).to eq(merge_request.diff_head_sha)
- expect(json_response.last['merge_commit_sha']).to be_nil
- expect(json_response.last['merge_commit_sha']).to eq(merge_request.merge_commit_sha)
- expect(json_response.last['downvotes']).to eq(1)
- expect(json_response.last['upvotes']).to eq(1)
- expect(json_response.last['labels']).to eq([label2.title, label.title])
- expect(json_response.first['title']).to eq(merge_request_merged.title)
- expect(json_response.first['sha']).to eq(merge_request_merged.diff_head_sha)
- expect(json_response.first['merge_commit_sha']).not_to be_nil
- expect(json_response.first['merge_commit_sha']).to eq(merge_request_merged.merge_commit_sha)
- end
-
- it 'returns an array of all merge_requests using simple mode' do
- path = endpoint_path + '?view=simple'
-
- get api(path, user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response.last.keys).to match_array(%w(id iid title web_url created_at description project_id state updated_at))
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(4)
- expect(json_response.last['iid']).to eq(merge_request.iid)
- expect(json_response.last['title']).to eq(merge_request.title)
- expect(json_response.last).to have_key('web_url')
- expect(json_response.first['iid']).to eq(merge_request_merged.iid)
- expect(json_response.first['title']).to eq(merge_request_merged.title)
- expect(json_response.first).to have_key('web_url')
- end
-
- it 'returns an array of all merge_requests' do
- path = endpoint_path + '?state'
-
- get api(path, user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(4)
- expect(json_response.last['title']).to eq(merge_request.title)
- end
-
- it 'returns an array of open merge_requests' do
- path = endpoint_path + '?state=opened'
-
- get api(path, user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
- expect(json_response.last['title']).to eq(merge_request.title)
- end
-
- it 'returns an array of closed merge_requests' do
- path = endpoint_path + '?state=closed'
-
- get api(path, user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
- expect(json_response.first['title']).to eq(merge_request_closed.title)
- end
-
- it 'returns an array of merged merge_requests' do
- path = endpoint_path + '?state=merged'
-
- get api(path, user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
- expect(json_response.first['title']).to eq(merge_request_merged.title)
- end
-
- it 'matches V4 response schema' do
- get api(endpoint_path, user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to match_response_schema('public_api/v4/merge_requests')
- end
-
- it 'returns an empty array if no issue matches milestone' do
- get api(endpoint_path, user), params: { milestone: '1.0.0' }
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
- end
-
- it 'returns an empty array if milestone does not exist' do
- get api(endpoint_path, user), params: { milestone: 'foo' }
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
- end
-
- it 'returns an array of merge requests in given milestone' do
- get api(endpoint_path, user), params: { milestone: '0.9' }
-
- closed_issues = json_response.select { |mr| mr['id'] == merge_request_closed.id }
- expect(closed_issues.length).to eq(1)
- expect(closed_issues.first['title']).to eq merge_request_closed.title
- end
-
- it 'returns an array of merge requests matching state in milestone' do
- get api(endpoint_path, user), params: { milestone: '0.9', state: 'closed' }
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
- expect(json_response.first['id']).to eq(merge_request_closed.id)
- end
-
- it 'returns an array of labeled merge requests' do
- path = endpoint_path + "?labels=#{label.title}"
-
- get api(path, user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
- expect(json_response.first['labels']).to eq([label2.title, label.title])
- end
-
- it 'returns an array of labeled merge requests where all labels match' do
- path = endpoint_path + "?labels=#{label.title},foo,bar"
-
- get api(path, user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
- end
-
- it 'returns an empty array if no merge request matches labels' do
- path = endpoint_path + '?labels=foo,bar'
-
- get api(path, user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(0)
- end
-
- it 'returns an array of labeled merge requests where all labels match' do
- path = endpoint_path + "?labels[]=#{label.title}&labels[]=#{label2.title}"
-
- get api(path, user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
- expect(json_response.first['labels']).to eq([label2.title, label.title])
- end
-
- it 'returns an array of merge requests with any label when filtering by any label' do
- get api(endpoint_path, user), params: { labels: [" #{label.title} ", " #{label2.title} "] }
-
- expect_paginated_array_response
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
- expect(json_response.first['labels']).to eq([label2.title, label.title])
- expect(json_response.first['id']).to eq(merge_request.id)
- end
-
- it 'returns an array of merge requests with any label when filtering by any label' do
- get api(endpoint_path, user), params: { labels: ["#{label.title} , #{label2.title}"] }
-
- expect_paginated_array_response
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
- expect(json_response.first['labels']).to eq([label2.title, label.title])
- expect(json_response.first['id']).to eq(merge_request.id)
- end
-
- it 'returns an array of merge requests with any label when filtering by any label' do
- get api(endpoint_path, user), params: { labels: IssuesFinder::FILTER_ANY }
-
- expect_paginated_array_response
- expect(json_response.length).to eq(1)
- expect(json_response.first['id']).to eq(merge_request.id)
- end
-
- it 'returns an array of merge requests without a label when filtering by no label' do
- get api(endpoint_path, user), params: { labels: IssuesFinder::FILTER_NONE }
-
- response_ids = json_response.map { |merge_request| merge_request['id'] }
-
- expect_paginated_array_response
- expect(response_ids).to contain_exactly(merge_request_closed.id, merge_request_merged.id, merge_request_locked.id)
- end
-
- it 'returns an array of labeled merge requests that are merged for a milestone' do
- bug_label = create(:label, title: 'bug', color: '#FFAABB', project: project)
-
- mr1 = create(:merge_request, state: 'merged', source_project: project, target_project: project, milestone: milestone)
- mr2 = create(:merge_request, state: 'merged', source_project: project, target_project: project, milestone: milestone1)
- mr3 = create(:merge_request, state: 'closed', source_project: project, target_project: project, milestone: milestone1)
- _mr = create(:merge_request, state: 'merged', source_project: project, target_project: project, milestone: milestone1)
-
- create(:label_link, label: bug_label, target: mr1)
- create(:label_link, label: bug_label, target: mr2)
- create(:label_link, label: bug_label, target: mr3)
-
- path = endpoint_path + "?labels=#{bug_label.title}&milestone=#{milestone1.title}&state=merged"
-
- get api(path, user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
- expect(json_response.first['id']).to eq(mr2.id)
- end
-
- context 'with ordering' do
- before do
- @mr_later = mr_with_later_created_and_updated_at_time
- @mr_earlier = mr_with_earlier_created_and_updated_at_time
- end
-
- it 'returns an array of merge_requests in ascending order' do
- path = endpoint_path + '?sort=asc'
-
- get api(path, user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(4)
- response_dates = json_response.map { |merge_request| merge_request['created_at'] }
- expect(response_dates).to eq(response_dates.sort)
- end
-
- it 'returns an array of merge_requests in descending order' do
- path = endpoint_path + '?sort=desc'
-
- get api(path, user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(4)
- response_dates = json_response.map { |merge_request| merge_request['created_at'] }
- expect(response_dates).to eq(response_dates.sort.reverse)
- end
-
- context '2 merge requests with equal created_at' do
- let!(:closed_mr2) do
- create :merge_request,
- state: 'closed',
- milestone: milestone1,
- author: user,
- assignee: user,
- source_project: project,
- target_project: project,
- title: "Test",
- created_at: @mr_earlier.created_at
- end
-
- it 'page breaks first page correctly' do
- get api("#{endpoint_path}?sort=desc&per_page=4", user)
-
- response_ids = json_response.map { |merge_request| merge_request['id'] }
-
- expect(response_ids).to include(closed_mr2.id)
- expect(response_ids).not_to include(@mr_earlier.id)
- end
-
- it 'page breaks second page correctly' do
- get api("#{endpoint_path}?sort=desc&per_page=4&page=2", user)
-
- response_ids = json_response.map { |merge_request| merge_request['id'] }
-
- expect(response_ids).not_to include(closed_mr2.id)
- expect(response_ids).to include(@mr_earlier.id)
- end
- end
-
- it 'returns an array of merge_requests ordered by updated_at' do
- path = endpoint_path + '?order_by=updated_at'
-
- get api(path, user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(4)
- response_dates = json_response.map { |merge_request| merge_request['updated_at'] }
- expect(response_dates).to eq(response_dates.sort.reverse)
- end
-
- it 'returns an array of merge_requests ordered by created_at' do
- path = endpoint_path + '?order_by=created_at&sort=asc'
-
- get api(path, user)
-
- expect(response).to have_gitlab_http_status(200)
- expect(response).to include_pagination_headers
- expect(json_response).to be_an Array
- expect(json_response.length).to eq(4)
- response_dates = json_response.map { |merge_request| merge_request['created_at'] }
- expect(response_dates).to eq(response_dates.sort)
- end
- end
-
- context 'source_branch param' do
- it 'returns merge requests with the given source branch' do
- get api(endpoint_path, user), params: { source_branch: merge_request_closed.source_branch, state: 'all' }
-
- expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked)
- end
- end
-
- context 'target_branch param' do
- it 'returns merge requests with the given target branch' do
- get api(endpoint_path, user), params: { target_branch: merge_request_closed.target_branch, state: 'all' }
-
- expect_response_contain_exactly(merge_request_closed, merge_request_merged, merge_request_locked)
- end
- end
- end
-end
diff --git a/spec/support/shared_examples/snippet_visibility.rb b/spec/support/shared_examples/snippet_visibility.rb
deleted file mode 100644
index 3a7c69b7877..00000000000
--- a/spec/support/shared_examples/snippet_visibility.rb
+++ /dev/null
@@ -1,322 +0,0 @@
-RSpec.shared_examples 'snippet visibility' do
- let!(:author) { create(:user) }
- let!(:member) { create(:user) }
- let!(:external) { create(:user, :external) }
-
- let!(:snippet_type_visibilities) do
- {
- public: Snippet::PUBLIC,
- internal: Snippet::INTERNAL,
- private: Snippet::PRIVATE
- }
- end
-
- context "For project snippets" do
- let!(:users) do
- {
- unauthenticated: nil,
- external: external,
- non_member: create(:user),
- member: member,
- author: author
- }
- end
-
- let!(:project_type_visibilities) do
- {
- public: Gitlab::VisibilityLevel::PUBLIC,
- internal: Gitlab::VisibilityLevel::INTERNAL,
- private: Gitlab::VisibilityLevel::PRIVATE
- }
- end
-
- let(:project_feature_visibilities) do
- {
- enabled: ProjectFeature::ENABLED,
- private: ProjectFeature::PRIVATE,
- disabled: ProjectFeature::DISABLED
- }
- end
-
- where(:project_type, :feature_visibility, :user_type, :snippet_type, :outcome) do
- [
- # Public projects
- [:public, :enabled, :unauthenticated, :public, true],
- [:public, :enabled, :unauthenticated, :internal, false],
- [:public, :enabled, :unauthenticated, :private, false],
-
- [:public, :enabled, :external, :public, true],
- [:public, :enabled, :external, :internal, false],
- [:public, :enabled, :external, :private, false],
-
- [:public, :enabled, :non_member, :public, true],
- [:public, :enabled, :non_member, :internal, true],
- [:public, :enabled, :non_member, :private, false],
-
- [:public, :enabled, :member, :public, true],
- [:public, :enabled, :member, :internal, true],
- [:public, :enabled, :member, :private, true],
-
- [:public, :enabled, :author, :public, true],
- [:public, :enabled, :author, :internal, true],
- [:public, :enabled, :author, :private, true],
-
- [:public, :private, :unauthenticated, :public, false],
- [:public, :private, :unauthenticated, :internal, false],
- [:public, :private, :unauthenticated, :private, false],
-
- [:public, :private, :external, :public, false],
- [:public, :private, :external, :internal, false],
- [:public, :private, :external, :private, false],
-
- [:public, :private, :non_member, :public, false],
- [:public, :private, :non_member, :internal, false],
- [:public, :private, :non_member, :private, false],
-
- [:public, :private, :member, :public, true],
- [:public, :private, :member, :internal, true],
- [:public, :private, :member, :private, true],
-
- [:public, :private, :author, :public, true],
- [:public, :private, :author, :internal, true],
- [:public, :private, :author, :private, true],
-
- [:public, :disabled, :unauthenticated, :public, false],
- [:public, :disabled, :unauthenticated, :internal, false],
- [:public, :disabled, :unauthenticated, :private, false],
-
- [:public, :disabled, :external, :public, false],
- [:public, :disabled, :external, :internal, false],
- [:public, :disabled, :external, :private, false],
-
- [:public, :disabled, :non_member, :public, false],
- [:public, :disabled, :non_member, :internal, false],
- [:public, :disabled, :non_member, :private, false],
-
- [:public, :disabled, :member, :public, false],
- [:public, :disabled, :member, :internal, false],
- [:public, :disabled, :member, :private, false],
-
- [:public, :disabled, :author, :public, false],
- [:public, :disabled, :author, :internal, false],
- [:public, :disabled, :author, :private, false],
-
- # Internal projects
- [:internal, :enabled, :unauthenticated, :public, false],
- [:internal, :enabled, :unauthenticated, :internal, false],
- [:internal, :enabled, :unauthenticated, :private, false],
-
- [:internal, :enabled, :external, :public, false],
- [:internal, :enabled, :external, :internal, false],
- [:internal, :enabled, :external, :private, false],
-
- [:internal, :enabled, :non_member, :public, true],
- [:internal, :enabled, :non_member, :internal, true],
- [:internal, :enabled, :non_member, :private, false],
-
- [:internal, :enabled, :member, :public, true],
- [:internal, :enabled, :member, :internal, true],
- [:internal, :enabled, :member, :private, true],
-
- [:internal, :enabled, :author, :public, true],
- [:internal, :enabled, :author, :internal, true],
- [:internal, :enabled, :author, :private, true],
-
- [:internal, :private, :unauthenticated, :public, false],
- [:internal, :private, :unauthenticated, :internal, false],
- [:internal, :private, :unauthenticated, :private, false],
-
- [:internal, :private, :external, :public, false],
- [:internal, :private, :external, :internal, false],
- [:internal, :private, :external, :private, false],
-
- [:internal, :private, :non_member, :public, false],
- [:internal, :private, :non_member, :internal, false],
- [:internal, :private, :non_member, :private, false],
-
- [:internal, :private, :member, :public, true],
- [:internal, :private, :member, :internal, true],
- [:internal, :private, :member, :private, true],
-
- [:internal, :private, :author, :public, true],
- [:internal, :private, :author, :internal, true],
- [:internal, :private, :author, :private, true],
-
- [:internal, :disabled, :unauthenticated, :public, false],
- [:internal, :disabled, :unauthenticated, :internal, false],
- [:internal, :disabled, :unauthenticated, :private, false],
-
- [:internal, :disabled, :external, :public, false],
- [:internal, :disabled, :external, :internal, false],
- [:internal, :disabled, :external, :private, false],
-
- [:internal, :disabled, :non_member, :public, false],
- [:internal, :disabled, :non_member, :internal, false],
- [:internal, :disabled, :non_member, :private, false],
-
- [:internal, :disabled, :member, :public, false],
- [:internal, :disabled, :member, :internal, false],
- [:internal, :disabled, :member, :private, false],
-
- [:internal, :disabled, :author, :public, false],
- [:internal, :disabled, :author, :internal, false],
- [:internal, :disabled, :author, :private, false],
-
- # Private projects
- [:private, :enabled, :unauthenticated, :public, false],
- [:private, :enabled, :unauthenticated, :internal, false],
- [:private, :enabled, :unauthenticated, :private, false],
-
- [:private, :enabled, :external, :public, true],
- [:private, :enabled, :external, :internal, true],
- [:private, :enabled, :external, :private, true],
-
- [:private, :enabled, :non_member, :public, false],
- [:private, :enabled, :non_member, :internal, false],
- [:private, :enabled, :non_member, :private, false],
-
- [:private, :enabled, :member, :public, true],
- [:private, :enabled, :member, :internal, true],
- [:private, :enabled, :member, :private, true],
-
- [:private, :enabled, :author, :public, true],
- [:private, :enabled, :author, :internal, true],
- [:private, :enabled, :author, :private, true],
-
- [:private, :private, :unauthenticated, :public, false],
- [:private, :private, :unauthenticated, :internal, false],
- [:private, :private, :unauthenticated, :private, false],
-
- [:private, :private, :external, :public, true],
- [:private, :private, :external, :internal, true],
- [:private, :private, :external, :private, true],
-
- [:private, :private, :non_member, :public, false],
- [:private, :private, :non_member, :internal, false],
- [:private, :private, :non_member, :private, false],
-
- [:private, :private, :member, :public, true],
- [:private, :private, :member, :internal, true],
- [:private, :private, :member, :private, true],
-
- [:private, :private, :author, :public, true],
- [:private, :private, :author, :internal, true],
- [:private, :private, :author, :private, true],
-
- [:private, :disabled, :unauthenticated, :public, false],
- [:private, :disabled, :unauthenticated, :internal, false],
- [:private, :disabled, :unauthenticated, :private, false],
-
- [:private, :disabled, :external, :public, false],
- [:private, :disabled, :external, :internal, false],
- [:private, :disabled, :external, :private, false],
-
- [:private, :disabled, :non_member, :public, false],
- [:private, :disabled, :non_member, :internal, false],
- [:private, :disabled, :non_member, :private, false],
-
- [:private, :disabled, :member, :public, false],
- [:private, :disabled, :member, :internal, false],
- [:private, :disabled, :member, :private, false],
-
- [:private, :disabled, :author, :public, false],
- [:private, :disabled, :author, :internal, false],
- [:private, :disabled, :author, :private, false]
- ]
- end
-
- with_them do
- let!(:project) { create(:project, visibility_level: project_type_visibilities[project_type]) }
- let!(:project_feature) { project.project_feature.update_column(:snippets_access_level, project_feature_visibilities[feature_visibility]) }
- let!(:user) { users[user_type] }
- let!(:snippet) { create(:project_snippet, visibility_level: snippet_type_visibilities[snippet_type], project: project, author: author) }
- let!(:members) do
- project.add_developer(author)
- project.add_developer(member)
- project.add_developer(external) if project.private?
- end
-
- context "For #{params[:project_type]} project and #{params[:user_type]} users" do
- it 'should agree with the read_project_snippet policy' do
- expect(can?(user, :read_project_snippet, snippet)).to eq(outcome)
- end
-
- it 'should return proper outcome' do
- results = described_class.new(user, project: project).execute
- expect(results.include?(snippet)).to eq(outcome)
- end
- end
-
- context "Without a given project and #{params[:user_type]} users" do
- it 'should return proper outcome' do
- results = described_class.new(user).execute
- expect(results.include?(snippet)).to eq(outcome)
- end
-
- it 'returns no snippets when the user cannot read cross project' do
- allow(Ability).to receive(:allowed?).and_call_original
- allow(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
-
- snippets = described_class.new(user).execute
-
- expect(snippets).to be_empty
- end
- end
- end
- end
-
- context 'For personal snippets' do
- let!(:users) do
- {
- unauthenticated: nil,
- external: external,
- non_member: create(:user),
- author: author
- }
- end
-
- where(:snippet_visibility, :user_type, :outcome) do
- [
- [:public, :unauthenticated, true],
- [:public, :external, true],
- [:public, :non_member, true],
- [:public, :author, true],
-
- [:internal, :unauthenticated, false],
- [:internal, :external, false],
- [:internal, :non_member, true],
- [:internal, :author, true],
-
- [:private, :unauthenticated, false],
- [:private, :external, false],
- [:private, :non_member, false],
- [:private, :author, true]
- ]
- end
-
- with_them do
- let!(:user) { users[user_type] }
- let!(:snippet) { create(:personal_snippet, visibility_level: snippet_type_visibilities[snippet_visibility], author: author) }
-
- context "For personal and #{params[:snippet_visibility]} snippets with #{params[:user_type]} user" do
- it 'should agree with read_personal_snippet policy' do
- expect(can?(user, :read_personal_snippet, snippet)).to eq(outcome)
- end
-
- it 'should return proper outcome' do
- results = described_class.new(user).execute
- expect(results.include?(snippet)).to eq(outcome)
- end
-
- it 'should return personal snippets when the user cannot read cross project' do
- allow(Ability).to receive(:allowed?).and_call_original
- allow(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
-
- results = described_class.new(user).execute
-
- expect(results.include?(snippet)).to eq(outcome)
- end
- end
- end
- end
-end
diff --git a/spec/support/shared_examples/snippet_visibility_shared_examples.rb b/spec/support/shared_examples/snippet_visibility_shared_examples.rb
new file mode 100644
index 00000000000..4f662db2120
--- /dev/null
+++ b/spec/support/shared_examples/snippet_visibility_shared_examples.rb
@@ -0,0 +1,306 @@
+RSpec.shared_examples 'snippet visibility' do
+ using RSpec::Parameterized::TableSyntax
+
+ # Make sure no snippets exist prior to running the test matrix
+ before(:context) do
+ DatabaseCleaner.clean_with(:truncation)
+ end
+
+ set(:author) { create(:user) }
+ set(:member) { create(:user) }
+ set(:external) { create(:user, :external) }
+
+ context "For project snippets" do
+ let!(:users) do
+ {
+ unauthenticated: nil,
+ external: external,
+ non_member: create(:user),
+ member: member,
+ author: author
+ }
+ end
+
+ where(:project_type, :feature_visibility, :user_type, :snippet_type, :outcome) do
+ [
+ # Public projects
+ [:public, ProjectFeature::ENABLED, :unauthenticated, Snippet::PUBLIC, true],
+ [:public, ProjectFeature::ENABLED, :unauthenticated, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::ENABLED, :unauthenticated, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::ENABLED, :external, Snippet::PUBLIC, true],
+ [:public, ProjectFeature::ENABLED, :external, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::ENABLED, :external, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::ENABLED, :non_member, Snippet::PUBLIC, true],
+ [:public, ProjectFeature::ENABLED, :non_member, Snippet::INTERNAL, true],
+ [:public, ProjectFeature::ENABLED, :non_member, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::ENABLED, :member, Snippet::PUBLIC, true],
+ [:public, ProjectFeature::ENABLED, :member, Snippet::INTERNAL, true],
+ [:public, ProjectFeature::ENABLED, :member, Snippet::PRIVATE, true],
+
+ [:public, ProjectFeature::ENABLED, :author, Snippet::PUBLIC, true],
+ [:public, ProjectFeature::ENABLED, :author, Snippet::INTERNAL, true],
+ [:public, ProjectFeature::ENABLED, :author, Snippet::PRIVATE, true],
+
+ [:public, ProjectFeature::PRIVATE, :unauthenticated, Snippet::PUBLIC, false],
+ [:public, ProjectFeature::PRIVATE, :unauthenticated, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::PRIVATE, :unauthenticated, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::PRIVATE, :external, Snippet::PUBLIC, false],
+ [:public, ProjectFeature::PRIVATE, :external, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::PRIVATE, :external, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::PRIVATE, :non_member, Snippet::PUBLIC, false],
+ [:public, ProjectFeature::PRIVATE, :non_member, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::PRIVATE, :non_member, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::PRIVATE, :member, Snippet::PUBLIC, true],
+ [:public, ProjectFeature::PRIVATE, :member, Snippet::INTERNAL, true],
+ [:public, ProjectFeature::PRIVATE, :member, Snippet::PRIVATE, true],
+
+ [:public, ProjectFeature::PRIVATE, :author, Snippet::PUBLIC, true],
+ [:public, ProjectFeature::PRIVATE, :author, Snippet::INTERNAL, true],
+ [:public, ProjectFeature::PRIVATE, :author, Snippet::PRIVATE, true],
+
+ [:public, ProjectFeature::DISABLED, :unauthenticated, Snippet::PUBLIC, false],
+ [:public, ProjectFeature::DISABLED, :unauthenticated, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::DISABLED, :unauthenticated, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::DISABLED, :external, Snippet::PUBLIC, false],
+ [:public, ProjectFeature::DISABLED, :external, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::DISABLED, :external, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::DISABLED, :non_member, Snippet::PUBLIC, false],
+ [:public, ProjectFeature::DISABLED, :non_member, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::DISABLED, :non_member, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::DISABLED, :member, Snippet::PUBLIC, false],
+ [:public, ProjectFeature::DISABLED, :member, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::DISABLED, :member, Snippet::PRIVATE, false],
+
+ [:public, ProjectFeature::DISABLED, :author, Snippet::PUBLIC, false],
+ [:public, ProjectFeature::DISABLED, :author, Snippet::INTERNAL, false],
+ [:public, ProjectFeature::DISABLED, :author, Snippet::PRIVATE, false],
+
+ # Internal projects
+ [:internal, ProjectFeature::ENABLED, :unauthenticated, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::ENABLED, :unauthenticated, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::ENABLED, :unauthenticated, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::ENABLED, :external, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::ENABLED, :external, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::ENABLED, :external, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::ENABLED, :non_member, Snippet::PUBLIC, true],
+ [:internal, ProjectFeature::ENABLED, :non_member, Snippet::INTERNAL, true],
+ [:internal, ProjectFeature::ENABLED, :non_member, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::ENABLED, :member, Snippet::PUBLIC, true],
+ [:internal, ProjectFeature::ENABLED, :member, Snippet::INTERNAL, true],
+ [:internal, ProjectFeature::ENABLED, :member, Snippet::PRIVATE, true],
+
+ [:internal, ProjectFeature::ENABLED, :author, Snippet::PUBLIC, true],
+ [:internal, ProjectFeature::ENABLED, :author, Snippet::INTERNAL, true],
+ [:internal, ProjectFeature::ENABLED, :author, Snippet::PRIVATE, true],
+
+ [:internal, ProjectFeature::PRIVATE, :unauthenticated, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::PRIVATE, :unauthenticated, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::PRIVATE, :unauthenticated, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::PRIVATE, :external, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::PRIVATE, :external, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::PRIVATE, :external, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::PRIVATE, :non_member, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::PRIVATE, :non_member, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::PRIVATE, :non_member, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::PRIVATE, :member, Snippet::PUBLIC, true],
+ [:internal, ProjectFeature::PRIVATE, :member, Snippet::INTERNAL, true],
+ [:internal, ProjectFeature::PRIVATE, :member, Snippet::PRIVATE, true],
+
+ [:internal, ProjectFeature::PRIVATE, :author, Snippet::PUBLIC, true],
+ [:internal, ProjectFeature::PRIVATE, :author, Snippet::INTERNAL, true],
+ [:internal, ProjectFeature::PRIVATE, :author, Snippet::PRIVATE, true],
+
+ [:internal, ProjectFeature::DISABLED, :unauthenticated, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::DISABLED, :unauthenticated, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::DISABLED, :unauthenticated, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::DISABLED, :external, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::DISABLED, :external, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::DISABLED, :external, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::DISABLED, :non_member, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::DISABLED, :non_member, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::DISABLED, :non_member, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::DISABLED, :member, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::DISABLED, :member, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::DISABLED, :member, Snippet::PRIVATE, false],
+
+ [:internal, ProjectFeature::DISABLED, :author, Snippet::PUBLIC, false],
+ [:internal, ProjectFeature::DISABLED, :author, Snippet::INTERNAL, false],
+ [:internal, ProjectFeature::DISABLED, :author, Snippet::PRIVATE, false],
+
+ # Private projects
+ [:private, ProjectFeature::ENABLED, :unauthenticated, Snippet::PUBLIC, false],
+ [:private, ProjectFeature::ENABLED, :unauthenticated, Snippet::INTERNAL, false],
+ [:private, ProjectFeature::ENABLED, :unauthenticated, Snippet::PRIVATE, false],
+
+ [:private, ProjectFeature::ENABLED, :external, Snippet::PUBLIC, true],
+ [:private, ProjectFeature::ENABLED, :external, Snippet::INTERNAL, true],
+ [:private, ProjectFeature::ENABLED, :external, Snippet::PRIVATE, true],
+
+ [:private, ProjectFeature::ENABLED, :non_member, Snippet::PUBLIC, false],
+ [:private, ProjectFeature::ENABLED, :non_member, Snippet::INTERNAL, false],
+ [:private, ProjectFeature::ENABLED, :non_member, Snippet::PRIVATE, false],
+
+ [:private, ProjectFeature::ENABLED, :member, Snippet::PUBLIC, true],
+ [:private, ProjectFeature::ENABLED, :member, Snippet::INTERNAL, true],
+ [:private, ProjectFeature::ENABLED, :member, Snippet::PRIVATE, true],
+
+ [:private, ProjectFeature::ENABLED, :author, Snippet::PUBLIC, true],
+ [:private, ProjectFeature::ENABLED, :author, Snippet::INTERNAL, true],
+ [:private, ProjectFeature::ENABLED, :author, Snippet::PRIVATE, true],
+
+ [:private, ProjectFeature::PRIVATE, :unauthenticated, Snippet::PUBLIC, false],
+ [:private, ProjectFeature::PRIVATE, :unauthenticated, Snippet::INTERNAL, false],
+ [:private, ProjectFeature::PRIVATE, :unauthenticated, Snippet::PRIVATE, false],
+
+ [:private, ProjectFeature::PRIVATE, :external, Snippet::PUBLIC, true],
+ [:private, ProjectFeature::PRIVATE, :external, Snippet::INTERNAL, true],
+ [:private, ProjectFeature::PRIVATE, :external, Snippet::PRIVATE, true],
+
+ [:private, ProjectFeature::PRIVATE, :non_member, Snippet::PUBLIC, false],
+ [:private, ProjectFeature::PRIVATE, :non_member, Snippet::INTERNAL, false],
+ [:private, ProjectFeature::PRIVATE, :non_member, Snippet::PRIVATE, false],
+
+ [:private, ProjectFeature::PRIVATE, :member, Snippet::PUBLIC, true],
+ [:private, ProjectFeature::PRIVATE, :member, Snippet::INTERNAL, true],
+ [:private, ProjectFeature::PRIVATE, :member, Snippet::PRIVATE, true],
+
+ [:private, ProjectFeature::PRIVATE, :author, Snippet::PUBLIC, true],
+ [:private, ProjectFeature::PRIVATE, :author, Snippet::INTERNAL, true],
+ [:private, ProjectFeature::PRIVATE, :author, Snippet::PRIVATE, true],
+
+ [:private, ProjectFeature::DISABLED, :unauthenticated, Snippet::PUBLIC, false],
+ [:private, ProjectFeature::DISABLED, :unauthenticated, Snippet::INTERNAL, false],
+ [:private, ProjectFeature::DISABLED, :unauthenticated, Snippet::PRIVATE, false],
+
+ [:private, ProjectFeature::DISABLED, :external, Snippet::PUBLIC, false],
+ [:private, ProjectFeature::DISABLED, :external, Snippet::INTERNAL, false],
+ [:private, ProjectFeature::DISABLED, :external, Snippet::PRIVATE, false],
+
+ [:private, ProjectFeature::DISABLED, :non_member, Snippet::PUBLIC, false],
+ [:private, ProjectFeature::DISABLED, :non_member, Snippet::INTERNAL, false],
+ [:private, ProjectFeature::DISABLED, :non_member, Snippet::PRIVATE, false],
+
+ [:private, ProjectFeature::DISABLED, :member, Snippet::PUBLIC, false],
+ [:private, ProjectFeature::DISABLED, :member, Snippet::INTERNAL, false],
+ [:private, ProjectFeature::DISABLED, :member, Snippet::PRIVATE, false],
+
+ [:private, ProjectFeature::DISABLED, :author, Snippet::PUBLIC, false],
+ [:private, ProjectFeature::DISABLED, :author, Snippet::INTERNAL, false],
+ [:private, ProjectFeature::DISABLED, :author, Snippet::PRIVATE, false]
+ ]
+ end
+
+ with_them do
+ let!(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel.level_value(project_type.to_s)) }
+ let!(:project_feature) { project.project_feature.update_column(:snippets_access_level, feature_visibility) }
+ let!(:user) { users[user_type] }
+ let!(:snippet) { create(:project_snippet, visibility_level: snippet_type, project: project, author: author) }
+ let!(:members) do
+ project.add_developer(author)
+ project.add_developer(member)
+ project.add_developer(external) if project.private?
+ end
+
+ context "For #{params[:project_type]} project and #{params[:user_type]} users" do
+ it 'should agree with the read_project_snippet policy' do
+ expect(can?(user, :read_project_snippet, snippet)).to eq(outcome)
+ end
+
+ it 'should return proper outcome' do
+ results = described_class.new(user, project: project).execute
+
+ expect(results.include?(snippet)).to eq(outcome)
+ end
+ end
+
+ context "Without a given project and #{params[:user_type]} users" do
+ it 'should return proper outcome' do
+ results = described_class.new(user).execute
+ expect(results.include?(snippet)).to eq(outcome)
+ end
+
+ it 'returns no snippets when the user cannot read cross project' do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
+
+ snippets = described_class.new(user).execute
+
+ expect(snippets).to be_empty
+ end
+ end
+ end
+ end
+
+ context 'For personal snippets' do
+ let!(:users) do
+ {
+ unauthenticated: nil,
+ external: external,
+ non_member: create(:user),
+ author: author
+ }
+ end
+
+ where(:snippet_visibility, :user_type, :outcome) do
+ [
+ [Snippet::PUBLIC, :unauthenticated, true],
+ [Snippet::PUBLIC, :external, true],
+ [Snippet::PUBLIC, :non_member, true],
+ [Snippet::PUBLIC, :author, true],
+
+ [Snippet::INTERNAL, :unauthenticated, false],
+ [Snippet::INTERNAL, :external, false],
+ [Snippet::INTERNAL, :non_member, true],
+ [Snippet::INTERNAL, :author, true],
+
+ [Snippet::PRIVATE, :unauthenticated, false],
+ [Snippet::PRIVATE, :external, false],
+ [Snippet::PRIVATE, :non_member, false],
+ [Snippet::PRIVATE, :author, true]
+ ]
+ end
+
+ with_them do
+ let!(:user) { users[user_type] }
+ let!(:snippet) { create(:personal_snippet, visibility_level: snippet_visibility, author: author) }
+
+ context "For personal and #{params[:snippet_visibility]} snippets with #{params[:user_type]} user" do
+ it 'should agree with read_personal_snippet policy' do
+ expect(can?(user, :read_personal_snippet, snippet)).to eq(outcome)
+ end
+
+ it 'should return proper outcome' do
+ results = described_class.new(user).execute
+ expect(results.include?(snippet)).to eq(outcome)
+ end
+
+ it 'should return personal snippets when the user cannot read cross project' do
+ allow(Ability).to receive(:allowed?).and_call_original
+ allow(Ability).to receive(:allowed?).with(user, :read_cross_project) { false }
+
+ results = described_class.new(user).execute
+
+ expect(results.include?(snippet)).to eq(outcome)
+ end
+ end
+ end
+ end
+end
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index a8fae4a88a3..bdbd39475b9 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -21,9 +21,6 @@ describe 'gitlab:app namespace rake task' do
# empty task as env is already loaded
Rake::Task.define_task :environment
-
- # We need this directory to run `gitlab:backup:create` task
- FileUtils.mkdir_p('public/uploads')
end
before do
@@ -38,6 +35,7 @@ describe 'gitlab:app namespace rake task' do
end
def run_rake_task(task_name)
+ FileUtils.mkdir_p('tmp/tests/public/uploads')
Rake::Task[task_name].reenable
Rake.application.invoke_task task_name
end
diff --git a/spec/tasks/gitlab/storage_rake_spec.rb b/spec/tasks/gitlab/storage_rake_spec.rb
index 6b50670c3c0..4b04d9cec39 100644
--- a/spec/tasks/gitlab/storage_rake_spec.rb
+++ b/spec/tasks/gitlab/storage_rake_spec.rb
@@ -1,6 +1,6 @@
require 'rake_helper'
-describe 'rake gitlab:storage:*' do
+describe 'rake gitlab:storage:*', :sidekiq do
before do
Rake.application.rake_require 'tasks/gitlab/storage'
@@ -43,9 +43,7 @@ describe 'rake gitlab:storage:*' do
end
end
- describe 'gitlab:storage:migrate_to_hashed' do
- let(:task) { 'gitlab:storage:migrate_to_hashed' }
-
+ shared_examples "make sure database is writable" do
context 'read-only database' do
it 'does nothing' do
expect(Gitlab::Database).to receive(:read_only?).and_return(true)
@@ -55,48 +53,68 @@ describe 'rake gitlab:storage:*' do
expect { run_rake_task(task) }.to output(/This task requires database write access. Exiting./).to_stderr
end
end
+ end
- context '0 legacy projects' do
- it 'does nothing' do
- expect(::HashedStorage::MigratorWorker).not_to receive(:perform_async)
+ shared_examples "handles custom BATCH env var" do |worker_klass|
+ context 'in batches of 1' do
+ before do
+ stub_env('BATCH' => 1)
+ end
+
+ it "enqueues one #{worker_klass} per project" do
+ projects.each do |project|
+ expect(worker_klass).to receive(:perform_async).with(project.id, project.id)
+ end
run_rake_task(task)
end
end
- context '3 legacy projects' do
- let(:projects) { create_list(:project, 3, :legacy_storage) }
+ context 'in batches of 2' do
+ before do
+ stub_env('BATCH' => 2)
+ end
- context 'in batches of 1' do
- before do
- stub_env('BATCH' => 1)
+ it "enqueues one #{worker_klass} per 2 projects" do
+ projects.map(&:id).sort.each_slice(2) do |first, last|
+ last ||= first
+ expect(worker_klass).to receive(:perform_async).with(first, last)
end
- it 'enqueues one HashedStorage::MigratorWorker per project' do
- projects.each do |project|
- expect(::HashedStorage::MigratorWorker).to receive(:perform_async).with(project.id, project.id)
- end
-
- run_rake_task(task)
- end
+ run_rake_task(task)
end
+ end
+ end
- context 'in batches of 2' do
- before do
- stub_env('BATCH' => 2)
- end
+ describe 'gitlab:storage:migrate_to_hashed' do
+ let(:task) { 'gitlab:storage:migrate_to_hashed' }
- it 'enqueues one HashedStorage::MigratorWorker per 2 projects' do
- projects.map(&:id).sort.each_slice(2) do |first, last|
- last ||= first
- expect(::HashedStorage::MigratorWorker).to receive(:perform_async).with(first, last)
- end
+ context 'with rollback already scheduled', :redis do
+ it 'does nothing' do
+ Sidekiq::Testing.disable! do
+ ::HashedStorage::RollbackerWorker.perform_async(1, 5)
+
+ expect(Project).not_to receive(:with_unmigrated_storage)
- run_rake_task(task)
+ expect { run_rake_task(task) }.to output(/There is already a rollback operation in progress/).to_stderr
end
end
end
+ context 'with 0 legacy projects' do
+ it 'does nothing' do
+ expect(::HashedStorage::MigratorWorker).not_to receive(:perform_async)
+
+ run_rake_task(task)
+ end
+ end
+
+ context 'with 3 legacy projects' do
+ let(:projects) { create_list(:project, 3, :legacy_storage) }
+
+ it_behaves_like "handles custom BATCH env var", ::HashedStorage::MigratorWorker
+ end
+
context 'with same id in range' do
it 'displays message when project cant be found' do
stub_env('ID_FROM', 99999)
@@ -123,6 +141,38 @@ describe 'rake gitlab:storage:*' do
end
end
+ describe 'gitlab:storage:rollback_to_legacy' do
+ let(:task) { 'gitlab:storage:rollback_to_legacy' }
+
+ it_behaves_like 'make sure database is writable'
+
+ context 'with migration already scheduled', :redis do
+ it 'does nothing' do
+ Sidekiq::Testing.disable! do
+ ::HashedStorage::MigratorWorker.perform_async(1, 5)
+
+ expect(Project).not_to receive(:with_unmigrated_storage)
+
+ expect { run_rake_task(task) }.to output(/There is already a migration operation in progress/).to_stderr
+ end
+ end
+ end
+
+ context 'with 0 hashed projects' do
+ it 'does nothing' do
+ expect(::HashedStorage::RollbackerWorker).not_to receive(:perform_async)
+
+ run_rake_task(task)
+ end
+ end
+
+ context 'with 3 hashed projects' do
+ let(:projects) { create_list(:project, 3) }
+
+ it_behaves_like "handles custom BATCH env var", ::HashedStorage::RollbackerWorker
+ end
+ end
+
describe 'gitlab:storage:legacy_projects' do
it_behaves_like 'rake entities summary', 'projects', 'Legacy' do
let(:task) { 'gitlab:storage:legacy_projects' }
diff --git a/spec/validators/devise_email_validator_spec.rb b/spec/validators/devise_email_validator_spec.rb
new file mode 100644
index 00000000000..7860b659bd3
--- /dev/null
+++ b/spec/validators/devise_email_validator_spec.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe DeviseEmailValidator do
+ let!(:user) { build(:user, public_email: 'test@example.com') }
+ subject { validator.validate(user) }
+
+ describe 'validations' do
+ context 'by default' do
+ let(:validator) { described_class.new(attributes: [:public_email]) }
+
+ it 'allows when email is valid' do
+ subject
+
+ expect(user.errors).to be_empty
+ end
+
+ it 'returns error when email is invalid' do
+ user.public_email = 'invalid'
+
+ subject
+
+ expect(user.errors).to be_present
+ expect(user.errors.first[1]).to eq 'is invalid'
+ end
+
+ it 'returns error when email is nil' do
+ user.public_email = nil
+
+ subject
+
+ expect(user.errors).to be_present
+ end
+
+ it 'returns error when email is blank' do
+ user.public_email = ''
+
+ subject
+
+ expect(user.errors).to be_present
+ expect(user.errors.first[1]).to eq 'is invalid'
+ end
+ end
+ end
+
+ context 'when regexp is set as Regexp' do
+ let(:validator) { described_class.new(attributes: [:public_email], regexp: /[0-9]/) }
+
+ it 'allows when value match' do
+ user.public_email = '1'
+
+ subject
+
+ expect(user.errors).to be_empty
+ end
+
+ it 'returns error when value does not match' do
+ subject
+
+ expect(user.errors).to be_present
+ end
+ end
+
+ context 'when regexp is set as String' do
+ it 'raise argument error' do
+ expect { described_class.new( { regexp: 'something' } ) }.to raise_error ArgumentError
+ end
+ end
+
+ context 'when allow_nil is set to true' do
+ let(:validator) { described_class.new(attributes: [:public_email], allow_nil: true) }
+
+ it 'allows when email is nil' do
+ user.public_email = nil
+
+ subject
+
+ expect(user.errors).to be_empty
+ end
+ end
+
+ context 'when allow_blank is set to true' do
+ let(:validator) { described_class.new(attributes: [:public_email], allow_blank: true) }
+
+ it 'allows when email is blank' do
+ user.public_email = ''
+
+ subject
+
+ expect(user.errors).to be_empty
+ end
+ end
+end
diff --git a/spec/validators/sha_validator_spec.rb b/spec/validators/sha_validator_spec.rb
index dc1539cf318..0a76570f65e 100644
--- a/spec/validators/sha_validator_spec.rb
+++ b/spec/validators/sha_validator_spec.rb
@@ -4,7 +4,7 @@ require 'spec_helper'
describe ShaValidator do
let(:validator) { described_class.new(attributes: [:base_commit_sha]) }
- let(:merge_diff) { build(:merge_request_diff) }
+ let!(:merge_diff) { build(:merge_request_diff) }
subject { validator.validate_each(merge_diff, :base_commit_sha, value) }
@@ -12,6 +12,8 @@ describe ShaValidator do
let(:value) { nil }
it 'does not add any error if value is empty' do
+ expect(Commit).not_to receive(:valid_hash?)
+
subject
expect(merge_diff.errors).to be_empty
@@ -21,7 +23,9 @@ describe ShaValidator do
context 'with valid sha' do
let(:value) { Digest::SHA1.hexdigest(SecureRandom.hex) }
- it 'does not add any error if value is empty' do
+ it 'does not add any error' do
+ expect(Commit).to receive(:valid_hash?).and_call_original
+
subject
expect(merge_diff.errors).to be_empty
@@ -32,6 +36,7 @@ describe ShaValidator do
let(:value) { 'foo' }
it 'adds error to the record' do
+ expect(Commit).to receive(:valid_hash?).and_call_original
expect(merge_diff.errors).to be_empty
subject
diff --git a/spec/views/groups/_home_panel.html.haml_spec.rb b/spec/views/groups/_home_panel.html.haml_spec.rb
new file mode 100644
index 00000000000..91c5ca261b9
--- /dev/null
+++ b/spec/views/groups/_home_panel.html.haml_spec.rb
@@ -0,0 +1,15 @@
+require 'spec_helper'
+
+describe 'groups/_home_panel' do
+ let(:group) { create(:group) }
+
+ before do
+ assign(:group, group)
+ end
+
+ it 'renders the group ID' do
+ render
+
+ expect(rendered).to have_content("Group ID: #{group.id}")
+ end
+end
diff --git a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
index 2a2539c80b5..b52fc719a64 100644
--- a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
+++ b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
@@ -5,6 +5,7 @@ describe 'projects/settings/ci_cd/_autodevops_form' do
before do
assign :project, project
+ allow(view).to receive(:auto_devops_enabled) { true }
end
it 'shows a warning message about Kubernetes cluster' do
diff --git a/spec/views/projects/settings/operations/show.html.haml_spec.rb b/spec/views/projects/settings/operations/show.html.haml_spec.rb
index 1bca8bba940..6762fe3759b 100644
--- a/spec/views/projects/settings/operations/show.html.haml_spec.rb
+++ b/spec/views/projects/settings/operations/show.html.haml_spec.rb
@@ -18,6 +18,7 @@ describe 'projects/settings/operations/show' do
allow(view).to receive(:error_tracking_setting)
.and_return(error_tracking_setting)
allow(view).to receive(:current_user).and_return(user)
+ allow(view).to receive(:incident_management_available?) { false }
end
let!(:error_tracking_setting) do
diff --git a/spec/workers/ci/build_prepare_worker_spec.rb b/spec/workers/ci/build_prepare_worker_spec.rb
new file mode 100644
index 00000000000..9f76696ee66
--- /dev/null
+++ b/spec/workers/ci/build_prepare_worker_spec.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Ci::BuildPrepareWorker do
+ subject { described_class.new.perform(build_id) }
+
+ context 'build exists' do
+ let(:build) { create(:ci_build) }
+ let(:build_id) { build.id }
+ let(:service) { double(execute: true) }
+
+ it 'calls the prepare build service' do
+ expect(Ci::PrepareBuildService).to receive(:new).with(build).and_return(service)
+ expect(service).to receive(:execute).once
+
+ subject
+ end
+ end
+
+ context 'build does not exist' do
+ let(:build_id) { -1 }
+
+ it 'does not attempt to prepare the build' do
+ expect(Ci::PrepareBuildService).not_to receive(:new)
+
+ subject
+ end
+ end
+end
diff --git a/spec/workers/cluster_configure_worker_spec.rb b/spec/workers/cluster_configure_worker_spec.rb
index 6918ee3d7d8..83f76809435 100644
--- a/spec/workers/cluster_configure_worker_spec.rb
+++ b/spec/workers/cluster_configure_worker_spec.rb
@@ -4,6 +4,11 @@ require 'spec_helper'
describe ClusterConfigureWorker, '#perform' do
let(:worker) { described_class.new }
+ let(:ci_preparing_state_enabled) { false }
+
+ before do
+ stub_feature_flags(ci_preparing_state: ci_preparing_state_enabled)
+ end
context 'when group cluster' do
let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
@@ -66,4 +71,15 @@ describe ClusterConfigureWorker, '#perform' do
described_class.new.perform(123)
end
end
+
+ context 'ci_preparing_state feature is enabled' do
+ let(:cluster) { create(:cluster) }
+ let(:ci_preparing_state_enabled) { true }
+
+ it 'does not configure the cluster' do
+ expect(Clusters::RefreshService).not_to receive(:create_or_update_namespaces_for_cluster)
+
+ described_class.new.perform(cluster.id)
+ end
+ end
end
diff --git a/spec/workers/cluster_project_configure_worker_spec.rb b/spec/workers/cluster_project_configure_worker_spec.rb
new file mode 100644
index 00000000000..afdea55adf4
--- /dev/null
+++ b/spec/workers/cluster_project_configure_worker_spec.rb
@@ -0,0 +1,21 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe ClusterProjectConfigureWorker, '#perform' do
+ let(:worker) { described_class.new }
+
+ context 'ci_preparing_state feature is enabled' do
+ let(:cluster) { create(:cluster) }
+
+ before do
+ stub_feature_flags(ci_preparing_state: true)
+ end
+
+ it 'does not configure the cluster' do
+ expect(Clusters::RefreshService).not_to receive(:create_or_update_namespaces_for_project)
+
+ described_class.new.perform(cluster.id)
+ end
+ end
+end
diff --git a/vendor/assets/javascripts/jquery.atwho.js b/vendor/assets/javascripts/jquery.atwho.js
deleted file mode 100644
index e058e13303a..00000000000
--- a/vendor/assets/javascripts/jquery.atwho.js
+++ /dev/null
@@ -1,1202 +0,0 @@
-/**
- * at.js - 1.5.1
- * Copyright (c) 2016 chord.luo <chord.luo@gmail.com>;
- * Homepage: http://ichord.github.com/At.js
- * License: MIT
- */
-(function (root, factory) {
- if (typeof define === 'function' && define.amd) {
- // AMD. Register as an anonymous module unless amdModuleId is set
- define(["jquery"], function (a0) {
- return (factory(a0));
- });
- } else if (typeof exports === 'object') {
- // Node. Does not work with strict CommonJS, but
- // only CommonJS-like environments that support module.exports,
- // like Node.
- module.exports = factory(require("jquery"));
- } else {
- factory(jQuery);
- }
-}(this, function ($) {
-var DEFAULT_CALLBACKS, KEY_CODE;
-
-KEY_CODE = {
- DOWN: 40,
- UP: 38,
- ESC: 27,
- TAB: 9,
- ENTER: 13,
- CTRL: 17,
- A: 65,
- P: 80,
- N: 78,
- LEFT: 37,
- UP: 38,
- RIGHT: 39,
- DOWN: 40,
- BACKSPACE: 8,
- SPACE: 32
-};
-
-DEFAULT_CALLBACKS = {
- beforeSave: function(data) {
- return Controller.arrayToDefaultHash(data);
- },
- matcher: function(flag, subtext, should_startWithSpace, acceptSpaceBar) {
- var _a, _y, match, regexp, space;
- flag = flag.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
- if (should_startWithSpace) {
- flag = '(?:^|\\s)' + flag;
- }
- _a = decodeURI("%C3%80");
- _y = decodeURI("%C3%BF");
- space = acceptSpaceBar ? "\ " : "";
- regexp = new RegExp(flag + "([A-Za-z" + _a + "-" + _y + "0-9_" + space + "\'\.\+\-]*)$|" + flag + "([^\\x00-\\xff]*)$", 'gi');
- match = regexp.exec(subtext);
- if (match) {
- return match[2] || match[1];
- } else {
- return null;
- }
- },
- filter: function(query, data, searchKey) {
- var _results, i, item, len;
- _results = [];
- for (i = 0, len = data.length; i < len; i++) {
- item = data[i];
- if (~new String(item[searchKey]).toLowerCase().indexOf(query.toLowerCase())) {
- _results.push(item);
- }
- }
- return _results;
- },
- remoteFilter: null,
- sorter: function(query, items, searchKey) {
- var _results, i, item, len;
- if (!query) {
- return items;
- }
- _results = [];
- for (i = 0, len = items.length; i < len; i++) {
- item = items[i];
- item.atwho_order = new String(item[searchKey]).toLowerCase().indexOf(query.toLowerCase());
- if (item.atwho_order > -1) {
- _results.push(item);
- }
- }
- return _results.sort(function(a, b) {
- return a.atwho_order - b.atwho_order;
- });
- },
- tplEval: function(tpl, map) {
- var error, error1, template;
- template = tpl;
- try {
- if (typeof tpl !== 'string') {
- template = tpl(map);
- }
- return template.replace(/\$\{([^\}]*)\}/g, function(tag, key, pos) {
- return map[key];
- });
- } catch (error1) {
- error = error1;
- return "";
- }
- },
- highlighter: function(li, query) {
- var regexp;
- if (!query) {
- return li;
- }
- regexp = new RegExp(">\\s*(\\w*?)(" + query.replace("+", "\\+") + ")(\\w*)\\s*<", 'ig');
- return li.replace(regexp, function(str, $1, $2, $3) {
- return '> ' + $1 + '<strong>' + $2 + '</strong>' + $3 + ' <';
- });
- },
- beforeInsert: function(value, $li, e) {
- return value;
- },
- beforeReposition: function(offset) {
- return offset;
- },
- afterMatchFailed: function(at, el) {}
-};
-
-var App;
-
-App = (function() {
- function App(inputor) {
- this.currentFlag = null;
- this.controllers = {};
- this.aliasMaps = {};
- this.$inputor = $(inputor);
- this.setupRootElement();
- this.listen();
- }
-
- App.prototype.createContainer = function(doc) {
- var ref;
- if ((ref = this.$el) != null) {
- ref.remove();
- }
- return $(doc.body).append(this.$el = $("<div class='atwho-container'></div>"));
- };
-
- App.prototype.setupRootElement = function(iframe, asRoot) {
- var error, error1;
- if (asRoot == null) {
- asRoot = false;
- }
- if (iframe) {
- this.window = iframe.contentWindow;
- this.document = iframe.contentDocument || this.window.document;
- this.iframe = iframe;
- } else {
- this.document = this.$inputor[0].ownerDocument;
- this.window = this.document.defaultView || this.document.parentWindow;
- try {
- this.iframe = this.window.frameElement;
- } catch (error1) {
- error = error1;
- this.iframe = null;
- if ($.fn.atwho.debug) {
- throw new Error("iframe auto-discovery is failed.\nPlease use `setIframe` to set the target iframe manually.\n" + error);
- }
- }
- }
- return this.createContainer((this.iframeAsRoot = asRoot) ? this.document : document);
- };
-
- App.prototype.controller = function(at) {
- var c, current, currentFlag, ref;
- if (this.aliasMaps[at]) {
- current = this.controllers[this.aliasMaps[at]];
- } else {
- ref = this.controllers;
- for (currentFlag in ref) {
- c = ref[currentFlag];
- if (currentFlag === at) {
- current = c;
- break;
- }
- }
- }
- if (current) {
- return current;
- } else {
- return this.controllers[this.currentFlag];
- }
- };
-
- App.prototype.setContextFor = function(at) {
- this.currentFlag = at;
- return this;
- };
-
- App.prototype.reg = function(flag, setting) {
- var base, controller;
- controller = (base = this.controllers)[flag] || (base[flag] = this.$inputor.is('[contentEditable]') ? new EditableController(this, flag) : new TextareaController(this, flag));
- if (setting.alias) {
- this.aliasMaps[setting.alias] = flag;
- }
- controller.init(setting);
- return this;
- };
-
- App.prototype.listen = function() {
- return this.$inputor.on('compositionstart', (function(_this) {
- return function(e) {
- var ref;
- if ((ref = _this.controller()) != null) {
- ref.view.hide();
- }
- _this.isComposing = true;
- return null;
- };
- })(this)).on('compositionend', (function(_this) {
- return function(e) {
- _this.isComposing = false;
- setTimeout(function(e) {
- return _this.dispatch(e);
- });
- return null;
- };
- })(this)).on('keyup.atwhoInner', (function(_this) {
- return function(e) {
- return _this.onKeyup(e);
- };
- })(this)).on('keydown.atwhoInner', (function(_this) {
- return function(e) {
- return _this.onKeydown(e);
- };
- })(this)).on('blur.atwhoInner', (function(_this) {
- return function(e) {
- var c;
- if (c = _this.controller()) {
- c.expectedQueryCBId = null;
- return c.view.hide(e, c.getOpt("displayTimeout"));
- }
- };
- })(this)).on('click.atwhoInner', (function(_this) {
- return function(e) {
- return _this.dispatch(e);
- };
- })(this)).on('scroll.atwhoInner', (function(_this) {
- return function() {
- var lastScrollTop;
- lastScrollTop = _this.$inputor.scrollTop();
- return function(e) {
- var currentScrollTop, ref;
- currentScrollTop = e.target.scrollTop;
- if (lastScrollTop !== currentScrollTop) {
- if ((ref = _this.controller()) != null) {
- ref.view.hide(e);
- }
- }
- lastScrollTop = currentScrollTop;
- return true;
- };
- };
- })(this)());
- };
-
- App.prototype.shutdown = function() {
- var _, c, ref;
- ref = this.controllers;
- for (_ in ref) {
- c = ref[_];
- c.destroy();
- delete this.controllers[_];
- }
- this.$inputor.off('.atwhoInner');
- return this.$el.remove();
- };
-
- App.prototype.dispatch = function(e) {
- var _, c, ref, results;
- ref = this.controllers;
- results = [];
- for (_ in ref) {
- c = ref[_];
- results.push(c.lookUp(e));
- }
- return results;
- };
-
- App.prototype.onKeyup = function(e) {
- var ref;
- switch (e.keyCode) {
- case KEY_CODE.ESC:
- e.preventDefault();
- if ((ref = this.controller()) != null) {
- ref.view.hide();
- }
- break;
- case KEY_CODE.DOWN:
- case KEY_CODE.UP:
- case KEY_CODE.CTRL:
- case KEY_CODE.ENTER:
- $.noop();
- break;
- case KEY_CODE.P:
- case KEY_CODE.N:
- if (!e.ctrlKey) {
- this.dispatch(e);
- }
- break;
- default:
- this.dispatch(e);
- }
- };
-
- App.prototype.onKeydown = function(e) {
- var ref, view;
- view = (ref = this.controller()) != null ? ref.view : void 0;
- if (!(view && view.visible())) {
- return;
- }
- switch (e.keyCode) {
- case KEY_CODE.ESC:
- e.preventDefault();
- view.hide(e);
- break;
- case KEY_CODE.UP:
- e.preventDefault();
- view.prev();
- break;
- case KEY_CODE.DOWN:
- e.preventDefault();
- view.next();
- break;
- case KEY_CODE.P:
- if (!e.ctrlKey) {
- return;
- }
- e.preventDefault();
- view.prev();
- break;
- case KEY_CODE.N:
- if (!e.ctrlKey) {
- return;
- }
- e.preventDefault();
- view.next();
- break;
- case KEY_CODE.TAB:
- case KEY_CODE.ENTER:
- case KEY_CODE.SPACE:
- if (!view.visible()) {
- return;
- }
- if (!this.controller().getOpt('spaceSelectsMatch') && e.keyCode === KEY_CODE.SPACE) {
- return;
- }
- if (!this.controller().getOpt('tabSelectsMatch') && e.keyCode === KEY_CODE.TAB) {
- return;
- }
- if (view.highlighted()) {
- e.preventDefault();
- view.choose(e);
- } else {
- view.hide(e);
- }
- break;
- default:
- $.noop();
- }
- };
-
- return App;
-
-})();
-
-var Controller,
- slice = [].slice;
-
-Controller = (function() {
- Controller.prototype.uid = function() {
- return (Math.random().toString(16) + "000000000").substr(2, 8) + (new Date().getTime());
- };
-
- function Controller(app, at1) {
- this.app = app;
- this.at = at1;
- this.$inputor = this.app.$inputor;
- this.id = this.$inputor[0].id || this.uid();
- this.expectedQueryCBId = null;
- this.setting = null;
- this.query = null;
- this.pos = 0;
- this.range = null;
- if ((this.$el = $("#atwho-ground-" + this.id, this.app.$el)).length === 0) {
- this.app.$el.append(this.$el = $("<div id='atwho-ground-" + this.id + "'></div>"));
- }
- this.model = new Model(this);
- this.view = new View(this);
- }
-
- Controller.prototype.init = function(setting) {
- this.setting = $.extend({}, this.setting || $.fn.atwho["default"], setting);
- this.view.init();
- return this.model.reload(this.setting.data);
- };
-
- Controller.prototype.destroy = function() {
- this.trigger('beforeDestroy');
- this.model.destroy();
- this.view.destroy();
- return this.$el.remove();
- };
-
- Controller.prototype.callDefault = function() {
- var args, error, error1, funcName;
- funcName = arguments[0], args = 2 <= arguments.length ? slice.call(arguments, 1) : [];
- try {
- return DEFAULT_CALLBACKS[funcName].apply(this, args);
- } catch (error1) {
- error = error1;
- return $.error(error + " Or maybe At.js doesn't have function " + funcName);
- }
- };
-
- Controller.prototype.trigger = function(name, data) {
- var alias, eventName;
- if (data == null) {
- data = [];
- }
- data.push(this);
- alias = this.getOpt('alias');
- eventName = alias ? name + "-" + alias + ".atwho" : name + ".atwho";
- return this.$inputor.trigger(eventName, data);
- };
-
- Controller.prototype.callbacks = function(funcName) {
- return this.getOpt("callbacks")[funcName] || DEFAULT_CALLBACKS[funcName];
- };
-
- Controller.prototype.getOpt = function(at, default_value) {
- var e, error1;
- try {
- return this.setting[at];
- } catch (error1) {
- e = error1;
- return null;
- }
- };
-
- Controller.prototype.insertContentFor = function($li) {
- var data, tpl;
- tpl = this.getOpt('insertTpl');
- data = $.extend({}, $li.data('itemData'), {
- 'atwho-at': this.at
- });
- return this.callbacks("tplEval").call(this, tpl, data, "onInsert");
- };
-
- Controller.prototype.renderView = function(data) {
- var searchKey;
- searchKey = this.getOpt("searchKey");
- data = this.callbacks("sorter").call(this, this.query.text, data.slice(0, 1001), searchKey);
- return this.view.render(data.slice(0, this.getOpt('limit')));
- };
-
- Controller.arrayToDefaultHash = function(data) {
- var i, item, len, results;
- if (!$.isArray(data)) {
- return data;
- }
- results = [];
- for (i = 0, len = data.length; i < len; i++) {
- item = data[i];
- if ($.isPlainObject(item)) {
- results.push(item);
- } else {
- results.push({
- name: item
- });
- }
- }
- return results;
- };
-
- Controller.prototype.lookUp = function(e) {
- var query, wait;
- if (e && e.type === 'click' && !this.getOpt('lookUpOnClick')) {
- return;
- }
- if (this.getOpt('suspendOnComposing') && this.app.isComposing) {
- return;
- }
- query = this.catchQuery(e);
- if (!query) {
- this.expectedQueryCBId = null;
- return query;
- }
- this.app.setContextFor(this.at);
- if (wait = this.getOpt('delay')) {
- this._delayLookUp(query, wait);
- } else {
- this._lookUp(query);
- }
- return query;
- };
-
- Controller.prototype._delayLookUp = function(query, wait) {
- var now, remaining;
- now = Date.now ? Date.now() : new Date().getTime();
- this.previousCallTime || (this.previousCallTime = now);
- remaining = wait - (now - this.previousCallTime);
- if ((0 < remaining && remaining < wait)) {
- this.previousCallTime = now;
- this._stopDelayedCall();
- return this.delayedCallTimeout = setTimeout((function(_this) {
- return function() {
- _this.previousCallTime = 0;
- _this.delayedCallTimeout = null;
- return _this._lookUp(query);
- };
- })(this), wait);
- } else {
- this._stopDelayedCall();
- if (this.previousCallTime !== now) {
- this.previousCallTime = 0;
- }
- return this._lookUp(query);
- }
- };
-
- Controller.prototype._stopDelayedCall = function() {
- if (this.delayedCallTimeout) {
- clearTimeout(this.delayedCallTimeout);
- return this.delayedCallTimeout = null;
- }
- };
-
- Controller.prototype._generateQueryCBId = function() {
- return {};
- };
-
- Controller.prototype._lookUp = function(query) {
- var _callback;
- _callback = function(queryCBId, data) {
- if (queryCBId !== this.expectedQueryCBId) {
- return;
- }
- if (data && data.length > 0) {
- return this.renderView(this.constructor.arrayToDefaultHash(data));
- } else {
- return this.view.hide();
- }
- };
- this.expectedQueryCBId = this._generateQueryCBId();
- return this.model.query(query.text, $.proxy(_callback, this, this.expectedQueryCBId));
- };
-
- return Controller;
-
-})();
-
-var TextareaController,
- extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
- hasProp = {}.hasOwnProperty;
-
-TextareaController = (function(superClass) {
- extend(TextareaController, superClass);
-
- function TextareaController() {
- return TextareaController.__super__.constructor.apply(this, arguments);
- }
-
- TextareaController.prototype.catchQuery = function() {
- var caretPos, content, end, isString, query, start, subtext;
- content = this.$inputor.val();
- caretPos = this.$inputor.caret('pos', {
- iframe: this.app.iframe
- });
- subtext = content.slice(0, caretPos);
- query = this.callbacks("matcher").call(this, this.at, subtext, this.getOpt('startWithSpace'), this.getOpt("acceptSpaceBar"));
- isString = typeof query === 'string';
- if (isString && query.length < this.getOpt('minLen', 0)) {
- return;
- }
- if (isString && query.length <= this.getOpt('maxLen', 20)) {
- start = caretPos - query.length;
- end = start + query.length;
- this.pos = start;
- query = {
- 'text': query,
- 'headPos': start,
- 'endPos': end
- };
- this.trigger("matched", [this.at, query.text]);
- } else {
- query = null;
- this.view.hide();
- }
- return this.query = query;
- };
-
- TextareaController.prototype.rect = function() {
- var c, iframeOffset, scaleBottom;
- if (!(c = this.$inputor.caret('offset', this.pos - 1, {
- iframe: this.app.iframe
- }))) {
- return;
- }
- if (this.app.iframe && !this.app.iframeAsRoot) {
- iframeOffset = $(this.app.iframe).offset();
- c.left += iframeOffset.left;
- c.top += iframeOffset.top;
- }
- scaleBottom = this.app.document.selection ? 0 : 2;
- return {
- left: c.left,
- top: c.top,
- bottom: c.top + c.height + scaleBottom
- };
- };
-
- TextareaController.prototype.insert = function(content, $li) {
- var $inputor, source, startStr, suffix, text;
- $inputor = this.$inputor;
- source = $inputor.val();
- startStr = source.slice(0, Math.max(this.query.headPos - this.at.length, 0));
- suffix = (suffix = this.getOpt('suffix')) === "" ? suffix : suffix || " ";
- content += suffix;
- text = "" + startStr + content + (source.slice(this.query['endPos'] || 0));
- $inputor.val(text);
- $inputor.caret('pos', startStr.length + content.length, {
- iframe: this.app.iframe
- });
- if (!$inputor.is(':focus')) {
- $inputor.focus();
- }
- return $inputor.change();
- };
-
- return TextareaController;
-
-})(Controller);
-
-var EditableController,
- extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
- hasProp = {}.hasOwnProperty;
-
-EditableController = (function(superClass) {
- extend(EditableController, superClass);
-
- function EditableController() {
- return EditableController.__super__.constructor.apply(this, arguments);
- }
-
- EditableController.prototype._getRange = function() {
- var sel;
- sel = this.app.window.getSelection();
- if (sel.rangeCount > 0) {
- return sel.getRangeAt(0);
- }
- };
-
- EditableController.prototype._setRange = function(position, node, range) {
- if (range == null) {
- range = this._getRange();
- }
- if (!range) {
- return;
- }
- node = $(node)[0];
- if (position === 'after') {
- range.setEndAfter(node);
- range.setStartAfter(node);
- } else {
- range.setEndBefore(node);
- range.setStartBefore(node);
- }
- range.collapse(false);
- return this._clearRange(range);
- };
-
- EditableController.prototype._clearRange = function(range) {
- var sel;
- if (range == null) {
- range = this._getRange();
- }
- sel = this.app.window.getSelection();
- if (this.ctrl_a_pressed == null) {
- sel.removeAllRanges();
- return sel.addRange(range);
- }
- };
-
- EditableController.prototype._movingEvent = function(e) {
- var ref;
- return e.type === 'click' || ((ref = e.which) === KEY_CODE.RIGHT || ref === KEY_CODE.LEFT || ref === KEY_CODE.UP || ref === KEY_CODE.DOWN);
- };
-
- EditableController.prototype._unwrap = function(node) {
- var next;
- node = $(node).unwrap().get(0);
- if ((next = node.nextSibling) && next.nodeValue) {
- node.nodeValue += next.nodeValue;
- $(next).remove();
- }
- return node;
- };
-
- EditableController.prototype.catchQuery = function(e) {
- var $inserted, $query, _range, index, inserted, isString, lastNode, matched, offset, query, query_content, range;
- if (!(range = this._getRange())) {
- return;
- }
- if (!range.collapsed) {
- return;
- }
- if (e.which === KEY_CODE.ENTER) {
- ($query = $(range.startContainer).closest('.atwho-query')).contents().unwrap();
- if ($query.is(':empty')) {
- $query.remove();
- }
- ($query = $(".atwho-query", this.app.document)).text($query.text()).contents().last().unwrap();
- this._clearRange();
- return;
- }
- if (/firefox/i.test(navigator.userAgent)) {
- if ($(range.startContainer).is(this.$inputor)) {
- this._clearRange();
- return;
- }
- if (e.which === KEY_CODE.BACKSPACE && range.startContainer.nodeType === document.ELEMENT_NODE && (offset = range.startOffset - 1) >= 0) {
- _range = range.cloneRange();
- _range.setStart(range.startContainer, offset);
- if ($(_range.cloneContents()).contents().last().is('.atwho-inserted')) {
- inserted = $(range.startContainer).contents().get(offset);
- this._setRange('after', $(inserted).contents().last());
- }
- } else if (e.which === KEY_CODE.LEFT && range.startContainer.nodeType === document.TEXT_NODE) {
- $inserted = $(range.startContainer.previousSibling);
- if ($inserted.is('.atwho-inserted') && range.startOffset === 0) {
- this._setRange('after', $inserted.contents().last());
- }
- }
- }
- $(range.startContainer).closest('.atwho-inserted').addClass('atwho-query').siblings().removeClass('atwho-query');
- if (($query = $(".atwho-query", this.app.document)).length > 0 && $query.is(':empty') && $query.text().length === 0) {
- $query.remove();
- }
- if (!this._movingEvent(e)) {
- $query.removeClass('atwho-inserted');
- }
- if ($query.length > 0) {
- switch (e.which) {
- case KEY_CODE.LEFT:
- this._setRange('before', $query.get(0), range);
- $query.removeClass('atwho-query');
- return;
- case KEY_CODE.RIGHT:
- this._setRange('after', $query.get(0).nextSibling, range);
- $query.removeClass('atwho-query');
- return;
- }
- }
- if ($query.length > 0 && (query_content = $query.attr('data-atwho-at-query'))) {
- $query.empty().html(query_content).attr('data-atwho-at-query', null);
- this._setRange('after', $query.get(0), range);
- }
- _range = range.cloneRange();
- _range.setStart(range.startContainer, 0);
- matched = this.callbacks("matcher").call(this, this.at, _range.toString(), this.getOpt('startWithSpace'), this.getOpt("acceptSpaceBar"));
- isString = typeof matched === 'string';
- if ($query.length === 0 && isString && (index = range.startOffset - this.at.length - matched.length) >= 0) {
- range.setStart(range.startContainer, index);
- $query = $('<span/>', this.app.document).attr(this.getOpt("editableAtwhoQueryAttrs")).addClass('atwho-query');
- range.surroundContents($query.get(0));
- lastNode = $query.contents().last().get(0);
- if (/firefox/i.test(navigator.userAgent)) {
- range.setStart(lastNode, lastNode.length);
- range.setEnd(lastNode, lastNode.length);
- this._clearRange(range);
- } else {
- this._setRange('after', lastNode, range);
- }
- }
- if (isString && matched.length < this.getOpt('minLen', 0)) {
- return;
- }
- if (isString && matched.length <= this.getOpt('maxLen', 20)) {
- query = {
- text: matched,
- el: $query
- };
- this.trigger("matched", [this.at, query.text]);
- return this.query = query;
- } else {
- this.view.hide();
- this.query = {
- el: $query
- };
- if ($query.text().indexOf(this.at) >= 0) {
- if (this._movingEvent(e) && $query.hasClass('atwho-inserted')) {
- $query.removeClass('atwho-query');
- } else if (false !== this.callbacks('afterMatchFailed').call(this, this.at, $query)) {
- this._setRange("after", this._unwrap($query.text($query.text()).contents().first()));
- }
- }
- return null;
- }
- };
-
- EditableController.prototype.rect = function() {
- var $iframe, iframeOffset, rect;
- rect = this.query.el.offset();
- if (this.app.iframe && !this.app.iframeAsRoot) {
- iframeOffset = ($iframe = $(this.app.iframe)).offset();
- rect.left += iframeOffset.left - this.$inputor.scrollLeft();
- rect.top += iframeOffset.top - this.$inputor.scrollTop();
- }
- rect.bottom = rect.top + this.query.el.height();
- return rect;
- };
-
- EditableController.prototype.insert = function(content, $li) {
- var data, range, suffix, suffixNode;
- if (!this.$inputor.is(':focus')) {
- this.$inputor.focus();
- }
- suffix = (suffix = this.getOpt('suffix')) === "" ? suffix : suffix || "\u00A0";
- data = $li.data('itemData');
- this.query.el.removeClass('atwho-query').addClass('atwho-inserted').html(content).attr('data-atwho-at-query', "" + data['atwho-at'] + this.query.text);
- if (range = this._getRange()) {
- range.setEndAfter(this.query.el[0]);
- range.collapse(false);
- range.insertNode(suffixNode = this.app.document.createTextNode("\u200D" + suffix));
- this._setRange('after', suffixNode, range);
- }
- if (!this.$inputor.is(':focus')) {
- this.$inputor.focus();
- }
- return this.$inputor.change();
- };
-
- return EditableController;
-
-})(Controller);
-
-var Model;
-
-Model = (function() {
- function Model(context) {
- this.context = context;
- this.at = this.context.at;
- this.storage = this.context.$inputor;
- }
-
- Model.prototype.destroy = function() {
- return this.storage.data(this.at, null);
- };
-
- Model.prototype.saved = function() {
- return this.fetch() > 0;
- };
-
- Model.prototype.query = function(query, callback) {
- var _remoteFilter, data, searchKey;
- data = this.fetch();
- searchKey = this.context.getOpt("searchKey");
- data = this.context.callbacks('filter').call(this.context, query, data, searchKey) || [];
- _remoteFilter = this.context.callbacks('remoteFilter');
- if (data.length > 0 || (!_remoteFilter && data.length === 0)) {
- return callback(data);
- } else {
- return _remoteFilter.call(this.context, query, callback);
- }
- };
-
- Model.prototype.fetch = function() {
- return this.storage.data(this.at) || [];
- };
-
- Model.prototype.save = function(data) {
- return this.storage.data(this.at, this.context.callbacks("beforeSave").call(this.context, data || []));
- };
-
- Model.prototype.load = function(data) {
- if (!(this.saved() || !data)) {
- return this._load(data);
- }
- };
-
- Model.prototype.reload = function(data) {
- return this._load(data);
- };
-
- Model.prototype._load = function(data) {
- if (typeof data === "string") {
- return $.ajax(data, {
- dataType: "json"
- }).done((function(_this) {
- return function(data) {
- return _this.save(data);
- };
- })(this));
- } else {
- return this.save(data);
- }
- };
-
- return Model;
-
-})();
-
-var View;
-
-View = (function() {
- function View(context) {
- this.context = context;
- this.$el = $("<div class='atwho-view'><ul class='atwho-view-ul'></ul></div>");
- this.$elUl = this.$el.children();
- this.timeoutID = null;
- this.context.$el.append(this.$el);
- this.bindEvent();
- }
-
- View.prototype.init = function() {
- var header_tpl, id;
- id = this.context.getOpt("alias") || this.context.at.charCodeAt(0);
- header_tpl = this.context.getOpt("headerTpl");
- if (header_tpl && this.$el.children().length === 1) {
- this.$el.prepend(header_tpl);
- }
- return this.$el.attr({
- 'id': "at-view-" + id
- });
- };
-
- View.prototype.destroy = function() {
- return this.$el.remove();
- };
-
- View.prototype.bindEvent = function() {
- var $menu, lastCoordX, lastCoordY;
- $menu = this.$el.find('ul');
- lastCoordX = 0;
- lastCoordY = 0;
- return $menu.on('mousemove.atwho-view', 'li', (function(_this) {
- return function(e) {
- var $cur;
- if (lastCoordX === e.clientX && lastCoordY === e.clientY) {
- return;
- }
- lastCoordX = e.clientX;
- lastCoordY = e.clientY;
- $cur = $(e.currentTarget);
- if ($cur.hasClass('cur')) {
- return;
- }
- $menu.find('.cur').removeClass('cur');
- return $cur.addClass('cur');
- };
- })(this)).on('click.atwho-view', 'li', (function(_this) {
- return function(e) {
- $menu.find('.cur').removeClass('cur');
- $(e.currentTarget).addClass('cur');
- _this.choose(e);
- return e.preventDefault();
- };
- })(this));
- };
-
- View.prototype.visible = function() {
- return this.$el.is(":visible");
- };
-
- View.prototype.highlighted = function() {
- return this.$el.find(".cur").length > 0;
- };
-
- View.prototype.choose = function(e) {
- var $li, content;
- if (($li = this.$el.find(".cur")).length) {
- content = this.context.insertContentFor($li);
- this.context._stopDelayedCall();
- this.context.insert(this.context.callbacks("beforeInsert").call(this.context, content, $li, e), $li);
- this.context.trigger("inserted", [$li, e]);
- this.hide(e);
- }
- if (this.context.getOpt("hideWithoutSuffix")) {
- return this.stopShowing = true;
- }
- };
-
- View.prototype.reposition = function(rect) {
- var _window, offset, overflowOffset, ref;
- _window = this.context.app.iframeAsRoot ? this.context.app.window : window;
- if (rect.bottom + this.$el.height() - $(_window).scrollTop() > $(_window).height()) {
- rect.bottom = rect.top - this.$el.height();
- }
- if (rect.left > (overflowOffset = $(_window).width() - this.$el.width() - 5)) {
- rect.left = overflowOffset;
- }
- offset = {
- left: rect.left,
- top: rect.bottom
- };
- if ((ref = this.context.callbacks("beforeReposition")) != null) {
- ref.call(this.context, offset);
- }
- this.$el.offset(offset);
- return this.context.trigger("reposition", [offset]);
- };
-
- View.prototype.next = function() {
- var cur, next, nextEl, offset;
- cur = this.$el.find('.cur').removeClass('cur');
- next = cur.next();
- if (!next.length) {
- next = this.$el.find('li:first');
- }
- next.addClass('cur');
- nextEl = next[0];
- offset = nextEl.offsetTop + nextEl.offsetHeight + (nextEl.nextSibling ? nextEl.nextSibling.offsetHeight : 0);
- return this.scrollTop(Math.max(0, offset - this.$el.height()));
- };
-
- View.prototype.prev = function() {
- var cur, offset, prev, prevEl;
- cur = this.$el.find('.cur').removeClass('cur');
- prev = cur.prev();
- if (!prev.length) {
- prev = this.$el.find('li:last');
- }
- prev.addClass('cur');
- prevEl = prev[0];
- offset = prevEl.offsetTop + prevEl.offsetHeight + (prevEl.nextSibling ? prevEl.nextSibling.offsetHeight : 0);
- return this.scrollTop(Math.max(0, offset - this.$el.height()));
- };
-
- View.prototype.scrollTop = function(scrollTop) {
- var scrollDuration;
- scrollDuration = this.context.getOpt('scrollDuration');
- if (scrollDuration) {
- return this.$elUl.animate({
- scrollTop: scrollTop
- }, scrollDuration);
- } else {
- return this.$elUl.scrollTop(scrollTop);
- }
- };
-
- View.prototype.show = function() {
- var rect;
- if (this.stopShowing) {
- this.stopShowing = false;
- return;
- }
- if (!this.visible()) {
- this.$el.show();
- this.$el.scrollTop(0);
- this.context.trigger('shown');
- }
- if (rect = this.context.rect()) {
- return this.reposition(rect);
- }
- };
-
- View.prototype.hide = function(e, time) {
- var callback;
- if (!this.visible()) {
- return;
- }
- if (isNaN(time)) {
- this.$el.hide();
- return this.context.trigger('hidden', [e]);
- } else {
- callback = (function(_this) {
- return function() {
- return _this.hide();
- };
- })(this);
- clearTimeout(this.timeoutID);
- return this.timeoutID = setTimeout(callback, time);
- }
- };
-
- View.prototype.render = function(list) {
- var $li, $ul, i, item, len, li, tpl;
- if (!($.isArray(list) && list.length > 0)) {
- this.hide();
- return;
- }
- this.$el.find('ul').empty();
- $ul = this.$el.find('ul');
- tpl = this.context.getOpt('displayTpl');
- for (i = 0, len = list.length; i < len; i++) {
- item = list[i];
- item = $.extend({}, item, {
- 'atwho-at': this.context.at
- });
- li = this.context.callbacks("tplEval").call(this.context, tpl, item, "onDisplay");
- $li = $(this.context.callbacks("highlighter").call(this.context, li, this.context.query.text));
- $li.data("item-data", item);
- $ul.append($li);
- }
- this.show();
- if (this.context.getOpt('highlightFirst')) {
- return $ul.find("li:first").addClass("cur");
- }
- };
-
- return View;
-
-})();
-
-var Api;
-
-Api = {
- load: function(at, data) {
- var c;
- if (c = this.controller(at)) {
- return c.model.load(data);
- }
- },
- isSelecting: function() {
- var ref;
- return !!((ref = this.controller()) != null ? ref.view.visible() : void 0);
- },
- hide: function() {
- var ref;
- return (ref = this.controller()) != null ? ref.view.hide() : void 0;
- },
- reposition: function() {
- var c;
- if (c = this.controller()) {
- return c.view.reposition(c.rect());
- }
- },
- setIframe: function(iframe, asRoot) {
- this.setupRootElement(iframe, asRoot);
- return null;
- },
- run: function() {
- return this.dispatch();
- },
- destroy: function() {
- this.shutdown();
- return this.$inputor.data('atwho', null);
- }
-};
-
-$.fn.atwho = function(method) {
- var _args, result;
- _args = arguments;
- result = null;
- this.filter('textarea, input, [contenteditable=""], [contenteditable=true]').each(function() {
- var $this, app;
- if (!(app = ($this = $(this)).data("atwho"))) {
- $this.data('atwho', (app = new App(this)));
- }
- if (typeof method === 'object' || !method) {
- return app.reg(method.at, method);
- } else if (Api[method] && app) {
- return result = Api[method].apply(app, Array.prototype.slice.call(_args, 1));
- } else {
- return $.error("Method " + method + " does not exist on jQuery.atwho");
- }
- });
- if (result != null) {
- return result;
- } else {
- return this;
- }
-};
-
-$.fn.atwho["default"] = {
- at: void 0,
- alias: void 0,
- data: null,
- displayTpl: "<li>${name}</li>",
- insertTpl: "${atwho-at}${name}",
- headerTpl: null,
- callbacks: DEFAULT_CALLBACKS,
- searchKey: "name",
- suffix: void 0,
- hideWithoutSuffix: false,
- startWithSpace: true,
- acceptSpaceBar: false,
- highlightFirst: true,
- limit: 5,
- maxLen: 20,
- minLen: 0,
- displayTimeout: 300,
- delay: null,
- spaceSelectsMatch: false,
- tabSelectsMatch: true,
- editableAtwhoQueryAttrs: {},
- scrollDuration: 150,
- suspendOnComposing: true,
- lookUpOnClick: true
-};
-
-$.fn.atwho.debug = false;
-
-}));
diff --git a/vendor/assets/javascripts/jquery.caret.js b/vendor/assets/javascripts/jquery.caret.js
deleted file mode 100644
index 811ec63ee47..00000000000
--- a/vendor/assets/javascripts/jquery.caret.js
+++ /dev/null
@@ -1,436 +0,0 @@
-(function (root, factory) {
- if (typeof define === 'function' && define.amd) {
- // AMD. Register as an anonymous module.
- define(["jquery"], function ($) {
- return (root.returnExportsGlobal = factory($));
- });
- } else if (typeof exports === 'object') {
- // Node. Does not work with strict CommonJS, but
- // only CommonJS-like enviroments that support module.exports,
- // like Node.
- module.exports = factory(require("jquery"));
- } else {
- factory(jQuery);
- }
-}(this, function ($) {
-
-/*
- Implement Github like autocomplete mentions
- http://ichord.github.com/At.js
-
- Copyright (c) 2013 chord.luo@gmail.com
- Licensed under the MIT license.
-*/
-
-/*
-本插件操作 textarea 或者 input 内的插入符
-只实现了获得插入符在文本框中的位置,我设置
-插入符的位置.
-*/
-
-"use strict";
-var EditableCaret, InputCaret, Mirror, Utils, discoveryIframeOf, methods, oDocument, oFrame, oWindow, pluginName, setContextBy;
-
-pluginName = 'caret';
-
-EditableCaret = (function() {
- function EditableCaret($inputor) {
- this.$inputor = $inputor;
- this.domInputor = this.$inputor[0];
- }
-
- EditableCaret.prototype.setPos = function(pos) {
- var fn, found, offset, sel;
- if (sel = oWindow.getSelection()) {
- offset = 0;
- found = false;
- (fn = function(pos, parent) {
- var node, range, _i, _len, _ref, _results;
- _ref = parent.childNodes;
- _results = [];
- for (_i = 0, _len = _ref.length; _i < _len; _i++) {
- node = _ref[_i];
- if (found) {
- break;
- }
- if (node.nodeType === 3) {
- if (offset + node.length >= pos) {
- found = true;
- range = oDocument.createRange();
- range.setStart(node, pos - offset);
- sel.removeAllRanges();
- sel.addRange(range);
- break;
- } else {
- _results.push(offset += node.length);
- }
- } else {
- _results.push(fn(pos, node));
- }
- }
- return _results;
- })(pos, this.domInputor);
- }
- return this.domInputor;
- };
-
- EditableCaret.prototype.getIEPosition = function() {
- return this.getPosition();
- };
-
- EditableCaret.prototype.getPosition = function() {
- var inputor_offset, offset;
- offset = this.getOffset();
- inputor_offset = this.$inputor.offset();
- offset.left -= inputor_offset.left;
- offset.top -= inputor_offset.top;
- return offset;
- };
-
- EditableCaret.prototype.getOldIEPos = function() {
- var preCaretTextRange, textRange;
- textRange = oDocument.selection.createRange();
- preCaretTextRange = oDocument.body.createTextRange();
- preCaretTextRange.moveToElementText(this.domInputor);
- preCaretTextRange.setEndPoint("EndToEnd", textRange);
- return preCaretTextRange.text.length;
- };
-
- EditableCaret.prototype.getPos = function() {
- var clonedRange, pos, range;
- if (range = this.range()) {
- clonedRange = range.cloneRange();
- clonedRange.selectNodeContents(this.domInputor);
- clonedRange.setEnd(range.endContainer, range.endOffset);
- pos = clonedRange.toString().length;
- clonedRange.detach();
- return pos;
- } else if (oDocument.selection) {
- return this.getOldIEPos();
- }
- };
-
- EditableCaret.prototype.getOldIEOffset = function() {
- var range, rect;
- range = oDocument.selection.createRange().duplicate();
- range.moveStart("character", -1);
- rect = range.getBoundingClientRect();
- return {
- height: rect.bottom - rect.top,
- left: rect.left,
- top: rect.top
- };
- };
-
- EditableCaret.prototype.getOffset = function(pos) {
- var clonedRange, offset, range, rect, shadowCaret;
- if (oWindow.getSelection && (range = this.range())) {
- if (range.endOffset - 1 > 0 && range.endContainer !== this.domInputor) {
- clonedRange = range.cloneRange();
- clonedRange.setStart(range.endContainer, range.endOffset - 1);
- clonedRange.setEnd(range.endContainer, range.endOffset);
- rect = clonedRange.getBoundingClientRect();
- offset = {
- height: rect.height,
- left: rect.left + rect.width,
- top: rect.top
- };
- clonedRange.detach();
- }
- if (!offset || (offset != null ? offset.height : void 0) === 0) {
- clonedRange = range.cloneRange();
- shadowCaret = $(oDocument.createTextNode("|"));
- clonedRange.insertNode(shadowCaret[0]);
- clonedRange.selectNode(shadowCaret[0]);
- rect = clonedRange.getBoundingClientRect();
- offset = {
- height: rect.height,
- left: rect.left,
- top: rect.top
- };
- shadowCaret.remove();
- clonedRange.detach();
- }
- } else if (oDocument.selection) {
- offset = this.getOldIEOffset();
- }
- if (offset) {
- offset.top += $(oWindow).scrollTop();
- offset.left += $(oWindow).scrollLeft();
- }
- return offset;
- };
-
- EditableCaret.prototype.range = function() {
- var sel;
- if (!oWindow.getSelection) {
- return;
- }
- sel = oWindow.getSelection();
- if (sel.rangeCount > 0) {
- return sel.getRangeAt(0);
- } else {
- return null;
- }
- };
-
- return EditableCaret;
-
-})();
-
-InputCaret = (function() {
- function InputCaret($inputor) {
- this.$inputor = $inputor;
- this.domInputor = this.$inputor[0];
- }
-
- InputCaret.prototype.getIEPos = function() {
- var endRange, inputor, len, normalizedValue, pos, range, textInputRange;
- inputor = this.domInputor;
- range = oDocument.selection.createRange();
- pos = 0;
- if (range && range.parentElement() === inputor) {
- normalizedValue = inputor.value.replace(/\r\n/g, "\n");
- len = normalizedValue.length;
- textInputRange = inputor.createTextRange();
- textInputRange.moveToBookmark(range.getBookmark());
- endRange = inputor.createTextRange();
- endRange.collapse(false);
- if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
- pos = len;
- } else {
- pos = -textInputRange.moveStart("character", -len);
- }
- }
- return pos;
- };
-
- InputCaret.prototype.getPos = function() {
- if (oDocument.selection) {
- return this.getIEPos();
- } else {
- return this.domInputor.selectionStart;
- }
- };
-
- InputCaret.prototype.setPos = function(pos) {
- var inputor, range;
- inputor = this.domInputor;
- if (oDocument.selection) {
- range = inputor.createTextRange();
- range.move("character", pos);
- range.select();
- } else if (inputor.setSelectionRange) {
- inputor.setSelectionRange(pos, pos);
- }
- return inputor;
- };
-
- InputCaret.prototype.getIEOffset = function(pos) {
- var h, textRange, x, y;
- textRange = this.domInputor.createTextRange();
- pos || (pos = this.getPos());
- textRange.move('character', pos);
- x = textRange.boundingLeft;
- y = textRange.boundingTop;
- h = textRange.boundingHeight;
- return {
- left: x,
- top: y,
- height: h
- };
- };
-
- InputCaret.prototype.getOffset = function(pos) {
- var $inputor, offset, position;
- $inputor = this.$inputor;
- if (oDocument.selection) {
- offset = this.getIEOffset(pos);
- offset.top += $(oWindow).scrollTop() + $inputor.scrollTop();
- offset.left += $(oWindow).scrollLeft() + $inputor.scrollLeft();
- return offset;
- } else {
- offset = $inputor.offset();
- position = this.getPosition(pos);
- return offset = {
- left: offset.left + position.left - $inputor.scrollLeft(),
- top: offset.top + position.top - $inputor.scrollTop(),
- height: position.height
- };
- }
- };
-
- InputCaret.prototype.getPosition = function(pos) {
- var $inputor, at_rect, end_range, format, html, mirror, start_range;
- $inputor = this.$inputor;
- format = function(value) {
- value = value.replace(/<|>|`|"|&/g, '?').replace(/\r\n|\r|\n/g, "<br/>");
- if (/firefox/i.test(navigator.userAgent)) {
- value = value.replace(/\s/g, '&nbsp;');
- }
- return value;
- };
- if (pos === void 0) {
- pos = this.getPos();
- }
- start_range = $inputor.val().slice(0, pos);
- end_range = $inputor.val().slice(pos);
- html = "<span style='position: relative; display: inline;'>" + format(start_range) + "</span>";
- html += "<span id='caret' style='position: relative; display: inline;'>|</span>";
- html += "<span style='position: relative; display: inline;'>" + format(end_range) + "</span>";
- mirror = new Mirror($inputor);
- return at_rect = mirror.create(html).rect();
- };
-
- InputCaret.prototype.getIEPosition = function(pos) {
- var h, inputorOffset, offset, x, y;
- offset = this.getIEOffset(pos);
- inputorOffset = this.$inputor.offset();
- x = offset.left - inputorOffset.left;
- y = offset.top - inputorOffset.top;
- h = offset.height;
- return {
- left: x,
- top: y,
- height: h
- };
- };
-
- return InputCaret;
-
-})();
-
-Mirror = (function() {
- Mirror.prototype.css_attr = ["borderBottomWidth", "borderLeftWidth", "borderRightWidth", "borderTopStyle", "borderRightStyle", "borderBottomStyle", "borderLeftStyle", "borderTopWidth", "boxSizing", "fontFamily", "fontSize", "fontWeight", "height", "letterSpacing", "lineHeight", "marginBottom", "marginLeft", "marginRight", "marginTop", "outlineWidth", "overflow", "overflowX", "overflowY", "paddingBottom", "paddingLeft", "paddingRight", "paddingTop", "textAlign", "textOverflow", "textTransform", "whiteSpace", "wordBreak", "wordWrap"];
-
- function Mirror($inputor) {
- this.$inputor = $inputor;
- }
-
- Mirror.prototype.mirrorCss = function() {
- var css,
- _this = this;
- css = {
- position: 'absolute',
- left: -9999,
- top: 0,
- zIndex: -20000
- };
- if (this.$inputor.prop('tagName') === 'TEXTAREA') {
- this.css_attr.push('width');
- }
- $.each(this.css_attr, function(i, p) {
- return css[p] = _this.$inputor.css(p);
- });
- return css;
- };
-
- Mirror.prototype.create = function(html) {
- this.$mirror = $('<div></div>');
- this.$mirror.css(this.mirrorCss());
- this.$mirror.html(html);
- this.$inputor.after(this.$mirror);
- return this;
- };
-
- Mirror.prototype.rect = function() {
- var $flag, pos, rect;
- $flag = this.$mirror.find("#caret");
- pos = $flag.position();
- rect = {
- left: pos.left,
- top: pos.top,
- height: $flag.height()
- };
- this.$mirror.remove();
- return rect;
- };
-
- return Mirror;
-
-})();
-
-Utils = {
- contentEditable: function($inputor) {
- return !!($inputor[0].contentEditable && $inputor[0].contentEditable === 'true');
- }
-};
-
-methods = {
- pos: function(pos) {
- if (pos || pos === 0) {
- return this.setPos(pos);
- } else {
- return this.getPos();
- }
- },
- position: function(pos) {
- if (oDocument.selection) {
- return this.getIEPosition(pos);
- } else {
- return this.getPosition(pos);
- }
- },
- offset: function(pos) {
- var offset;
- offset = this.getOffset(pos);
- return offset;
- }
-};
-
-oDocument = null;
-
-oWindow = null;
-
-oFrame = null;
-
-setContextBy = function(settings) {
- var iframe;
- if (iframe = settings != null ? settings.iframe : void 0) {
- oFrame = iframe;
- oWindow = iframe.contentWindow;
- return oDocument = iframe.contentDocument || oWindow.document;
- } else {
- oFrame = void 0;
- oWindow = window;
- return oDocument = document;
- }
-};
-
-discoveryIframeOf = function($dom) {
- var error;
- oDocument = $dom[0].ownerDocument;
- oWindow = oDocument.defaultView || oDocument.parentWindow;
- try {
- return oFrame = oWindow.frameElement;
- } catch (_error) {
- error = _error;
- }
-};
-
-$.fn.caret = function(method, value, settings) {
- var caret;
- if (methods[method]) {
- if ($.isPlainObject(value)) {
- setContextBy(value);
- value = void 0;
- } else {
- setContextBy(settings);
- }
- caret = Utils.contentEditable(this) ? new EditableCaret(this) : new InputCaret(this);
- return methods[method].apply(caret, [value]);
- } else {
- return $.error("Method " + method + " does not exist on jQuery.caret");
- }
-};
-
-$.fn.caret.EditableCaret = EditableCaret;
-
-$.fn.caret.InputCaret = InputCaret;
-
-$.fn.caret.Utils = Utils;
-
-$.fn.caret.apis = methods;
-
-
-}));
diff --git a/vendor/licenses.csv b/vendor/licenses.csv
index ed79ec5bac3..de6e32cb998 100644
--- a/vendor/licenses.csv
+++ b/vendor/licenses.csv
@@ -606,7 +606,6 @@ iterall,1.2.2,MIT
jed,1.1.1,MIT
jira-ruby,1.4.1,MIT
jquery,3.3.1,MIT
-jquery-atwho-rails,1.3.2,MIT
jquery-ujs,1.2.2,MIT
jquery.waitforimages,2.2.0,MIT
js-beautify,1.8.8,MIT
diff --git a/vendor/project_templates/android.tar.gz b/vendor/project_templates/android.tar.gz
new file mode 100644
index 00000000000..277aedaa1ca
--- /dev/null
+++ b/vendor/project_templates/android.tar.gz
Binary files differ
diff --git a/yarn.lock b/yarn.lock
index 1ac6b322469..7123650b003 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -663,10 +663,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.54.0.tgz#00320e845efd46716042cde0c348b990d4908daf"
integrity sha512-DR17iy8TM5IbXEacqiDP0p8SuC/J8EL+98xbfVz5BKvRsPHpeZJQNlBF/petIV5d+KWM5A9v3GZTY7uMU7z/JQ==
-"@gitlab/ui@^2.2.2":
- version "2.2.2"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-2.2.2.tgz#de8f436d6b52c168c4752b221a88cf7665fe0134"
- integrity sha512-xVY8lGfDA3D2EtyfZVpJVeNUXLbpn/xJqNF4fleqHJfqrwt+IcVlsQ7yEs/LBukmIw6tXPliD4Mm9uefnQhkXA==
+"@gitlab/ui@^3.0.0":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-3.0.0.tgz#33ca2808dbd4395e69a366a219d1edc1f3dbccd5"
+ integrity sha512-pDEa2k6ln5GE/N2z0V7dNEeFtSTW0p9ipO2/N9q6QMxO7fhhOhpMC0QVbdIljKTbglspDWI5v6BcqUjzYri5Pg==
dependencies:
"@babel/standalone" "^7.0.0"
bootstrap-vue "^2.0.0-rc.11"
@@ -1378,6 +1378,11 @@ asynckit@^0.4.0:
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
+at.js@^1.5.4:
+ version "1.5.4"
+ resolved "https://registry.yarnpkg.com/at.js/-/at.js-1.5.4.tgz#8fc60cc80eadbe4874449b166a818e7ae1d784c1"
+ integrity sha512-G8mgUb/PqShPoH8AyjuxsTGvIr1o716BtQUKDM44C8qN2W615y7KGJ68MlTGamd0J0D/m28emUkzagaHTdrGZw==
+
atob@^2.1.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9"
@@ -6070,6 +6075,11 @@ jquery-ujs@1.2.2:
dependencies:
jquery ">=1.8.0"
+jquery.caret@^0.3.1:
+ version "0.3.1"
+ resolved "https://registry.yarnpkg.com/jquery.caret/-/jquery.caret-0.3.1.tgz#9c093318faf327eff322e826ca9f3241368bc7b8"
+ integrity sha1-nAkzGPrzJ+/zIugmyp8yQTaLx7g=
+
jquery.waitforimages@^2.2.0:
version "2.2.0"
resolved "https://registry.yarnpkg.com/jquery.waitforimages/-/jquery.waitforimages-2.2.0.tgz#63f23131055a1b060dc913e6d874bcc9b9e6b16b"
@@ -8086,16 +8096,16 @@ prepend-http@^2.0.0:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
-prettier@1.16.1:
- version "1.16.1"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.1.tgz#534c2c9d7853f8845e5e078384e71973bd74089f"
- integrity sha512-XXUITwIkGb3CPJ2hforHah/zTINRyie5006Jd2HKy2qz7snEJXl0KLfsJZW/wst9g6R2rFvqba3VpNYdu1hDcA==
-
prettier@1.16.3:
version "1.16.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.3.tgz#8c62168453badef702f34b45b6ee899574a6a65d"
integrity sha512-kn/GU6SMRYPxUakNXhpP0EedT/KmaPzr0H5lIsDogrykbaxOpOfAFfk5XA7DZrJyMAv1wlMV3CPcZruGXVVUZw==
+prettier@1.16.4:
+ version "1.16.4"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.16.4.tgz#73e37e73e018ad2db9c76742e2647e21790c9717"
+ integrity sha512-ZzWuos7TI5CKUeQAtFd6Zhm2s6EpAD/ZLApIhsF9pRvRtM1RFo61dM/4MSRUA0SuLugA/zgrZD8m0BaY46Og7g==
+
pretty-format@^24.0.0:
version "24.0.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-24.0.0.tgz#cb6599fd73ac088e37ed682f61291e4678f48591"