Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--.gitlab-ci.yml39
-rw-r--r--.gitlab/issue_templates/Add style proposal.md (renamed from .gitlab/issue_templates/Add style proposal)0
-rw-r--r--.gitlab/issue_templates/Feature proposal.md8
-rw-r--r--.gitlab/issue_templates/Security Release.md69
-rw-r--r--.gitlab/issue_templates/Security developer workflow.md2
-rw-r--r--.rubocop_todo.yml11
-rw-r--r--CHANGELOG.md18
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_PAGES_VERSION2
-rw-r--r--Gemfile12
-rw-r--r--Gemfile.lock29
-rw-r--r--PROCESS.md2
-rw-r--r--app/assets/javascripts/badges/components/badge_list_row.vue4
-rw-r--r--app/assets/javascripts/boards/components/board_card.vue2
-rw-r--r--app/assets/javascripts/boards/components/board_new_issue.vue2
-rw-r--r--app/assets/javascripts/boards/components/issue_card_inner.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/empty_state.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/footer.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/header.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/list.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/lists_dropdown.vue2
-rw-r--r--app/assets/javascripts/boards/components/modal/tabs.vue4
-rw-r--r--app/assets/javascripts/contextual_sidebar.js24
-rw-r--r--app/assets/javascripts/deploy_keys/components/key.vue2
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions.vue4
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions_dropdown.vue14
-rw-r--r--app/assets/javascripts/diffs/components/diff_content.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_discussions.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_gutter_content.vue2
-rw-r--r--app/assets/javascripts/diffs/components/diff_line_note_form.vue6
-rw-r--r--app/assets/javascripts/diffs/components/image_diff_overlay.vue4
-rw-r--r--app/assets/javascripts/diffs/components/inline_diff_comment_row.vue1
-rw-r--r--app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue1
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue6
-rw-r--r--app/assets/javascripts/environments/components/environment_actions.vue2
-rw-r--r--app/assets/javascripts/environments/components/environments_app.vue6
-rw-r--r--app/assets/javascripts/environments/components/environments_table.vue6
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_bundle.js8
-rw-r--r--app/assets/javascripts/error_tracking/components/error_tracking_list.vue118
-rw-r--r--app/assets/javascripts/error_tracking/index.js35
-rw-r--r--app/assets/javascripts/error_tracking/services/index.js7
-rw-r--r--app/assets/javascripts/error_tracking/store/actions.js31
-rw-r--r--app/assets/javascripts/error_tracking/store/index.js19
-rw-r--r--app/assets/javascripts/error_tracking/store/mutation_types.js3
-rw-r--r--app/assets/javascripts/error_tracking/store/mutations.js14
-rw-r--r--app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue4
-rw-r--r--app/assets/javascripts/fly_out_nav.js5
-rw-r--r--app/assets/javascripts/ide/components/activity_bar.vue6
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/message_field.vue4
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue4
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue4
-rw-r--r--app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue2
-rw-r--r--app/assets/javascripts/ide/components/editor_mode_dropdown.vue4
-rw-r--r--app/assets/javascripts/ide/components/file_finder/index.vue6
-rw-r--r--app/assets/javascripts/ide/components/file_templates/dropdown.vue2
-rw-r--r--app/assets/javascripts/ide/components/ide_status_bar.vue2
-rw-r--r--app/assets/javascripts/ide/components/ide_tree.vue4
-rw-r--r--app/assets/javascripts/ide/components/jobs/detail.vue2
-rw-r--r--app/assets/javascripts/ide/components/merge_requests/list.vue4
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/index.vue10
-rw-r--r--app/assets/javascripts/ide/components/new_dropdown/modal.vue2
-rw-r--r--app/assets/javascripts/ide/components/panes/right.vue2
-rw-r--r--app/assets/javascripts/ide/components/repo_editor.vue4
-rw-r--r--app/assets/javascripts/ide/components/repo_tab.vue4
-rw-r--r--app/assets/javascripts/ide/components/resizable_panel.vue4
-rw-r--r--app/assets/javascripts/ide/components/shared/tokened_input.vue4
-rw-r--r--app/assets/javascripts/ide/index.js9
-rw-r--r--app/assets/javascripts/jobs/components/job_app.vue2
-rw-r--r--app/assets/javascripts/jobs/components/sidebar.vue70
-rw-r--r--app/assets/javascripts/jobs/components/stages_dropdown.vue2
-rw-r--r--app/assets/javascripts/jobs/store/getters.js16
-rw-r--r--app/assets/javascripts/layout_nav.js65
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js19
-rw-r--r--app/assets/javascripts/locale/sprintf.js4
-rw-r--r--app/assets/javascripts/monitoring/components/graph.vue6
-rw-r--r--app/assets/javascripts/notes/components/comment_form.vue14
-rw-r--r--app/assets/javascripts/notes/components/discussion_filter.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_awards_list.vue2
-rw-r--r--app/assets/javascripts/notes/components/note_form.vue14
-rw-r--r--app/assets/javascripts/notes/components/noteable_discussion.vue2
-rw-r--r--app/assets/javascripts/pages/projects/error_tracking/index.js5
-rw-r--r--app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue8
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue45
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines_actions.vue2
-rw-r--r--app/assets/javascripts/pipelines/mixins/graph_component_mixin.js44
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue4
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue2
-rw-r--r--app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue4
-rw-r--r--app/assets/javascripts/registry/components/table_registry.vue2
-rw-r--r--app/assets/javascripts/reports/components/modal_open_name.vue2
-rw-r--r--app/assets/javascripts/reports/components/test_issue_body.vue2
-rw-r--r--app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue2
-rw-r--r--app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue4
-rw-r--r--app/assets/javascripts/terminal/terminal.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue25
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue34
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/bar_chart.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/deprecated_modal.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/file_row.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/gl_modal.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/header_ci_component.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/field.vue6
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue9
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/navigation_tabs.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/notes/system_note.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/recaptcha_modal.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue2
-rw-r--r--app/assets/javascripts/vue_shared/components/table_pagination.vue2
-rw-r--r--app/assets/stylesheets/bootstrap_migration.scss2
-rw-r--r--app/assets/stylesheets/framework/animations.scss10
-rw-r--r--app/assets/stylesheets/framework/awards.scss2
-rw-r--r--app/assets/stylesheets/framework/buttons.scss2
-rw-r--r--app/assets/stylesheets/framework/dropdowns.scss6
-rw-r--r--app/assets/stylesheets/framework/forms.scss2
-rw-r--r--app/assets/stylesheets/framework/gitlab_theme.scss40
-rw-r--r--app/assets/stylesheets/framework/header.scss4
-rw-r--r--app/assets/stylesheets/framework/icons.scss4
-rw-r--r--app/assets/stylesheets/framework/markdown_area.scss2
-rw-r--r--app/assets/stylesheets/framework/stacked_progress_bar.scss8
-rw-r--r--app/assets/stylesheets/framework/typography.scss2
-rw-r--r--app/assets/stylesheets/framework/variables.scss24
-rw-r--r--app/assets/stylesheets/framework/variables_overrides.scss4
-rw-r--r--app/assets/stylesheets/page_bundles/ide.scss28
-rw-r--r--app/assets/stylesheets/pages/boards.scss8
-rw-r--r--app/assets/stylesheets/pages/builds.scss20
-rw-r--r--app/assets/stylesheets/pages/environments.scss12
-rw-r--r--app/assets/stylesheets/pages/graph.scss6
-rw-r--r--app/assets/stylesheets/pages/groups.scss2
-rw-r--r--app/assets/stylesheets/pages/issuable.scss2
-rw-r--r--app/assets/stylesheets/pages/labels.scss10
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss6
-rw-r--r--app/assets/stylesheets/pages/milestone.scss2
-rw-r--r--app/assets/stylesheets/pages/note_form.scss2
-rw-r--r--app/assets/stylesheets/pages/notes.scss2
-rw-r--r--app/assets/stylesheets/pages/pipelines.scss1
-rw-r--r--app/assets/stylesheets/pages/profiles/preferences.scss4
-rw-r--r--app/assets/stylesheets/pages/projects.scss10
-rw-r--r--app/assets/stylesheets/pages/reports.scss2
-rw-r--r--app/assets/stylesheets/pages/settings.scss2
-rw-r--r--app/controllers/application_controller.rb8
-rw-r--r--app/controllers/dashboard/projects_controller.rb3
-rw-r--r--app/controllers/explore/projects_controller.rb3
-rw-r--r--app/controllers/groups/children_controller.rb2
-rw-r--r--app/controllers/import/base_controller.rb2
-rw-r--r--app/controllers/import/bitbucket_server_controller.rb4
-rw-r--r--app/controllers/import/github_controller.rb33
-rw-r--r--app/controllers/projects/blob_controller.rb2
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/controllers/projects/jobs_controller.rb14
-rw-r--r--app/controllers/projects/pipelines_settings_controller.rb2
-rw-r--r--app/helpers/environments_helper.rb9
-rw-r--r--app/helpers/projects/error_tracking_helper.rb15
-rw-r--r--app/helpers/projects_helper.rb2
-rw-r--r--app/helpers/release_blog_post_helper.rb7
-rw-r--r--app/models/clusters/applications/ingress.rb2
-rw-r--r--app/models/concerns/manual_inverse_association.rb4
-rw-r--r--app/models/external_issue.rb8
-rw-r--r--app/models/merge_request.rb22
-rw-r--r--app/models/project.rb49
-rw-r--r--app/models/project_import_data.rb4
-rw-r--r--app/models/remote_mirror.rb12
-rw-r--r--app/models/suggestion.rb12
-rw-r--r--app/serializers/base_serializer.rb10
-rw-r--r--app/serializers/merge_request_diff_entity.rb8
-rw-r--r--app/services/import/base_service.rb35
-rw-r--r--app/services/import/github_service.rb48
-rw-r--r--app/services/labels/create_service.rb2
-rw-r--r--app/services/labels/update_service.rb2
-rw-r--r--app/services/lfs/locks_finder_service.rb2
-rw-r--r--app/services/projects/protect_default_branch_service.rb67
-rw-r--r--app/services/suggestions/apply_service.rb34
-rw-r--r--app/services/users/update_service.rb2
-rw-r--r--app/uploaders/records_uploads.rb20
-rw-r--r--app/views/clusters/clusters/_integration_form.html.haml20
-rw-r--r--app/views/dashboard/_projects_head.html.haml2
-rw-r--r--app/views/layouts/header/_help_dropdown.html.haml6
-rw-r--r--app/views/layouts/nav/sidebar/_project.html.haml12
-rw-r--r--app/views/profiles/accounts/show.html.haml34
-rw-r--r--app/views/projects/_export.html.haml3
-rw-r--r--app/views/projects/_home_panel.html.haml2
-rw-r--r--app/views/projects/artifacts/_tree_file.html.haml2
-rw-r--r--app/views/projects/branches/_branch.html.haml2
-rw-r--r--app/views/projects/branches/_panel.html.haml2
-rw-r--r--app/views/projects/branches/index.html.haml2
-rw-r--r--app/views/projects/environments/folder.html.haml5
-rw-r--r--app/views/projects/error_tracking/index.html.haml2
-rw-r--r--app/views/projects/issues/_nav_btns.html.haml2
-rw-r--r--app/views/projects/jobs/index.html.haml4
-rw-r--r--app/views/projects/project_members/_groups.html.haml3
-rw-r--r--app/views/projects/project_members/_new_project_group.html.haml5
-rw-r--r--app/views/projects/project_members/_new_project_member.html.haml15
-rw-r--r--app/views/projects/project_members/_team.html.haml7
-rw-r--r--app/views/projects/project_members/import.html.haml12
-rw-r--r--app/views/projects/project_members/index.html.haml23
-rw-r--r--app/views/projects/settings/operations/_error_tracking.html.haml2
-rw-r--r--app/views/search/_category.html.haml26
-rw-r--r--app/views/search/_filter.html.haml20
-rw-r--r--app/views/search/_form.html.haml6
-rw-r--r--app/views/search/_results.html.haml6
-rw-r--r--app/views/search/results/_empty.html.haml2
-rw-r--r--app/views/search/results/_issue.html.haml2
-rw-r--r--app/views/search/results/_merge_request.html.haml4
-rw-r--r--app/views/search/results/_note.html.haml8
-rw-r--r--app/views/search/results/_snippet_blob.html.haml4
-rw-r--r--app/views/search/results/_snippet_title.html.haml2
-rw-r--r--app/views/search/show.html.haml2
-rw-r--r--app/views/shared/_personal_access_tokens_created_container.html.haml2
-rw-r--r--app/views/shared/_personal_access_tokens_form.html.haml4
-rw-r--r--app/views/shared/_personal_access_tokens_table.html.haml2
-rw-r--r--app/views/shared/empty_states/_issues.html.haml2
-rw-r--r--app/views/shared/issuable/_board_create_list_dropdown.html.haml2
-rw-r--r--app/views/shared/tokens/_scopes_form.html.haml2
-rw-r--r--app/workers/git_garbage_collect_worker.rb1
-rw-r--r--app/workers/object_pool/join_worker.rb11
-rw-r--r--app/workers/remote_mirror_notification_worker.rb3
-rwxr-xr-xbin/changelog10
-rw-r--r--changelogs/unreleased/25341-add-what-s-new-menu-item-in-top-navigation.yml5
-rw-r--r--changelogs/unreleased/37990-task-list-bracket.yml5
-rw-r--r--changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml5
-rw-r--r--changelogs/unreleased/52278-squash-checkbox-fix.yml5
-rw-r--r--changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml5
-rw-r--r--changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml5
-rw-r--r--changelogs/unreleased/55884-adjust-emoji-and-cancel-buttons-height-in-user-status-modal-when-emoji-is-changed.yml5
-rw-r--r--changelogs/unreleased/55945-suggested-change-highlight.yml5
-rw-r--r--changelogs/unreleased/55945-suggested-change-preview-highlight.yml5
-rw-r--r--changelogs/unreleased/56172-docs-fix-add-include-to-ci-param-list.yml5
-rw-r--r--changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml6
-rw-r--r--changelogs/unreleased/8688-recursive-pipelines-ce-backport.yml5
-rw-r--r--changelogs/unreleased/ac-pages-subgroups.yml5
-rw-r--r--changelogs/unreleased/actioncontroller-parameters-deprecations.yml5
-rw-r--r--changelogs/unreleased/add-badge-count-to-projects-and-groups.yml5
-rw-r--r--changelogs/unreleased/an-gilab-process-name.yml5
-rw-r--r--changelogs/unreleased/an-opentracing-factory.yml5
-rw-r--r--changelogs/unreleased/api-wiki-dot-slug.yml5
-rw-r--r--changelogs/unreleased/auto-devops-custom-domains.yml5
-rw-r--r--changelogs/unreleased/auto-devops-kubectl-1-11-6.yml5
-rw-r--r--changelogs/unreleased/bump-ingress-chart-112.yml5
-rw-r--r--changelogs/unreleased/deprecated-force-reload.yml6
-rw-r--r--changelogs/unreleased/error_tracking_feature_flag_fe.yml5
-rw-r--r--changelogs/unreleased/features-document-graphicsmagick-source-installation.yml5
-rw-r--r--changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml5
-rw-r--r--changelogs/unreleased/gitaly-update-1-13-0.yml5
-rw-r--r--changelogs/unreleased/gt-externalize-app-views-projects-project_members.yml5
-rw-r--r--changelogs/unreleased/gt-rename-gray-theme-color-variables.yml5
-rw-r--r--changelogs/unreleased/homepage-proj-descr-cutoff.yml5
-rw-r--r--changelogs/unreleased/jivl-update-placeholder-sentry-config.yml5
-rw-r--r--changelogs/unreleased/move-job-cancel-btn.yml5
-rw-r--r--changelogs/unreleased/osw-fix-quick-suggestion-application-being-reverted.yml5
-rw-r--r--changelogs/unreleased/remove-cancel-all-button-in-job-list-view.yml5
-rw-r--r--changelogs/unreleased/security-2770-verify-bundle-import-files.yml5
-rw-r--r--changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml5
-rw-r--r--changelogs/unreleased/sh-fix-bitbucket-server-error-handling.yml5
-rw-r--r--changelogs/unreleased/sh-fix-real-size-warnings.yml5
-rw-r--r--changelogs/unreleased/sh-fix-request-profiles-html.yml5
-rw-r--r--changelogs/unreleased/shared_with_group_path.yml5
-rw-r--r--changelogs/unreleased/suggestion-dashes.yml5
-rw-r--r--changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml5
-rw-r--r--changelogs/unreleased/update-gitlab-styles.yml5
-rw-r--r--changelogs/unreleased/update-sidekiq-cron.yml6
-rw-r--r--changelogs/unreleased/winh-add-list-dropdown-height.yml5
-rw-r--r--changelogs/unreleased/zj-feature-gate-set-project-path.yml5
-rw-r--r--config/initializers/sentry.rb2
-rw-r--r--config/initializers/tracing.rb13
-rw-r--r--config/jsdocs.config.js14
-rw-r--r--config/routes/project.rb2
-rw-r--r--danger/commit_messages/Dangerfile10
-rw-r--r--db/fixtures/development/10_merge_requests.rb3
-rw-r--r--db/migrate/20190115054216_add_error_notification_sent_to_remote_mirrors.rb11
-rw-r--r--db/schema.rb3
-rw-r--r--doc/administration/gitaly/index.md2
-rw-r--r--doc/api/applications.md47
-rw-r--r--doc/api/avatar.md38
-rw-r--r--doc/api/award_emoji.md197
-rw-r--r--doc/api/branches.md209
-rw-r--r--doc/api/features.md5
-rw-r--r--doc/api/groups.md2
-rw-r--r--doc/api/import.md33
-rw-r--r--doc/api/oauth2.md202
-rw-r--r--doc/api/projects.md2
-rw-r--r--doc/ci/yaml/README.md555
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/contributing/issue_workflow.md16
-rw-r--r--doc/development/documentation/styleguide.md50
-rw-r--r--doc/development/fe_guide/index.md4
-rw-r--r--doc/development/import_export.md352
-rw-r--r--doc/development/testing_guide/flaky_tests.md12
-rw-r--r--doc/install/installation.md516
-rw-r--r--doc/integration/oauth_provider.md9
-rw-r--r--doc/security/webhooks.md2
-rw-r--r--doc/topics/autodevops/index.md2
-rw-r--r--doc/update/11.7-to-11.8.md393
-rw-r--r--doc/user/award_emojis.md32
-rw-r--r--doc/user/group/subgroups/index.md8
-rw-r--r--doc/user/markdown.md2
-rw-r--r--doc/user/project/index.md4
-rw-r--r--doc/user/project/members/index.md2
-rw-r--r--doc/user/project/pages/getting_started_part_one.md10
-rw-r--r--doc/user/project/pages/introduction.md5
-rw-r--r--doc/user/project/pipelines/schedules.md2
-rw-r--r--doc/user/project/repository/branches/index.md16
-rw-r--r--doc/user/project/settings/import_export.md2
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/entities.rb5
-rw-r--r--lib/api/features.rb15
-rw-r--r--lib/api/import_github.rb46
-rw-r--r--lib/api/settings.rb2
-rw-r--r--lib/api/wikis.rb6
-rw-r--r--lib/bitbucket_server/client.rb14
-rw-r--r--lib/bitbucket_server/connection.rb17
-rw-r--r--lib/feature.rb38
-rw-r--r--lib/gitlab.rb50
-rw-r--r--lib/gitlab/access/branch_protection.rb42
-rw-r--r--lib/gitlab/auth/ldap/person.rb4
-rw-r--r--lib/gitlab/background_migration/backfill_project_repositories.rb14
-rw-r--r--lib/gitlab/ci/config/entry/job.rb14
-rw-r--r--lib/gitlab/ci/config/entry/policy.rb3
-rw-r--r--lib/gitlab/ci/config/entry/retry.rb3
-rw-r--r--lib/gitlab/ci/config/entry/variables.rb2
-rw-r--r--lib/gitlab/ci/cron_parser.rb7
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml12
-rw-r--r--lib/gitlab/config/entry/configurable.rb1
-rw-r--r--lib/gitlab/config/entry/factory.rb17
-rw-r--r--lib/gitlab/config/entry/node.rb4
-rw-r--r--lib/gitlab/config/entry/simplifiable.rb7
-rw-r--r--lib/gitlab/git/bundle_file.rb30
-rw-r--r--lib/gitlab/git/repository.rb5
-rw-r--r--lib/gitlab/gon_helper.rb15
-rw-r--r--lib/gitlab/middleware/read_only/controller.rb18
-rw-r--r--lib/gitlab/release_blog_post.rb40
-rw-r--r--lib/gitlab/sentry.rb8
-rw-r--r--lib/gitlab/tracing.rb17
-rw-r--r--lib/gitlab/tracing/factory.rb61
-rw-r--r--lib/gitlab/tracing/jaeger_factory.rb97
-rw-r--r--lib/gitlab/version_info.rb8
-rw-r--r--lib/system_check/base_check.rb8
-rw-r--r--locale/gitlab.pot179
-rw-r--r--package.json9
-rw-r--r--qa/Rakefile6
-rw-r--r--qa/qa.rb9
-rw-r--r--qa/qa/git/repository.rb13
-rw-r--r--qa/qa/page/profile/personal_access_tokens.rb38
-rw-r--r--qa/qa/page/project/branches/show.rb62
-rw-r--r--qa/qa/page/project/menu.rb31
-rw-r--r--qa/qa/page/project/settings/deploy_keys.rb33
-rw-r--r--qa/qa/page/project/sub_menus/common.rb23
-rw-r--r--qa/qa/page/project/sub_menus/repository.rb44
-rw-r--r--qa/qa/resource/deploy_key.rb6
-rw-r--r--qa/qa/resource/repository/push.rb2
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb92
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb10
-rw-r--r--qa/qa/tools/revoke_all_personal_access_tokens.rb44
-rw-r--r--rubocop/cop/inject_enterprise_edition_module.rb10
-rw-r--r--rubocop/spec_helpers.rb11
-rw-r--r--spec/controllers/application_controller_spec.rb2
-rw-r--r--spec/controllers/import/bitbucket_server_controller_spec.rb2
-rw-r--r--spec/controllers/omniauth_callbacks_controller_spec.rb4
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb19
-rw-r--r--spec/controllers/projects/jobs_controller_spec.rb44
-rw-r--r--spec/controllers/projects/pages_controller_spec.rb4
-rw-r--r--spec/factories/wiki_pages.rb2
-rw-r--r--spec/features/boards/boards_spec.rb6
-rw-r--r--spec/features/dashboard/help_spec.rb22
-rw-r--r--spec/features/dashboard/projects_spec.rb3
-rw-r--r--spec/features/dashboard/todos/todos_spec.rb2
-rw-r--r--spec/features/merge_request/user_merges_immediately_spec.rb2
-rw-r--r--spec/features/projects/artifacts/user_browses_artifacts_spec.rb4
-rw-r--r--spec/features/projects/jobs_spec.rb23
-rw-r--r--spec/features/projects/pipelines/pipeline_spec.rb7
-rw-r--r--spec/features/projects_spec.rb24
-rw-r--r--spec/fixtures/malicious.bundle1
-rw-r--r--spec/helpers/projects/error_tracking_helper_spec.rb58
-rw-r--r--spec/javascripts/diffs/components/compare_versions_dropdown_spec.js33
-rw-r--r--spec/javascripts/diffs/components/compare_versions_spec.js6
-rw-r--r--spec/javascripts/diffs/mock_data/merge_request_diffs.js52
-rw-r--r--spec/javascripts/error_tracking/components/error_tracking_list_spec.js100
-rw-r--r--spec/javascripts/error_tracking/store/mutation_spec.js36
-rw-r--r--spec/javascripts/jobs/components/sidebar_spec.js4
-rw-r--r--spec/javascripts/jobs/store/getters_spec.js24
-rw-r--r--spec/javascripts/lib/utils/common_utils_spec.js19
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js66
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js100
-rw-r--r--spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js6
-rw-r--r--spec/lib/bitbucket_server/client_spec.rb6
-rw-r--r--spec/lib/bitbucket_server/connection_spec.rb18
-rw-r--r--spec/lib/feature_spec.rb14
-rw-r--r--spec/lib/gitlab/access/branch_protection_spec.rb54
-rw-r--r--spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb18
-rw-r--r--spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/delete_diff_files_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb2
-rw-r--r--spec/lib/gitlab/ci/config/entry/global_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/entry/job_spec.rb3
-rw-r--r--spec/lib/gitlab/ci/config/entry/jobs_spec.rb6
-rw-r--r--spec/lib/gitlab/ci/config/entry/policy_spec.rb27
-rw-r--r--spec/lib/gitlab/config/entry/configurable_spec.rb10
-rw-r--r--spec/lib/gitlab/git/bundle_file_spec.rb26
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb17
-rw-r--r--spec/lib/gitlab/gon_helper_spec.rb9
-rw-r--r--spec/lib/gitlab/middleware/read_only_spec.rb34
-rw-r--r--spec/lib/gitlab/release_blog_post_spec.rb97
-rw-r--r--spec/lib/gitlab/tracing/factory_spec.rb43
-rw-r--r--spec/lib/gitlab/tracing/jaeger_factory_spec.rb45
-rw-r--r--spec/lib/gitlab_spec.rb77
-rw-r--r--spec/migrations/add_foreign_keys_to_todos_spec.rb6
-rw-r--r--spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb12
-rw-r--r--spec/models/clusters/applications/ingress_spec.rb6
-rw-r--r--spec/models/concerns/manual_inverse_association_spec.rb4
-rw-r--r--spec/models/gpg_key_spec.rb2
-rw-r--r--spec/models/project_import_data_spec.rb11
-rw-r--r--spec/models/project_spec.rb80
-rw-r--r--spec/models/remote_mirror_spec.rb19
-rw-r--r--spec/requests/api/features_spec.rb34
-rw-r--r--spec/requests/api/import_github_spec.rb56
-rw-r--r--spec/requests/api/issues_spec.rb318
-rw-r--r--spec/requests/api/projects_spec.rb2
-rw-r--r--spec/requests/api/settings_spec.rb4
-rw-r--r--spec/requests/api/wikis_spec.rb2
-rw-r--r--spec/rubocop/cop/inject_enterprise_edition_module_spec.rb35
-rw-r--r--spec/services/ci/process_pipeline_service_spec.rb2
-rw-r--r--spec/services/projects/protect_default_branch_service_spec.rb242
-rw-r--r--spec/services/suggestions/apply_service_spec.rb173
-rw-r--r--spec/sidekiq/cron/job_gem_dependency_spec.rb4
-rw-r--r--spec/spec_helper.rb12
-rw-r--r--spec/support/helpers/api_helpers.rb7
-rw-r--r--spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb4
-rw-r--r--spec/views/help/instance_configuration.html.haml_spec.rb6
-rw-r--r--spec/views/projects/commit/show.html.haml_spec.rb4
-rw-r--r--spec/workers/git_garbage_collect_worker_spec.rb11
-rw-r--r--spec/workers/remote_mirror_notification_worker_spec.rb39
-rw-r--r--spec/workers/repository_update_remote_mirror_worker_spec.rb7
-rw-r--r--yarn.lock191
442 files changed, 6528 insertions, 2302 deletions
diff --git a/.gitignore b/.gitignore
index 65f61e1fade..0696dd217af 100644
--- a/.gitignore
+++ b/.gitignore
@@ -80,3 +80,4 @@ eslint-report.html
package-lock.json
/junit_*.xml
/coverage-frontend/
+jsdoc/
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 45de5ce61c6..68dea56a67d 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -499,6 +499,22 @@ rspec-mysql:
<<: *rspec-metadata-mysql
parallel: 50
+.rspec-quarantine: &rspec-quarantine
+ script:
+ - export CACHE_CLASSES=true
+ - scripts/gitaly-test-spawn
+ - bin/rspec --color --format documentation --tag quarantine spec/
+
+rspec-pg-quarantine:
+ <<: *rspec-metadata-pg
+ <<: *rspec-quarantine
+ allow_failure: true
+
+rspec-mysql-quarantine:
+ <<: *rspec-metadata-mysql
+ <<: *rspec-quarantine
+ allow_failure: true
+
static-analysis:
<<: *dedicated-no-docs-no-db-pull-cache-job
dependencies:
@@ -788,6 +804,7 @@ qa:selectors:
- bundle exec bin/qa Test::Sanity::Selectors
.qa-frontend-node: &qa-frontend-node
+ <<: *dedicated-no-docs-no-db-pull-cache-job
stage: test
variables:
NODE_OPTIONS: --max_old_space_size=3584
@@ -802,11 +819,6 @@ qa:selectors:
- yarn install --frozen-lockfile --cache-folder .yarn-cache
- date
- yarn run webpack-prod
- <<: *except-docs
-
-qa-frontend-node:6:
- <<: *qa-frontend-node
- image: node:6-alpine
qa-frontend-node:8:
<<: *qa-frontend-node
@@ -854,6 +866,21 @@ lint:javascript:report:
paths:
- eslint-report.html
+jsdoc:
+ <<: *dedicated-no-docs-pull-cache-job
+ stage: post-test
+ dependencies:
+ - compile-assets
+ before_script: []
+ script:
+ - date
+ - yarn run jsdoc || true # ignore exit code
+ artifacts:
+ name: jsdoc
+ expire_in: 31d
+ paths:
+ - jsdoc/
+
pages:
<<: *dedicated-no-docs-no-db-pull-cache-job
before_script: []
@@ -863,6 +890,7 @@ pages:
- karma
- gitlab:assets:compile
- lint:javascript:report
+ - jsdoc
script:
- mv public/ .public/
- mkdir public/
@@ -872,6 +900,7 @@ pages:
- mv webpack-report/ public/webpack-report/ || true
- cp .public/assets/application-*.css public/application.css || true
- cp .public/assets/application-*.css.gz public/application.css.gz || true
+ - mv jsdoc/ public/jsdoc/ || true
artifacts:
paths:
- public
diff --git a/.gitlab/issue_templates/Add style proposal b/.gitlab/issue_templates/Add style proposal.md
index 1a3be44bea0..1a3be44bea0 100644
--- a/.gitlab/issue_templates/Add style proposal
+++ b/.gitlab/issue_templates/Add style proposal.md
diff --git a/.gitlab/issue_templates/Feature proposal.md b/.gitlab/issue_templates/Feature proposal.md
index 639a236631d..4d4d3bfda15 100644
--- a/.gitlab/issue_templates/Feature proposal.md
+++ b/.gitlab/issue_templates/Feature proposal.md
@@ -4,7 +4,7 @@
### Target audience
-<!--- For whom are we doing this? Include either a persona from https://design.gitlab.com/getting-started/personas or define a specific company role. e.a. "Release Manager" or "Security Analyst" -->
+<!--- For whom are we doing this? Include either a persona from https://design.gitlab.com/getting-started/personas or define a specific company role. e.a. "Release Manager" or "Security Analyst". Use the persona labels as well https://gitlab.com/groups/gitlab-org/-/labels?utf8=%E2%9C%93&subscribed=&search=persona%3A -->
### Further details
@@ -12,12 +12,12 @@
### Proposal
-<!--- How are we going to solve the problem? -->
+<!--- How are we going to solve the problem? Try to include the user journey! -->
### What does success look like, and how can we measure that?
-<!--- If no way to measure success, link to an issue that will implement a way to measure this -->
+<!--- Define both the success metrics and acceptance criteria. Note thet success metrics indicate the desired business outcomes, while acceptance criteria indicate when the solution is working correctly. If there is no way to measure success, link to an issue that will implement a way to measure this -->
### Links / references
-/label ~"feature proposal"
+/label ~feature
diff --git a/.gitlab/issue_templates/Security Release.md b/.gitlab/issue_templates/Security Release.md
new file mode 100644
index 00000000000..1734e915ad2
--- /dev/null
+++ b/.gitlab/issue_templates/Security Release.md
@@ -0,0 +1,69 @@
+<!--
+# Read me first!
+
+Set the title to: `Security Release: 11.4.X, 11.3.X, and 11.2.X`
+-->
+
+## Releases tasks
+
+- https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/release-manager.md
+- https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/developer.md
+- https://gitlab.com/gitlab-org/release/docs/blob/master/general/security/security-engineer.md
+
+## Version issues:
+
+* 11.4.X: {release task link}
+* 11.3.X: {release task link}
+* 11.2.X: {release task link}
+
+## Security Issues:
+
+### CE
+
+* {https://gitlab.com/gitlab-org/gitlab-ce/issues link}
+
+### EE
+
+* {https://gitlab.com/gitlab-org/gitlab-ee/issues link}
+
+## Security Issues in dev.gitlab.org:
+
+### CE
+
+- {https://dev.gitlab.org/gitlab/gitlabhq/issues link}
+
+| Version | MR | Status|
+|---------|----|-------|
+| 11.4 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | |
+| 11.3 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | |
+| 11.2 | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | |
+| master | {https://dev.gitlab.org/gitlab/gitlabhq/merge_requests/ link} | |
+
+
+
+### EE
+
+* {https://dev.gitlab.org/gitlab/gitlabhq/issues/ link}
+
+
+| Version | MR | Status|
+|---------|----|-------|
+| 11.4| {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | |
+| 11.3 | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | |
+| 11.2 | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | |
+| master | {https://dev.gitlab.org/gitlab/gitlab-ee/merge_requests/ link} | |
+
+
+## QA
+{QA issue link}
+
+## Blog post
+
+Dev: {https://dev.gitlab.org/gitlab/www-gitlab-com/merge_requests/ link}<br/>
+gitlab.com: {https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/ link}
+
+## Email notification
+{https://gitlab.com/gitlab-com/marketing/general/issues/ link}
+
+/label ~security
+/confidential
diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md
index 08651195d98..f9bf700f809 100644
--- a/.gitlab/issue_templates/Security developer workflow.md
+++ b/.gitlab/issue_templates/Security developer workflow.md
@@ -20,7 +20,7 @@ Set the title to: `[Security] Description of the original issue`
#### Backports
-- [ ] Once the MR is ready to be merged, create MRs targetting the last 3 releases
+- [ ] Once the MR is ready to be merged, create MRs targetting the last 3 releases, plus the current RC if between the 7th and 22nd of the month.
- [ ] At this point, it might be easy to squash the commits from the MR into one
- You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [secpick documentation]
- [ ] Create the branch `security-X-Y` from `X-Y-stable` if it doesn't exist (and make sure it's up to date with stable)
diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml
index 847a0f74aa2..f2ba9fdb174 100644
--- a/.rubocop_todo.yml
+++ b/.rubocop_todo.yml
@@ -686,17 +686,6 @@ Style/TrailingUnderscoreVariable:
- 'spec/lib/gitlab/etag_caching/middleware_spec.rb'
- 'spec/services/quick_actions/interpret_service_spec.rb'
-# Offense count: 5
-# Cop supports --auto-correct.
-# Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, Whitelist.
-# Whitelist: to_ary, to_a, to_c, to_enum, to_h, to_hash, to_i, to_int, to_io, to_open, to_path, to_proc, to_r, to_regexp, to_str, to_s, to_sym
-Style/TrivialAccessors:
- Exclude:
- - 'app/models/external_issue.rb'
- - 'app/serializers/base_serializer.rb'
- - 'lib/gitlab/auth/ldap/person.rb'
- - 'lib/system_check/base_check.rb'
-
# Offense count: 4
# Cop supports --auto-correct.
Style/UnlessElse:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index e86c818298b..b47dc4e19ac 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,24 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 11.6.5 (2019-01-17)
+
+### Fixed (5 changes)
+
+- Add syntax highlighting to suggestion diff. !24156
+- Fix broken templated "Too many changes to show" text. !24282
+- Fix requests profiler in admin page not rendering HTML properly. !24291
+- Fix no avatar not showing in user selection box. !24346
+- Fixed diff suggestions removing dashes.
+
+
+## 11.6.4 (2019-01-15)
+
+### Security (1 change)
+
+- Validate bundle files before unpacking them.
+
+
## 11.6.3 (2019-01-04)
### Fixed (1 change)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 0eed1a29efd..f88cf52e6ef 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.12.0
+1.13.0 \ No newline at end of file
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 3a3cd8cc8b0..88c5fb891dc 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-1.3.1
+1.4.0
diff --git a/Gemfile b/Gemfile
index 1df4584afb7..9ef4b791375 100644
--- a/Gemfile
+++ b/Gemfile
@@ -112,7 +112,7 @@ gem 'seed-fu', '~> 2.3.7'
# Markdown and HTML processing
gem 'html-pipeline', '~> 2.8'
-gem 'deckar01-task_list', '2.0.0'
+gem 'deckar01-task_list', '2.0.1'
gem 'gitlab-markup', '~> 1.6.5'
gem 'github-markup', '~> 1.7.0', require: 'github/markup'
gem 'redcarpet', '~> 3.4'
@@ -160,12 +160,12 @@ gem 'acts-as-taggable-on', '~> 5.0'
# Background jobs
gem 'sidekiq', '~> 5.2.1'
-gem 'sidekiq-cron', '~> 0.6.0'
+gem 'sidekiq-cron', '~> 1.0'
gem 'redis-namespace', '~> 1.6.0'
gem 'gitlab-sidekiq-fetcher', '~> 0.4.0', require: 'sidekiq-reliable-fetch'
# Cron Parser
-gem 'rufus-scheduler', '~> 3.4'
+gem 'fugit', '~> 1.1'
# HTTP requests
gem 'httparty', '~> 0.13.3'
@@ -304,6 +304,12 @@ group :metrics do
gem 'raindrops', '~> 0.18'
end
+group :tracing do
+ # OpenTracing
+ gem 'opentracing', '~> 0.4.3'
+ gem 'jaeger-client', '~> 0.10.0'
+end
+
group :development do
gem 'foreman', '~> 0.84.0'
gem 'brakeman', '~> 4.2', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 075aa616215..122e22af167 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -143,7 +143,7 @@ GEM
database_cleaner (1.7.0)
debug_inspector (0.0.3)
debugger-ruby_core_source (1.3.8)
- deckar01-task_list (2.0.0)
+ deckar01-task_list (2.0.1)
html-pipeline
declarative (0.0.10)
declarative-option (0.1.0)
@@ -185,7 +185,7 @@ GEM
erubi (1.7.1)
erubis (2.7.0)
escape_utils (1.2.1)
- et-orbi (1.0.3)
+ et-orbi (1.1.7)
tzinfo
eventmachine (1.2.7)
excon (0.62.0)
@@ -258,6 +258,9 @@ GEM
foreman (0.84.0)
thor (~> 0.19.1)
formatador (0.2.5)
+ fugit (1.1.7)
+ et-orbi (~> 1.1, >= 1.1.7)
+ raabro (~> 1.1)
fuubar (2.2.0)
rspec-core (~> 3.0)
ruby-progressbar (~> 1.4)
@@ -282,7 +285,7 @@ GEM
gitlab-markup (1.6.5)
gitlab-sidekiq-fetcher (0.4.0)
sidekiq (~> 5)
- gitlab-styles (2.4.1)
+ gitlab-styles (2.5.1)
rubocop (~> 0.54.0)
rubocop-gitlab-security (~> 0.1.0)
rubocop-rspec (~> 1.19)
@@ -389,6 +392,9 @@ GEM
cause
json
ipaddress (0.8.3)
+ jaeger-client (0.10.0)
+ opentracing (~> 0.3)
+ thrift
jira-ruby (1.4.1)
activesupport
multipart-post
@@ -544,6 +550,7 @@ GEM
activesupport
nokogiri (>= 1.4.4)
omniauth (~> 1.0)
+ opentracing (0.4.3)
org-ruby (0.9.12)
rubypants (~> 0.2)
orm_adapter (0.5.0)
@@ -606,6 +613,7 @@ GEM
get_process_mem (~> 0.2)
puma (>= 2.7, < 4)
pyu-ruby-sasl (0.0.3.3)
+ raabro (1.1.6)
rack (2.0.6)
rack-accept (0.4.5)
rack (>= 0.4)
@@ -775,8 +783,6 @@ GEM
rubyntlm (0.6.2)
rubypants (0.2.0)
rubyzip (1.2.2)
- rufus-scheduler (3.4.0)
- et-orbi (~> 1.0)
rugged (0.27.5)
safe_yaml (1.0.4)
sanitize (4.6.6)
@@ -820,8 +826,8 @@ GEM
connection_pool (~> 2.2, >= 2.2.2)
rack-protection (>= 1.5.0)
redis (>= 3.3.5, < 5)
- sidekiq-cron (0.6.0)
- rufus-scheduler (>= 3.3.0)
+ sidekiq-cron (1.0.4)
+ fugit (~> 1.1)
sidekiq (>= 4.2.1)
signet (0.11.0)
addressable (~> 2.3)
@@ -868,6 +874,7 @@ GEM
rack (>= 1, < 3)
thor (0.19.4)
thread_safe (0.3.6)
+ thrift (0.11.0.0)
tilt (2.0.8)
timecop (0.8.1)
timfel-krb5-auth (0.8.3)
@@ -974,7 +981,7 @@ DEPENDENCIES
connection_pool (~> 2.0)
creole (~> 0.5.0)
database_cleaner (~> 1.7.0)
- deckar01-task_list (= 2.0.0)
+ deckar01-task_list (= 2.0.1)
device_detector
devise (~> 4.4)
devise-two-factor (~> 3.0.0)
@@ -1003,6 +1010,7 @@ DEPENDENCIES
fog-rackspace (~> 0.1.1)
font-awesome-rails (~> 4.7)
foreman (~> 0.84.0)
+ fugit (~> 1.1)
fuubar (~> 2.2.0)
gemojione (~> 3.3)
gettext (~> 3.2.2)
@@ -1037,6 +1045,7 @@ DEPENDENCIES
httparty (~> 0.13.3)
icalendar
influxdb (~> 0.2)
+ jaeger-client (~> 0.10.0)
jira-ruby (~> 1.4)
jquery-atwho-rails (~> 1.3.2)
js_regex (~> 2.2.1)
@@ -1077,6 +1086,7 @@ DEPENDENCIES
omniauth-shibboleth (~> 1.3.0)
omniauth-twitter (~> 1.4)
omniauth_crowd (~> 2.2.0)
+ opentracing (~> 0.4.3)
org-ruby (~> 0.9.12)
peek (~> 1.0.1)
peek-gc (~> 0.0.2)
@@ -1127,7 +1137,6 @@ DEPENDENCIES
ruby-prof (~> 0.17.0)
ruby-progressbar
ruby_parser (~> 3.8)
- rufus-scheduler (~> 3.4)
rugged (~> 0.27)
sanitize (~> 4.6)
sass (~> 3.5)
@@ -1141,7 +1150,7 @@ DEPENDENCIES
sham_rack (~> 1.3.6)
shoulda-matchers (~> 3.1.2)
sidekiq (~> 5.2.1)
- sidekiq-cron (~> 0.6.0)
+ sidekiq-cron (~> 1.0)
simple_po_parser (~> 1.1.2)
simplecov (~> 0.14.0)
slack-notifier (~> 1.5.1)
diff --git a/PROCESS.md b/PROCESS.md
index f2eed5544fc..7fdac098807 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -56,7 +56,7 @@ Below we describe the contributing process to GitLab for two reasons:
Several people from the [GitLab team][team] are helping community members to get
their contributions accepted by meeting our [Definition of done][done].
-What you can expect from them is described at https://about.gitlab.com/roles/merge-request-coach/.
+What you can expect from them is described at https://about.gitlab.com/job-families/expert/merge-request-coach/.
### Milestones on community contribution issues
diff --git a/app/assets/javascripts/badges/components/badge_list_row.vue b/app/assets/javascripts/badges/components/badge_list_row.vue
index 9051be1e102..cad5611c8c5 100644
--- a/app/assets/javascripts/badges/components/badge_list_row.vue
+++ b/app/assets/javascripts/badges/components/badge_list_row.vue
@@ -55,7 +55,7 @@ export default {
:disabled="badge.isDeleting"
class="btn btn-default append-right-8"
type="button"
- @click="editBadge(badge);"
+ @click="editBadge(badge)"
>
<icon :size="16" :aria-label="__('Edit')" name="pencil" />
</button>
@@ -65,7 +65,7 @@ export default {
type="button"
data-toggle="modal"
data-target="#delete-badge-modal"
- @click="updateBadgeInModal(badge);"
+ @click="updateBadgeInModal(badge)"
>
<icon :size="16" :aria-label="__('Delete')" name="remove" />
</button>
diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue
index 30fbdb9e97f..f569322ab70 100644
--- a/app/assets/javascripts/boards/components/board_card.vue
+++ b/app/assets/javascripts/boards/components/board_card.vue
@@ -86,7 +86,7 @@ export default {
class="board-card"
@mousedown="mouseDown"
@mousemove="mouseMove"
- @mouseup="showIssue($event);"
+ @mouseup="showIssue($event)"
>
<issue-card-inner
:list="list"
diff --git a/app/assets/javascripts/boards/components/board_new_issue.vue b/app/assets/javascripts/boards/components/board_new_issue.vue
index 93bcb4e129e..28d96dab605 100644
--- a/app/assets/javascripts/boards/components/board_new_issue.vue
+++ b/app/assets/javascripts/boards/components/board_new_issue.vue
@@ -96,7 +96,7 @@ export default {
<template>
<div class="board-new-issue-form">
<div class="board-card">
- <form @submit="submit($event);">
+ <form @submit="submit($event)">
<div v-if="error" class="flash-container">
<div class="flash-alert">An error occurred. Please try again.</div>
</div>
diff --git a/app/assets/javascripts/boards/components/issue_card_inner.vue b/app/assets/javascripts/boards/components/issue_card_inner.vue
index 0f581c3d37d..90ab3a76342 100644
--- a/app/assets/javascripts/boards/components/issue_card_inner.vue
+++ b/app/assets/javascripts/boards/components/issue_card_inner.vue
@@ -184,7 +184,7 @@ export default {
:title="label.description"
class="badge color-label append-right-4 prepend-top-4"
type="button"
- @click="filterByLabel(label);"
+ @click="filterByLabel(label)"
>
{{ label.title }}
</button>
diff --git a/app/assets/javascripts/boards/components/modal/empty_state.vue b/app/assets/javascripts/boards/components/modal/empty_state.vue
index defd857b92c..2a0008467c4 100644
--- a/app/assets/javascripts/boards/components/modal/empty_state.vue
+++ b/app/assets/javascripts/boards/components/modal/empty_state.vue
@@ -58,7 +58,7 @@ export default {
v-if="activeTab === 'selected'"
class="btn btn-default"
type="button"
- @click="changeTab('all');"
+ @click="changeTab('all')"
>
Open issues
</button>
diff --git a/app/assets/javascripts/boards/components/modal/footer.vue b/app/assets/javascripts/boards/components/modal/footer.vue
index b1bc7d87086..d4afd9d59da 100644
--- a/app/assets/javascripts/boards/components/modal/footer.vue
+++ b/app/assets/javascripts/boards/components/modal/footer.vue
@@ -71,7 +71,7 @@ export default {
<span class="inline add-issues-footer-to-list"> to list </span>
<lists-dropdown />
</div>
- <button class="btn btn-default float-right" type="button" @click="toggleModal(false);">
+ <button class="btn btn-default float-right" type="button" @click="toggleModal(false)">
Cancel
</button>
</footer>
diff --git a/app/assets/javascripts/boards/components/modal/header.vue b/app/assets/javascripts/boards/components/modal/header.vue
index d0e285a149e..1f0961e02d8 100644
--- a/app/assets/javascripts/boards/components/modal/header.vue
+++ b/app/assets/javascripts/boards/components/modal/header.vue
@@ -58,7 +58,7 @@ export default {
class="close"
data-dismiss="modal"
aria-label="Close"
- @click="toggleModal(false);"
+ @click="toggleModal(false)"
>
<span aria-hidden="true">×</span>
</button>
diff --git a/app/assets/javascripts/boards/components/modal/list.vue b/app/assets/javascripts/boards/components/modal/list.vue
index 878bb002c6c..e9ed2de859d 100644
--- a/app/assets/javascripts/boards/components/modal/list.vue
+++ b/app/assets/javascripts/boards/components/modal/list.vue
@@ -130,7 +130,7 @@ export default {
<div
:class="{ 'is-active': issue.selected }"
class="board-card"
- @click="toggleIssue($event, issue);"
+ @click="toggleIssue($event, issue)"
>
<issue-card-inner :issue="issue" :issue-link-base="issueLinkBase" :root-path="rootPath" />
<icon
diff --git a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
index 820d0679df5..3fbe8fe1be7 100644
--- a/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
+++ b/app/assets/javascripts/boards/components/modal/lists_dropdown.vue
@@ -38,7 +38,7 @@ export default {
:class="{ 'is-active': list.id == selected.id }"
href="#"
role="button"
- @click.prevent="modal.selectedList = list;"
+ @click.prevent="modal.selectedList = list"
>
<span :style="{ backgroundColor: list.label.color }" class="dropdown-label-box"> </span>
{{ list.title }}
diff --git a/app/assets/javascripts/boards/components/modal/tabs.vue b/app/assets/javascripts/boards/components/modal/tabs.vue
index 7b800a6ab97..2d2920e312e 100644
--- a/app/assets/javascripts/boards/components/modal/tabs.vue
+++ b/app/assets/javascripts/boards/components/modal/tabs.vue
@@ -21,12 +21,12 @@ export default {
<div class="top-area prepend-top-10 append-bottom-10">
<ul class="nav-links issues-state-filters">
<li :class="{ active: activeTab == 'all' }">
- <a href="#" role="button" @click.prevent="changeTab('all');">
+ <a href="#" role="button" @click.prevent="changeTab('all')">
Open issues <span class="badge badge-pill"> {{ issuesCount }} </span>
</a>
</li>
<li :class="{ active: activeTab == 'selected' }">
- <a href="#" role="button" @click.prevent="changeTab('selected');">
+ <a href="#" role="button" @click.prevent="changeTab('selected')">
Selected issues <span class="badge badge-pill"> {{ selectedCount }} </span>
</a>
</li>
diff --git a/app/assets/javascripts/contextual_sidebar.js b/app/assets/javascripts/contextual_sidebar.js
index 10f02739ec8..50efecb3475 100644
--- a/app/assets/javascripts/contextual_sidebar.js
+++ b/app/assets/javascripts/contextual_sidebar.js
@@ -13,6 +13,9 @@ export default class ContextualSidebar {
initDomElements() {
this.$page = $('.layout-page');
this.$sidebar = $('.nav-sidebar');
+
+ if (!this.$sidebar.length) return;
+
this.$innerScroll = $('.nav-sidebar-inner-scroll', this.$sidebar);
this.$overlay = $('.mobile-overlay');
this.$openSidebar = $('.toggle-mobile-nav');
@@ -21,12 +24,14 @@ export default class ContextualSidebar {
}
bindEvents() {
+ if (!this.$sidebar.length) return;
+
document.addEventListener('click', e => {
if (
!e.target.closest('.nav-sidebar') &&
(bp.getBreakpointSize() === 'sm' || bp.getBreakpointSize() === 'md')
) {
- this.toggleCollapsedSidebar(true);
+ this.toggleCollapsedSidebar(true, true);
}
});
this.$openSidebar.on('click', () => this.toggleSidebarNav(true));
@@ -34,7 +39,7 @@ export default class ContextualSidebar {
this.$overlay.on('click', () => this.toggleSidebarNav(false));
this.$sidebarToggle.on('click', () => {
const value = !this.$sidebar.hasClass('sidebar-collapsed-desktop');
- this.toggleCollapsedSidebar(value);
+ this.toggleCollapsedSidebar(value, true);
});
$(window).on('resize', () => _.debounce(this.render(), 100));
@@ -53,16 +58,19 @@ export default class ContextualSidebar {
this.$sidebar.removeClass('sidebar-collapsed-desktop');
}
- toggleCollapsedSidebar(collapsed) {
+ toggleCollapsedSidebar(collapsed, saveCookie) {
const breakpoint = bp.getBreakpointSize();
if (this.$sidebar.length) {
this.$sidebar.toggleClass('sidebar-collapsed-desktop', collapsed);
this.$page.toggleClass('page-with-icon-sidebar', breakpoint === 'sm' ? true : collapsed);
}
- ContextualSidebar.setCollapsedCookie(collapsed);
- this.toggleSidebarOverflow();
+ if (saveCookie) {
+ ContextualSidebar.setCollapsedCookie(collapsed);
+ }
+
+ requestIdleCallback(() => this.toggleSidebarOverflow());
}
toggleSidebarOverflow() {
@@ -74,13 +82,15 @@ export default class ContextualSidebar {
}
render() {
+ if (!this.$sidebar.length) return;
+
const breakpoint = bp.getBreakpointSize();
if (breakpoint === 'sm' || breakpoint === 'md') {
- this.toggleCollapsedSidebar(true);
+ this.toggleCollapsedSidebar(true, false);
} else if (breakpoint === 'lg') {
const collapse = parseBoolean(Cookies.get('sidebar_collapsed'));
- this.toggleCollapsedSidebar(collapse);
+ this.toggleCollapsedSidebar(collapse, false);
}
}
}
diff --git a/app/assets/javascripts/deploy_keys/components/key.vue b/app/assets/javascripts/deploy_keys/components/key.vue
index f01e6f2a639..6ffb8c4e1c0 100644
--- a/app/assets/javascripts/deploy_keys/components/key.vue
+++ b/app/assets/javascripts/deploy_keys/components/key.vue
@@ -113,7 +113,7 @@ export default {
<div class="gl-responsive-table-row deploy-key">
<div class="table-section section-40">
<div role="rowheader" class="table-mobile-header">{{ s__('DeployKeys|Deploy key') }}</div>
- <div class="table-mobile-content">
+ <div class="table-mobile-content qa-key">
<strong class="title qa-key-title"> {{ deployKey.title }} </strong>
<div class="fingerprint qa-key-fingerprint">{{ deployKey.fingerprint }}</div>
</div>
diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue
index f75345d31f8..f0a827be7e8 100644
--- a/app/assets/javascripts/diffs/components/compare_versions.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions.vue
@@ -54,6 +54,9 @@ export default {
showDropdowns() {
return !this.commit && this.mergeRequestDiffs.length;
},
+ baseVersionPath() {
+ return this.mergeRequestDiff.base_version_path;
+ },
},
methods: {
...mapActions('diffs', [
@@ -95,6 +98,7 @@ export default {
and
<compare-versions-dropdown
:other-versions="comparableDiffs"
+ :base-version-path="baseVersionPath"
:start-version="startVersion"
:target-branch="targetBranch"
class="mr-version-compare-dropdown"
diff --git a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
index b9b1ee02697..561188c1e8f 100644
--- a/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions_dropdown.vue
@@ -34,14 +34,13 @@ export default {
required: false,
default: false,
},
+ baseVersionPath: {
+ type: String,
+ required: false,
+ default: null,
+ },
},
computed: {
- baseVersion() {
- return {
- name: 'hii',
- versionIndex: -1,
- };
- },
targetVersions() {
if (this.mergeRequestVersion) {
return this.otherVersions;
@@ -62,6 +61,9 @@ export default {
);
},
href(version) {
+ if (this.isBase(version)) {
+ return this.baseVersionPath;
+ }
if (this.showCommitCount) {
return version.version_path;
}
diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue
index ba6dcd63880..6dc2f5d3f68 100644
--- a/app/assets/javascripts/diffs/components/diff_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_content.vue
@@ -127,7 +127,7 @@ export default {
:save-button-title="__('Comment')"
class="diff-comment-form new-note discussion-form discussion-form-container"
@handleFormUpdate="handleSaveNote"
- @cancelForm="closeDiffFileCommentForm(diffFile.file_hash);"
+ @cancelForm="closeDiffFileCommentForm(diffFile.file_hash)"
/>
</div>
</diff-viewer>
diff --git a/app/assets/javascripts/diffs/components/diff_discussions.vue b/app/assets/javascripts/diffs/components/diff_discussions.vue
index b2021cd6061..4c73eea4049 100644
--- a/app/assets/javascripts/diffs/components/diff_discussions.vue
+++ b/app/assets/javascripts/diffs/components/diff_discussions.vue
@@ -68,7 +68,7 @@ export default {
}"
type="button"
class="js-diff-notes-toggle"
- @click="toggleDiscussion({ discussionId: discussion.id });"
+ @click="toggleDiscussion({ discussionId: discussion.id })"
>
<icon v-if="discussion.expanded" name="collapse" class="collapse-icon" />
<template v-else>
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index f75a01b023b..b58f704bebb 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -145,7 +145,7 @@ export default {
<div
ref="header"
class="js-file-title file-title file-title-flex-parent"
- @click="handleToggleFile($event, true);"
+ @click="handleToggleFile($event, true)"
>
<div class="file-header-content">
<icon
diff --git a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
index c0613d80d37..6709df48637 100644
--- a/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_gutter_content.vue
@@ -179,7 +179,7 @@ export default {
v-if="lineNumber"
:data-linenumber="lineNumber"
:href="lineHref"
- @click="setHighlightedRow(lineCode);"
+ @click="setHighlightedRow(lineCode)"
>
</a>
<diff-gutter-avatars v-if="shouldShowAvatarsOnGutter" :discussions="line.discussions" />
diff --git a/app/assets/javascripts/diffs/components/diff_line_note_form.vue b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
index e7569ba7b84..18edbe286ba 100644
--- a/app/assets/javascripts/diffs/components/diff_line_note_form.vue
+++ b/app/assets/javascripts/diffs/components/diff_line_note_form.vue
@@ -28,6 +28,11 @@ export default {
type: Object,
required: true,
},
+ helpPagePath: {
+ type: String,
+ required: false,
+ default: '',
+ },
},
computed: {
...mapState({
@@ -95,6 +100,7 @@ export default {
:is-editing="true"
:line-code="line.line_code"
:line="line"
+ :help-page-path="helpPagePath"
save-button-title="Comment"
class="diff-comment-form"
@cancelForm="handleCancelCommentForm"
diff --git a/app/assets/javascripts/diffs/components/image_diff_overlay.vue b/app/assets/javascripts/diffs/components/image_diff_overlay.vue
index d30e64312aa..4a83c5a72a5 100644
--- a/app/assets/javascripts/diffs/components/image_diff_overlay.vue
+++ b/app/assets/javascripts/diffs/components/image_diff_overlay.vue
@@ -97,7 +97,7 @@ export default {
v-if="canComment"
type="button"
class="btn-transparent position-absolute image-diff-overlay-add-comment w-100 h-100 js-add-image-diff-note-button"
- @click="clickedImage($event.offsetX, $event.offsetY);"
+ @click="clickedImage($event.offsetX, $event.offsetY)"
>
<span class="sr-only"> {{ __('Add image comment') }} </span>
</button>
@@ -109,7 +109,7 @@ export default {
:disabled="!shouldToggleDiscussion"
class="js-image-badge"
type="button"
- @click="toggleDiscussion({ discussionId: discussion.id });"
+ @click="toggleDiscussion({ discussionId: discussion.id })"
>
<icon v-if="showCommentIcon" name="image-comment-dark" />
<template v-else>
diff --git a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
index 814ee0b7c02..69146f1f6fd 100644
--- a/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/inline_diff_comment_row.vue
@@ -54,6 +54,7 @@ export default {
:diff-file-hash="diffFileHash"
:line="line"
:note-target-line="line"
+ :help-page-path="helpPagePath"
/>
</div>
</td>
diff --git a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
index a65cf025cde..370cb6e339a 100644
--- a/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
+++ b/app/assets/javascripts/diffs/components/parallel_diff_comment_row.vue
@@ -101,6 +101,7 @@ export default {
:diff-file-hash="diffFileHash"
:line="line.left"
:note-target-line="line.left"
+ :help-page-path="helpPagePath"
line-position="left"
/>
</td>
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index eb8f274aff3..097587c5ac1 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -81,7 +81,7 @@ export default {
:placeholder="s__('MergeRequest|Filter files')"
type="search"
class="form-control"
- @focus="toggleFocusSearch(true);"
+ @focus="toggleFocusSearch(true)"
@blur="blurSearch"
/>
<button
@@ -104,7 +104,7 @@ export default {
}"
class="btn btn-default pt-0 pb-0 d-flex align-items-center"
type="button"
- @click="toggleRenderTreeList(false);"
+ @click="toggleRenderTreeList(false)"
>
<icon name="hamburger" />
</button>
@@ -117,7 +117,7 @@ export default {
}"
class="btn btn-default pt-0 pb-0 d-flex align-items-center"
type="button"
- @click="toggleRenderTreeList(true);"
+ @click="toggleRenderTreeList(true)"
>
<icon name="file-tree" />
</button>
diff --git a/app/assets/javascripts/environments/components/environment_actions.vue b/app/assets/javascripts/environments/components/environment_actions.vue
index 1f7dab9fbd2..208bd19f6b0 100644
--- a/app/assets/javascripts/environments/components/environment_actions.vue
+++ b/app/assets/javascripts/environments/components/environment_actions.vue
@@ -92,7 +92,7 @@ export default {
:disabled="isActionDisabled(action)"
type="button"
class="js-manual-action-link no-btn btn d-flex align-items-center"
- @click="onClickAction(action);"
+ @click="onClickAction(action)"
>
<span class="flex-fill"> {{ action.name }} </span>
<span v-if="action.scheduledAt" class="text-secondary">
diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue
index ae9459a2482..87c1d44dd40 100644
--- a/app/assets/javascripts/environments/components/environments_app.vue
+++ b/app/assets/javascripts/environments/components/environments_app.vue
@@ -96,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>
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index 533e90e2222..75bdf87137f 100644
--- a/app/assets/javascripts/environments/components/environments_table.vue
+++ b/app/assets/javascripts/environments/components/environments_table.vue
@@ -85,9 +85,9 @@ 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>
diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
index 3cf6e4ad14d..982e550e73c 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js
+++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
@@ -15,11 +15,11 @@ export default () =>
const environmentsData = document.querySelector(this.$options.el).dataset;
return {
- endpoint: environmentsData.endpoint,
- folderName: environmentsData.folderName,
+ endpoint: environmentsData.environmentsDataEndpoint,
+ folderName: environmentsData.environmentsDataFolderName,
cssContainerClass: environmentsData.cssClass,
- canCreateDeployment: parseBoolean(environmentsData.canCreateDeployment),
- canReadEnvironment: parseBoolean(environmentsData.canReadEnvironment),
+ canCreateDeployment: parseBoolean(environmentsData.environmentsDataCanCreateDeployment),
+ canReadEnvironment: parseBoolean(environmentsData.environmentsDataCanReadEnvironment),
};
},
render(createElement) {
diff --git a/app/assets/javascripts/error_tracking/components/error_tracking_list.vue b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
new file mode 100644
index 00000000000..6981afe1ead
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/components/error_tracking_list.vue
@@ -0,0 +1,118 @@
+<script>
+import { mapActions, mapState } from 'vuex';
+import { GlEmptyState, GlButton, GlLink, GlLoadingIcon, GlTable } from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
+import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
+import { __ } from '~/locale';
+
+export default {
+ fields: [
+ { key: 'error', label: __('Open errors') },
+ { key: 'events', label: __('Events') },
+ { key: 'users', label: __('Users') },
+ { key: 'lastSeen', label: __('Last seen') },
+ ],
+ components: {
+ GlEmptyState,
+ GlButton,
+ GlLink,
+ GlLoadingIcon,
+ GlTable,
+ Icon,
+ TimeAgo,
+ },
+ props: {
+ indexPath: {
+ type: String,
+ required: true,
+ },
+ enableErrorTrackingLink: {
+ type: String,
+ required: true,
+ },
+ errorTrackingEnabled: {
+ type: Boolean,
+ required: true,
+ },
+ illustrationPath: {
+ type: String,
+ required: true,
+ },
+ },
+ computed: {
+ ...mapState(['errors', 'externalUrl', 'loading']),
+ },
+ created() {
+ if (this.errorTrackingEnabled) {
+ this.startPolling(this.indexPath);
+ }
+ },
+ methods: {
+ ...mapActions(['startPolling']),
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div v-if="errorTrackingEnabled">
+ <div v-if="loading" class="py-3"><gl-loading-icon :size="3" /></div>
+ <div v-else>
+ <div class="d-flex justify-content-end">
+ <gl-button class="my-3 ml-auto" variant="primary" :href="externalUrl" target="_blank"
+ >View in Sentry <icon name="external-link" />
+ </gl-button>
+ </div>
+ <gl-table
+ :items="errors"
+ :fields="$options.fields"
+ :show-empty="true"
+ :empty-text="__('No errors to display')"
+ >
+ <template slot="HEAD_events" slot-scope="data">
+ <div class="text-right">{{ data.label }}</div>
+ </template>
+ <template slot="HEAD_users" slot-scope="data">
+ <div class="text-right">{{ data.label }}</div>
+ </template>
+ <template slot="error" slot-scope="errors">
+ <div class="d-flex flex-column">
+ <div class="d-flex">
+ <gl-link :href="errors.item.externalUrl" class="d-flex text-dark" target="_blank">
+ <strong>{{ errors.item.title.trim() }}</strong>
+ <icon name="external-link" class="ml-1" />
+ </gl-link>
+ <span class="text-secondary ml-2">{{ errors.item.culprit }}</span>
+ </div>
+ {{ errors.item.message || __('No details available') }}
+ </div>
+ </template>
+
+ <template slot="events" slot-scope="errors">
+ <div class="text-right">{{ errors.item.count }}</div>
+ </template>
+
+ <template slot="users" slot-scope="errors">
+ <div class="text-right">{{ errors.item.userCount }}</div>
+ </template>
+
+ <template slot="lastSeen" slot-scope="errors">
+ <div class="d-flex align-items-center">
+ <icon name="calendar" css-classes="text-secondary mr-1" />
+ <time-ago :time="errors.item.lastSeen" class="text-secondary" />
+ </div>
+ </template>
+ </gl-table>
+ </div>
+ </div>
+ <div v-else>
+ <gl-empty-state
+ :title="__('Get started with error tracking')"
+ :description="__('Monitor your errors by integrating with Sentry')"
+ :primary-button-text="__('Enable error tracking')"
+ :primary-button-link="enableErrorTrackingLink"
+ :svg-path="illustrationPath"
+ />
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/error_tracking/index.js b/app/assets/javascripts/error_tracking/index.js
new file mode 100644
index 00000000000..808ae2c9a41
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/index.js
@@ -0,0 +1,35 @@
+import Vue from 'vue';
+import { parseBoolean } from '~/lib/utils/common_utils';
+import store from './store';
+import ErrorTrackingList from './components/error_tracking_list.vue';
+
+export default () => {
+ if (!gon.features.errorTracking) {
+ return;
+ }
+
+ // eslint-disable-next-line no-new
+ new Vue({
+ el: '#js-error_tracking',
+ components: {
+ ErrorTrackingList,
+ },
+ store,
+ render(createElement) {
+ const domEl = document.querySelector(this.$options.el);
+ const { indexPath, enableErrorTrackingLink, illustrationPath } = domEl.dataset;
+ let { errorTrackingEnabled } = domEl.dataset;
+
+ errorTrackingEnabled = parseBoolean(errorTrackingEnabled);
+
+ return createElement('error-tracking-list', {
+ props: {
+ indexPath,
+ enableErrorTrackingLink,
+ errorTrackingEnabled,
+ illustrationPath,
+ },
+ });
+ },
+ });
+};
diff --git a/app/assets/javascripts/error_tracking/services/index.js b/app/assets/javascripts/error_tracking/services/index.js
new file mode 100644
index 00000000000..ab89521dc46
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/services/index.js
@@ -0,0 +1,7 @@
+import axios from '~/lib/utils/axios_utils';
+
+export default {
+ getErrorList({ endpoint }) {
+ return axios.get(endpoint);
+ },
+};
diff --git a/app/assets/javascripts/error_tracking/store/actions.js b/app/assets/javascripts/error_tracking/store/actions.js
new file mode 100644
index 00000000000..2e192c958ba
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/store/actions.js
@@ -0,0 +1,31 @@
+import Service from '../services';
+import * as types from './mutation_types';
+import createFlash from '~/flash';
+import Poll from '~/lib/utils/poll';
+import { __ } from '~/locale';
+
+let eTagPoll;
+
+export function startPolling({ commit }, endpoint) {
+ eTagPoll = new Poll({
+ resource: Service,
+ method: 'getErrorList',
+ data: { endpoint },
+ successCallback: ({ data }) => {
+ if (!data) {
+ return;
+ }
+ commit(types.SET_ERRORS, data.errors);
+ commit(types.SET_EXTERNAL_URL, data.external_url);
+ commit(types.SET_LOADING, false);
+ },
+ errorCallback: () => {
+ commit(types.SET_LOADING, false);
+ createFlash(__('Failed to load errors from Sentry'));
+ },
+ });
+
+ eTagPoll.makeRequest();
+}
+
+export default () => {};
diff --git a/app/assets/javascripts/error_tracking/store/index.js b/app/assets/javascripts/error_tracking/store/index.js
new file mode 100644
index 00000000000..3136682fb64
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/store/index.js
@@ -0,0 +1,19 @@
+import Vue from 'vue';
+import Vuex from 'vuex';
+import * as actions from './actions';
+import mutations from './mutations';
+
+Vue.use(Vuex);
+
+export const createStore = () =>
+ new Vuex.Store({
+ state: {
+ errors: [],
+ externalUrl: '',
+ loading: true,
+ },
+ actions,
+ mutations,
+ });
+
+export default createStore();
diff --git a/app/assets/javascripts/error_tracking/store/mutation_types.js b/app/assets/javascripts/error_tracking/store/mutation_types.js
new file mode 100644
index 00000000000..f9d77a6b08e
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/store/mutation_types.js
@@ -0,0 +1,3 @@
+export const SET_ERRORS = 'SET_ERRORS';
+export const SET_EXTERNAL_URL = 'SET_EXTERNAL_URL';
+export const SET_LOADING = 'SET_LOADING';
diff --git a/app/assets/javascripts/error_tracking/store/mutations.js b/app/assets/javascripts/error_tracking/store/mutations.js
new file mode 100644
index 00000000000..e4bd81db9c9
--- /dev/null
+++ b/app/assets/javascripts/error_tracking/store/mutations.js
@@ -0,0 +1,14 @@
+import * as types from './mutation_types';
+import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
+
+export default {
+ [types.SET_ERRORS](state, data) {
+ state.errors = convertObjectPropsToCamelCase(data, { deep: true });
+ },
+ [types.SET_EXTERNAL_URL](state, url) {
+ state.externalUrl = url;
+ },
+ [types.SET_LOADING](state, loading) {
+ state.loading = loading;
+ },
+};
diff --git a/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue b/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
index 6b1a934d3fe..19bc3313373 100644
--- a/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
+++ b/app/assets/javascripts/filtered_search/components/recent_searches_dropdown_content.vue
@@ -66,7 +66,7 @@ export default {
<button
type="button"
class="filtered-search-history-dropdown-item"
- @click="onItemActivated(item.text);"
+ @click="onItemActivated(item.text)"
>
<span>
<span
@@ -88,7 +88,7 @@ export default {
<button
type="button"
class="filtered-search-history-clear-button"
- @click="onRequestClearRecentSearches($event);"
+ @click="onRequestClearRecentSearches($event)"
>
Clear recent searches
</button>
diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js
index 3ac00c51df4..2b6af9060d1 100644
--- a/app/assets/javascripts/fly_out_nav.js
+++ b/app/assets/javascripts/fly_out_nav.js
@@ -24,6 +24,9 @@ export const slope = (a, b) => (b.y - a.y) / (b.x - a.x);
let headerHeight = 50;
export const getHeaderHeight = () => headerHeight;
+const setHeaderHeight = () => {
+ headerHeight = sidebar.offsetTop;
+};
export const isSidebarCollapsed = () =>
sidebar && sidebar.classList.contains('sidebar-collapsed-desktop');
@@ -186,7 +189,7 @@ export default () => {
});
}
- headerHeight = document.querySelector('.nav-sidebar').offsetTop;
+ requestIdleCallback(setHeaderHeight);
items.forEach(el => {
const subItems = el.querySelector('.sidebar-sub-level-items');
diff --git a/app/assets/javascripts/ide/components/activity_bar.vue b/app/assets/javascripts/ide/components/activity_bar.vue
index a1f66ff764d..7c769ab7fa0 100644
--- a/app/assets/javascripts/ide/components/activity_bar.vue
+++ b/app/assets/javascripts/ide/components/activity_bar.vue
@@ -45,7 +45,7 @@ export default {
data-placement="right"
type="button"
class="ide-sidebar-link js-ide-edit-mode"
- @click.prevent="changedActivityView($event, $options.activityBarViews.edit);"
+ @click.prevent="changedActivityView($event, $options.activityBarViews.edit)"
>
<icon name="code" />
</button>
@@ -62,7 +62,7 @@ export default {
data-placement="right"
type="button"
class="ide-sidebar-link js-ide-review-mode"
- @click.prevent="changedActivityView($event, $options.activityBarViews.review);"
+ @click.prevent="changedActivityView($event, $options.activityBarViews.review)"
>
<icon name="file-modified" />
</button>
@@ -79,7 +79,7 @@ export default {
data-placement="right"
type="button"
class="ide-sidebar-link js-ide-commit-mode"
- @click.prevent="changedActivityView($event, $options.activityBarViews.commit);"
+ @click.prevent="changedActivityView($event, $options.activityBarViews.commit)"
>
<icon name="commit" />
</button>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
index 6f1ded91753..00b2d236da3 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/message_field.vue
@@ -111,8 +111,8 @@ export default {
name="commit-message"
@scroll="handleScroll"
@input="onInput"
- @focus="updateIsFocused(true);"
- @blur="updateIsFocused(false);"
+ @focus="updateIsFocused(true)"
+ @blur="updateIsFocused(false)"
@keydown.ctrl.enter="onCtrlEnter"
@keydown.meta.enter="onCtrlEnter"
>
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 3525084b1cb..2b44438f849 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/radio_group.vue
@@ -65,7 +65,7 @@ export default {
:disabled="disabled"
type="radio"
name="commit-action"
- @change="updateCommitAction($event.target.value);"
+ @change="updateCommitAction($event.target.value)"
/>
<span class="prepend-left-10">
<span v-if="label" class="ide-radio-label"> {{ label }} </span> <slot v-else></slot>
@@ -76,7 +76,7 @@ export default {
:placeholder="newBranchName"
type="text"
class="form-control monospace"
- @input="updateBranchName($event.target.value);"
+ @input="updateBranchName($event.target.value)"
/>
</div>
</fieldset>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
index 02c2004d495..e054be86c1e 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/stage_button.vue
@@ -48,7 +48,7 @@ export default {
data-container="body"
data-boundary="viewport"
data-placement="bottom"
- @click.stop.prevent="stageChange(path);"
+ @click.stop.prevent="stageChange(path)"
>
<icon :size="16" name="mobile-issue-close" class="ml-auto mr-auto" />
</button>
@@ -70,7 +70,7 @@ export default {
:header-title-text="modalTitle"
:footer-primary-button-text="__('Discard changes')"
footer-primary-button-variant="danger"
- @submit="discardFileChanges(path);"
+ @submit="discardFileChanges(path)"
>
{{ __("You will loose all changes you've made to this file. This action cannot be undone.") }}
</gl-modal>
diff --git a/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue b/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue
index ce41fcdb087..0567ef54ff3 100644
--- a/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue
+++ b/app/assets/javascripts/ide/components/commit_sidebar/unstage_button.vue
@@ -33,7 +33,7 @@ export default {
data-container="body"
data-boundary="viewport"
data-placement="bottom"
- @click.stop.prevent="unstageChange(path);"
+ @click.stop.prevent="unstageChange(path)"
>
<icon :size="16" name="redo" class="ml-auto mr-auto" />
</button>
diff --git a/app/assets/javascripts/ide/components/editor_mode_dropdown.vue b/app/assets/javascripts/ide/components/editor_mode_dropdown.vue
index 5f99261ec39..732fa0786b0 100644
--- a/app/assets/javascripts/ide/components/editor_mode_dropdown.vue
+++ b/app/assets/javascripts/ide/components/editor_mode_dropdown.vue
@@ -40,7 +40,7 @@ export default {
'is-active': viewer === $options.viewerTypes.mr,
}"
href="#"
- @click.prevent="changeMode($options.viewerTypes.mr);"
+ @click.prevent="changeMode($options.viewerTypes.mr)"
>
<strong class="dropdown-menu-inner-title"> {{ mergeReviewLine }} </strong>
<span class="dropdown-menu-inner-content">
@@ -54,7 +54,7 @@ export default {
'is-active': viewer === $options.viewerTypes.diff,
}"
href="#"
- @click.prevent="changeMode($options.viewerTypes.diff);"
+ @click.prevent="changeMode($options.viewerTypes.diff)"
>
<strong class="dropdown-menu-inner-title">{{ __('Reviewing') }}</strong>
<span class="dropdown-menu-inner-content">
diff --git a/app/assets/javascripts/ide/components/file_finder/index.vue b/app/assets/javascripts/ide/components/file_finder/index.vue
index bb391912572..0b0cd7b75eb 100644
--- a/app/assets/javascripts/ide/components/file_finder/index.vue
+++ b/app/assets/javascripts/ide/components/file_finder/index.vue
@@ -164,7 +164,7 @@ export default {
</script>
<template>
- <div class="ide-file-finder-overlay" @mousedown.self="toggleFileFinder(false);">
+ <div class="ide-file-finder-overlay" @mousedown.self="toggleFileFinder(false)">
<div class="dropdown-menu diff-file-changes ide-file-finder show">
<div class="dropdown-input">
<input
@@ -174,8 +174,8 @@ export default {
type="search"
class="dropdown-input-field"
autocomplete="off"
- @keydown="onKeydown($event);"
- @keyup="onKeyup($event);"
+ @keydown="onKeydown($event)"
+ @keyup="onKeyup($event)"
/>
<i
:class="{
diff --git a/app/assets/javascripts/ide/components/file_templates/dropdown.vue b/app/assets/javascripts/ide/components/file_templates/dropdown.vue
index 414ea9c7d4d..343e0cca672 100644
--- a/app/assets/javascripts/ide/components/file_templates/dropdown.vue
+++ b/app/assets/javascripts/ide/components/file_templates/dropdown.vue
@@ -91,7 +91,7 @@ export default {
<gl-loading-icon v-if="showLoading" :size="2" />
<ul v-else>
<li v-for="(item, index) in outputData" :key="index">
- <button type="button" @click="clickItem(item);">{{ item.name }}</button>
+ <button type="button" @click="clickItem(item)">{{ item.name }}</button>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/ide/components/ide_status_bar.vue b/app/assets/javascripts/ide/components/ide_status_bar.vue
index e2e0acc22b1..f1d40586903 100644
--- a/app/assets/javascripts/ide/components/ide_status_bar.vue
+++ b/app/assets/javascripts/ide/components/ide_status_bar.vue
@@ -84,7 +84,7 @@ export default {
<button
type="button"
class="p-0 border-0 h-50"
- @click="openRightPane($options.rightSidebarViews.pipelines);"
+ @click="openRightPane($options.rightSidebarViews.pipelines)"
>
<ci-icon
v-tooltip
diff --git a/app/assets/javascripts/ide/components/ide_tree.vue b/app/assets/javascripts/ide/components/ide_tree.vue
index 9fc21adae7c..f93496132a4 100644
--- a/app/assets/javascripts/ide/components/ide_tree.vue
+++ b/app/assets/javascripts/ide/components/ide_tree.vue
@@ -43,7 +43,7 @@ export default {
:show-label="false"
class="d-flex border-0 p-0 mr-3 qa-new-file"
icon="doc-new"
- @click="openNewEntryModal({ type: 'blob' });"
+ @click="openNewEntryModal({ type: 'blob' })"
/>
<upload
:show-label="false"
@@ -56,7 +56,7 @@ export default {
:show-label="false"
class="d-flex border-0 p-0"
icon="folder-new"
- @click="openNewEntryModal({ type: 'tree' });"
+ @click="openNewEntryModal({ type: 'tree' })"
/>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/jobs/detail.vue b/app/assets/javascripts/ide/components/jobs/detail.vue
index e8fe5fc696d..7710bfb49ec 100644
--- a/app/assets/javascripts/ide/components/jobs/detail.vue
+++ b/app/assets/javascripts/ide/components/jobs/detail.vue
@@ -75,7 +75,7 @@ export default {
<template>
<div class="ide-pipeline build-page d-flex flex-column flex-fill">
<header class="ide-job-header d-flex align-items-center">
- <button class="btn btn-default btn-sm d-flex" @click="setDetailJob(null);">
+ <button class="btn btn-default btn-sm d-flex" @click="setDetailJob(null)">
<icon name="chevron-left" /> {{ __('View jobs') }}
</button>
</header>
diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue
index ac2b0eddfb4..2d55ffb3c65 100644
--- a/app/assets/javascripts/ide/components/merge_requests/list.vue
+++ b/app/assets/javascripts/ide/components/merge_requests/list.vue
@@ -84,7 +84,7 @@ export default {
:placeholder="__('Search merge requests')"
@focus="onSearchFocus"
@input="searchMergeRequests"
- @removeToken="setSearchType(null);"
+ @removeToken="setSearchType(null)"
/>
<icon :size="18" name="search" class="input-icon" />
</div>
@@ -102,7 +102,7 @@ export default {
<button
type="button"
class="btn-link d-flex align-items-center"
- @click.stop="setSearchType(searchType);"
+ @click.stop="setSearchType(searchType)"
>
<span class="d-flex append-right-default ide-search-list-current-icon">
<icon :size="18" name="search" />
diff --git a/app/assets/javascripts/ide/components/new_dropdown/index.vue b/app/assets/javascripts/ide/components/new_dropdown/index.vue
index a50d729036f..d7a7b1b4d78 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/index.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/index.vue
@@ -73,7 +73,7 @@ export default {
:aria-label="__('Create new file or directory')"
type="button"
class="rounded border-0 d-flex ide-entry-dropdown-toggle"
- @click.stop="openDropdown();"
+ @click.stop="openDropdown()"
>
<icon name="ellipsis_v" /> <icon name="arrow-down" />
</button>
@@ -85,7 +85,7 @@ export default {
class="d-flex"
icon="doc-new"
icon-classes="mr-2"
- @click="createNewItem('blob');"
+ @click="createNewItem('blob')"
/>
</li>
<li><upload :path="path" @create="createTempEntry" /></li>
@@ -95,7 +95,7 @@ export default {
class="d-flex"
icon="folder-new"
icon-classes="mr-2"
- @click="createNewItem($options.modalTypes.tree);"
+ @click="createNewItem($options.modalTypes.tree)"
/>
</li>
<li class="divider"></li>
@@ -106,7 +106,7 @@ export default {
class="d-flex"
icon="pencil"
icon-classes="mr-2"
- @click="createNewItem($options.modalTypes.rename);"
+ @click="createNewItem($options.modalTypes.rename)"
/>
</li>
<li>
@@ -115,7 +115,7 @@ export default {
class="d-flex"
icon="remove"
icon-classes="mr-2"
- @click="deleteEntry(path);"
+ @click="deleteEntry(path)"
/>
</li>
</ul>
diff --git a/app/assets/javascripts/ide/components/new_dropdown/modal.vue b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
index 63cbf41b89b..04ecd4ba4e7 100644
--- a/app/assets/javascripts/ide/components/new_dropdown/modal.vue
+++ b/app/assets/javascripts/ide/components/new_dropdown/modal.vue
@@ -114,7 +114,7 @@ export default {
<button
type="button"
class="btn btn-missing p-1 pr-2 pl-2"
- @click="createFromTemplate(template);"
+ @click="createFromTemplate(template)"
>
{{ template.name }}
</button>
diff --git a/app/assets/javascripts/ide/components/panes/right.vue b/app/assets/javascripts/ide/components/panes/right.vue
index 7a57ccf2dd3..2e6bd85feec 100644
--- a/app/assets/javascripts/ide/components/panes/right.vue
+++ b/app/assets/javascripts/ide/components/panes/right.vue
@@ -122,7 +122,7 @@ export default {
data-placement="left"
class="ide-sidebar-link is-right"
type="button"
- @click="clickTab($event, tab);"
+ @click="clickTab($event, tab)"
>
<icon :size="16" :name="tab.icon" />
</button>
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue
index c13d3ec094b..94a9e87369c 100644
--- a/app/assets/javascripts/ide/components/repo_editor.vue
+++ b/app/assets/javascripts/ide/components/repo_editor.vue
@@ -219,7 +219,7 @@ export default {
<a
href="javascript:void(0);"
role="button"
- @click.prevent="setFileViewMode({ file, viewMode: 'editor' });"
+ @click.prevent="setFileViewMode({ file, viewMode: 'editor' })"
>
<template v-if="viewer === $options.viewerTypes.edit">
{{ __('Edit') }}
@@ -233,7 +233,7 @@ export default {
<a
href="javascript:void(0);"
role="button"
- @click.prevent="setFileViewMode({ file, viewMode: 'preview' });"
+ @click.prevent="setFileViewMode({ file, viewMode: 'preview' })"
>
{{ file.previewMode.previewTitle }}
</a>
diff --git a/app/assets/javascripts/ide/components/repo_tab.vue b/app/assets/javascripts/ide/components/repo_tab.vue
index 4b87b83db8a..f6aa2295844 100644
--- a/app/assets/javascripts/ide/components/repo_tab.vue
+++ b/app/assets/javascripts/ide/components/repo_tab.vue
@@ -74,7 +74,7 @@ export default {
active: tab.active,
disabled: tab.pending,
}"
- @click="clickFile(tab);"
+ @click="clickFile(tab)"
@mouseover="mouseOverTab"
@mouseout="mouseOutTab"
>
@@ -88,7 +88,7 @@ export default {
:disabled="tab.pending"
type="button"
class="multi-file-tab-close"
- @click.stop.prevent="closeFile(tab);"
+ @click.stop.prevent="closeFile(tab)"
>
<icon v-if="!showChangedIcon" :size="12" name="close" />
<changed-file-icon v-else :file="tab" />
diff --git a/app/assets/javascripts/ide/components/resizable_panel.vue b/app/assets/javascripts/ide/components/resizable_panel.vue
index a89de56ab5c..7277fcb7617 100644
--- a/app/assets/javascripts/ide/components/resizable_panel.vue
+++ b/app/assets/javascripts/ide/components/resizable_panel.vue
@@ -78,8 +78,8 @@ export default {
:min-size="minSize"
:max-size="$options.maxSize"
:side="side === 'right' ? 'left' : 'right'"
- @resize-start="setResizingStatus(true);"
- @resize-end="setResizingStatus(false);"
+ @resize-start="setResizingStatus(true)"
+ @resize-end="setResizingStatus(false)"
/>
</div>
</template>
diff --git a/app/assets/javascripts/ide/components/shared/tokened_input.vue b/app/assets/javascripts/ide/components/shared/tokened_input.vue
index f58e08c2cc9..de3e71dad92 100644
--- a/app/assets/javascripts/ide/components/shared/tokened_input.vue
+++ b/app/assets/javascripts/ide/components/shared/tokened_input.vue
@@ -76,8 +76,8 @@ export default {
<button
class="selectable btn-blank"
type="button"
- @click.stop="removeToken(token);"
- @keyup.delete="removeToken(token);"
+ @click.stop="removeToken(token)"
+ @keyup.delete="removeToken(token)"
>
<div class="value-container rounded">
<div class="value">{{ token.label }}</div>
diff --git a/app/assets/javascripts/ide/index.js b/app/assets/javascripts/ide/index.js
index 6351948f750..5a2b680c2f7 100644
--- a/app/assets/javascripts/ide/index.js
+++ b/app/assets/javascripts/ide/index.js
@@ -10,13 +10,20 @@ import { parseBoolean } from '../lib/utils/common_utils';
Vue.use(Translate);
/**
+ * Function that receives the default store and returns an extended one.
+ * @callback extendStoreCallback
+ * @param {Vuex.Store} store
+ * @param {Element} el
+ */
+
+/**
* Initialize the IDE on the given element.
*
* @param {Element} el - The element that will contain the IDE.
* @param {Object} options - Extra options for the IDE (Used by EE).
* @param {Component} options.rootComponent -
* Component that overrides the root component.
- * @param {(store:Vuex.Store, el:Element) => Vuex.Store} options.extendStore -
+ * @param {extendStoreCallback} options.extendStore -
* Function that receives the default store and returns an extended one.
*/
export function initIde(el, options = {}) {
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue
index d2b7ce18290..d473d6a482d 100644
--- a/app/assets/javascripts/jobs/components/job_app.vue
+++ b/app/assets/javascripts/jobs/components/job_app.vue
@@ -80,7 +80,6 @@ export default {
'hasError',
]),
...mapGetters([
- 'headerActions',
'headerTime',
'shouldRenderCalloutMessage',
'shouldRenderTriggeredLabel',
@@ -202,7 +201,6 @@ export default {
:item-id="job.id"
:time="headerTime"
:user="job.user"
- :actions="headerActions"
:has-sidebar-button="true"
:should-render-triggered-label="shouldRenderTriggeredLabel"
:item-name="__('Job')"
diff --git a/app/assets/javascripts/jobs/components/sidebar.vue b/app/assets/javascripts/jobs/components/sidebar.vue
index ad3e7dabc79..a2141dc3760 100644
--- a/app/assets/javascripts/jobs/components/sidebar.vue
+++ b/app/assets/javascripts/jobs/components/sidebar.vue
@@ -48,8 +48,7 @@ export default {
return `${this.job.runner.description} (#${this.job.runner.id})`;
},
retryButtonClass() {
- let className =
- 'js-retry-button float-right btn btn-retry d-none d-md-block d-lg-block d-xl-block';
+ let className = 'js-retry-button btn btn-retry';
className +=
this.job.status && this.job.recoverable ? ' btn-primary' : ' btn-inverted-secondary';
return className;
@@ -110,24 +109,27 @@ export default {
<aside class="right-sidebar build-sidebar" data-offset-top="101" data-spy="affix">
<div class="sidebar-container">
<div class="blocks-container">
- <div class="block d-flex align-items-center">
- <h4 class="flex-grow-1 prepend-top-8 m-0">{{ job.name }}</h4>
- <gl-link
- v-if="job.retry_path"
- :class="retryButtonClass"
- :href="job.retry_path"
- data-method="post"
- rel="nofollow"
- >{{ __('Retry') }}</gl-link
- >
- <gl-link
- v-if="job.terminal_path"
- :href="job.terminal_path"
- class="js-terminal-link pull-right btn btn-primary btn-inverted visible-md-block visible-lg-block"
- target="_blank"
- >
- {{ __('Debug') }} <icon name="external-link" />
- </gl-link>
+ <div class="block d-flex flex-nowrap align-items-center">
+ <h4 class="my-0 mr-2">{{ job.name }}</h4>
+ <div class="flex-grow-1 flex-shrink-0 text-right">
+ <gl-link
+ v-if="job.retry_path"
+ :class="retryButtonClass"
+ :href="job.retry_path"
+ data-method="post"
+ rel="nofollow"
+ >{{ __('Retry') }}</gl-link
+ >
+ <gl-link
+ v-if="job.cancel_path"
+ :href="job.cancel_path"
+ class="js-cancel-job btn btn-default"
+ data-method="post"
+ rel="nofollow"
+ >{{ __('Cancel') }}</gl-link
+ >
+ </div>
+
<gl-button
:aria-label="__('Toggle Sidebar')"
type="button"
@@ -137,22 +139,24 @@ export default {
<i aria-hidden="true" data-hidden="true" class="fa fa-angle-double-right"></i>
</gl-button>
</div>
- <div v-if="job.retry_path || job.new_issue_path" class="block retry-link">
+
+ <div v-if="job.terminal_path || job.new_issue_path" class="block retry-link">
<gl-link
v-if="job.new_issue_path"
:href="job.new_issue_path"
- class="js-new-issue btn btn-success btn-inverted"
+ class="js-new-issue btn btn-success btn-inverted float-left mr-2"
>{{ __('New issue') }}</gl-link
>
<gl-link
- v-if="job.retry_path"
- :href="job.retry_path"
- class="js-retry-job btn btn-inverted-secondary"
- data-method="post"
- rel="nofollow"
- >{{ __('Retry') }}</gl-link
+ v-if="job.terminal_path"
+ :href="job.terminal_path"
+ class="js-terminal-link btn btn-primary btn-inverted visible-md-block visible-lg-block float-left"
+ target="_blank"
>
+ {{ __('Debug') }} <icon name="external-link" :size="14" />
+ </gl-link>
</div>
+
<div :class="{ block: renderBlock }">
<detail-row
v-if="job.duration"
@@ -193,16 +197,6 @@ export default {
tag
}}</span>
</p>
-
- <div v-if="job.cancel_path" class="btn-group prepend-top-5" role="group">
- <gl-link
- :href="job.cancel_path"
- class="js-cancel-job btn btn-sm btn-default"
- data-method="post"
- rel="nofollow"
- >{{ __('Cancel') }}</gl-link
- >
- </div>
</div>
<artifacts-block v-if="hasArtifact" :artifact="job.artifact" />
diff --git a/app/assets/javascripts/jobs/components/stages_dropdown.vue b/app/assets/javascripts/jobs/components/stages_dropdown.vue
index 7f79e92067f..91332c21b52 100644
--- a/app/assets/javascripts/jobs/components/stages_dropdown.vue
+++ b/app/assets/javascripts/jobs/components/stages_dropdown.vue
@@ -55,7 +55,7 @@ export default {
<ul class="dropdown-menu">
<li v-for="stage in stages" :key="stage.name">
- <button type="button" class="js-stage-item stage-item" @click="onStageClick(stage);">
+ <button type="button" class="js-stage-item stage-item" @click="onStageClick(stage)">
{{ stage.name }}
</button>
</li>
diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js
index 35e92b0b5d9..98911717381 100644
--- a/app/assets/javascripts/jobs/store/getters.js
+++ b/app/assets/javascripts/jobs/store/getters.js
@@ -1,22 +1,6 @@
import _ from 'underscore';
-import { __ } from '~/locale';
import { isScrolledToBottom } from '~/lib/utils/scroll_utils';
-export const headerActions = state => {
- if (state.job.new_issue_path) {
- return [
- {
- label: __('New issue'),
- path: state.job.new_issue_path,
- cssClass:
- 'js-new-issue btn btn-success btn-inverted d-none d-md-block d-lg-block d-xl-block',
- type: 'link',
- },
- ];
- }
- return [];
-};
-
export const headerTime = state => (state.job.started ? state.job.started : state.job.created_at);
export const shouldRenderCalloutMessage = state =>
diff --git a/app/assets/javascripts/layout_nav.js b/app/assets/javascripts/layout_nav.js
index b8c3c237eb3..4314e5e1afb 100644
--- a/app/assets/javascripts/layout_nav.js
+++ b/app/assets/javascripts/layout_nav.js
@@ -11,48 +11,53 @@ function hideEndFade($scrollingTabs) {
});
}
+function initDeferred() {
+ $(document).trigger('init.scrolling-tabs');
+}
+
export default function initLayoutNav() {
const contextualSidebar = new ContextualSidebar();
contextualSidebar.bindEvents();
initFlyOutNav();
- $(document)
- .on('init.scrolling-tabs', () => {
- const $scrollingTabs = $('.scrolling-tabs').not('.is-initialized');
- $scrollingTabs.addClass('is-initialized');
+ // We need to init it on DomContentLoaded as others could also call it
+ $(document).on('init.scrolling-tabs', () => {
+ const $scrollingTabs = $('.scrolling-tabs').not('.is-initialized');
+ $scrollingTabs.addClass('is-initialized');
- $(window)
- .on('resize.nav', () => {
- hideEndFade($scrollingTabs);
- })
- .trigger('resize.nav');
+ $(window)
+ .on('resize.nav', () => {
+ hideEndFade($scrollingTabs);
+ })
+ .trigger('resize.nav');
- $scrollingTabs.on('scroll', function tabsScrollEvent() {
- const $this = $(this);
- const currentPosition = $this.scrollLeft();
- const maxPosition = $this.prop('scrollWidth') - $this.outerWidth();
+ $scrollingTabs.on('scroll', function tabsScrollEvent() {
+ const $this = $(this);
+ const currentPosition = $this.scrollLeft();
+ const maxPosition = $this.prop('scrollWidth') - $this.outerWidth();
- $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0);
- $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1);
- });
+ $this.siblings('.fade-left').toggleClass('scrolling', currentPosition > 0);
+ $this.siblings('.fade-right').toggleClass('scrolling', currentPosition < maxPosition - 1);
+ });
- $scrollingTabs.each(function scrollTabsEachLoop() {
- const $this = $(this);
- const scrollingTabWidth = $this.width();
- const $active = $this.find('.active');
- const activeWidth = $active.width();
+ $scrollingTabs.each(function scrollTabsEachLoop() {
+ const $this = $(this);
+ const scrollingTabWidth = $this.width();
+ const $active = $this.find('.active');
+ const activeWidth = $active.width();
- if ($active.length) {
- const offset = $active.offset().left + activeWidth;
+ if ($active.length) {
+ const offset = $active.offset().left + activeWidth;
- if (offset > scrollingTabWidth - 30) {
- const scrollLeft = offset - scrollingTabWidth / 2 - activeWidth / 2;
+ if (offset > scrollingTabWidth - 30) {
+ const scrollLeft = offset - scrollingTabWidth / 2 - activeWidth / 2;
- $this.scrollLeft(scrollLeft);
- }
+ $this.scrollLeft(scrollLeft);
}
- });
- })
- .trigger('init.scrolling-tabs');
+ }
+ });
+ });
+
+ requestIdleCallback(initDeferred);
}
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index fc34d243dd7..3b6a57dad44 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -1,3 +1,7 @@
+/**
+ * @module common-utils
+ */
+
import $ from 'jquery';
import axios from './axios_utils';
import { getLocationHash } from './url_utility';
@@ -426,13 +430,14 @@ export const historyPushState = newUrl => {
};
/**
- * Returns true for a String "true" and false otherwise.
- * This is the opposite of Boolean(...).toString()
+ * Returns true for a String value of "true" and false otherwise.
+ * This is the opposite of Boolean(...).toString().
+ * `parseBoolean` is idempotent.
*
* @param {String} value
* @returns {Boolean}
*/
-export const parseBoolean = value => value === 'true';
+export const parseBoolean = value => (value && value.toString()) === 'true';
/**
* Converts permission provided as strings to booleans.
@@ -450,10 +455,16 @@ export const convertPermissionToBoolean = permission => {
};
/**
+ * @callback backOffCallback
+ * @param {Function} next
+ * @param {Function} stop
+ */
+
+/**
* Back Off exponential algorithm
* backOff :: (Function<next, stop>, Number) -> Promise<Any, Error>
*
- * @param {Function<next, stop>} fn function to be called
+ * @param {backOffCallback} fn function to be called
* @param {Number} timeout
* @return {Promise<Any, Error>}
* @example
diff --git a/app/assets/javascripts/locale/sprintf.js b/app/assets/javascripts/locale/sprintf.js
index 5246c49842e..68b64a3a16a 100644
--- a/app/assets/javascripts/locale/sprintf.js
+++ b/app/assets/javascripts/locale/sprintf.js
@@ -4,8 +4,8 @@ import _ from 'underscore';
Very limited implementation of sprintf supporting only named parameters.
@param input (translated) text with parameters (e.g. '%{num_users} users use us')
- @param parameters object mapping parameter names to values (e.g. { num_users: 5 })
- @param escapeParameters whether parameter values should be escaped (see http://underscorejs.org/#escape)
+ @param {Object} parameters object mapping parameter names to values (e.g. { num_users: 5 })
+ @param {Boolean} escapeParameters whether parameter values should be escaped (see http://underscorejs.org/#escape)
@returns {String} the text with parameters replaces (e.g. '5 users use us')
@see https://ruby-doc.org/core-2.3.3/Kernel.html#method-i-sprintf
diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue
index 64a1df80a8e..309b73f5a4d 100644
--- a/app/assets/javascripts/monitoring/components/graph.vue
+++ b/app/assets/javascripts/monitoring/components/graph.vue
@@ -257,8 +257,8 @@ export default {
<template>
<div
class="prometheus-graph"
- @mouseover="showFlagContent = true;"
- @mouseleave="showFlagContent = false;"
+ @mouseover="showFlagContent = true"
+ @mouseleave="showFlagContent = false"
>
<div class="prometheus-graph-header">
<h5 class="prometheus-graph-title">{{ graphData.title }}</h5>
@@ -300,7 +300,7 @@ export default {
:height="graphHeight - 100"
class="prometheus-graph-overlay"
transform="translate(-5, 20)"
- @mousemove="handleMouseOverGraph($event);"
+ @mousemove="handleMouseOverGraph($event)"
/>
</svg>
<svg v-else :viewBox="innerViewBox" class="js-no-data-to-display">
diff --git a/app/assets/javascripts/notes/components/comment_form.vue b/app/assets/javascripts/notes/components/comment_form.vue
index 7c17147dd01..d669ba5a8fa 100644
--- a/app/assets/javascripts/notes/components/comment_form.vue
+++ b/app/assets/javascripts/notes/components/comment_form.vue
@@ -357,9 +357,9 @@ js-gfm-input js-autosize markdown-area js-vue-textarea qa-comment-input"
data-supports-quick-actions="true"
aria-label="Description"
placeholder="Write a comment or drag your files here…"
- @keydown.up="editCurrentUserLastNote();"
- @keydown.meta.enter="handleSave();"
- @keydown.ctrl.enter="handleSave();"
+ @keydown.up="editCurrentUserLastNote()"
+ @keydown.meta.enter="handleSave()"
+ @keydown.ctrl.enter="handleSave()"
>
</textarea>
</markdown-field>
@@ -373,7 +373,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
class="btn btn-success js-comment-button js-comment-submit-button
qa-comment-button"
type="submit"
- @click.prevent="handleSave();"
+ @click.prevent="handleSave()"
>
{{ __(commentButtonTitle) }}
</button>
@@ -394,7 +394,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
<button
type="button"
class="btn btn-transparent"
- @click.prevent="setNoteType('comment');"
+ @click.prevent="setNoteType('comment')"
>
<i aria-hidden="true" class="fa fa-check icon"> </i>
<div class="description">
@@ -408,7 +408,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
<button
type="button"
class="btn btn-transparent qa-discussion-option"
- @click.prevent="setNoteType('discussion');"
+ @click.prevent="setNoteType('discussion')"
>
<i aria-hidden="true" class="fa fa-check icon"> </i>
<div class="description">
@@ -429,7 +429,7 @@ append-right-10 comment-type-dropdown js-comment-type-dropdown droplab-dropdown"
]"
:disabled="isToggleStateButtonLoading || isSubmitting"
:label="issueActionButtonTitle"
- @click="handleSave(true);"
+ @click="handleSave(true)"
/>
</div>
</form>
diff --git a/app/assets/javascripts/notes/components/discussion_filter.vue b/app/assets/javascripts/notes/components/discussion_filter.vue
index 2d7c04ea614..e03d6e9cd02 100644
--- a/app/assets/javascripts/notes/components/discussion_filter.vue
+++ b/app/assets/javascripts/notes/components/discussion_filter.vue
@@ -112,7 +112,7 @@ export default {
:class="{ 'is-active': filter.value === currentValue }"
class="qa-filter-options"
type="button"
- @click="selectFilter(filter.value);"
+ @click="selectFilter(filter.value)"
>
{{ filter.title }}
</button>
diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue
index bde00ea87ff..3efdd1c5c17 100644
--- a/app/assets/javascripts/notes/components/note_awards_list.vue
+++ b/app/assets/javascripts/notes/components/note_awards_list.vue
@@ -174,7 +174,7 @@ export default {
data-placement="bottom"
class="btn award-control"
type="button"
- @click="handleAward(awardName);"
+ @click="handleAward(awardName)"
>
<span v-html="getAwardHTML(awardName)"></span>
<span class="award-control-text js-counter">{{ awardList.length }}</span>
diff --git a/app/assets/javascripts/notes/components/note_form.vue b/app/assets/javascripts/notes/components/note_form.vue
index db62ddb3ecd..6dbb858e93d 100644
--- a/app/assets/javascripts/notes/components/note_form.vue
+++ b/app/assets/javascripts/notes/components/note_form.vue
@@ -219,10 +219,10 @@ export default {
class="note-textarea js-gfm-input js-note-text js-autosize markdown-area js-vue-issue-note-form js-vue-textarea qa-reply-input"
aria-label="Description"
placeholder="Write a comment or drag your files here…"
- @keydown.meta.enter="handleKeySubmit();"
- @keydown.ctrl.enter="handleKeySubmit();"
- @keydown.up="editMyLastNote();"
- @keydown.esc="cancelHandler(true);"
+ @keydown.meta.enter="handleKeySubmit()"
+ @keydown.ctrl.enter="handleKeySubmit()"
+ @keydown.up="editMyLastNote()"
+ @keydown.esc="cancelHandler(true)"
></textarea>
</markdown-field>
<div class="note-form-actions clearfix">
@@ -230,21 +230,21 @@ export default {
:disabled="isDisabled"
type="button"
class="js-vue-issue-save btn btn-success js-comment-button qa-reply-comment-button"
- @click="handleUpdate();"
+ @click="handleUpdate()"
>
{{ saveButtonTitle }}
</button>
<button
v-if="discussion.resolvable"
class="btn btn-nr btn-default append-right-10 js-comment-resolve-button"
- @click.prevent="handleUpdate(true);"
+ @click.prevent="handleUpdate(true)"
>
{{ resolveButtonTitle }}
</button>
<button
class="btn btn-cancel note-edit-cancel js-close-discussion-note-form"
type="button"
- @click="cancelHandler();"
+ @click="cancelHandler()"
>
Cancel
</button>
diff --git a/app/assets/javascripts/notes/components/noteable_discussion.vue b/app/assets/javascripts/notes/components/noteable_discussion.vue
index 7c3f5d00308..4480ec74182 100644
--- a/app/assets/javascripts/notes/components/noteable_discussion.vue
+++ b/app/assets/javascripts/notes/components/noteable_discussion.vue
@@ -440,7 +440,7 @@ Please check your network connection and try again.`;
<button
type="button"
class="btn btn-default ml-sm-2"
- @click="resolveHandler();"
+ @click="resolveHandler()"
>
<i v-if="isResolving" aria-hidden="true" class="fa fa-spinner fa-spin"></i>
{{ resolveButtonTitle }}
diff --git a/app/assets/javascripts/pages/projects/error_tracking/index.js b/app/assets/javascripts/pages/projects/error_tracking/index.js
new file mode 100644
index 00000000000..5a8fe137e9a
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/error_tracking/index.js
@@ -0,0 +1,5 @@
+import ErrorTracking from '~/error_tracking';
+
+document.addEventListener('DOMContentLoaded', () => {
+ ErrorTracking();
+});
diff --git a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
index db2a4041ec0..bd4309e47ad 100644
--- a/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
+++ b/app/assets/javascripts/pages/projects/pipeline_schedules/shared/components/interval_pattern_input.vue
@@ -70,7 +70,7 @@ export default {
:checked="isEditable"
class="label-bold"
type="radio"
- @click="toggleCustomInput(true);"
+ @click="toggleCustomInput(true)"
/>
<label for="custom"> {{ s__('PipelineSheduleIntervalPattern|Custom') }} </label>
@@ -88,7 +88,7 @@ export default {
:value="cronIntervalPresets.everyDay"
class="label-bold"
type="radio"
- @click="toggleCustomInput(false);"
+ @click="toggleCustomInput(false)"
/>
<label class="label-bold" for="every-day"> {{ __('Every day (at 4:00am)') }} </label>
@@ -102,7 +102,7 @@ export default {
:value="cronIntervalPresets.everyWeek"
class="label-bold"
type="radio"
- @click="toggleCustomInput(false);"
+ @click="toggleCustomInput(false)"
/>
<label class="label-bold" for="every-week">
@@ -118,7 +118,7 @@ export default {
:value="cronIntervalPresets.everyMonth"
class="label-bold"
type="radio"
- @click="toggleCustomInput(false);"
+ @click="toggleCustomInput(false)"
/>
<label class="label-bold" for="every-month">
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index 59cebaba717..a49dc311bd0 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -1,59 +1,20 @@
<script>
-import _ from 'underscore';
import { GlLoadingIcon } from '@gitlab/ui';
import StageColumnComponent from './stage_column_component.vue';
+import GraphMixin from '../../mixins/graph_component_mixin';
export default {
components: {
StageColumnComponent,
GlLoadingIcon,
},
- props: {
- isLoading: {
- type: Boolean,
- required: true,
- },
- pipeline: {
- type: Object,
- required: true,
- },
- },
- computed: {
- graph() {
- return this.pipeline.details && this.pipeline.details.stages;
- },
- },
- methods: {
- capitalizeStageName(name) {
- const escapedName = _.escape(name);
- return escapedName.charAt(0).toUpperCase() + escapedName.slice(1);
- },
- isFirstColumn(index) {
- return index === 0;
- },
- stageConnectorClass(index, stage) {
- let className;
-
- // If it's the first stage column and only has one job
- if (index === 0 && stage.groups.length === 1) {
- className = 'no-margin';
- } else if (index > 0) {
- // If it is not the first column
- className = 'left-margin';
- }
-
- return className;
- },
- refreshPipelineGraph() {
- this.$emit('refreshPipelineGraph');
- },
- },
+ mixins: [GraphMixin],
};
</script>
<template>
<div class="build-content middle-block js-pipeline-graph">
<div class="pipeline-visualization pipeline-graph pipeline-tab-content">
- <div class="text-center"><gl-loading-icon v-if="isLoading" :size="3" /></div>
+ <div v-if="isLoading" class="m-auto"><gl-loading-icon :size="3" /></div>
<ul v-if="!isLoading" class="stage-column-list">
<stage-column-component
diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
index 2e9f2519fcb..0152e2fbe04 100644
--- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue
@@ -77,7 +77,7 @@ export default {
:class="{ disabled: isActionDisabled(action) }"
:disabled="isActionDisabled(action)"
class="js-pipeline-action-link no-btn btn"
- @click="onClickAction(action);"
+ @click="onClickAction(action)"
>
{{ action.name }}
<span v-if="action.scheduled_at" class="pull-right">
diff --git a/app/assets/javascripts/pipelines/mixins/graph_component_mixin.js b/app/assets/javascripts/pipelines/mixins/graph_component_mixin.js
new file mode 100644
index 00000000000..66e9476dadf
--- /dev/null
+++ b/app/assets/javascripts/pipelines/mixins/graph_component_mixin.js
@@ -0,0 +1,44 @@
+import _ from 'underscore';
+
+export default {
+ props: {
+ isLoading: {
+ type: Boolean,
+ required: true,
+ },
+ pipeline: {
+ type: Object,
+ required: true,
+ },
+ },
+ computed: {
+ graph() {
+ return this.pipeline.details && this.pipeline.details.stages;
+ },
+ },
+ methods: {
+ capitalizeStageName(name) {
+ const escapedName = _.escape(name);
+ return escapedName.charAt(0).toUpperCase() + escapedName.slice(1);
+ },
+ isFirstColumn(index) {
+ return index === 0;
+ },
+ stageConnectorClass(index, stage) {
+ let className;
+
+ // If it's the first stage column and only has one job
+ if (index === 0 && stage.groups.length === 1) {
+ className = 'no-margin';
+ } else if (index > 0) {
+ // If it is not the first column
+ className = 'left-margin';
+ }
+
+ return className;
+ },
+ refreshPipelineGraph() {
+ this.$emit('refreshPipelineGraph');
+ },
+ },
+};
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue
index 21095fcba16..83811ab489a 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue
+++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_machine_type_dropdown.vue
@@ -108,9 +108,7 @@ export default {
</span>
</li>
<li v-for="result in results" :key="result.id">
- <button type="button" @click.prevent="setItem(result.name);">
- {{ result.name }}
- </button>
+ <button type="button" @click.prevent="setItem(result.name)">{{ result.name }}</button>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue
index 056584c8865..a2eb79af4f9 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue
+++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_project_id_dropdown.vue
@@ -169,7 +169,7 @@ export default {
</span>
</li>
<li v-for="result in results" :key="result.project_number">
- <button type="button" @click.prevent="setItem(result);">{{ result.name }}</button>
+ <button type="button" @click.prevent="setItem(result)">{{ result.name }}</button>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue
index 728616a441f..5f8a4946f4a 100644
--- a/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue
+++ b/app/assets/javascripts/projects/gke_cluster_dropdowns/components/gke_zone_dropdown.vue
@@ -82,9 +82,7 @@ export default {
</span>
</li>
<li v-for="result in results" :key="result.id">
- <button type="button" @click.prevent="setItem(result.name);">
- {{ result.name }}
- </button>
+ <button type="button" @click.prevent="setItem(result.name)">{{ result.name }}</button>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/registry/components/table_registry.vue b/app/assets/javascripts/registry/components/table_registry.vue
index 2c19973a114..81fe0a48c06 100644
--- a/app/assets/javascripts/registry/components/table_registry.vue
+++ b/app/assets/javascripts/registry/components/table_registry.vue
@@ -106,7 +106,7 @@ export default {
:aria-label="s__('ContainerRegistry|Remove tag')"
variant="danger"
class="js-delete-registry d-none d-sm-block float-right"
- @click="handleDeleteRegistry(item);"
+ @click="handleDeleteRegistry(item)"
>
<icon name="remove" />
</gl-button>
diff --git a/app/assets/javascripts/reports/components/modal_open_name.vue b/app/assets/javascripts/reports/components/modal_open_name.vue
index 118e4b02c46..4f81cee2a38 100644
--- a/app/assets/javascripts/reports/components/modal_open_name.vue
+++ b/app/assets/javascripts/reports/components/modal_open_name.vue
@@ -26,7 +26,7 @@ export default {
<button
type="button"
class="btn-link btn-blank text-left break-link vulnerability-name-button"
- @click="handleIssueClick();"
+ @click="handleIssueClick()"
>
{{ issue.title }}
</button>
diff --git a/app/assets/javascripts/reports/components/test_issue_body.vue b/app/assets/javascripts/reports/components/test_issue_body.vue
index 938e83de546..7700f49bf7d 100644
--- a/app/assets/javascripts/reports/components/test_issue_body.vue
+++ b/app/assets/javascripts/reports/components/test_issue_body.vue
@@ -30,7 +30,7 @@ export default {
<button
type="button"
class="btn-link btn-blank text-left break-link vulnerability-name-button"
- @click="openModal({ issue });"
+ @click="openModal({ issue })"
>
<div v-if="isNew" class="badge badge-danger append-right-5">{{ s__('New') }}</div>
{{ issue.name }}
diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
index f04f7606976..7f86741ed29 100644
--- a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
+++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue
@@ -219,7 +219,7 @@ export default {
name="button"
type="button"
class="js-clear-user-status-button clear-user-status btn"
- @click="clearStatusInputs();"
+ @click="clearStatusInputs()"
>
<icon name="close" />
</button>
diff --git a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
index d3a4f9c81e0..c03b2a68c78 100644
--- a/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
+++ b/app/assets/javascripts/sidebar/components/time_tracking/time_tracker.vue
@@ -102,13 +102,13 @@ export default {
/>
<div class="title hide-collapsed">
{{ __('Time tracking') }}
- <div v-if="!showHelpState" class="help-button float-right" @click="toggleHelpState(true);">
+ <div v-if="!showHelpState" class="help-button float-right" @click="toggleHelpState(true)">
<i class="fa fa-question-circle" aria-hidden="true"> </i>
</div>
<div
v-if="showHelpState"
class="close-help-button float-right"
- @click="toggleHelpState(false);"
+ @click="toggleHelpState(false)"
>
<i class="fa fa-close" aria-hidden="true"> </i>
</div>
diff --git a/app/assets/javascripts/terminal/terminal.js b/app/assets/javascripts/terminal/terminal.js
index 560f50ebf8f..e5dd7a465ea 100644
--- a/app/assets/javascripts/terminal/terminal.js
+++ b/app/assets/javascripts/terminal/terminal.js
@@ -2,11 +2,13 @@ import _ from 'underscore';
import $ from 'jquery';
import { Terminal } from 'xterm';
import * as fit from 'xterm/lib/addons/fit/fit';
+import * as webLinks from 'xterm/lib/addons/webLinks/webLinks';
import { canScrollUp, canScrollDown } from '~/lib/utils/dom_utils';
const SCROLL_MARGIN = 5;
Terminal.applyAddon(fit);
+Terminal.applyAddon(webLinks);
export default class GLTerminal {
constructor(element, options = {}) {
@@ -48,6 +50,7 @@ export default class GLTerminal {
this.terminal.open(this.container);
this.terminal.fit();
+ this.terminal.webLinksInit();
this.terminal.focus();
this.socket.onopen = () => {
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 84c8a3464a5..0cafa73362e 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
@@ -12,7 +12,7 @@ export default {
name: 'ReadyToMerge',
components: {
statusIcon,
- 'squash-before-merge': SquashBeforeMerge,
+ SquashBeforeMerge,
},
props: {
mr: { type: Object, required: true },
@@ -28,6 +28,7 @@ export default {
isMakingRequest: false,
isMergingImmediately: false,
commitMessage: this.mr.commitMessage,
+ squashBeforeMerge: this.mr.squash,
successSvg,
warningSvg,
};
@@ -110,12 +111,6 @@ export default {
return enableSquashBeforeMerge && commitsCount > 1;
},
},
- created() {
- eventHub.$on('MRWidgetUpdateSquash', this.handleUpdateSquash);
- },
- beforeDestroy() {
- eventHub.$off('MRWidgetUpdateSquash', this.handleUpdateSquash);
- },
methods: {
shouldShowMergeControls() {
return this.mr.isMergeAllowed || this.shouldShowMergeWhenPipelineSucceedsText;
@@ -143,7 +138,7 @@ export default {
commit_message: this.commitMessage,
merge_when_pipeline_succeeds: this.setToMergeWhenPipelineSucceeds,
should_remove_source_branch: this.removeSourceBranch === true,
- squash: this.mr.squash,
+ squash: this.squashBeforeMerge,
};
this.isMakingRequest = true;
@@ -166,9 +161,6 @@ export default {
new Flash('Something went wrong. Please try again.'); // eslint-disable-line
});
},
- handleUpdateSquash(val) {
- this.mr.squash = val;
- },
initiateMergePolling() {
simplePoll((continuePolling, stopPolling) => {
this.handleMergePolling(continuePolling, stopPolling);
@@ -249,7 +241,7 @@ export default {
:class="mergeButtonClass"
type="button"
class="qa-merge-button"
- @click="handleMergeButtonClick();"
+ @click="handleMergeButtonClick()"
>
<i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
{{ mergeButtonText }}
@@ -273,7 +265,7 @@ export default {
<a
class="merge_when_pipeline_succeeds qa-merge-when-pipeline-succeeds-option"
href="#"
- @click.prevent="handleMergeButtonClick(true);"
+ @click.prevent="handleMergeButtonClick(true)"
>
<span class="media">
<span class="merge-opt-icon" aria-hidden="true" v-html="successSvg"></span>
@@ -285,7 +277,7 @@ export default {
<a
class="accept-merge-request qa-merge-immediately-option"
href="#"
- @click.prevent="handleMergeButtonClick(false, true);"
+ @click.prevent="handleMergeButtonClick(false, true)"
>
<span class="media">
<span class="merge-opt-icon" aria-hidden="true" v-html="warningSvg"></span>
@@ -311,8 +303,9 @@ export default {
<!-- Placeholder for EE extension of this component -->
<squash-before-merge
v-if="shouldShowSquashBeforeMerge"
- :mr="mr"
- :is-merge-button-disabled="isMergeButtonDisabled"
+ v-model="squashBeforeMerge"
+ :help-path="mr.squashBeforeMergeHelpPath"
+ :is-disabled="isMergeButtonDisabled"
/>
<span v-if="mr.ffOnlyEnabled" class="js-fast-forward-message">
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
index e71acf0d7dd..b1f5655a15a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/squash_before_merge.vue
@@ -1,6 +1,5 @@
<script>
import Icon from '~/vue_shared/components/icon.vue';
-import eventHub from '~/vue_merge_request_widget/event_hub';
import tooltip from '~/vue_shared/directives/tooltip';
export default {
@@ -11,23 +10,19 @@ export default {
tooltip,
},
props: {
- mr: {
- type: Object,
- required: true,
- },
- isMergeButtonDisabled: {
+ value: {
type: Boolean,
required: true,
},
- },
- data() {
- return {
- squashBeforeMerge: this.mr.squash,
- };
- },
- methods: {
- updateSquashModel() {
- eventHub.$emit('MRWidgetUpdateSquash', this.squashBeforeMerge);
+ helpPath: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ isDisabled: {
+ type: Boolean,
+ required: false,
+ default: false,
},
},
};
@@ -37,18 +32,19 @@ export default {
<div class="accept-control inline">
<label class="merge-param-checkbox">
<input
- v-model="squashBeforeMerge"
- :disabled="isMergeButtonDisabled"
+ :checked="value"
+ :disabled="isDisabled"
type="checkbox"
name="squash"
class="qa-squash-checkbox"
- @change="updateSquashModel"
+ @change="$emit('input', $event.target.checked)"
/>
{{ __('Squash commits') }}
</label>
<a
+ v-if="helpPath"
v-tooltip
- :href="mr.squashBeforeMergeHelpPath"
+ :href="helpPath"
data-title="About this feature"
data-placement="bottom"
target="_blank"
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index b7f12076958..5a9d86594b1 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -32,7 +32,6 @@ import MRWidgetStore from './stores/ee_switch_mr_widget_store';
import MRWidgetService from './services/ee_switch_mr_widget_service';
import eventHub from './event_hub';
import stateMaps from './stores/ee_switch_state_maps';
-import SquashBeforeMerge from './components/states/squash_before_merge.vue';
import notify from '~/lib/utils/notify';
import SourceBranchRemovalStatus from './components/source_branch_removal_status.vue';
import GroupedTestReportsApp from '../reports/components/grouped_test_reports_app.vue';
@@ -59,7 +58,6 @@ export default {
'mr-widget-missing-branch': MissingBranchState,
'mr-widget-ready-to-merge': ReadyToMergeState,
'sha-mismatch': ShaMismatchState,
- 'mr-widget-squash-before-merge': SquashBeforeMerge,
'mr-widget-checking': CheckingState,
'mr-widget-unresolved-discussions': UnresolvedDiscussionsState,
'mr-widget-pipeline-blocked': PipelineBlockedState,
diff --git a/app/assets/javascripts/vue_shared/components/bar_chart.vue b/app/assets/javascripts/vue_shared/components/bar_chart.vue
index 4abf795f7bd..eabf5d4bf60 100644
--- a/app/assets/javascripts/vue_shared/components/bar_chart.vue
+++ b/app/assets/javascripts/vue_shared/components/bar_chart.vue
@@ -293,8 +293,8 @@ export default {
:title="setTooltipTitle(data)"
class="bar-rect"
data-placement="top"
- @mouseover="barHoveredIn(index);"
- @mouseout="barHoveredOut(index);"
+ @mouseover="barHoveredIn(index)"
+ @mouseout="barHoveredOut(index)"
/>
</template>
</g>
diff --git a/app/assets/javascripts/vue_shared/components/deprecated_modal.vue b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
index 2129f90d497..36b3ee05456 100644
--- a/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/deprecated_modal.vue
@@ -95,7 +95,7 @@ export default {
class="close float-right"
data-dismiss="modal"
aria-label="Close"
- @click="emitCancel($event);"
+ @click="emitCancel($event)"
>
<span aria-hidden="true">&times;</span>
</button>
@@ -112,7 +112,7 @@ export default {
type="button"
class="btn"
data-dismiss="modal"
- @click="emitCancel($event);"
+ @click="emitCancel($event)"
>
{{ closeButtonLabel }}
</button>
@@ -130,7 +130,7 @@ export default {
type="button"
class="btn js-primary-button"
data-dismiss="modal"
- @click="emitSubmit($event);"
+ @click="emitSubmit($event)"
>
{{ primaryButtonLabel }}
</button>
diff --git a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue
index d5fda7e4ed3..cab92297ca7 100644
--- a/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue
+++ b/app/assets/javascripts/vue_shared/components/diff_viewer/viewers/image_diff_viewer.vue
@@ -75,7 +75,7 @@ export default {
:class="{
active: mode === $options.imageViewMode.twoup,
}"
- @click="changeMode($options.imageViewMode.twoup);"
+ @click="changeMode($options.imageViewMode.twoup)"
>
{{ s__('ImageDiffViewer|2-up') }}
</li>
@@ -83,7 +83,7 @@ export default {
:class="{
active: mode === $options.imageViewMode.swipe,
}"
- @click="changeMode($options.imageViewMode.swipe);"
+ @click="changeMode($options.imageViewMode.swipe)"
>
{{ s__('ImageDiffViewer|Swipe') }}
</li>
@@ -91,7 +91,7 @@ export default {
:class="{
active: mode === $options.imageViewMode.onion,
}"
- @click="changeMode($options.imageViewMode.onion);"
+ @click="changeMode($options.imageViewMode.onion)"
>
{{ s__('ImageDiffViewer|Onion skin') }}
</li>
diff --git a/app/assets/javascripts/vue_shared/components/file_row.vue b/app/assets/javascripts/vue_shared/components/file_row.vue
index 4c884c55a30..f54033efc54 100644
--- a/app/assets/javascripts/vue_shared/components/file_row.vue
+++ b/app/assets/javascripts/vue_shared/components/file_row.vue
@@ -139,8 +139,8 @@ export default {
class="file-row"
role="button"
@click="clickFile"
- @mouseover="toggleHover(true);"
- @mouseout="toggleHover(false);"
+ @mouseover="toggleHover(true)"
+ @mouseout="toggleHover(false)"
>
<div class="file-row-name-container">
<span ref="textOutput" :style="levelIndentation" class="file-row-name str-truncated">
diff --git a/app/assets/javascripts/vue_shared/components/gl_modal.vue b/app/assets/javascripts/vue_shared/components/gl_modal.vue
index faf4181bbaf..438851e5ac7 100644
--- a/app/assets/javascripts/vue_shared/components/gl_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/gl_modal.vue
@@ -81,7 +81,7 @@ export default {
type="button"
class="close js-modal-close-action"
data-dismiss="modal"
- @click="emitCancel($event);"
+ @click="emitCancel($event)"
>
<span aria-hidden="true">&times;</span>
</button>
@@ -96,7 +96,7 @@ export default {
type="button"
class="btn js-modal-cancel-action qa-modal-cancel-button"
data-dismiss="modal"
- @click="emitCancel($event);"
+ @click="emitCancel($event)"
>
{{ s__('Modal|Cancel') }}
</button>
@@ -105,7 +105,7 @@ export default {
type="button"
class="btn js-modal-primary-action qa-modal-primary-button"
data-dismiss="modal"
- @click="emitSubmit($event);"
+ @click="emitSubmit($event)"
>
{{ footerPrimaryButtonText }}
</button>
diff --git a/app/assets/javascripts/vue_shared/components/header_ci_component.vue b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
index c830f5b49b6..3f45dc7853b 100644
--- a/app/assets/javascripts/vue_shared/components/header_ci_component.vue
+++ b/app/assets/javascripts/vue_shared/components/header_ci_component.vue
@@ -148,7 +148,7 @@ export default {
:class="action.cssClass"
container-class="d-inline"
:label="action.label"
- @click="onClickAction(action);"
+ @click="onClickAction(action)"
/>
</template>
</section>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue
index 937a2847a58..cc07ef46064 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/field.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue
@@ -182,9 +182,9 @@ export default {
this.hasSuggestion = data.references.suggestions && data.references.suggestions.length;
}
- this.$nextTick(() => {
- $(this.$refs['markdown-preview']).renderGFM();
- });
+ this.$nextTick()
+ .then(() => $(this.$refs['markdown-preview']).renderGFM())
+ .catch(() => new Flash(__('Error rendering markdown preview')));
},
versionedPreviewPath() {
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index bf4d42670ee..dbfa32cd0ce 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -78,12 +78,7 @@ export default {
<div class="md-header">
<ul class="nav-links clearfix">
<li :class="{ active: !previewMarkdown }" class="md-header-tab">
- <button
- class="js-write-link"
- tabindex="-1"
- type="button"
- @click="writeMarkdownTab($event);"
- >
+ <button class="js-write-link" tabindex="-1" type="button" @click="writeMarkdownTab($event)">
Write
</button>
</li>
@@ -92,7 +87,7 @@ export default {
class="js-preview-link js-md-preview-button"
tabindex="-1"
type="button"
- @click="previewMarkdownTab($event);"
+ @click="previewMarkdownTab($event)"
>
Preview
</button>
diff --git a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
index 563e2f94fcc..c5a2aa1f2af 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/suggestion_diff_header.vue
@@ -42,7 +42,7 @@ export default {
<div class="md-suggestion-header border-bottom-0 mt-2">
<div class="qa-suggestion-diff-header font-weight-bold">
{{ __('Suggested change') }}
- <a v-if="helpPagePath" :href="helpPagePath" :aria-label="__('Help')">
+ <a v-if="helpPagePath" :href="helpPagePath" :aria-label="__('Help')" class="js-help-btn">
<icon name="question-o" css-classes="link-highlight" />
</a>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
index 09a64502819..f8983a3d29a 100644
--- a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
+++ b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue
@@ -58,7 +58,7 @@ export default {
active: tab.isActive,
}"
>
- <a :class="`js-${scope}-tab-${tab.scope}`" role="button" @click="onTabClick(tab);">
+ <a :class="`js-${scope}-tab-${tab.scope}`" role="button" @click="onTabClick(tab)">
{{ tab.name }}
<span v-if="shouldRenderBadge(tab.count)" class="badge badge-pill"> {{ tab.count }} </span>
diff --git a/app/assets/javascripts/vue_shared/components/notes/system_note.vue b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
index 31df26f7b05..b0af8399955 100644
--- a/app/assets/javascripts/vue_shared/components/notes/system_note.vue
+++ b/app/assets/javascripts/vue_shared/components/notes/system_note.vue
@@ -97,7 +97,7 @@ export default {
v-html="note.note_html"
></div>
<div v-if="hasMoreCommits" class="flex-list">
- <div class="system-note-commit-list-toggler flex-row" @click="expanded = !expanded;">
+ <div class="system-note-commit-list-toggler flex-row" @click="expanded = !expanded">
<icon :name="toggleIcon" :size="8" class="append-right-5" />
<span>Toggle commit list</span>
</div>
diff --git a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
index 1c6c3fc4734..df19906309c 100644
--- a/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
+++ b/app/assets/javascripts/vue_shared/components/recaptcha_modal.vue
@@ -19,7 +19,7 @@ export default {
data() {
return {
script: {},
- scriptSrc: 'https://www.google.com/recaptcha/api.js',
+ scriptSrc: 'https://www.recaptcha.net/recaptcha/api.js',
};
},
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
index 82067129c57..6c0c7f15943 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
@@ -134,7 +134,7 @@ export default {
<button
type="button"
class="btn-blank btn-link btn-secondary-hover-link"
- @click="newDateSelected(null);"
+ @click="newDateSelected(null)"
>
remove
</button>
diff --git a/app/assets/javascripts/vue_shared/components/table_pagination.vue b/app/assets/javascripts/vue_shared/components/table_pagination.vue
index 01e655d27e5..2a34b4630f2 100644
--- a/app/assets/javascripts/vue_shared/components/table_pagination.vue
+++ b/app/assets/javascripts/vue_shared/components/table_pagination.vue
@@ -149,7 +149,7 @@ export default {
}"
class="page-item"
>
- <a class="page-link" @click.prevent="changePage(item.title, item.disabled);">
+ <a class="page-link" @click.prevent="changePage(item.title, item.disabled)">
{{ item.title }}
</a>
</li>
diff --git a/app/assets/stylesheets/bootstrap_migration.scss b/app/assets/stylesheets/bootstrap_migration.scss
index 587127bb059..c8357f7751c 100644
--- a/app/assets/stylesheets/bootstrap_migration.scss
+++ b/app/assets/stylesheets/bootstrap_migration.scss
@@ -225,7 +225,7 @@ h3.popover-header {
}
.info-well {
- background: $theme-gray-50;
+ background: $gray-50;
color: $gl-text-color;
border: 1px solid $border-color;
border-radius: 4px;
diff --git a/app/assets/stylesheets/framework/animations.scss b/app/assets/stylesheets/framework/animations.scss
index 43d4044033f..4fb787887a1 100644
--- a/app/assets/stylesheets/framework/animations.scss
+++ b/app/assets/stylesheets/framework/animations.scss
@@ -204,7 +204,7 @@ a {
[class^="skeleton-line-"] {
position: relative;
- background-color: $theme-gray-100;
+ background-color: $gray-100;
height: 10px;
overflow: hidden;
@@ -220,10 +220,10 @@ a {
background-size: cover;
background-image: linear-gradient(
to right,
- $theme-gray-100 0%,
- $theme-gray-50 20%,
- $theme-gray-100 40%,
- $theme-gray-100 100%
+ $gray-100 0%,
+ $gray-50 20%,
+ $gray-100 40%,
+ $gray-100 100%
);
height: 10px;
}
diff --git a/app/assets/stylesheets/framework/awards.scss b/app/assets/stylesheets/framework/awards.scss
index 7a95db5976d..ad650d45314 100644
--- a/app/assets/stylesheets/framework/awards.scss
+++ b/app/assets/stylesheets/framework/awards.scss
@@ -176,7 +176,7 @@
&.user-authored {
cursor: default;
background-color: $gray-light;
- border-color: $theme-gray-200;
+ border-color: $gray-200;
color: $gl-text-color-disabled;
gl-emoji {
diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss
index a4a9276c580..5d2cbdde8dc 100644
--- a/app/assets/stylesheets/framework/buttons.scss
+++ b/app/assets/stylesheets/framework/buttons.scss
@@ -483,7 +483,7 @@
// All disabled buttons, regardless of color, type, etc
%disabled {
background-color: $gray-light !important;
- border-color: $theme-gray-200 !important;
+ border-color: $gray-200 !important;
color: $gl-text-color-disabled !important;
opacity: 1 !important;
cursor: default !important;
diff --git a/app/assets/stylesheets/framework/dropdowns.scss b/app/assets/stylesheets/framework/dropdowns.scss
index afcb230797a..b90db135b4a 100644
--- a/app/assets/stylesheets/framework/dropdowns.scss
+++ b/app/assets/stylesheets/framework/dropdowns.scss
@@ -32,6 +32,10 @@
max-height: $dropdown-max-height;
overflow-y: auto;
+ &.dropdown-extended-height {
+ max-height: 400px;
+ }
+
@include media-breakpoint-down(xs) {
width: 100%;
}
@@ -125,7 +129,7 @@
@extend .dropdown-toggle;
padding-right: 25px;
position: relative;
- width: 163px;
+ width: 160px;
text-overflow: ellipsis;
overflow: hidden;
diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss
index 0a0ef2071e9..cbf9ee24ec5 100644
--- a/app/assets/stylesheets/framework/forms.scss
+++ b/app/assets/stylesheets/framework/forms.scss
@@ -261,7 +261,7 @@ label {
right: 0.8em;
top: 50%;
transform: translateY(-50%);
- color: $theme-gray-600;
+ color: $gray-600;
}
}
diff --git a/app/assets/stylesheets/framework/gitlab_theme.scss b/app/assets/stylesheets/framework/gitlab_theme.scss
index 0ef50e139f2..418eafa153c 100644
--- a/app/assets/stylesheets/framework/gitlab_theme.scss
+++ b/app/assets/stylesheets/framework/gitlab_theme.scss
@@ -282,31 +282,31 @@ body {
&.ui-dark {
@include gitlab-theme(
- $theme-gray-200,
- $theme-gray-500,
- $theme-gray-700,
- $theme-gray-800,
- $theme-gray-900,
+ $gray-200,
+ $gray-500,
+ $gray-700,
+ $gray-800,
+ $gray-900,
$white-light
);
}
&.ui-light {
@include gitlab-theme(
- $theme-gray-700,
- $theme-gray-800,
- $theme-gray-700,
- $theme-gray-700,
- $theme-gray-100,
- $theme-gray-700
+ $gray-700,
+ $gray-800,
+ $gray-700,
+ $gray-700,
+ $gray-100,
+ $gray-700
);
.navbar-gitlab {
- background-color: $theme-gray-100;
+ background-color: $gray-100;
box-shadow: 0 1px 0 0 $border-color;
.logo-text svg {
- fill: $theme-gray-900;
+ fill: $gray-900;
}
.navbar-sub-nav,
@@ -315,7 +315,7 @@ body {
> a:hover,
> a:focus,
> button:hover {
- color: $theme-gray-900;
+ color: $gray-900;
}
&.active > a,
@@ -329,8 +329,8 @@ body {
.container-fluid {
.navbar-toggler,
.navbar-toggler:hover {
- color: $theme-gray-700;
- border-left: 1px solid $theme-gray-200;
+ color: $gray-700;
+ border-left: 1px solid $gray-200;
}
}
}
@@ -348,7 +348,7 @@ body {
.search-input-wrap {
.search-icon {
- fill: $theme-gray-200;
+ fill: $gray-200;
}
.search-input {
@@ -359,16 +359,16 @@ body {
.nav-sidebar li.active {
> a {
- color: $theme-gray-900;
+ color: $gray-900;
}
svg {
- fill: $theme-gray-900;
+ fill: $gray-900;
}
}
.sidebar-top-level-items > li.active .badge.badge-pill {
- color: $theme-gray-900;
+ color: $gray-900;
}
}
}
diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss
index 5574873fa22..36dd1cee4de 100644
--- a/app/assets/stylesheets/framework/header.scss
+++ b/app/assets/stylesheets/framework/header.scss
@@ -596,6 +596,10 @@
.emoji-menu-toggle-button {
@include emoji-menu-toggle-button;
}
+
+ .input-group {
+ height: 34px;
+ }
}
.nav-links > li > a {
diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss
index 8db7d63266e..49b9b7014ae 100644
--- a/app/assets/stylesheets/framework/icons.scss
+++ b/app/assets/stylesheets/framework/icons.scss
@@ -87,8 +87,8 @@
display: flex;
align-items: center;
justify-content: center;
- border: $border-size solid $theme-gray-400;
+ border: $border-size solid $gray-400;
border-radius: 50%;
padding: $gl-padding-8 - $border-size;
- color: $theme-gray-700;
+ color: $gray-700;
}
diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss
index ce46d760d7b..679148ddf7b 100644
--- a/app/assets/stylesheets/framework/markdown_area.scss
+++ b/app/assets/stylesheets/framework/markdown_area.scss
@@ -158,7 +158,7 @@
max-height: calc(100vh - 100px);
}
- table {
+ table:not(.js-syntax-highlight) {
@include markdown-table;
}
}
diff --git a/app/assets/stylesheets/framework/stacked_progress_bar.scss b/app/assets/stylesheets/framework/stacked_progress_bar.scss
index 29a2d5881f7..2255d3be75b 100644
--- a/app/assets/stylesheets/framework/stacked_progress_bar.scss
+++ b/app/assets/stylesheets/framework/stacked_progress_bar.scss
@@ -3,7 +3,7 @@
height: 16px;
border-radius: 10px;
overflow: hidden;
- background-color: $theme-gray-100;
+ background-color: $gray-100;
.status-unavailable,
.status-green,
@@ -24,7 +24,7 @@
.status-unavailable {
padding: 0 10px;
- color: $theme-gray-700;
+ color: $gray-700;
}
.status-green {
@@ -36,11 +36,11 @@
}
.status-neutral {
- background-color: $theme-gray-200;
+ background-color: $gray-200;
color: $gl-gray-dark;
&:hover {
- background-color: $theme-gray-300;
+ background-color: $gray-300;
}
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index 0c81dc2e156..45dab036d35 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -143,7 +143,7 @@
margin: 0 0 16px;
}
- table {
+ table:not(.js-syntax-highlight) {
@extend .table;
@extend .table-bordered;
margin: 16px 0;
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 242977e8543..c1666c728f3 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -97,6 +97,18 @@ $red-800: #8b2615;
$red-900: #711e11;
$red-950: #4b140b;
+$gray-50: #fafafa;
+$gray-100: #f2f2f2;
+$gray-200: #dfdfdf;
+$gray-300: #cccccc;
+$gray-400: #bababa;
+$gray-500: #a7a7a7;
+$gray-600: #919191;
+$gray-700: #707070;
+$gray-800: #4f4f4f;
+$gray-900: #2e2e2e;
+$gray-950: #1f1f1f;
+
// GitLab themes
$indigo-50: #f7f7ff;
@@ -111,18 +123,6 @@ $indigo-800: #393982;
$indigo-900: #292961;
$indigo-950: #1a1a40;
-$theme-gray-50: #fafafa;
-$theme-gray-100: #f2f2f2;
-$theme-gray-200: #dfdfdf;
-$theme-gray-300: #cccccc;
-$theme-gray-400: #bababa;
-$theme-gray-500: #a7a7a7;
-$theme-gray-600: #919191;
-$theme-gray-700: #707070;
-$theme-gray-800: #4f4f4f;
-$theme-gray-900: #2e2e2e;
-$theme-gray-950: #1f1f1f;
-
$theme-blue-50: #f4f8fc;
$theme-blue-100: #e6edf5;
$theme-blue-200: #c8d7e6;
diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss
index 069f45bff49..d5f8e3fb4ee 100644
--- a/app/assets/stylesheets/framework/variables_overrides.scss
+++ b/app/assets/stylesheets/framework/variables_overrides.scss
@@ -5,7 +5,7 @@
$secondary: $gray-light;
$input-disabled-bg: $gray-light;
-$input-border-color: $theme-gray-200;
+$input-border-color: $gray-200;
$input-color: $gl-text-color;
$font-family-sans-serif: $regular-font;
$font-family-monospace: $monospace-font;
@@ -20,7 +20,7 @@ $warning: $orange-500;
$danger: $red-500;
$zindex-modal-backdrop: 1040;
$nav-divider-margin-y: ($grid-size / 2);
-$dropdown-divider-bg: $theme-gray-200;
+$dropdown-divider-bg: $gray-200;
$dropdown-item-padding-y: 8px;
$dropdown-item-padding-x: 12px;
$popover-max-width: 300px;
diff --git a/app/assets/stylesheets/page_bundles/ide.scss b/app/assets/stylesheets/page_bundles/ide.scss
index 98d0a2d43ea..553cc44fe83 100644
--- a/app/assets/stylesheets/page_bundles/ide.scss
+++ b/app/assets/stylesheets/page_bundles/ide.scss
@@ -130,7 +130,7 @@ $ide-commit-header-height: 48px;
background: none;
border: 0;
border-radius: $border-radius-default;
- color: $theme-gray-900;
+ color: $gray-900;
svg {
position: relative;
@@ -145,7 +145,7 @@ $ide-commit-header-height: 48px;
}
&:not([disabled]):hover {
- background-color: $theme-gray-200;
+ background-color: $gray-200;
}
&:not([disabled]):focus {
@@ -265,7 +265,7 @@ $ide-commit-header-height: 48px;
.margin {
background-color: $white-light;
- border-right: 1px solid $theme-gray-100;
+ border-right: 1px solid $gray-100;
.line-insert {
border-right: 1px solid $line-added-dark;
@@ -292,7 +292,7 @@ $ide-commit-header-height: 48px;
.monaco-editor,
.monaco-editor-background,
.monaco-editor .inputarea.ime-input {
- background-color: $theme-gray-50;
+ background-color: $gray-50;
}
}
}
@@ -527,7 +527,7 @@ $ide-commit-header-height: 48px;
display: block;
margin-left: auto;
margin-right: auto;
- color: $theme-gray-700;
+ color: $gray-700;
}
.file-status-icon {
@@ -551,7 +551,7 @@ $ide-commit-header-height: 48px;
&:hover,
&:focus {
- background: $theme-gray-100;
+ background: $gray-100;
outline: 0;
@@ -563,7 +563,7 @@ $ide-commit-header-height: 48px;
}
&:active {
- background: $theme-gray-200;
+ background: $gray-200;
}
&.is-active {
@@ -767,12 +767,12 @@ $ide-commit-header-height: 48px;
&:hover {
color: $gl-text-color;
- background-color: $theme-gray-100;
+ background-color: $gray-100;
}
&:focus {
color: $gl-text-color;
- background-color: $theme-gray-200;
+ background-color: $gray-200;
}
&.active {
@@ -1273,10 +1273,10 @@ $ide-commit-header-height: 48px;
.ide-entry-dropdown-toggle {
padding: $gl-padding-4;
color: $gl-text-color;
- background-color: $theme-gray-100;
+ background-color: $gray-100;
&:hover {
- background-color: $theme-gray-200;
+ background-color: $gray-200;
}
&:active,
@@ -1331,7 +1331,7 @@ $ide-commit-header-height: 48px;
&:focus {
outline: 0;
box-shadow: none;
- border-color: $theme-gray-200;
+ border-color: $gray-200;
}
}
@@ -1358,7 +1358,7 @@ $ide-commit-header-height: 48px;
.ide-commit-editor-header {
height: 65px;
padding: 8px 16px;
- background-color: $theme-gray-50;
+ background-color: $gray-50;
box-shadow: inset 0 -1px $white-dark;
}
@@ -1370,7 +1370,7 @@ $ide-commit-header-height: 48px;
.ide-file-icon-holder {
display: flex;
align-items: center;
- color: $theme-gray-700;
+ color: $gray-700;
}
.file-row:hover,
diff --git a/app/assets/stylesheets/pages/boards.scss b/app/assets/stylesheets/pages/boards.scss
index 37984a8666f..e1d1e598da8 100644
--- a/app/assets/stylesheets/pages/boards.scss
+++ b/app/assets/stylesheets/pages/boards.scss
@@ -29,10 +29,6 @@
.dropdown-menu-issues-board-new {
width: 320px;
- .open & {
- max-height: 400px;
- }
-
.dropdown-content {
max-height: 162px;
}
@@ -281,7 +277,7 @@
padding: $gl-padding;
background: $white-light;
border-radius: $border-radius-default;
- border: 1px solid $theme-gray-200;
+ border: 1px solid $gray-200;
box-shadow: 0 1px 2px $issue-boards-card-shadow;
list-style: none;
line-height: $gl-padding;
@@ -673,7 +669,7 @@
}
.board-card-info-icon {
- color: $theme-gray-600;
+ color: $gray-600;
margin-right: $gl-padding-4;
}
diff --git a/app/assets/stylesheets/pages/builds.scss b/app/assets/stylesheets/pages/builds.scss
index 1352a004206..65f46e3852a 100644
--- a/app/assets/stylesheets/pages/builds.scss
+++ b/app/assets/stylesheets/pages/builds.scss
@@ -261,7 +261,7 @@
.trigger-variables-table-cell {
font-size: $gl-font-size-small;
line-height: $gl-line-height;
- border: 1px solid $theme-gray-200;
+ border: 1px solid $gray-200;
padding: $gl-padding-4 6px;
width: 50%;
vertical-align: top;
@@ -272,7 +272,13 @@
}
.retry-link {
- display: none;
+ display: block;
+
+ .btn {
+ i {
+ margin-left: 5px;
+ }
+ }
.btn-inverted-secondary {
color: $blue-500;
@@ -281,16 +287,6 @@
color: $white-light;
}
}
-
- @include media-breakpoint-down(sm) {
- display: block;
-
- .btn {
- i {
- margin-left: 5px;
- }
- }
- }
}
.stage-item {
diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss
index 75166ffcada..b6abb792709 100644
--- a/app/assets/stylesheets/pages/environments.scss
+++ b/app/assets/stylesheets/pages/environments.scss
@@ -267,7 +267,7 @@
.prometheus-graph-cursor {
position: absolute;
- background: $theme-gray-600;
+ background: $gray-600;
width: 1px;
}
@@ -309,7 +309,7 @@
> .arrow::after {
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
- border-left: 4px solid $theme-gray-50;
+ border-left: 4px solid $gray-50;
}
.arrow-shadow {
@@ -331,7 +331,7 @@
> .arrow::after {
border-top: 6px solid transparent;
border-bottom: 6px solid transparent;
- border-right: 4px solid $theme-gray-50;
+ border-right: 4px solid $gray-50;
}
.arrow-shadow {
@@ -364,7 +364,7 @@
}
> .popover-title {
- background-color: $theme-gray-50;
+ background-color: $gray-50;
border-radius: $border-radius-default $border-radius-default 0 0;
}
}
@@ -439,7 +439,7 @@
}
> text {
- fill: $theme-gray-600;
+ fill: $gray-600;
font-size: 10px;
}
}
@@ -482,5 +482,5 @@
}
.prometheus-table-row-highlight {
- background-color: $theme-gray-100;
+ background-color: $gray-100;
}
diff --git a/app/assets/stylesheets/pages/graph.scss b/app/assets/stylesheets/pages/graph.scss
index 4fb1a956fab..83b1680512d 100644
--- a/app/assets/stylesheets/pages/graph.scss
+++ b/app/assets/stylesheets/pages/graph.scss
@@ -44,7 +44,7 @@
}
.x-axis-text {
- fill: $theme-gray-900;
+ fill: $gray-900;
}
.bar-rect {
@@ -64,7 +64,7 @@
text {
font-weight: bold;
font-size: 12px;
- fill: $theme-gray-800;
+ fill: $gray-800;
}
}
}
@@ -87,5 +87,5 @@
.animate-flicker {
animation: flickerAnimation 1.5s infinite;
- fill: $theme-gray-500;
+ fill: $gray-500;
}
diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss
index f0cb81e0bc3..ebbb5beed81 100644
--- a/app/assets/stylesheets/pages/groups.scss
+++ b/app/assets/stylesheets/pages/groups.scss
@@ -78,7 +78,7 @@
&:hover {
background-color: $gray-darker;
- color: $theme-gray-900;
+ color: $gray-900;
}
}
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index a1069aa9783..e0bdc1341b1 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -934,7 +934,7 @@
.sidebar-collapsed-divider {
line-height: 5px;
font-size: 12px;
- color: $theme-gray-700;
+ color: $gray-700;
+ .sidebar-collapsed-icon {
padding-top: 0;
diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss
index d2b9470be69..2372640277e 100644
--- a/app/assets/stylesheets/pages/labels.scss
+++ b/app/assets/stylesheets/pages/labels.scss
@@ -82,7 +82,7 @@
justify-content: space-between;
padding: $gl-padding;
border-radius: $border-radius-default;
- border: 1px solid $theme-gray-100;
+ border: 1px solid $gray-100;
&:last-child {
margin-bottom: 0;
@@ -257,7 +257,7 @@
}
.label-badge {
- color: $theme-gray-900;
+ color: $gray-900;
font-weight: $gl-font-weight-normal;
padding: $gl-padding-4 $gl-padding-8;
border-radius: $border-radius-default;
@@ -269,7 +269,7 @@
}
.label-badge-gray {
- background-color: $theme-gray-100;
+ background-color: $gray-100;
}
.label-links {
@@ -326,11 +326,11 @@
}
.label-action {
- color: $theme-gray-800;
+ color: $gray-800;
cursor: pointer;
svg {
- fill: $theme-gray-800;
+ fill: $gray-800;
}
&:hover {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 221b4e934ff..1e4b8d8b7e4 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -64,7 +64,7 @@
&::before {
content: '';
- border-left: 1px solid $theme-gray-200;
+ border-left: 1px solid $gray-200;
position: absolute;
left: 32px;
top: -17px;
@@ -907,7 +907,7 @@
}
.btn svg {
- fill: $theme-gray-700;
+ fill: $gray-700;
}
.dropdown-menu {
@@ -951,7 +951,7 @@
.coverage {
font-size: 12px;
- color: $theme-gray-700;
+ color: $gray-700;
line-height: initial;
}
diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss
index 94bf32945fc..15f3a2ef4a8 100644
--- a/app/assets/stylesheets/pages/milestone.scss
+++ b/app/assets/stylesheets/pages/milestone.scss
@@ -8,7 +8,7 @@ $status-box-line-height: 26px;
padding: $gl-padding-8;
margin-top: $gl-padding-8;
border-radius: $border-radius-default;
- background-color: $theme-gray-100;
+ background-color: $gray-100;
.milestone {
border: 0;
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index 86f571dd90d..51f755c67af 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -147,7 +147,7 @@
}
.sidebar-item-value & {
- fill: $theme-gray-700;
+ fill: $gray-700;
}
}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 69b7b80dbf4..23b9e4f9416 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -5,7 +5,7 @@ $note-form-margin-left: 72px;
@mixin vertical-line($left) {
&::before {
content: '';
- border-left: 2px solid $theme-gray-100;
+ border-left: 2px solid $gray-100;
position: absolute;
top: 0;
bottom: 0;
diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss
index 7a47e0a2836..058b0ffef5f 100644
--- a/app/assets/stylesheets/pages/pipelines.scss
+++ b/app/assets/stylesheets/pages/pipelines.scss
@@ -433,6 +433,7 @@
}
.pipeline-tab-content {
+ display: flex;
width: 100%;
background-color: $gray-light;
padding: $gl-padding;
diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss
index a353f301d07..45e62913f37 100644
--- a/app/assets/stylesheets/pages/profiles/preferences.scss
+++ b/app/assets/stylesheets/pages/profiles/preferences.scss
@@ -60,11 +60,11 @@
}
&.ui-dark {
- background-color: $theme-gray-900;
+ background-color: $gray-900;
}
&.ui-light {
- background-color: $theme-gray-200;
+ background-color: $gray-200;
}
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 2d28333689f..505f6e036e3 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -459,7 +459,7 @@
margin-right: $gl-padding-4;
margin-bottom: $gl-padding-4;
color: $gl-text-color-secondary;
- background-color: $theme-gray-100;
+ background-color: $gray-100;
line-height: $gl-btn-line-height;
&:hover {
@@ -914,7 +914,7 @@
}
.repository-language-bar-tooltip-share {
- color: $theme-gray-400;
+ color: $gray-400;
}
pre.light-well {
@@ -1025,8 +1025,10 @@ pre.light-well {
margin: 0;
}
- @include media-breakpoint-up(md) {
- .description {
+ .description {
+ line-height: 1.5;
+
+ @include media-breakpoint-up(md) {
color: $gl-text-color;
}
}
diff --git a/app/assets/stylesheets/pages/reports.scss b/app/assets/stylesheets/pages/reports.scss
index ecd51aa06a4..f7619ccbd20 100644
--- a/app/assets/stylesheets/pages/reports.scss
+++ b/app/assets/stylesheets/pages/reports.scss
@@ -96,7 +96,7 @@
}
&.neutral svg {
- color: $theme-gray-700;
+ color: $gray-700;
}
.ci-status-icon {
diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss
index ccfa4e00a5b..c5b9d1f6885 100644
--- a/app/assets/stylesheets/pages/settings.scss
+++ b/app/assets/stylesheets/pages/settings.scss
@@ -297,7 +297,7 @@
.btn-clipboard {
background-color: $white-light;
- border: 1px solid $theme-gray-200;
+ border: 1px solid $gray-200;
}
.deploy-token-help-block {
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index a8fc848c879..26cd5dc801f 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -177,11 +177,17 @@ class ApplicationController < ActionController::Base
# hide existence of the resource, rather tell them they cannot access it using
# the provided message
status ||= message.present? ? :forbidden : :not_found
+ template =
+ if status == :not_found
+ "errors/not_found"
+ else
+ "errors/access_denied"
+ end
respond_to do |format|
format.any { head status }
format.html do
- render "errors/access_denied",
+ render template,
layout: "errors",
status: status,
locals: { message: message }
diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb
index f073b6de444..b1d224d026f 100644
--- a/app/controllers/dashboard/projects_controller.rb
+++ b/app/controllers/dashboard/projects_controller.rb
@@ -53,6 +53,9 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def load_projects(finder_params)
+ @total_user_projects_count = ProjectsFinder.new(params: { non_public: true }, current_user: current_user).execute
+ @total_starred_projects_count = ProjectsFinder.new(params: { starred: true }, current_user: current_user).execute
+
projects = ProjectsFinder
.new(params: finder_params, current_user: current_user)
.execute
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index 778fdda8dbd..9f074690cbc 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -55,6 +55,9 @@ class Explore::ProjectsController < Explore::ApplicationController
# rubocop: disable CodeReuse/ActiveRecord
def load_projects
+ @total_user_projects_count = ProjectsFinder.new(params: { non_public: true }, current_user: current_user).execute
+ @total_starred_projects_count = ProjectsFinder.new(params: { starred: true }, current_user: current_user).execute
+
projects = ProjectsFinder.new(current_user: current_user, params: params)
.execute
.includes(:route, :creator, :group, namespace: [:route, :owner])
diff --git a/app/controllers/groups/children_controller.rb b/app/controllers/groups/children_controller.rb
index d549f793ad7..236a19a8dc4 100644
--- a/app/controllers/groups/children_controller.rb
+++ b/app/controllers/groups/children_controller.rb
@@ -35,7 +35,7 @@ module Groups
def setup_children(parent)
@children = GroupDescendantsFinder.new(current_user: current_user,
parent_group: parent,
- params: params).execute
+ params: params.to_unsafe_h).execute
@children = @children.page(params[:page])
end
end
diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb
index 042b6b1264f..9b45be6db99 100644
--- a/app/controllers/import/base_controller.rb
+++ b/app/controllers/import/base_controller.rb
@@ -18,6 +18,7 @@ class Import::BaseController < ApplicationController
end
# rubocop: enable CodeReuse/ActiveRecord
+ # deprecated: being replaced by app/services/import/base_service.rb
def find_or_create_namespace(names, owner)
names = params[:target_namespace].presence || names
@@ -32,6 +33,7 @@ class Import::BaseController < ApplicationController
current_user.namespace
end
+ # deprecated: being replaced by app/services/import/base_service.rb
def project_save_error(project)
project.errors.full_messages.join(', ')
end
diff --git a/app/controllers/import/bitbucket_server_controller.rb b/app/controllers/import/bitbucket_server_controller.rb
index 575c40d5f6f..87338488eba 100644
--- a/app/controllers/import/bitbucket_server_controller.rb
+++ b/app/controllers/import/bitbucket_server_controller.rb
@@ -40,7 +40,7 @@ class Import::BitbucketServerController < Import::BaseController
else
render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
end
- rescue BitbucketServer::Client::ServerError => e
+ rescue BitbucketServer::Connection::ConnectionError => e
render json: { errors: "Unable to connect to server: #{e}" }, status: :unprocessable_entity
end
@@ -62,7 +62,7 @@ class Import::BitbucketServerController < Import::BaseController
already_added_projects_names = @already_added_projects.pluck(:import_source)
@repos.reject! { |repo| already_added_projects_names.include?(repo.browse_url) }
- rescue BitbucketServer::Connection::ConnectionError, BitbucketServer::Client::ServerError => e
+ rescue BitbucketServer::Connection::ConnectionError => e
flash[:alert] = "Unable to connect to server: #{e}"
clear_session_data
redirect_to new_import_bitbucket_server_path
diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb
index d4c26fa0709..34c7dbdc2fe 100644
--- a/app/controllers/import/github_controller.rb
+++ b/app/controllers/import/github_controller.rb
@@ -39,28 +39,25 @@ class Import::GithubController < Import::BaseController
end
def create
- repo = client.repo(params[:repo_id].to_i)
- project_name = params[:new_name].presence || repo.name
- namespace_path = params[:target_namespace].presence || current_user.namespace_path
- target_namespace = find_or_create_namespace(namespace_path, current_user.namespace_path)
-
- if can?(current_user, :create_projects, target_namespace)
- project = Gitlab::LegacyGithubImport::ProjectCreator
- .new(repo, project_name, target_namespace, current_user, access_params, type: provider)
- .execute(extra_project_attrs)
-
- if project.persisted?
- render json: ProjectSerializer.new.represent(project)
- else
- render json: { errors: project_save_error(project) }, status: :unprocessable_entity
- end
+ result = Import::GithubService.new(client, current_user, import_params).execute(access_params, provider)
+
+ if result[:status] == :success
+ render json: ProjectSerializer.new.represent(result[:project])
else
- render json: { errors: 'This namespace has already been taken! Please choose another one.' }, status: :unprocessable_entity
+ render json: { errors: result[:message] }, status: result[:http_status]
end
end
private
+ def import_params
+ params.permit(permitted_import_params)
+ end
+
+ def permitted_import_params
+ [:repo_id, :new_name, :target_namespace]
+ end
+
def client
@client ||= Gitlab::LegacyGithubImport::Client.new(session[access_token_key], client_options)
end
@@ -124,10 +121,6 @@ class Import::GithubController < Import::BaseController
{}
end
- def extra_project_attrs
- {}
- end
-
def extra_import_params
{}
end
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index ff286c0ccf0..77672e7d9fc 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -93,7 +93,7 @@ class Projects::BlobController < Projects::ApplicationController
@blob.load_all_data!
@lines = @blob.present.highlight.lines
- @form = UnfoldForm.new(params)
+ @form = UnfoldForm.new(params.to_unsafe_h)
@lines = @lines[@form.since - 1..@form.to - 1].map(&:html_safe)
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 21688e54481..e3e60665506 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -178,8 +178,6 @@ class Projects::IssuesController < Projects::ApplicationController
end
def import_csv
- return render_404 unless Feature.enabled?(:issues_import_csv)
-
if uploader = UploadService.new(project, params[:file]).execute
ImportIssuesCsvWorker.perform_async(current_user.id, project.id, uploader.upload.id)
diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb
index bfbbcba883f..d5ce790e2d9 100644
--- a/app/controllers/projects/jobs_controller.rb
+++ b/app/controllers/projects/jobs_controller.rb
@@ -4,10 +4,10 @@ class Projects::JobsController < Projects::ApplicationController
include SendFileUpload
include ContinueParams
- before_action :build, except: [:index, :cancel_all]
+ before_action :build, except: [:index]
before_action :authorize_read_build!
before_action :authorize_update_build!,
- except: [:index, :show, :status, :raw, :trace, :cancel_all, :erase]
+ except: [:index, :show, :status, :raw, :trace, :erase]
before_action :authorize_erase_build!, only: [:erase]
before_action :authorize_use_build_terminal!, only: [:terminal, :terminal_websocket_authorize]
before_action :verify_api_request!, only: :terminal_websocket_authorize
@@ -39,16 +39,6 @@ class Projects::JobsController < Projects::ApplicationController
end
# rubocop: enable CodeReuse/ActiveRecord
- def cancel_all
- return access_denied! unless can?(current_user, :update_build, project)
-
- @project.builds.running_or_pending.each do |build|
- build.cancel if can?(current_user, :update_build, build)
- end
-
- redirect_to project_jobs_path(project)
- end
-
# rubocop: disable CodeReuse/ActiveRecord
def show
@pipeline = @build.pipeline
diff --git a/app/controllers/projects/pipelines_settings_controller.rb b/app/controllers/projects/pipelines_settings_controller.rb
index 192e6d38f36..7f988110977 100644
--- a/app/controllers/projects/pipelines_settings_controller.rb
+++ b/app/controllers/projects/pipelines_settings_controller.rb
@@ -4,6 +4,6 @@ class Projects::PipelinesSettingsController < Projects::ApplicationController
before_action :authorize_admin_pipeline!
def show
- redirect_to project_settings_ci_cd_path(@project, params: params)
+ redirect_to project_settings_ci_cd_path(@project, params: params.to_unsafe_h)
end
end
diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb
index 7b22bc8f98f..b3935ae350d 100644
--- a/app/helpers/environments_helper.rb
+++ b/app/helpers/environments_helper.rb
@@ -7,6 +7,15 @@ module EnvironmentsHelper
}
end
+ def environments_folder_list_view_data
+ {
+ "endpoint" => folder_project_environments_path(@project, @folder, format: :json),
+ "folder-name" => @folder,
+ "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
+ "can-read-environment" => can?(current_user, :read_environment, @project).to_s
+ }
+ end
+
def metrics_data(project, environment)
{
"settings-path" => edit_project_service_path(project, 'prometheus'),
diff --git a/app/helpers/projects/error_tracking_helper.rb b/app/helpers/projects/error_tracking_helper.rb
new file mode 100644
index 00000000000..6daf2e21ca2
--- /dev/null
+++ b/app/helpers/projects/error_tracking_helper.rb
@@ -0,0 +1,15 @@
+# frozen_string_literal: true
+
+module Projects::ErrorTrackingHelper
+ def error_tracking_data(project)
+ error_tracking_enabled = !!project.error_tracking_setting&.enabled?
+
+ {
+ 'index-path' => project_error_tracking_index_path(project,
+ format: :json),
+ 'enable-error-tracking-link' => project_settings_operations_path(project),
+ 'error-tracking-enabled' => error_tracking_enabled.to_s,
+ 'illustration-path' => image_path('illustrations/cluster_popover.svg')
+ }
+ end
+end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index e67c327f7f8..ebbed08f78a 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -335,6 +335,7 @@ module ProjectsHelper
builds: :read_build,
clusters: :read_cluster,
serverless: :read_cluster,
+ error_tracking: :read_sentry_issue,
labels: :read_label,
issues: :read_issue,
project_members: :read_project_member,
@@ -579,6 +580,7 @@ module ProjectsHelper
environments
clusters
functions
+ error_tracking
user
gcp
]
diff --git a/app/helpers/release_blog_post_helper.rb b/app/helpers/release_blog_post_helper.rb
new file mode 100644
index 00000000000..31b5b7edc39
--- /dev/null
+++ b/app/helpers/release_blog_post_helper.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module ReleaseBlogPostHelper
+ def blog_post_url
+ Gitlab::ReleaseBlogPost.instance.blog_post_url
+ end
+end
diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb
index 7799f069742..7c15aaa4825 100644
--- a/app/models/clusters/applications/ingress.rb
+++ b/app/models/clusters/applications/ingress.rb
@@ -3,7 +3,7 @@
module Clusters
module Applications
class Ingress < ActiveRecord::Base
- VERSION = '0.23.0'.freeze
+ VERSION = '1.1.2'.freeze
self.table_name = 'clusters_applications_ingress'
diff --git a/app/models/concerns/manual_inverse_association.rb b/app/models/concerns/manual_inverse_association.rb
index e18edd33ba7..ff61412767e 100644
--- a/app/models/concerns/manual_inverse_association.rb
+++ b/app/models/concerns/manual_inverse_association.rb
@@ -5,8 +5,8 @@ module ManualInverseAssociation
class_methods do
def manual_inverse_association(association, inverse)
- define_method(association) do |*args|
- super(*args).tap do |value|
+ define_method(association) do
+ super().tap do |value|
next unless value
child_association = value.association(inverse)
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index 4f73beaafc5..68b2353556e 100644
--- a/app/models/external_issue.rb
+++ b/app/models/external_issue.rb
@@ -3,6 +3,8 @@
class ExternalIssue
include Referable
+ attr_reader :project
+
def initialize(issue_identifier, project)
@issue_identifier, @project = issue_identifier, project
end
@@ -32,12 +34,8 @@ class ExternalIssue
[self.class, to_s].hash
end
- def project
- @project
- end
-
def project_id
- @project.id
+ project.id
end
def to_reference(_from = nil, full: nil)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index a9d1ece0d7e..7206d858dae 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -560,15 +560,19 @@ class MergeRequest < ActiveRecord::Base
end
def diff_refs
- if persisted?
- merge_request_diff.diff_refs
- else
- Gitlab::Diff::DiffRefs.new(
- base_sha: diff_base_sha,
- start_sha: diff_start_sha,
- head_sha: diff_head_sha
- )
- end
+ persisted? ? merge_request_diff.diff_refs : repository_diff_refs
+ end
+
+ # Instead trying to fetch the
+ # persisted diff_refs, this method goes
+ # straight to the repository to get the
+ # most recent data possible.
+ def repository_diff_refs
+ Gitlab::Diff::DiffRefs.new(
+ base_sha: branch_merge_base_sha,
+ start_sha: target_branch_sha,
+ head_sha: source_branch_sha
+ )
end
def branch_merge_base_sha
diff --git a/app/models/project.rb b/app/models/project.rb
index 7ab2fc30c24..15465d9b356 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -331,7 +331,7 @@ class Project < ActiveRecord::Base
ports: ->(project) { project.persisted? ? VALID_MIRROR_PORTS : VALID_IMPORT_PORTS },
enforce_user: true }, if: [:external_import?, :import_url_changed?]
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
- validate :check_limit, on: :create
+ validate :check_personal_projects_limit, on: :create
validate :check_repository_path_availability, on: :update, if: ->(project) { project.renamed? }
validate :visibility_level_allowed_by_group, if: -> { changes.has_key?(:visibility_level) }
validate :visibility_level_allowed_as_fork, if: -> { changes.has_key?(:visibility_level) }
@@ -809,18 +809,22 @@ class Project < ActiveRecord::Base
::Gitlab::CurrentSettings.mirror_available
end
- def check_limit
- unless creator.can_create_project? || namespace.kind == 'group'
- projects_limit = creator.projects_limit
+ def check_personal_projects_limit
+ # Since this method is called as validation hook, `creator` might not be
+ # present. Since the validation for that will fail, we can just return
+ # early.
+ return if !creator || creator.can_create_project? ||
+ namespace.kind == 'group'
- if projects_limit == 0
- self.errors.add(:limit_reached, "Personal project creation is not allowed. Please contact your administrator with questions")
+ limit = creator.projects_limit
+ error =
+ if limit.zero?
+ _('Personal project creation is not allowed. Please contact your administrator with questions')
else
- self.errors.add(:limit_reached, "Your project limit is #{projects_limit} projects! Please contact your administrator to increase it")
+ _('Your project limit is %{limit} projects! Please contact your administrator to increase it')
end
- end
- rescue
- self.errors.add(:base, "Can't check your ability to create project")
+
+ self.errors.add(:limit_reached, error % { limit: limit })
end
def visibility_level_allowed_by_group
@@ -1531,7 +1535,7 @@ class Project < ActiveRecord::Base
end
def pages_available?
- Gitlab.config.pages.enabled && !namespace.subgroup?
+ Gitlab.config.pages.enabled
end
def remove_private_deploy_keys
@@ -1602,24 +1606,7 @@ class Project < ActiveRecord::Base
# rubocop: disable CodeReuse/ServiceClass
def after_create_default_branch
- return unless default_branch
-
- # Ensure HEAD points to the default branch in case it is not master
- change_head(default_branch)
-
- if Gitlab::CurrentSettings.default_branch_protection != Gitlab::Access::PROTECTION_NONE && !ProtectedBranch.protected?(self, default_branch)
- params = {
- name: default_branch,
- push_access_levels_attributes: [{
- access_level: Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_PUSH ? Gitlab::Access::DEVELOPER : Gitlab::Access::MAINTAINER
- }],
- merge_access_levels_attributes: [{
- access_level: Gitlab::CurrentSettings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE ? Gitlab::Access::DEVELOPER : Gitlab::Access::MAINTAINER
- }]
- }
-
- ProtectedBranches::CreateService.new(self, creator, params).execute(skip_authorization: true)
- end
+ Projects::ProtectDefaultBranchService.new(self).execute
end
# rubocop: enable CodeReuse/ServiceClass
@@ -2039,6 +2026,10 @@ class Project < ActiveRecord::Base
pool_repository&.unlink_repository(repository) && update_column(:pool_repository_id, nil)
end
+ def link_pool_repository
+ pool_repository&.link_repository(repository)
+ end
+
private
def merge_requests_allowing_collaboration(source_branch = nil)
diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb
index 525725034a5..aa0c121fe99 100644
--- a/app/models/project_import_data.rb
+++ b/app/models/project_import_data.rb
@@ -30,4 +30,8 @@ class ProjectImportData < ActiveRecord::Base
def merge_credentials(hash)
self.credentials = credentials.to_h.merge(hash) unless hash.empty?
end
+
+ def clear_credentials
+ self.credentials = {}
+ end
end
diff --git a/app/models/remote_mirror.rb b/app/models/remote_mirror.rb
index a3fa67c72bf..5eba7ddd75c 100644
--- a/app/models/remote_mirror.rb
+++ b/app/models/remote_mirror.rb
@@ -61,7 +61,10 @@ class RemoteMirror < ActiveRecord::Base
timestamp = Time.now
remote_mirror.update!(
- last_update_at: timestamp, last_successful_update_at: timestamp, last_error: nil
+ last_update_at: timestamp,
+ last_successful_update_at: timestamp,
+ last_error: nil,
+ error_notification_sent: false
)
end
@@ -179,6 +182,10 @@ class RemoteMirror < ActiveRecord::Base
project.repository.add_remote(remote_name, remote_url)
end
+ def after_sent_notification
+ update_column(:error_notification_sent, true)
+ end
+
private
def store_credentials
@@ -221,7 +228,8 @@ class RemoteMirror < ActiveRecord::Base
last_error: nil,
last_update_at: nil,
last_successful_update_at: nil,
- update_status: 'finished'
+ update_status: 'finished',
+ error_notification_sent: false
)
end
diff --git a/app/models/suggestion.rb b/app/models/suggestion.rb
index c76b8e71507..7eee4fbbe5f 100644
--- a/app/models/suggestion.rb
+++ b/app/models/suggestion.rb
@@ -5,8 +5,7 @@ class Suggestion < ApplicationRecord
validates :note, presence: true
validates :commit_id, presence: true, if: :applied?
- delegate :original_position, :position, :diff_file,
- :noteable, to: :note
+ delegate :original_position, :position, :noteable, to: :note
def project
noteable.source_project
@@ -16,6 +15,15 @@ class Suggestion < ApplicationRecord
noteable.source_branch
end
+ def file_path
+ position.file_path
+ end
+
+ def diff_file
+ repository = project.repository
+ position.diff_file(repository)
+ end
+
# For now, suggestions only serve as a way to send patches that
# will change a single line (being able to apply multiple in the same place),
# which explains `from_line` and `to_line` being the same line.
diff --git a/app/serializers/base_serializer.rb b/app/serializers/base_serializer.rb
index 7b65bd22f54..4744a7c1cc8 100644
--- a/app/serializers/base_serializer.rb
+++ b/app/serializers/base_serializer.rb
@@ -16,11 +16,11 @@ class BaseSerializer
.as_json
end
- def self.entity(entity_class)
- @entity_class ||= entity_class
- end
+ class << self
+ attr_reader :entity_class
- def self.entity_class
- @entity_class
+ def entity(entity_class)
+ @entity_class ||= entity_class
+ end
end
end
diff --git a/app/serializers/merge_request_diff_entity.rb b/app/serializers/merge_request_diff_entity.rb
index 433bfe60474..7e3053e5881 100644
--- a/app/serializers/merge_request_diff_entity.rb
+++ b/app/serializers/merge_request_diff_entity.rb
@@ -24,6 +24,14 @@ class MergeRequestDiffEntity < Grape::Entity
short_sha(merge_request_diff.head_commit_sha)
end
+ expose :base_version_path do |merge_request_diff|
+ project = merge_request.target_project
+
+ next unless project
+
+ merge_request_version_path(project, merge_request, merge_request_diff)
+ end
+
expose :version_path do |merge_request_diff|
start_sha = options[:start_sha]
project = merge_request.target_project
diff --git a/app/services/import/base_service.rb b/app/services/import/base_service.rb
new file mode 100644
index 00000000000..2683c75e41f
--- /dev/null
+++ b/app/services/import/base_service.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+module Import
+ class BaseService < ::BaseService
+ def initialize(client, user, params)
+ @client = client
+ @current_user = user
+ @params = params
+ end
+
+ private
+
+ def find_or_create_namespace(namespace, owner)
+ namespace = params[:target_namespace].presence || namespace
+
+ return current_user.namespace if namespace == owner
+
+ group = Groups::NestedCreateService.new(current_user, group_path: namespace).execute
+
+ group.errors.any? ? current_user.namespace : group
+ rescue => e
+ Gitlab::AppLogger.error(e)
+
+ current_user.namespace
+ end
+
+ def project_save_error(project)
+ project.errors.full_messages.join(', ')
+ end
+
+ def success(project)
+ super().merge(project: project, status: :success)
+ end
+ end
+end
diff --git a/app/services/import/github_service.rb b/app/services/import/github_service.rb
new file mode 100644
index 00000000000..a2533683da9
--- /dev/null
+++ b/app/services/import/github_service.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+module Import
+ class GithubService < Import::BaseService
+ attr_accessor :client
+ attr_reader :params, :current_user
+
+ def execute(access_params, provider)
+ unless authorized?
+ return error('This namespace has already been taken! Please choose another one.', :unprocessable_entity)
+ end
+
+ project = Gitlab::LegacyGithubImport::ProjectCreator
+ .new(repo, project_name, target_namespace, current_user, access_params, type: provider)
+ .execute(extra_project_attrs)
+
+ if project.persisted?
+ success(project)
+ else
+ error(project_save_error(project), :unprocessable_entity)
+ end
+ end
+
+ def repo
+ @repo ||= client.repo(params[:repo_id].to_i)
+ end
+
+ def project_name
+ @project_name ||= params[:new_name].presence || repo.name
+ end
+
+ def namespace_path
+ @namespace_path ||= params[:target_namespace].presence || current_user.namespace_path
+ end
+
+ def target_namespace
+ @target_namespace ||= find_or_create_namespace(namespace_path, current_user.namespace_path)
+ end
+
+ def extra_project_attrs
+ {}
+ end
+
+ def authorized?
+ can?(current_user, :create_projects, target_namespace)
+ end
+ end
+end
diff --git a/app/services/labels/create_service.rb b/app/services/labels/create_service.rb
index fe34be41ac1..db710bac900 100644
--- a/app/services/labels/create_service.rb
+++ b/app/services/labels/create_service.rb
@@ -3,7 +3,7 @@
module Labels
class CreateService < Labels::BaseService
def initialize(params = {})
- @params = params.dup.with_indifferent_access
+ @params = params.to_h.dup.with_indifferent_access
end
# returns the created label
diff --git a/app/services/labels/update_service.rb b/app/services/labels/update_service.rb
index c3a720a1c66..e563447c64c 100644
--- a/app/services/labels/update_service.rb
+++ b/app/services/labels/update_service.rb
@@ -3,7 +3,7 @@
module Labels
class UpdateService < Labels::BaseService
def initialize(params = {})
- @params = params.dup.with_indifferent_access
+ @params = params.to_h.dup.with_indifferent_access
end
# returns the updated label
diff --git a/app/services/lfs/locks_finder_service.rb b/app/services/lfs/locks_finder_service.rb
index 4a5b2a52921..192ce3d3c2a 100644
--- a/app/services/lfs/locks_finder_service.rb
+++ b/app/services/lfs/locks_finder_service.rb
@@ -12,7 +12,7 @@ module Lfs
# rubocop: disable CodeReuse/ActiveRecord
def find_locks
- options = params.slice(:id, :path).compact.symbolize_keys
+ options = params.slice(:id, :path).to_h.compact.symbolize_keys
project.lfs_file_locks.where(options)
end
diff --git a/app/services/projects/protect_default_branch_service.rb b/app/services/projects/protect_default_branch_service.rb
new file mode 100644
index 00000000000..245490791bf
--- /dev/null
+++ b/app/services/projects/protect_default_branch_service.rb
@@ -0,0 +1,67 @@
+# frozen_string_literal: true
+
+module Projects
+ # Service class that can be used to execute actions necessary after creating a
+ # default branch.
+ class ProtectDefaultBranchService
+ attr_reader :project, :default_branch_protection
+
+ # @param [Project] project
+ def initialize(project)
+ @project = project
+
+ @default_branch_protection = Gitlab::Access::BranchProtection
+ .new(Gitlab::CurrentSettings.default_branch_protection)
+ end
+
+ def execute
+ protect_default_branch if default_branch
+ end
+
+ def protect_default_branch
+ # Ensure HEAD points to the default branch in case it is not master
+ project.change_head(default_branch)
+
+ create_protected_branch if protect_branch?
+ end
+
+ def create_protected_branch
+ params = {
+ name: default_branch,
+ push_access_levels_attributes: [{ access_level: push_access_level }],
+ merge_access_levels_attributes: [{ access_level: merge_access_level }]
+ }
+
+ # The creator of the project is always allowed to create protected
+ # branches, so we skip the authorization check in this service class.
+ ProtectedBranches::CreateService
+ .new(project, project.creator, params)
+ .execute(skip_authorization: true)
+ end
+
+ def protect_branch?
+ default_branch_protection.any? &&
+ !ProtectedBranch.protected?(project, default_branch)
+ end
+
+ def default_branch
+ project.default_branch
+ end
+
+ def push_access_level
+ if default_branch_protection.developer_can_push?
+ Gitlab::Access::DEVELOPER
+ else
+ Gitlab::Access::MAINTAINER
+ end
+ end
+
+ def merge_access_level
+ if default_branch_protection.developer_can_merge?
+ Gitlab::Access::DEVELOPER
+ else
+ Gitlab::Access::MAINTAINER
+ end
+ end
+ end
+end
diff --git a/app/services/suggestions/apply_service.rb b/app/services/suggestions/apply_service.rb
index d931d528c86..cc47b46b527 100644
--- a/app/services/suggestions/apply_service.rb
+++ b/app/services/suggestions/apply_service.rb
@@ -11,6 +11,10 @@ module Suggestions
return error('Suggestion is not appliable')
end
+ unless latest_diff_refs?(suggestion)
+ return error('The file has been changed')
+ end
+
params = file_update_params(suggestion)
result = ::Files::UpdateService.new(suggestion.project, @current_user, params).execute
@@ -19,30 +23,44 @@ module Suggestions
end
result
+ rescue Files::UpdateService::FileChangedError
+ error('The file has been changed')
end
private
- def file_update_params(suggestion)
- diff_file = suggestion.diff_file
+ # Checks whether the latest diff refs for the branch matches with
+ # the position refs we're using to update the file content. Since
+ # the persisted refs are updated async (for MergeRequest),
+ # it's more consistent to fetch this data directly from the repository.
+ def latest_diff_refs?(suggestion)
+ suggestion.position.diff_refs == suggestion.noteable.repository_diff_refs
+ end
- file_path = diff_file.file_path
- branch_name = suggestion.noteable.source_branch
- file_content = new_file_content(suggestion)
+ def file_update_params(suggestion)
+ blob = suggestion.diff_file.new_blob
+ file_path = suggestion.file_path
+ branch_name = suggestion.branch
+ file_content = new_file_content(suggestion, blob)
commit_message = "Apply suggestion to #{file_path}"
+ file_last_commit =
+ Gitlab::Git::Commit.last_for_path(suggestion.project.repository,
+ blob.commit_id,
+ blob.path)
+
{
file_path: file_path,
branch_name: branch_name,
start_branch: branch_name,
commit_message: commit_message,
- file_content: file_content
+ file_content: file_content,
+ last_commit_sha: file_last_commit&.id
}
end
- def new_file_content(suggestion)
+ def new_file_content(suggestion, blob)
range = suggestion.from_line_index..suggestion.to_line_index
- blob = suggestion.diff_file.new_blob
blob.load_all_data!
content = blob.data.lines
diff --git a/app/services/users/update_service.rb b/app/services/users/update_service.rb
index af4fe1aebb9..0b00bd135eb 100644
--- a/app/services/users/update_service.rb
+++ b/app/services/users/update_service.rb
@@ -55,7 +55,7 @@ module Users
params.reject! { |key, _| read_only.include?(key.to_sym) }
end
- @user.assign_attributes(params) if params.any?
+ @user.assign_attributes(params) unless params.empty?
end
end
end
diff --git a/app/uploaders/records_uploads.rb b/app/uploaders/records_uploads.rb
index 0efca895a50..9a243e07936 100644
--- a/app/uploaders/records_uploads.rb
+++ b/app/uploaders/records_uploads.rb
@@ -23,13 +23,23 @@ module RecordsUploads
return unless model
return unless file && file.exists?
- Upload.transaction do
- uploads.where(path: upload_path).delete_all
- upload.delete if upload
-
- self.upload = build_upload.tap(&:save!)
+ # MySQL InnoDB may encounter a deadlock if a deletion and an
+ # insert is in the same transaction due to its next-key locking
+ # algorithm, so we need to skip the transaction.
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/55161#note_131556351
+ if Gitlab::Database.mysql?
+ readd_upload
+ else
+ Upload.transaction { readd_upload }
end
end
+
+ def readd_upload
+ uploads.where(path: upload_path).delete_all
+ upload.delete if upload
+
+ self.upload = build_upload.tap(&:save!)
+ end
# rubocop: enable CodeReuse/ActiveRecord
def upload_path
diff --git a/app/views/clusters/clusters/_integration_form.html.haml b/app/views/clusters/clusters/_integration_form.html.haml
index 5e451f60c9d..4c47e11927e 100644
--- a/app/views/clusters/clusters/_integration_form.html.haml
+++ b/app/views/clusters/clusters/_integration_form.html.haml
@@ -13,19 +13,19 @@
= sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked')
.form-text.text-muted= s_('ClusterIntegration|Enable or disable GitLab\'s connection to your Kubernetes cluster.')
- - if has_multiple_clusters?
- .form-group
- %h5= s_('ClusterIntegration|Environment scope')
+ .form-group
+ %h5= s_('ClusterIntegration|Environment scope')
+ - if has_multiple_clusters?
= field.text_field :environment_scope, class: 'col-md-6 form-control js-select-on-focus', placeholder: s_('ClusterIntegration|Environment scope')
.form-text.text-muted= s_("ClusterIntegration|Choose which of your environments will use this cluster.")
+ - else
+ = text_field_tag :environment_scope, '*', class: 'col-md-6 form-control disabled', placeholder: s_('ClusterIntegration|Environment scope'), disabled: true
+ - environment_scope_url = 'https://docs.gitlab.com/ee/user/project/clusters/#setting-the-environment-scope-premium'
+ - environment_scope_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: environment_scope_url }
+ .form-text.text-muted
+ %code *
+ = s_("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}").html_safe % { environment_scope_start: environment_scope_start, environment_scope_end: '</a>'.html_safe }
- if can?(current_user, :update_cluster, @cluster)
.form-group
= field.submit _('Save changes'), class: 'btn btn-success'
-
- - unless has_multiple_clusters?
- %h5= s_('ClusterIntegration|Environment scope')
- %p
- %code *
- is the default environment scope for this cluster. This means that all jobs, regardless of their environment, will use this cluster.
- = link_to 'More information', ('https://docs.gitlab.com/ee/user/project/clusters/#setting-the-environment-scope')
diff --git a/app/views/dashboard/_projects_head.html.haml b/app/views/dashboard/_projects_head.html.haml
index 1050945b15a..ae67192cbcd 100644
--- a/app/views/dashboard/_projects_head.html.haml
+++ b/app/views/dashboard/_projects_head.html.haml
@@ -15,9 +15,11 @@
= nav_link(page: [dashboard_projects_path, root_path]) do
= link_to dashboard_projects_path, class: 'shortcuts-activity', data: {placement: 'right'} do
Your projects
+ %span.badge.badge-pill= limited_counter_with_delimiter(@total_user_projects_count)
= nav_link(page: starred_dashboard_projects_path) do
= link_to starred_dashboard_projects_path, data: {placement: 'right'} do
Starred projects
+ %span.badge.badge-pill= limited_counter_with_delimiter(@total_starred_projects_count)
= nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do
= link_to explore_root_path, data: {placement: 'right'} do
Explore projects
diff --git a/app/views/layouts/header/_help_dropdown.html.haml b/app/views/layouts/header/_help_dropdown.html.haml
index 04409408ce0..513902890af 100644
--- a/app/views/layouts/header/_help_dropdown.html.haml
+++ b/app/views/layouts/header/_help_dropdown.html.haml
@@ -1,9 +1,13 @@
+- show_blog_link = current_user_menu?(:help) && blog_post_url.present?
%ul
- if current_user_menu?(:help)
%li
= link_to _("Help"), help_path
%li.divider
+ - if show_blog_link
%li
- = link_to _("Submit feedback"), "https://about.gitlab.com/submit-feedback"
+ = link_to _("What's new?"), blog_post_url
+ %li
+ = link_to _("Submit feedback"), "https://about.gitlab.com/submit-feedback"
- if current_user_menu?(:help) || current_user_menu?(:settings) || current_user_menu?(:profile)
= render 'shared/user_dropdown_contributing_link'
diff --git a/app/views/layouts/nav/sidebar/_project.html.haml b/app/views/layouts/nav/sidebar/_project.html.haml
index c8fdc0112b4..4b67069d9ac 100644
--- a/app/views/layouts/nav/sidebar/_project.html.haml
+++ b/app/views/layouts/nav/sidebar/_project.html.haml
@@ -43,7 +43,7 @@
- if project_nav_tab? :files
= nav_link(controller: sidebar_repository_paths) do
- = link_to project_tree_path(@project), class: 'shortcuts-tree' do
+ = link_to project_tree_path(@project), class: 'shortcuts-tree qa-project-menu-repo' do
.nav-icon-container
= sprite_icon('doc-text')
%span.nav-item-name
@@ -64,7 +64,7 @@
= _('Commits')
= nav_link(html_options: {class: branches_tab_class}) do
- = link_to project_branches_path(@project) do
+ = link_to project_branches_path(@project), class: 'qa-branches-link' do
= _('Branches')
= nav_link(controller: [:tags]) do
@@ -201,7 +201,7 @@
- if project_nav_tab? :operations
= nav_link(controller: sidebar_operations_paths) do
- = link_to sidebar_operations_link_path, class: 'shortcuts-operations' do
+ = link_to sidebar_operations_link_path, class: 'shortcuts-operations qa-link-operations' do
.nav-icon-container
= sprite_icon('cloud-gear')
%span.nav-item-name
@@ -227,6 +227,12 @@
%span
= _('Environments')
+ - if project_nav_tab?(:error_tracking) && Feature.enabled?(:error_tracking, @project)
+ = nav_link(controller: :error_tracking) do
+ = link_to project_error_tracking_index_path(@project), title: _('Error Tracking'), class: 'shortcuts-tracking qa-operations-tracking-link' do
+ %span
+ = _('Error Tracking')
+
- if project_nav_tab? :serverless
= nav_link(controller: :functions) do
= link_to project_serverless_functions_path(@project), title: _('Serverless') do
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index e167e094240..ee2c5a13b8a 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,37 +1,37 @@
-- page_title "Account"
+- page_title _('Account')
- @content_class = "limit-container-width" unless fluid_layout
- if current_user.ldap_user?
.alert.alert-info
- Some options are unavailable for LDAP accounts
+ = s_('Profiles|Some options are unavailable for LDAP accounts')
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
- Two-Factor Authentication
+ = s_('Profiles|Two-Factor Authentication')
%p
- Increase your account's security by enabling Two-Factor Authentication (2FA).
+ = s_("Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)")
.col-lg-8
%p
- Status: #{current_user.two_factor_enabled? ? 'Enabled' : 'Disabled'}
+ #{_('Status')}: #{current_user.two_factor_enabled? ? _('Enabled') : _('Disabled')}
- if current_user.two_factor_enabled?
- = link_to 'Manage two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-info'
+ = link_to _('Manage two-factor authentication'), profile_two_factor_auth_path, class: 'btn btn-info'
- else
.append-bottom-10
- = link_to 'Enable two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-success'
+ = link_to _('Enable two-factor authentication'), profile_two_factor_auth_path, class: 'btn btn-success'
%hr
- if display_providers_on_profile?
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0
- Social sign-in
+ = s_('Profiles|Social sign-in')
%p
- Activate signin with one of the following services
+ = s_('Profiles|Activate signin with one of the following services')
.col-lg-8
%label.label-bold
- Connected Accounts
- %p Click on icon to activate signin with one of the following services
+ = 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
@@ -39,24 +39,24 @@
- if auth_active?(provider)
- if unlink_allowed?(provider)
= link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'provider-btn' do
- Disconnect
+ = s_('Profiles|Disconnect')
- else
%a.provider-btn
- Active
+ = s_('Profiles|Active')
- else
= link_to omniauth_authorize_path(:user, provider), method: :post, class: 'provider-btn not-active' do
- Connect
+ = s_('Profiles|Connect')
= render_if_exists 'profiles/accounts/group_saml_unlink_buttons', group_saml_identities: local_assigns[:group_saml_identities]
%hr
- if current_user.can_change_username?
.row.prepend-top-default
.col-lg-4.profile-settings-sidebar
%h4.prepend-top-0.warning-title
- Change username
+ = s_('Profiles|Change username')
%p
- Changing your username can have unintended side effects.
+ = s_('Profiles|Changing your username can have unintended side effects.')
= succeed '.' do
- = link_to 'Learn more', help_page_path('user/profile/index', anchor: 'changing-your-username'), target: '_blank'
+ = link_to s_('Profiles|Learn more'), help_page_path('user/profile/index', anchor: 'changing-your-username'), target: '_blank'
.col-lg-8
- data = { initial_username: current_user.username, root_url: root_url, action_url: update_username_profile_path(format: :json) }
#update-username{ data: data }
diff --git a/app/views/projects/_export.html.haml b/app/views/projects/_export.html.haml
index aa980da7e95..91deffe07c1 100644
--- a/app/views/projects/_export.html.haml
+++ b/app/views/projects/_export.html.haml
@@ -19,7 +19,7 @@
%ul
%li Project and wiki repositories
%li Project uploads
- %li Project configuration including web hooks and services
+ %li Project configuration, including services
%li Issues with comments, merge requests with diffs and comments, labels, milestones, snippets, and other project entities
%li LFS objects
%p
@@ -28,6 +28,7 @@
%li Job traces and artifacts
%li Container registry images
%li CI variables
+ %li Webhooks
%li Any encrypted tokens
%p
Once the exported file is ready, you will receive a notification email with a download link, or you can download it from this page.
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index d2587af11dd..e8cc3d6bcf0 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -23,7 +23,7 @@
= sprite_icon('tag', size: 16, css_class: 'icon append-right-4')
= @project.topics_to_show
- if @project.has_extra_topics?
- = _("+ %{count} more") % { count: @project.count_of_extra_tags_not_shown }
+ = _("+ %{count} more") % { count: @project.count_of_extra_topics_not_shown }
.project-repo-buttons.col-md-12.col-lg-6.d-inline-flex.flex-wrap.justify-content-lg-end
diff --git a/app/views/projects/artifacts/_tree_file.html.haml b/app/views/projects/artifacts/_tree_file.html.haml
index cfb91568061..f42d5128715 100644
--- a/app/views/projects/artifacts/_tree_file.html.haml
+++ b/app/views/projects/artifacts/_tree_file.html.haml
@@ -14,4 +14,4 @@
= link_to path_to_file, class: 'str-truncated' do
%span= blob.name
%td
- = number_to_human_size(blob.size, precision: 2)
+ = number_to_human_size(blob.size)
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 88f9b7dfc9f..4b0ea15335e 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -76,7 +76,7 @@
= icon("trash-o")
- else
= link_to project_branch_path(@project, branch.name),
- class: "btn btn-remove remove-row js-ajax-loading-spinner has-tooltip",
+ class: "btn btn-remove remove-row qa-remove-btn js-ajax-loading-spinner has-tooltip",
title: s_('Branches|Delete branch'),
method: :delete,
data: { confirm: s_("Branches|Deleting the '%{branch_name}' branch cannot be undone. Are you sure?") % { branch_name: branch.name } },
diff --git a/app/views/projects/branches/_panel.html.haml b/app/views/projects/branches/_panel.html.haml
index 0e4b119bb54..93061452e12 100644
--- a/app/views/projects/branches/_panel.html.haml
+++ b/app/views/projects/branches/_panel.html.haml
@@ -10,7 +10,7 @@
.card.prepend-top-10
.card-header
= panel_title
- %ul.content-list.all-branches
+ %ul.content-list.all-branches.qa-all-branches
- branches.first(overview_max_branches).each do |branch|
= render "projects/branches/branch", branch: branch, merged: project.repository.merged_to_root_ref?(branch)
- if branches.size > overview_max_branches
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index ca867961f6b..43f1cd01b67 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -35,7 +35,7 @@
- if can? current_user, :push_code, @project
= link_to project_merged_branches_path(@project),
- class: 'btn btn-inverted btn-remove has-tooltip',
+ class: 'btn btn-inverted btn-remove has-tooltip qa-delete-merged-branches',
title: s_("Branches|Delete all branches that are merged into '%{default_branch}'") % { default_branch: @project.repository.root_ref },
method: :delete,
data: { confirm: s_('Branches|Deleting the merged branches cannot be undone. Are you sure?'),
diff --git a/app/views/projects/environments/folder.html.haml b/app/views/projects/environments/folder.html.haml
index b7e1cf85cb7..aebd176af9b 100644
--- a/app/views/projects/environments/folder.html.haml
+++ b/app/views/projects/environments/folder.html.haml
@@ -1,8 +1,5 @@
- @no_container = true
- page_title _("Environments")
-#environments-folder-list-view{ data: { endpoint: folder_project_environments_path(@project, @folder, format: :json),
- "folder-name" => @folder,
- "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
- "can-read-environment" => can?(current_user, :read_environment, @project).to_s,
+#environments-folder-list-view{ data: { environments_data: environments_folder_list_view_data,
"css-class" => container_class } }
diff --git a/app/views/projects/error_tracking/index.html.haml b/app/views/projects/error_tracking/index.html.haml
index a3e0dc75f6f..bc02c5f0e5a 100644
--- a/app/views/projects/error_tracking/index.html.haml
+++ b/app/views/projects/error_tracking/index.html.haml
@@ -1 +1,3 @@
- page_title _('Errors')
+
+#js-error_tracking{ data: error_tracking_data(@project) }
diff --git a/app/views/projects/issues/_nav_btns.html.haml b/app/views/projects/issues/_nav_btns.html.haml
index fd6559e37ba..329efa0cdbf 100644
--- a/app/views/projects/issues/_nav_btns.html.haml
+++ b/app/views/projects/issues/_nav_btns.html.haml
@@ -1,5 +1,5 @@
- show_feed_buttons = local_assigns.fetch(:show_feed_buttons, true)
-- show_import_button = local_assigns.fetch(:show_import_button, true) && Feature.enabled?(:issues_import_csv) && can?(current_user, :import_issues, @project)
+- show_import_button = local_assigns.fetch(:show_import_button, true) && can?(current_user, :import_issues, @project)
- show_export_button = local_assigns.fetch(:show_export_button, true)
.nav-controls.issues-nav-controls
diff --git a/app/views/projects/jobs/index.html.haml b/app/views/projects/jobs/index.html.haml
index 59592abcf6a..afea5268006 100644
--- a/app/views/projects/jobs/index.html.haml
+++ b/app/views/projects/jobs/index.html.haml
@@ -8,10 +8,6 @@
.nav-controls
- if can?(current_user, :update_build, @project)
- - if @all_builds.running_or_pending.limit(1).any? # rubocop: disable CodeReuse/ActiveRecord
- = link_to 'Cancel running', cancel_all_project_jobs_path(@project),
- data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
-
- unless @repository.gitlab_ci_yml
= link_to 'Get started with Pipelines', help_page_path('ci/quick_start/README'), class: 'btn btn-info'
diff --git a/app/views/projects/project_members/_groups.html.haml b/app/views/projects/project_members/_groups.html.haml
index 3f05e06b0c6..b5d397e3065 100644
--- a/app/views/projects/project_members/_groups.html.haml
+++ b/app/views/projects/project_members/_groups.html.haml
@@ -1,7 +1,6 @@
.card.project-members-groups
.card-header
- Groups with access to
- %strong= @project.name
+ = _("Groups with access to <strong>%{project_name}</strong>").html_safe % { project_name: sanitize_project_name(@project.name) }
%span.badge.badge-pill= group_links.size
%ul.content-list.members-list
= render partial: 'shared/members/group', collection: group_links, as: :group_link
diff --git a/app/views/projects/project_members/_new_project_group.html.haml b/app/views/projects/project_members/_new_project_group.html.haml
index 88e68f89024..079811e4e79 100644
--- a/app/views/projects/project_members/_new_project_group.html.haml
+++ b/app/views/projects/project_members/_new_project_group.html.haml
@@ -10,8 +10,9 @@
= select_tag :link_group_access, options_for_select(ProjectGroupLink.access_options, ProjectGroupLink.default_access), class: "form-control select-control"
= icon('chevron-down')
.form-text.text-muted.append-bottom-10
- = link_to _("Read more"), help_page_path("user/permissions")
- about role permissions
+ - permissions_docs_path = help_page_path('user/permissions')
+ - link_start = %q{<a href="%{url}">}.html_safe % { url: permissions_docs_path }
+ = _("%{link_start}Read more%{link_end} about role permissions").html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
.form-group
= label_tag :expires_at, _('Access expiration date'), class: 'label-bold'
.clearable-input
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
index 1de7d9c6957..0590578c3fe 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -2,20 +2,21 @@
.col-sm-12
= form_for @project_member, as: :project_member, url: project_project_members_path(@project), html: { class: 'users-project-form' } do |f|
.form-group
- = label_tag :user_ids, "Select members to invite", class: "label-bold"
+ = label_tag :user_ids, _("Select members to invite"), class: "label-bold"
= users_select_tag(:user_ids, multiple: true, class: "input-clamp qa-member-select-input", scope: :all, email_user: true, placeholder: "Search for members to update or invite")
.form-group
- = label_tag :access_level, "Choose a role permission", class: "label-bold"
+ = label_tag :access_level, _("Choose a role permission"), class: "label-bold"
.select-wrapper
= select_tag :access_level, options_for_select(ProjectMember.access_level_roles, @project_member.access_level), class: "form-control project-access-select select-control"
= icon('chevron-down')
.form-text.text-muted.append-bottom-10
- = link_to "Read more", help_page_path("user/permissions")
- about role permissions
+ - permissions_docs_path = help_page_path('user/permissions')
+ - link_start = %q{<a href="%{url}">}.html_safe % { url: permissions_docs_path }
+ = _("%{link_start}Read more%{link_end} about role permissions").html_safe % { link_start: link_start, link_end: '</a>'.html_safe }
.form-group
.clearable-input
- = label_tag :expires_at, 'Access expiration date', class: 'label-bold'
+ = label_tag :expires_at, _('Access expiration date'), class: 'label-bold'
= text_field_tag :expires_at, nil, class: 'form-control js-access-expiration-date', placeholder: 'Expiration date'
%i.clear-icon.js-clear-input
- = f.submit "Add to project", class: "btn btn-success qa-add-member-button"
- = link_to "Import", import_project_project_members_path(@project), class: "btn btn-default", title: "Import members from another project"
+ = f.submit _("Add to project"), class: "btn btn-success qa-add-member-button"
+ = link_to _("Import"), import_project_project_members_path(@project), class: "btn btn-default", title: _("Import members from another project")
diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml
index 9682f8ac922..b92ecf4412f 100644
--- a/app/views/projects/project_members/_team.html.haml
+++ b/app/views/projects/project_members/_team.html.haml
@@ -4,14 +4,13 @@
.card
.card-header.flex-project-members-panel
%span.flex-project-title
- Members of
- %strong= project.name
+ = _("Members of <strong>%{project_name}</strong>").html_safe % { project_name: sanitize_project_name(project.name) }
%span.badge.badge-pill= members.total_count
= form_tag project_project_members_path(project), method: :get, class: 'form-inline member-search-form flex-project-members-form' do
.form-group
.position-relative
- = search_field_tag :search, params[:search], { placeholder: 'Find existing members by name', class: 'form-control', spellcheck: false }
- %button.member-search-btn{ type: "submit", "aria-label" => "Submit search" }
+ = search_field_tag :search, params[:search], { placeholder: _('Find existing members by name'), class: 'form-control', spellcheck: false }
+ %button.member-search-btn{ type: "submit", "aria-label" => _("Submit search") }
= icon("search")
= render 'shared/members/sort_dropdown'
%ul.content-list.members-list.qa-members-list
diff --git a/app/views/projects/project_members/import.html.haml b/app/views/projects/project_members/import.html.haml
index 8b93e81cd31..bcca943de6a 100644
--- a/app/views/projects/project_members/import.html.haml
+++ b/app/views/projects/project_members/import.html.haml
@@ -1,15 +1,15 @@
-- page_title "Import members"
+- page_title _("Import members")
%h3.page-title
- Import members from another project
+ = _("Import members from another project")
%p.light
- Only project members will be imported. Group members will be skipped.
+ = _("Only project members will be imported. Group members will be skipped.")
%hr
= form_tag apply_import_project_project_members_path(@project), method: 'post' do
.form-group.row
- = label_tag :source_project_id, "Project", class: 'col-form-label col-sm-2'
+ = label_tag :source_project_id, _("Project"), class: 'col-form-label col-sm-2'
.col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(@projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true)
.form-actions
- = button_tag 'Import project members', class: "btn btn-success"
- = link_to "Cancel", project_project_members_path(@project), class: "btn btn-cancel"
+ = button_tag _('Import project members'), class: "btn btn-success"
+ = link_to _("Cancel"), project_project_members_path(@project), class: "btn btn-cancel"
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 14ed3345765..242ff91f539 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -1,39 +1,34 @@
-- page_title "Members"
+- page_title _("Members")
.row.prepend-top-default
.col-lg-12
%h4
- Project members
+ = _("Project members")
- if can?(current_user, :admin_project_member, @project)
%p
- You can invite a new member to
- %strong= @project.name
- or invite another group.
+ = _("You can invite a new member to <strong>%{project_name}</strong> or invite another group.").html_safe % { project_name: sanitize_project_name(@project.name) }
- else
%p
- Members can be added by project
- %i Maintainers
- or
- %i Owners
+ = _("Members can be added by project <i>Maintainers</i> or <i>Owners</i>").html_safe
.light
- if can?(current_user, :admin_project_member, @project)
%ul.nav-links.nav.nav-tabs.gitlab-tabs{ role: 'tablist' }
%li.nav-tab{ role: 'presentation' }
- %a.nav-link.active{ href: '#invite-member-pane', id: 'invite-member-tab', data: { toggle: 'tab' }, role: 'tab' } Invite member
+ %a.nav-link.active{ href: '#invite-member-pane', id: 'invite-member-tab', data: { toggle: 'tab' }, role: 'tab' }= _("Invite member")
- if @project.allowed_to_share_with_group?
%li.nav-tab{ role: 'presentation' }
- %a.nav-link{ href: '#invite-group-pane', id: 'invite-group-tab', data: { toggle: 'tab' }, role: 'tab' } Invite group
+ %a.nav-link{ href: '#invite-group-pane', id: 'invite-group-tab', data: { toggle: 'tab' }, role: 'tab' }= _("Invite group")
.tab-content.gitlab-tab-content
.tab-pane.active{ id: 'invite-member-pane', role: 'tabpanel' }
- = render 'projects/project_members/new_project_member', tab_title: 'Invite member'
+ = render 'projects/project_members/new_project_member', tab_title: _('Invite member')
.tab-pane{ id: 'invite-group-pane', role: 'tabpanel' }
- = render 'projects/project_members/new_project_group', tab_title: 'Invite group'
+ = render 'projects/project_members/new_project_group', tab_title: _('Invite group')
= render 'shared/members/requests', membership_source: @project, requesters: @requesters
.clearfix
%h5.member.existing-title
- Existing members and groups
+ = _("Existing members and groups")
- if @group_links.any?
= render 'projects/project_members/groups', group_links: @group_links
diff --git a/app/views/projects/settings/operations/_error_tracking.html.haml b/app/views/projects/settings/operations/_error_tracking.html.haml
index 71335e4dfd0..871b60f05ba 100644
--- a/app/views/projects/settings/operations/_error_tracking.html.haml
+++ b/app/views/projects/settings/operations/_error_tracking.html.haml
@@ -18,7 +18,7 @@
= form.label :enabled, _('Active'), class: 'form-check-label'
.form-group
= form.label :api_url, _('Sentry API URL'), class: 'label-bold'
- = form.url_field :api_url, class: 'form-control', placeholder: _('http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/issues/')
+ = form.url_field :api_url, class: 'form-control', placeholder: _('http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/')
%p.form-text.text-muted
= _('Enter your Sentry API URL')
.form-group
diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml
index ff9a7b53a86..aaf9b973cda 100644
--- a/app/views/search/_category.html.haml
+++ b/app/views/search/_category.html.haml
@@ -6,75 +6,75 @@
- if project_search_tabs?(:blobs)
%li{ class: active_when(@scope == 'blobs') }
= link_to search_filter_path(scope: 'blobs') do
- Code
+ = _("Code")
%span.badge.badge-pill
= @search_results.blobs_count
- if project_search_tabs?(:issues)
%li{ class: active_when(@scope == 'issues') }
= link_to search_filter_path(scope: 'issues') do
- Issues
+ = _("Issues")
%span.badge.badge-pill
= limited_count(@search_results.limited_issues_count)
- if project_search_tabs?(:merge_requests)
%li{ class: active_when(@scope == 'merge_requests') }
= link_to search_filter_path(scope: 'merge_requests') do
- Merge requests
+ = _("Merge requests")
%span.badge.badge-pill
= limited_count(@search_results.limited_merge_requests_count)
- if project_search_tabs?(:milestones)
%li{ class: active_when(@scope == 'milestones') }
= link_to search_filter_path(scope: 'milestones') do
- Milestones
+ = _("Milestones")
%span.badge.badge-pill
= limited_count(@search_results.limited_milestones_count)
- if project_search_tabs?(:notes)
%li{ class: active_when(@scope == 'notes') }
= link_to search_filter_path(scope: 'notes') do
- Comments
+ = _("Comments")
%span.badge.badge-pill
= limited_count(@search_results.limited_notes_count)
- if project_search_tabs?(:wiki)
%li{ class: active_when(@scope == 'wiki_blobs') }
= link_to search_filter_path(scope: 'wiki_blobs') do
- Wiki
+ = _("Wiki")
%span.badge.badge-pill
= @search_results.wiki_blobs_count
- if project_search_tabs?(:commits)
%li{ class: active_when(@scope == 'commits') }
= link_to search_filter_path(scope: 'commits') do
- Commits
+ = _("Commits")
%span.badge.badge-pill
= @search_results.commits_count
- elsif @show_snippets
%li{ class: active_when(@scope == 'snippet_blobs') }
= link_to search_filter_path(scope: 'snippet_blobs', snippets: true, group_id: nil, project_id: nil) do
- Snippet Contents
+ = _("Snippet Contents")
%span.badge.badge-pill
= @search_results.snippet_blobs_count
%li{ class: active_when(@scope == 'snippet_titles') }
= link_to search_filter_path(scope: 'snippet_titles', snippets: true, group_id: nil, project_id: nil) do
- Titles and Filenames
+ = _("Titles and Filenames")
%span.badge.badge-pill
= @search_results.snippet_titles_count
- else
%li{ class: active_when(@scope == 'projects') }
= link_to search_filter_path(scope: 'projects') do
- Projects
+ = _("Projects")
%span.badge.badge-pill
= limited_count(@search_results.limited_projects_count)
%li{ class: active_when(@scope == 'issues') }
= link_to search_filter_path(scope: 'issues') do
- Issues
+ = _("Issues")
%span.badge.badge-pill
= limited_count(@search_results.limited_issues_count)
%li{ class: active_when(@scope == 'merge_requests') }
= link_to search_filter_path(scope: 'merge_requests') do
- Merge requests
+ = _("Merge requests")
%span.badge.badge-pill
= limited_count(@search_results.limited_merge_requests_count)
%li{ class: active_when(@scope == 'milestones') }
= link_to search_filter_path(scope: 'milestones') do
- Milestones
+ = _("Milestones")
%span.badge.badge-pill
= limited_count(@search_results.limited_milestones_count)
diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml
index 6837f64f132..c8b6a3258ab 100644
--- a/app/views/search/_filter.html.haml
+++ b/app/views/search/_filter.html.haml
@@ -3,31 +3,31 @@
- if params[:project_id].present?
= hidden_field_tag :project_id, params[:project_id]
.dropdown
- %button.dropdown-menu-toggle.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Group:", group_id: params[:group_id] } }
+ %button.dropdown-menu-toggle.js-search-group-dropdown{ type: "button", data: { toggle: "dropdown", default_label: _('Group:'), group_id: params[:group_id] } }
%span.dropdown-toggle-text
- Group:
+ = _("Group:")
- if @group.present?
= @group.name
- else
- Any
+ = _("Any")
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-right
- = dropdown_title("Filter results by group")
- = dropdown_filter("Search groups")
+ = dropdown_title(_("Filter results by group"))
+ = dropdown_filter(_("Search groups"))
= dropdown_content
= dropdown_loading
.dropdown.project-filter
- %button.dropdown-menu-toggle.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: "Project:" } }
+ %button.dropdown-menu-toggle.js-search-project-dropdown{ type: "button", data: { toggle: "dropdown", default_label: _('Project:') } }
%span.dropdown-toggle-text
- Project:
+ = _("Project:")
- if @project.present?
= @project.full_name
- else
- Any
+ = _("Any")
= icon("chevron-down")
.dropdown-menu.dropdown-select.dropdown-menu-selectable.dropdown-menu-right
- = dropdown_title("Filter results by project")
- = dropdown_filter("Search projects")
+ = dropdown_title(_("Filter results by project"))
+ = dropdown_filter(_("Search projects"))
= dropdown_content
= dropdown_loading
diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml
index a4a5cec1314..4af0c6bf84a 100644
--- a/app/views/search/_form.html.haml
+++ b/app/views/search/_form.html.haml
@@ -4,12 +4,12 @@
.search-holder
.search-field-holder
- = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false
+ = search_field_tag :search, params[:search], placeholder: _("Search for projects, issues, etc."), class: "form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false
= icon("search", class: "search-icon")
%button.search-clear.js-search-clear{ class: ("hidden" if !params[:search].present?), type: "button", tabindex: "-1" }
= icon("times-circle")
%span.sr-only
- Clear search
+ = _("Clear search")
- unless params[:snippets].eql? 'true'
= render 'filter'
- = button_tag "Search", class: "btn btn-success btn-search"
+ = button_tag _("Search"), class: "btn btn-success btn-search"
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index c4d52431d6e..be7a2436d16 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -6,9 +6,11 @@
= search_entries_info(@search_objects, @scope, @search_term)
- unless @show_snippets
- if @project
- in project #{link_to @project.full_name, [@project.namespace.becomes(Namespace), @project]}
+ - link_to_project = link_to(@project.full_name, [@project.namespace.becomes(Namespace), @project])
+ = _("in project %{link_to_project}").html_safe % { link_to_project: link_to_project }
- elsif @group
- in group #{link_to @group.name, @group}
+ - link_to_group = link_to(@group.name, @group)
+ = _("in group %{link_to_group}").html_safe % { link_to_group: link_to_group }
.results.prepend-top-10
- if @scope == 'commits'
diff --git a/app/views/search/results/_empty.html.haml b/app/views/search/results/_empty.html.haml
index 821a39d61f5..9d15995bb51 100644
--- a/app/views/search/results/_empty.html.haml
+++ b/app/views/search/results/_empty.html.haml
@@ -2,5 +2,5 @@
.search_glyph
%h4
= icon('search')
- We couldn't find any results matching
+ = _("We couldn't find any results matching")
%code= @search_term
diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml
index c413c3d4abb..796782035f2 100644
--- a/app/views/search/results/_issue.html.haml
+++ b/app/views/search/results/_issue.html.haml
@@ -4,7 +4,7 @@
= link_to [issue.project.namespace.becomes(Namespace), issue.project, issue] do
%span.term.str-truncated= issue.title
- if issue.closed?
- %span.badge.badge-danger.prepend-left-5 Closed
+ %span.badge.badge-danger.prepend-left-5= _("Closed")
.float-right ##{issue.iid}
- if issue.description.present?
.description.term
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index 519176af108..f0e0af11f27 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -3,9 +3,9 @@
= link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do
%span.term.str-truncated= merge_request.title
- if merge_request.merged?
- %span.badge.badge-primary.prepend-left-5 Merged
+ %span.badge.badge-primary.prepend-left-5= _("Merged")
- elsif merge_request.closed?
- %span.badge.badge-danger.prepend-left-5 Closed
+ %span.badge.badge-danger.prepend-left-5= _("Closed")
.float-right= merge_request.to_reference
- if merge_request.description.present?
.description.term
diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml
index e4ab7b0541f..6717939d034 100644
--- a/app/views/search/results/_note.html.haml
+++ b/app/views/search/results/_note.html.haml
@@ -6,14 +6,14 @@
%h5.note-search-caption.str-truncated
%i.fa.fa-comment
= link_to_member(project, note.author, avatar: false)
- commented on
- = link_to project.full_name, project
+ - link_to_project = link_to(project.full_name, project)
+ = _("commented on %{link_to_project}").html_safe % { link_to_project: link_to_project }
&middot;
- if note.for_commit?
- = link_to_if(noteable_identifier, "Commit #{truncate_sha(note.commit_id)}", note_url) do
+ = link_to_if(noteable_identifier, _("Commit %{commit_id}") % { commit_id: truncate_sha(note.commit_id) }, note_url) do
= truncate_sha(note.commit_id)
- %span.light Commit deleted
+ %span.light= _("Commit deleted")
- else
%span #{note.noteable_type.titleize} ##{noteable_identifier}
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index b4ecd7bbae9..e0130f9a4b5 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -24,7 +24,7 @@
= markup(snippet.file_name, chunk[:data], legacy_render_context(params))
- else
.file-content.code
- .nothing-here-block Empty file
+ .nothing-here-block= _("Empty file")
- else
.file-content.code.js-syntax-highlight
.line-numbers
@@ -42,4 +42,4 @@
= highlight(snippet.file_name, chunk[:data])
- else
.file-content.code
- .nothing-here-block Empty file
+ .nothing-here-block= _("Empty file")
diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml
index 9c8afb2165b..1e01088d9e6 100644
--- a/app/views/search/results/_snippet_title.html.haml
+++ b/app/views/search/results/_snippet_title.html.haml
@@ -5,7 +5,7 @@
- if snippet_title.private?
%span.badge.badge-gray
%i.fa.fa-lock
- private
+ = _("private")
%span.cgray.monospace.tiny.float-right.term
= snippet_title.file_name
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index 499697f2777..3260d05f509 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -1,5 +1,5 @@
- @hide_top_links = true
-- breadcrumb_title "Search"
+- breadcrumb_title _("Search")
- page_title @search_term
.prepend-top-default
diff --git a/app/views/shared/_personal_access_tokens_created_container.html.haml b/app/views/shared/_personal_access_tokens_created_container.html.haml
index 3150d39b84a..a8d3de66418 100644
--- a/app/views/shared/_personal_access_tokens_created_container.html.haml
+++ b/app/views/shared/_personal_access_tokens_created_container.html.haml
@@ -6,7 +6,7 @@
= container_title
.form-group
.input-group
- = text_field_tag 'created-personal-access-token', new_token_value, readonly: true, class: "form-control js-select-on-focus", 'aria-describedby' => "created-token-help-block"
+ = text_field_tag 'created-personal-access-token', new_token_value, readonly: true, class: "qa-created-personal-access-token form-control js-select-on-focus", 'aria-describedby' => "created-token-help-block"
%span.input-group-append
= clipboard_button(text: new_token_value, title: clipboard_button_title, placement: "left", class: "input-group-text btn-default btn-clipboard")
%span#created-token-help-block.form-text.text-muted.text-danger Make sure you save it - you won't be able to access it again.
diff --git a/app/views/shared/_personal_access_tokens_form.html.haml b/app/views/shared/_personal_access_tokens_form.html.haml
index f4df7bdcd83..0891b3459ec 100644
--- a/app/views/shared/_personal_access_tokens_form.html.haml
+++ b/app/views/shared/_personal_access_tokens_form.html.haml
@@ -12,7 +12,7 @@
.row
.form-group.col-md-6
= f.label :name, class: 'label-bold'
- = f.text_field :name, class: "form-control", required: true
+ = f.text_field :name, class: "form-control qa-personal-access-token-name-field", required: true
.row
.form-group.col-md-6
@@ -26,4 +26,4 @@
= render 'shared/tokens/scopes_form', prefix: 'personal_access_token', token: token, scopes: scopes
.prepend-top-default
- = f.submit "Create #{type} token", class: "btn btn-success"
+ = f.submit "Create #{type} token", class: "btn btn-success qa-create-token-button"
diff --git a/app/views/shared/_personal_access_tokens_table.html.haml b/app/views/shared/_personal_access_tokens_table.html.haml
index 2efd03d4867..49f3aae0f98 100644
--- a/app/views/shared/_personal_access_tokens_table.html.haml
+++ b/app/views/shared/_personal_access_tokens_table.html.haml
@@ -29,7 +29,7 @@
%span.token-never-expires-label Never
%td= token.scopes.present? ? token.scopes.join(", ") : "<no scopes selected>"
- path = impersonation ? revoke_admin_user_impersonation_token_path(token.user, token) : revoke_profile_personal_access_token_path(token)
- %td= link_to "Revoke", path, method: :put, class: "btn btn-danger float-right", data: { confirm: "Are you sure you want to revoke this #{type} Token? This action cannot be undone." }
+ %td= link_to "Revoke", path, method: :put, class: "btn btn-danger float-right qa-revoke-button", data: { confirm: "Are you sure you want to revoke this #{type} Token? This action cannot be undone." }
- else
.settings-message.text-center
This user has no active #{type} Tokens.
diff --git a/app/views/shared/empty_states/_issues.html.haml b/app/views/shared/empty_states/_issues.html.haml
index 2691ec4cd46..9173b802dd4 100644
--- a/app/views/shared/empty_states/_issues.html.haml
+++ b/app/views/shared/empty_states/_issues.html.haml
@@ -1,6 +1,6 @@
- button_path = local_assigns.fetch(:button_path, false)
- project_select_button = local_assigns.fetch(:project_select_button, false)
-- show_import_button = local_assigns.fetch(:show_import_button, false) && Feature.enabled?(:issues_import_csv) && can?(current_user, :import_issues, @project)
+- show_import_button = local_assigns.fetch(:show_import_button, false) && can?(current_user, :import_issues, @project)
- has_button = button_path || project_select_button
- closed_issues_count = issuables_count_for_state(:issues, :closed)
- opened_issues_count = issuables_count_for_state(:issues, :opened)
diff --git a/app/views/shared/issuable/_board_create_list_dropdown.html.haml b/app/views/shared/issuable/_board_create_list_dropdown.html.haml
index 4597d9439fa..fd413bd68c8 100644
--- a/app/views/shared/issuable/_board_create_list_dropdown.html.haml
+++ b/app/views/shared/issuable/_board_create_list_dropdown.html.haml
@@ -1,7 +1,7 @@
.dropdown.prepend-left-10#js-add-list
%button.btn.btn-success.btn-inverted.js-new-board-list{ type: "button", data: board_list_data }
Add list
- .dropdown-menu.dropdown-menu-paging.dropdown-menu-right.dropdown-menu-issues-board-new.dropdown-menu-selectable.js-tab-container-labels
+ .dropdown-menu.dropdown-extended-height.dropdown-menu-paging.dropdown-menu-right.dropdown-menu-issues-board-new.dropdown-menu-selectable.js-tab-container-labels
= render partial: "shared/issuable/label_page_default", locals: { show_footer: true, show_create: true, show_boards_content: true, title: "Add list" }
- if can?(current_user, :admin_label, board.parent)
= render partial: "shared/issuable/label_page_create"
diff --git a/app/views/shared/tokens/_scopes_form.html.haml b/app/views/shared/tokens/_scopes_form.html.haml
index af9db5f59a8..a5d3e1c8de0 100644
--- a/app/views/shared/tokens/_scopes_form.html.haml
+++ b/app/views/shared/tokens/_scopes_form.html.haml
@@ -4,6 +4,6 @@
- scopes.each do |scope|
%fieldset.form-group.form-check
- = check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}", class: 'form-check-input'
+ = check_box_tag "#{prefix}[scopes][]", scope, token.scopes.include?(scope), id: "#{prefix}_scopes_#{scope}", class: "form-check-input qa-#{scope}-radio"
= label_tag ("#{prefix}_scopes_#{scope}"), scope, class: 'label-bold form-check-label'
.text-secondary= t scope, scope: [:doorkeeper, :scope_desc]
diff --git a/app/workers/git_garbage_collect_worker.rb b/app/workers/git_garbage_collect_worker.rb
index d3628b23189..b33e9b1f718 100644
--- a/app/workers/git_garbage_collect_worker.rb
+++ b/app/workers/git_garbage_collect_worker.rb
@@ -23,6 +23,7 @@ class GitGarbageCollectWorker
end
task = task.to_sym
+ project.link_pool_repository
gitaly_call(task, project.repository.raw_repository)
# Refresh the branch cache in case garbage collection caused a ref lookup to fail
diff --git a/app/workers/object_pool/join_worker.rb b/app/workers/object_pool/join_worker.rb
index 07676011b2a..9c5161fd55a 100644
--- a/app/workers/object_pool/join_worker.rb
+++ b/app/workers/object_pool/join_worker.rb
@@ -5,14 +5,13 @@ module ObjectPool
include ApplicationWorker
include ObjectPoolQueue
- def perform(pool_id, project_id)
- pool = PoolRepository.find_by_id(pool_id)
- return unless pool&.joinable?
-
+ # The use of pool id is deprecated. Keeping the argument allows old jobs to
+ # still be performed.
+ def perform(_pool_id, project_id)
project = Project.find_by_id(project_id)
- return unless project
+ return unless project&.pool_repository&.joinable?
- pool.link_repository(project.repository)
+ project.link_pool_repository
Projects::HousekeepingService.new(project).execute
end
diff --git a/app/workers/remote_mirror_notification_worker.rb b/app/workers/remote_mirror_notification_worker.rb
index 70c2e857d09..5bafe8e2046 100644
--- a/app/workers/remote_mirror_notification_worker.rb
+++ b/app/workers/remote_mirror_notification_worker.rb
@@ -9,7 +9,10 @@ class RemoteMirrorNotificationWorker
# We check again if there's an error because a newer run since this job was
# fired could've completed successfully.
return unless remote_mirror && remote_mirror.last_error.present?
+ return if remote_mirror.error_notification_sent?
NotificationService.new.remote_mirror_update_failed(remote_mirror)
+
+ remote_mirror.after_sent_notification
end
end
diff --git a/bin/changelog b/bin/changelog
index 758c036161e..328d9495b96 100755
--- a/bin/changelog
+++ b/bin/changelog
@@ -148,7 +148,7 @@ class ChangelogEntry
def execute
assert_feature_branch!
- assert_title!
+ assert_title! unless editor
assert_new_file!
# Read type from $stdin unless is already set
@@ -162,6 +162,10 @@ class ChangelogEntry
write
amend_commit if options.amend
end
+
+ if editor
+ system("#{editor} '#{file_path}'")
+ end
end
private
@@ -180,6 +184,10 @@ class ChangelogEntry
File.write(file_path, contents)
end
+ def editor
+ ENV['EDITOR']
+ end
+
def amend_commit
fail_with "git add failed" unless system(*%W[git add #{file_path}])
diff --git a/changelogs/unreleased/25341-add-what-s-new-menu-item-in-top-navigation.yml b/changelogs/unreleased/25341-add-what-s-new-menu-item-in-top-navigation.yml
new file mode 100644
index 00000000000..da1777827cb
--- /dev/null
+++ b/changelogs/unreleased/25341-add-what-s-new-menu-item-in-top-navigation.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve Add What's new menu item in top navigation
+merge_request: 23186
+author:
+type: added
diff --git a/changelogs/unreleased/37990-task-list-bracket.yml b/changelogs/unreleased/37990-task-list-bracket.yml
new file mode 100644
index 00000000000..ffa77cf0af7
--- /dev/null
+++ b/changelogs/unreleased/37990-task-list-bracket.yml
@@ -0,0 +1,5 @@
+---
+title: Fix ambiguous brackets in task lists
+merge_request: 18514
+author: Jared Deckard <jared.deckard@gmail.com>
+type: fixed
diff --git a/changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml b/changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml
new file mode 100644
index 00000000000..c1cde0ceff6
--- /dev/null
+++ b/changelogs/unreleased/52275-fix-master-to-be-hyperlink.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve In Merge Request diff screen, master is not a hyperlink
+merge_request: 23874
+author:
+type: fixed
diff --git a/changelogs/unreleased/52278-squash-checkbox-fix.yml b/changelogs/unreleased/52278-squash-checkbox-fix.yml
new file mode 100644
index 00000000000..c81748ae419
--- /dev/null
+++ b/changelogs/unreleased/52278-squash-checkbox-fix.yml
@@ -0,0 +1,5 @@
+---
+title: Resolve When merging an MR, the squash checkbox isnt always supported
+merge_request: 24296
+author:
+type: fixed
diff --git a/changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml b/changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml
new file mode 100644
index 00000000000..07cb35e6529
--- /dev/null
+++ b/changelogs/unreleased/52363-modifies-environment-scope-field-on-cluster-page.yml
@@ -0,0 +1,5 @@
+---
+title: Modifies environment scope UI on cluster page
+merge_request: 24376
+author:
+type: other
diff --git a/changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml b/changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml
new file mode 100644
index 00000000000..b609fc2d60b
--- /dev/null
+++ b/changelogs/unreleased/55111-gitlab-api-does-not-manage-default_branch_protection-3-value.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Fix default_branch_protection admin setting'
+merge_request: 24398
+author: Robert Schilling
+type: fixed
diff --git a/changelogs/unreleased/55884-adjust-emoji-and-cancel-buttons-height-in-user-status-modal-when-emoji-is-changed.yml b/changelogs/unreleased/55884-adjust-emoji-and-cancel-buttons-height-in-user-status-modal-when-emoji-is-changed.yml
new file mode 100644
index 00000000000..2fbf334f5e9
--- /dev/null
+++ b/changelogs/unreleased/55884-adjust-emoji-and-cancel-buttons-height-in-user-status-modal-when-emoji-is-changed.yml
@@ -0,0 +1,5 @@
+---
+title: Emoji and cancel button are taller than input in set user status modal
+merge_request: 24173
+author: Dhiraj Bodicherla
+type: fixed
diff --git a/changelogs/unreleased/55945-suggested-change-highlight.yml b/changelogs/unreleased/55945-suggested-change-highlight.yml
deleted file mode 100644
index 611854d36ab..00000000000
--- a/changelogs/unreleased/55945-suggested-change-highlight.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Add syntax highlighting to suggestion diff
-merge_request: 24156
-author:
-type: fixed
diff --git a/changelogs/unreleased/55945-suggested-change-preview-highlight.yml b/changelogs/unreleased/55945-suggested-change-preview-highlight.yml
new file mode 100644
index 00000000000..997290a5d50
--- /dev/null
+++ b/changelogs/unreleased/55945-suggested-change-preview-highlight.yml
@@ -0,0 +1,5 @@
+---
+title: Fix syntax highlighting for suggested changes preview
+merge_request: 24358
+author:
+type: fixed
diff --git a/changelogs/unreleased/56172-docs-fix-add-include-to-ci-param-list.yml b/changelogs/unreleased/56172-docs-fix-add-include-to-ci-param-list.yml
new file mode 100644
index 00000000000..92592290ac4
--- /dev/null
+++ b/changelogs/unreleased/56172-docs-fix-add-include-to-ci-param-list.yml
@@ -0,0 +1,5 @@
+---
+title: Update CI YAML param table with include
+merge_request: !24309
+author:
+type: fixed
diff --git a/changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml b/changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml
new file mode 100644
index 00000000000..7c923422534
--- /dev/null
+++ b/changelogs/unreleased/56363-inconsitent-file-size-indication-across-different-ci-pages.yml
@@ -0,0 +1,6 @@
+---
+title: Show CI artifact file size with 3 significant digits on 'browse job artifacts'
+ page
+merge_request: 24387
+author:
+type: fixed
diff --git a/changelogs/unreleased/8688-recursive-pipelines-ce-backport.yml b/changelogs/unreleased/8688-recursive-pipelines-ce-backport.yml
new file mode 100644
index 00000000000..cd7b56a1e05
--- /dev/null
+++ b/changelogs/unreleased/8688-recursive-pipelines-ce-backport.yml
@@ -0,0 +1,5 @@
+---
+title: Creates mixin to reduce code duplication between CE and EE in graph component
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/ac-pages-subgroups.yml b/changelogs/unreleased/ac-pages-subgroups.yml
new file mode 100644
index 00000000000..ef5a0c1872e
--- /dev/null
+++ b/changelogs/unreleased/ac-pages-subgroups.yml
@@ -0,0 +1,5 @@
+---
+title: Pages for subgroups
+merge_request: 23505
+author:
+type: added
diff --git a/changelogs/unreleased/actioncontroller-parameters-deprecations.yml b/changelogs/unreleased/actioncontroller-parameters-deprecations.yml
new file mode 100644
index 00000000000..ddd15c37542
--- /dev/null
+++ b/changelogs/unreleased/actioncontroller-parameters-deprecations.yml
@@ -0,0 +1,5 @@
+---
+title: Fix several ActionController::Parameters deprecations
+merge_request: 24332
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/add-badge-count-to-projects-and-groups.yml b/changelogs/unreleased/add-badge-count-to-projects-and-groups.yml
new file mode 100644
index 00000000000..e200bbaa806
--- /dev/null
+++ b/changelogs/unreleased/add-badge-count-to-projects-and-groups.yml
@@ -0,0 +1,5 @@
+---
+title: Add badge count to projects
+merge_request: 18425
+author: George Tsiolis
+type: added
diff --git a/changelogs/unreleased/an-gilab-process-name.yml b/changelogs/unreleased/an-gilab-process-name.yml
new file mode 100644
index 00000000000..72d811ee21f
--- /dev/null
+++ b/changelogs/unreleased/an-gilab-process-name.yml
@@ -0,0 +1,5 @@
+---
+title: Extract process_name from GitLab::Sentry
+merge_request: 24422
+author:
+type: other
diff --git a/changelogs/unreleased/an-opentracing-factory.yml b/changelogs/unreleased/an-opentracing-factory.yml
new file mode 100644
index 00000000000..c04736f3e63
--- /dev/null
+++ b/changelogs/unreleased/an-opentracing-factory.yml
@@ -0,0 +1,5 @@
+---
+title: Conditionally initialize the global opentracing tracer
+merge_request: 24186
+author:
+type: other
diff --git a/changelogs/unreleased/api-wiki-dot-slug.yml b/changelogs/unreleased/api-wiki-dot-slug.yml
new file mode 100644
index 00000000000..82c76fa7450
--- /dev/null
+++ b/changelogs/unreleased/api-wiki-dot-slug.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Support dots in wiki slugs'
+merge_request: 24383
+author: Robert Schilling
+type: fixed
diff --git a/changelogs/unreleased/auto-devops-custom-domains.yml b/changelogs/unreleased/auto-devops-custom-domains.yml
new file mode 100644
index 00000000000..37e8ee26a4d
--- /dev/null
+++ b/changelogs/unreleased/auto-devops-custom-domains.yml
@@ -0,0 +1,5 @@
+---
+title: Added support for custom hosts/domains to Auto DevOps
+merge_request: 24248
+author: walkafwalka
+type: added
diff --git a/changelogs/unreleased/auto-devops-kubectl-1-11-6.yml b/changelogs/unreleased/auto-devops-kubectl-1-11-6.yml
new file mode 100644
index 00000000000..1a8cdead4ac
--- /dev/null
+++ b/changelogs/unreleased/auto-devops-kubectl-1-11-6.yml
@@ -0,0 +1,5 @@
+---
+title: Bump kubectl in Auto DevOps to 1.11.6
+merge_request: 24176
+author:
+type: other
diff --git a/changelogs/unreleased/bump-ingress-chart-112.yml b/changelogs/unreleased/bump-ingress-chart-112.yml
new file mode 100644
index 00000000000..8a46fedb4b0
--- /dev/null
+++ b/changelogs/unreleased/bump-ingress-chart-112.yml
@@ -0,0 +1,5 @@
+---
+title: Bump nginx-ingress chart to 1.1.2
+merge_request: 24203
+author:
+type: other
diff --git a/changelogs/unreleased/deprecated-force-reload.yml b/changelogs/unreleased/deprecated-force-reload.yml
new file mode 100644
index 00000000000..2a0e97089e0
--- /dev/null
+++ b/changelogs/unreleased/deprecated-force-reload.yml
@@ -0,0 +1,6 @@
+---
+title: 'Fix deprecation: Passing an argument to force an association to reload is
+ now deprecated'
+merge_request: 24136
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/error_tracking_feature_flag_fe.yml b/changelogs/unreleased/error_tracking_feature_flag_fe.yml
new file mode 100644
index 00000000000..607929eb6b8
--- /dev/null
+++ b/changelogs/unreleased/error_tracking_feature_flag_fe.yml
@@ -0,0 +1,5 @@
+---
+title: Display a list of Sentry Issues in GitLab
+merge_request: 23770
+author:
+type: added
diff --git a/changelogs/unreleased/features-document-graphicsmagick-source-installation.yml b/changelogs/unreleased/features-document-graphicsmagick-source-installation.yml
new file mode 100644
index 00000000000..b224cace4bf
--- /dev/null
+++ b/changelogs/unreleased/features-document-graphicsmagick-source-installation.yml
@@ -0,0 +1,5 @@
+---
+title: Document graphicsmagick installation for source installation
+merge_request: 24404
+author: Alexis Reigel
+type: added
diff --git a/changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml b/changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml
new file mode 100644
index 00000000000..eda69b32094
--- /dev/null
+++ b/changelogs/unreleased/fix-403-page-is-rendered-but-404-is-the-response.yml
@@ -0,0 +1,5 @@
+---
+title: Show the correct error page when access is denied
+merge_request: 23932
+author:
+type: fixed
diff --git a/changelogs/unreleased/gitaly-update-1-13-0.yml b/changelogs/unreleased/gitaly-update-1-13-0.yml
new file mode 100644
index 00000000000..73de25a532d
--- /dev/null
+++ b/changelogs/unreleased/gitaly-update-1-13-0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade Gitaly to 1.13.0
+merge_request: 24429
+author:
+type: other
diff --git a/changelogs/unreleased/gt-externalize-app-views-projects-project_members.yml b/changelogs/unreleased/gt-externalize-app-views-projects-project_members.yml
new file mode 100644
index 00000000000..1acea10fcaa
--- /dev/null
+++ b/changelogs/unreleased/gt-externalize-app-views-projects-project_members.yml
@@ -0,0 +1,5 @@
+---
+title: Externalize strings from `/app/views/projects/project_members`
+merge_request: 23227
+author: Tao Wang
+type: other
diff --git a/changelogs/unreleased/gt-rename-gray-theme-color-variables.yml b/changelogs/unreleased/gt-rename-gray-theme-color-variables.yml
new file mode 100644
index 00000000000..b612bb3ee39
--- /dev/null
+++ b/changelogs/unreleased/gt-rename-gray-theme-color-variables.yml
@@ -0,0 +1,5 @@
+---
+title: Remove all `$theme-gray-{weight}` variables in favor of `$gray-{weight}`
+merge_request: 24333
+author: George Tsiolis
+type: other
diff --git a/changelogs/unreleased/homepage-proj-descr-cutoff.yml b/changelogs/unreleased/homepage-proj-descr-cutoff.yml
new file mode 100644
index 00000000000..837c01f6722
--- /dev/null
+++ b/changelogs/unreleased/homepage-proj-descr-cutoff.yml
@@ -0,0 +1,5 @@
+---
+title: Increase line height of project summaries
+merge_request:
+author: gfyoung
+type: fixed
diff --git a/changelogs/unreleased/jivl-update-placeholder-sentry-config.yml b/changelogs/unreleased/jivl-update-placeholder-sentry-config.yml
new file mode 100644
index 00000000000..eb860fd3905
--- /dev/null
+++ b/changelogs/unreleased/jivl-update-placeholder-sentry-config.yml
@@ -0,0 +1,5 @@
+---
+title: Update url placeholder for the sentry configuration page
+merge_request: 24338
+author:
+type: other
diff --git a/changelogs/unreleased/move-job-cancel-btn.yml b/changelogs/unreleased/move-job-cancel-btn.yml
new file mode 100644
index 00000000000..41f8e1be5f8
--- /dev/null
+++ b/changelogs/unreleased/move-job-cancel-btn.yml
@@ -0,0 +1,5 @@
+---
+title: Move cancel & new issue button on job page
+merge_request: 24074
+author:
+type: changed
diff --git a/changelogs/unreleased/osw-fix-quick-suggestion-application-being-reverted.yml b/changelogs/unreleased/osw-fix-quick-suggestion-application-being-reverted.yml
new file mode 100644
index 00000000000..1f80d7535a5
--- /dev/null
+++ b/changelogs/unreleased/osw-fix-quick-suggestion-application-being-reverted.yml
@@ -0,0 +1,5 @@
+---
+title: Adjust applied suggestion reverting previous changes
+merge_request: 24250
+author:
+type: fixed
diff --git a/changelogs/unreleased/remove-cancel-all-button-in-job-list-view.yml b/changelogs/unreleased/remove-cancel-all-button-in-job-list-view.yml
new file mode 100644
index 00000000000..06546bc5a8e
--- /dev/null
+++ b/changelogs/unreleased/remove-cancel-all-button-in-job-list-view.yml
@@ -0,0 +1,5 @@
+---
+title: Remove Cancel all jobs button in general jobs list view
+merge_request:
+author: Jordi Llull
+type: removed
diff --git a/changelogs/unreleased/security-2770-verify-bundle-import-files.yml b/changelogs/unreleased/security-2770-verify-bundle-import-files.yml
new file mode 100644
index 00000000000..dea40dd1ef1
--- /dev/null
+++ b/changelogs/unreleased/security-2770-verify-bundle-import-files.yml
@@ -0,0 +1,5 @@
+---
+title: Validate bundle files before unpacking them
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml b/changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml
new file mode 100644
index 00000000000..d1d4412eb50
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-backfill-project-repo-migration.yml
@@ -0,0 +1,5 @@
+---
+title: Fix duplicate project disk path in BackfillLegacyProjectRepositories
+merge_request: 24213
+author:
+type: changed
diff --git a/changelogs/unreleased/sh-fix-bitbucket-server-error-handling.yml b/changelogs/unreleased/sh-fix-bitbucket-server-error-handling.yml
new file mode 100644
index 00000000000..87405fa0a78
--- /dev/null
+++ b/changelogs/unreleased/sh-fix-bitbucket-server-error-handling.yml
@@ -0,0 +1,5 @@
+---
+title: Fix Bitbucket Server importer error handling
+merge_request: 24343
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-fix-real-size-warnings.yml b/changelogs/unreleased/sh-fix-real-size-warnings.yml
deleted file mode 100644
index 5062ffd677c..00000000000
--- a/changelogs/unreleased/sh-fix-real-size-warnings.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix broken templated "Too many changes to show" text
-merge_request: 24282
-author:
-type: fixed
diff --git a/changelogs/unreleased/sh-fix-request-profiles-html.yml b/changelogs/unreleased/sh-fix-request-profiles-html.yml
deleted file mode 100644
index 74e4115db8e..00000000000
--- a/changelogs/unreleased/sh-fix-request-profiles-html.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix requests profiler in admin page not rendering HTML properly
-merge_request: 24291
-author:
-type: fixed
diff --git a/changelogs/unreleased/shared_with_group_path.yml b/changelogs/unreleased/shared_with_group_path.yml
new file mode 100644
index 00000000000..73ba9a9f30a
--- /dev/null
+++ b/changelogs/unreleased/shared_with_group_path.yml
@@ -0,0 +1,5 @@
+---
+title: Add group full path to project's shared_with_groups
+merge_request: 24052
+author: Mathieu Parent
+type: added
diff --git a/changelogs/unreleased/suggestion-dashes.yml b/changelogs/unreleased/suggestion-dashes.yml
deleted file mode 100644
index e99ab30b263..00000000000
--- a/changelogs/unreleased/suggestion-dashes.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fixed diff suggestions removing dashes
-merge_request:
-author:
-type: fixed
diff --git a/changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml b/changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml
new file mode 100644
index 00000000000..1af1fe09f33
--- /dev/null
+++ b/changelogs/unreleased/twang2218-gitlab-ce-i18n-extract-app-views-search.yml
@@ -0,0 +1,5 @@
+---
+title: 'i18n: externalize strings from ''app/views/search'''
+merge_request: 24297
+author: Tao Wang
+type: other
diff --git a/changelogs/unreleased/update-gitlab-styles.yml b/changelogs/unreleased/update-gitlab-styles.yml
new file mode 100644
index 00000000000..379f0ad4486
--- /dev/null
+++ b/changelogs/unreleased/update-gitlab-styles.yml
@@ -0,0 +1,5 @@
+---
+title: Update gitlab-styles to 2.5.1
+merge_request: 24336
+author: Jasper Maes
+type: other
diff --git a/changelogs/unreleased/update-sidekiq-cron.yml b/changelogs/unreleased/update-sidekiq-cron.yml
new file mode 100644
index 00000000000..edce32e3753
--- /dev/null
+++ b/changelogs/unreleased/update-sidekiq-cron.yml
@@ -0,0 +1,6 @@
+---
+title: Update sidekiq-cron to 1.0.4 and use fugit to replace rufus-scheduler to parse
+ cron syntax
+merge_request: 24235
+author:
+type: other
diff --git a/changelogs/unreleased/winh-add-list-dropdown-height.yml b/changelogs/unreleased/winh-add-list-dropdown-height.yml
new file mode 100644
index 00000000000..6bcedc15cc9
--- /dev/null
+++ b/changelogs/unreleased/winh-add-list-dropdown-height.yml
@@ -0,0 +1,5 @@
+---
+title: Adjust height of "Add list" dropdown in issue boards
+merge_request: 24227
+author:
+type: fixed
diff --git a/changelogs/unreleased/zj-feature-gate-set-project-path.yml b/changelogs/unreleased/zj-feature-gate-set-project-path.yml
new file mode 100644
index 00000000000..b426a2f3fe7
--- /dev/null
+++ b/changelogs/unreleased/zj-feature-gate-set-project-path.yml
@@ -0,0 +1,5 @@
+---
+title: Allow setting of feature gates per project
+merge_request: 24184
+author:
+type: added
diff --git a/config/initializers/sentry.rb b/config/initializers/sentry.rb
index 2a6c5148f71..abc91c3ae51 100644
--- a/config/initializers/sentry.rb
+++ b/config/initializers/sentry.rb
@@ -19,7 +19,7 @@ def configure_sentry
config.sanitize_fields = Rails.application.config.filter_parameters.map(&:to_s)
# Sanitize authentication headers
config.sanitize_http_headers = %w[Authorization Private-Token]
- config.tags = { program: Gitlab::Sentry.program_context }
+ config.tags = { program: Gitlab.process_name }
end
end
end
diff --git a/config/initializers/tracing.rb b/config/initializers/tracing.rb
new file mode 100644
index 00000000000..be95f30d075
--- /dev/null
+++ b/config/initializers/tracing.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+if Gitlab::Tracing.enabled?
+ require 'opentracing'
+
+ # In multi-processed clustered architectures (puma, unicorn) don't
+ # start tracing until the worker processes are spawned. This works
+ # around issues when the opentracing implementation spawns threads
+ Gitlab::Cluster::LifecycleEvents.on_worker_start do
+ tracer = Gitlab::Tracing::Factory.create_tracer(Gitlab.process_name, Gitlab::Tracing.connection_string)
+ OpenTracing.global_tracer = tracer if tracer
+ end
+end
diff --git a/config/jsdocs.config.js b/config/jsdocs.config.js
new file mode 100644
index 00000000000..52635b1ce13
--- /dev/null
+++ b/config/jsdocs.config.js
@@ -0,0 +1,14 @@
+module.exports = {
+ source: {
+ include: ['app/assets/javascripts/'],
+ },
+ opts: {
+ template: 'node_modules/docdash',
+ destination: 'jsdoc/',
+ recurse: true,
+ },
+ docdash: {
+ search: true,
+ static: true,
+ },
+};
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 797bf6de37b..d9afb4e7bc8 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -256,8 +256,6 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
resources :jobs, only: [:index, :show], constraints: { id: /\d+/ } do
collection do
- post :cancel_all
-
resources :artifacts, only: [] do
collection do
get :latest_succeeded,
diff --git a/danger/commit_messages/Dangerfile b/danger/commit_messages/Dangerfile
index 6a5a75b6eba..c20c8b77e6a 100644
--- a/danger/commit_messages/Dangerfile
+++ b/danger/commit_messages/Dangerfile
@@ -2,6 +2,9 @@
require 'json'
+URL_LIMIT_SUBJECT = "https://chris.beams.io/posts/git-commit/#limit-50"
+URL_GIT_COMMIT = "https://chris.beams.io/posts/git-commit/"
+
# rubocop: disable Style/SignalException
# rubocop: disable Metrics/CyclomaticComplexity
# rubocop: disable Metrics/PerceivedComplexity
@@ -101,10 +104,7 @@ def lint_commits(commits)
elsif subject.length > 50
warn_commit(
commit,
- "This commit's subject line could be improved. " \
- 'Commit subjects are ideally no longer than roughly 50 characters, ' \
- 'though we allow up to 72 characters in the subject. ' \
- 'If possible, try to reduce the length of the subject to roughly 50 characters.'
+ "This commit's subject line is acceptable, but please try to [reduce it to 50 characters](#{URL_LIMIT_SUBJECT})."
)
end
@@ -196,7 +196,7 @@ def lint_commits(commits)
One or more commit messages do not meet our Git commit message standards.
For more information on how to write a good commit message, take a look at
- [How to Write a Git Commit Message](https://chris.beams.io/posts/git-commit/).
+ [How to Write a Git Commit Message](#{URL_GIT_COMMIT}).
Here is an example of a good commit message:
diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb
index 8bdc7c6556c..2051bcff8f0 100644
--- a/db/fixtures/development/10_merge_requests.rb
+++ b/db/fixtures/development/10_merge_requests.rb
@@ -27,6 +27,9 @@ Gitlab::Seeder.quiet do
Sidekiq::Worker.skipping_transaction_check do
MergeRequests::CreateService.new(project, developer, params).execute
+ rescue Repository::AmbiguousRefError
+ # Ignore pipelines creation errors for now, we can doing that after
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/55966. will be resolved.
end
print '.'
end
diff --git a/db/migrate/20190115054216_add_error_notification_sent_to_remote_mirrors.rb b/db/migrate/20190115054216_add_error_notification_sent_to_remote_mirrors.rb
new file mode 100644
index 00000000000..d8f979a1848
--- /dev/null
+++ b/db/migrate/20190115054216_add_error_notification_sent_to_remote_mirrors.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddErrorNotificationSentToRemoteMirrors < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :remote_mirrors, :error_notification_sent, :boolean
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 87826881d58..c4902116a3a 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20190103140724) do
+ActiveRecord::Schema.define(version: 20190115054216) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -1849,6 +1849,7 @@ ActiveRecord::Schema.define(version: 20190103140724) do
t.string "encrypted_credentials_salt"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.boolean "error_notification_sent"
t.index ["last_successful_update_at"], name: "index_remote_mirrors_on_last_successful_update_at", using: :btree
t.index ["project_id"], name: "index_remote_mirrors_on_project_id", using: :btree
end
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index cf37eaa0b61..05c1923f0cb 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -217,6 +217,8 @@ repository from your GitLab server over HTTP.
## TLS support
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22602) in GitLab 11.7.
+
Gitaly supports TLS credentials for GRPC authentication. To be able to communicate
with a gitaly instance that listens for secure connections you will need to use `tls://` url
scheme in the `gitaly_address` of the corresponding storage entry in the gitlab configuration.
diff --git a/doc/api/applications.md b/doc/api/applications.md
index 7f95c136168..82955f0c1db 100644
--- a/doc/api/applications.md
+++ b/doc/api/applications.md
@@ -1,28 +1,36 @@
# Applications API
-> [Introduced][ce-8160] in GitLab 10.5
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8160) in GitLab 10.5.
-[ce-8160]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8160
+Applications API operates on OAuth applications for:
-Only admin user can use the Applications API.
+- [Using GitLab as an authentication provider](../integration/oauth_provider.md).
+- [Allowing access to GitLab resources on a user's behalf](oauth2.md).
-## Create a application
+NOTE: **Note:**
+Only admin users can use the Applications API.
-Create a application by posting a JSON payload.
+## Create an application
+
+Create an application by posting a JSON payload.
Returns `200` if the request succeeds.
-```
+```text
POST /applications
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `name` | string | yes | The name of the application |
-| `redirect_uri` | string | yes | The redirect URI of the application |
-| `scopes` | string | yes | The scopes of the application |
+Parameters:
+
+| Attribute | Type | Required | Description |
+|:---------------|:-------|:---------|:---------------------------------|
+| `name` | string | yes | Name of the application. |
+| `redirect_uri` | string | yes | Redirect URI of the application. |
+| `scopes` | string | yes | Scopes of the application. |
+
+Example request:
-```bash
+```sh
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --data "name=MyApplication&redirect_uri=http://redirect.uri&scopes=" https://gitlab.example.com/api/v4/applications
```
@@ -42,11 +50,13 @@ Example response:
List all registered applications.
-```
+```text
GET /applications
```
-```bash
+Example request:
+
+```sh
curl --request GET --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/applications
```
@@ -63,7 +73,8 @@ Example response:
]
```
-> Note: the `secret` value will not be exposed by this API.
+NOTE: **Note:**
+The `secret` value will not be exposed by this API.
## Delete an application
@@ -71,7 +82,7 @@ Delete a specific application.
Returns `204` if the request succeeds.
-```
+```text
DELETE /applications/:id
```
@@ -79,6 +90,8 @@ Parameters:
- `id` (required) - The id of the application (not the application_id)
-```bash
+Example request:
+
+```sh
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/applications/:id
```
diff --git a/doc/api/avatar.md b/doc/api/avatar.md
index aa6f7c185ae..e55fffba4b2 100644
--- a/doc/api/avatar.md
+++ b/doc/api/avatar.md
@@ -1,33 +1,41 @@
# Avatar API
-> [Introduced][ce-19121] in GitLab 11.0
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19121) in GitLab 11.0.
## Get a single avatar URL
-Get a single avatar URL for a given email address. If user with matching public
-email address is not found, results from external avatar services are returned.
-This endpoint can be accessed without authentication. In case public visibility
-is restricted, response will be `403 Forbidden` when unauthenticated.
+Get a single [avatar](../user/profile/index.md#profile-settings) URL for a user with the given email address.
-```
+If:
+
+- No user with the given public email address is found, results from external avatar services are
+ returned.
+- Public visibility is restricted, response will be `403 Forbidden` when unauthenticated.
+
+NOTE: **Note:**
+This endpoint can be accessed without authentication.
+
+```text
GET /avatar?email=admin@example.com
```
-| Attribute | Type | Required | Description |
-| --------- | ------- | -------- | --------------------- |
-| `email` | string | yes | Public email address of the user |
-| `size` | integer | no | Single pixel dimension (since images are squares). Only used for avatar lookups at `Gravatar` or at the configured `Libravatar` server |
+Parameters:
-```bash
-curl https://gitlab.example.com/api/v4/avatar?email=admin@example.com
+| Attribute | Type | Required | Description |
+|:----------|:--------|:---------|:----------------------------------------------------------------------------------------------------------------------------------------|
+| `email` | string | yes | Public email address of the user. |
+| `size` | integer | no | Single pixel dimension (since images are squares). Only used for avatar lookups at `Gravatar` or at the configured `Libravatar` server. |
+
+Example request:
+
+```sh
+curl https://gitlab.example.com/api/v4/avatar?email=admin@example.com&size=32
```
Example response:
```json
{
- "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon"
+ "avatar_url": "https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=64&d=identicon"
}
```
-
-[ce-19121]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/19121
diff --git a/doc/api/award_emoji.md b/doc/api/award_emoji.md
index 41e39c31069..92936a277ac 100644
--- a/doc/api/award_emoji.md
+++ b/doc/api/award_emoji.md
@@ -1,19 +1,26 @@
# Award Emoji API
-> [Introduced][ce-4575] in GitLab 8.9, Snippet support in 8.12
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4575) in GitLab 8.9. Snippet support added in 8.12.
+An [awarded emoji](../user/award_emojis.md) tells a thousand words.
-An awarded emoji tells a thousand words, and can be awarded on issues, merge
-requests, snippets, and notes/comments. Issues, merge requests, snippets, and notes are further called
-`awardables`.
+Emoji can be awarded on the following (known as "awardables"):
+
+- [Issues](../user/project/issues/index.md) ([API](issues.md)).
+- [Merge requests](../user/project/merge_requests/index.md) ([API](merge_requests.md)).
+- [Snippets](../user/snippets.md) ([API](snippets.md)).
+
+Emoji can also [be awarded](../user/award_emojis.html#award-emoji-for-comments) on comments (also known as notes). See also [Notes API](notes.md).
## Issues, merge requests, and snippets
+See [Award Emoji on Comments](#award-emoji-on-comments) for information on using these endpoints with comments.
+
### List an awardable's award emoji
-Gets a list of all award emoji
+Get a list of all award emoji for a specified awardable.
-```
+```text
GET /projects/:id/issues/:issue_iid/award_emoji
GET /projects/:id/merge_requests/:merge_request_iid/award_emoji
GET /projects/:id/snippets/:snippet_id/award_emoji
@@ -21,16 +28,18 @@ GET /projects/:id/snippets/:snippet_id/award_emoji
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `awardable_id` | integer | yes | The ID (`iid` for merge requests/issues, `id` for snippets) of an awardable |
+| Attribute | Type | Required | Description |
+|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `awardable_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
-```bash
+Example request:
+
+```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji
```
-Example Response:
+Example response:
```json
[
@@ -71,9 +80,9 @@ Example Response:
### Get single award emoji
-Gets a single award emoji from an issue, snippet, or merge request.
+Get a single award emoji from an issue, snippet, or merge request.
-```
+```text
GET /projects/:id/issues/:issue_iid/award_emoji/:award_id
GET /projects/:id/merge_requests/:merge_request_iid/award_emoji/:award_id
GET /projects/:id/snippets/:snippet_id/award_emoji/:award_id
@@ -81,17 +90,19 @@ GET /projects/:id/snippets/:snippet_id/award_emoji/:award_id
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `awardable_id` | integer | yes | The ID (`iid` for merge requests/issues, `id` for snippets) of an awardable |
-| `award_id` | integer | yes | The ID of the award emoji |
+| Attribute | Type | Required | Description |
+|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `awardable_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
+| `award_id` | integer | yes | ID of the award emoji. |
+
+Example request:
-```bash
+```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/1
```
-Example Response:
+Example response:
```json
{
@@ -114,9 +125,9 @@ Example Response:
### Award a new emoji
-This end point creates an award emoji on the specified resource
+Create an award emoji on the specified awardable.
-```
+```text
POST /projects/:id/issues/:issue_iid/award_emoji
POST /projects/:id/merge_requests/:merge_request_iid/award_emoji
POST /projects/:id/snippets/:snippet_id/award_emoji
@@ -124,13 +135,13 @@ POST /projects/:id/snippets/:snippet_id/award_emoji
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `awardable_id` | integer | yes | The ID (`iid` for merge requests/issues, `id` for snippets) of an awardable |
-| `name` | string | yes | The name of the emoji, without colons |
+| Attribute | Type | Required | Description |
+|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `awardable_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
+| `name` | string | yes | Name of the emoji without colons. |
-```bash
+```sh
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji?name=blowfish
```
@@ -157,10 +168,12 @@ Example Response:
### Delete an award emoji
-Sometimes its just not meant to be, and you'll have to remove your award. Only available to
-admins or the author of the award.
+Sometimes it's just not meant to be and you'll have to remove the award.
-```
+NOTE: **Note:**
+Only available to administrators or the author of the award.
+
+```text
DELETE /projects/:id/issues/:issue_iid/award_emoji/:award_id
DELETE /projects/:id/merge_requests/:merge_request_iid/award_emoji/:award_id
DELETE /projects/:id/snippets/:snippet_id/award_emoji/:award_id
@@ -168,43 +181,47 @@ DELETE /projects/:id/snippets/:snippet_id/award_emoji/:award_id
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of an issue |
-| `award_id` | integer | yes | The ID of an award_emoji |
+| Attribute | Type | Required | Description |
+|:---------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `awardable_id` | integer | yes | ID (`iid` for merge requests/issues, `id` for snippets) of an awardable. |
+| `award_id` | integer | yes | ID of an award emoji. |
-```bash
+```sh
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/344
```
-## Award Emoji on Notes
+## Award Emoji on Comments
-The endpoints documented above are available for Notes as well. Notes
-are a sub-resource of Issues, Merge Requests, or Snippets. The examples below
-describe working with Award Emoji on notes for an Issue, but can be
-easily adapted for notes on a Merge Request.
+Comments (also known as notes) are a sub-resource of issues, merge requests, and snippets.
-### List a note's award emoji
+NOTE: **Note:**
+The examples below describe working with award emoji on comments for an issue, but can be
+easily adapted for comments on a merge request.
-```
+### List a comment's award emoji
+
+Get all award emoji for a comment (note).
+
+```text
GET /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji
```
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of an issue |
-| `note_id` | integer | yes | The ID of a note |
+| Attribute | Type | Required | Description |
+|:------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `issue_iid` | integer | yes | Internal ID of an issue. |
+| `note_id` | integer | yes | ID of a comment (note). |
+Example request:
-```bash
+```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/notes/1/award_emoji
```
-Example Response:
+Example response:
```json
[
@@ -227,26 +244,30 @@ Example Response:
]
```
-### Get single note's award emoji
+### Get an award emoji for a comment
-```
+Get a single award emoji for a comment (note).
+
+```text
GET /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji/:award_id
```
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of an issue |
-| `note_id` | integer | yes | The ID of a note |
-| `award_id` | integer | yes | The ID of the award emoji |
+| Attribute | Type | Required | Description |
+|:------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `issue_iid` | integer | yes | Internal ID of an issue. |
+| `note_id` | integer | yes | ID of a comment (note). |
+| `award_id` | integer | yes | ID of the award emoji. |
+
+Example request:
-```bash
+```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/notes/1/award_emoji/2
```
-Example Response:
+Example response:
```json
{
@@ -267,26 +288,30 @@ Example Response:
}
```
-### Award a new emoji on a note
+### Award a new emoji on a comment
-```
+Create an award emoji on the specified comment (note).
+
+```text
POST /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji
```
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of an issue |
-| `note_id` | integer | yes | The ID of a note |
-| `name` | string | yes | The name of the emoji, without colons |
+| Attribute | Type | Required | Description |
+|:------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `issue_iid` | integer | yes | Internal ID of an issue. |
+| `note_id` | integer | yes | ID of a comment (note). |
+| `name` | string | yes | Name of the emoji without colons. |
-```bash
+Example request:
+
+```sh
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/notes/1/award_emoji?name=rocket
```
-Example Response:
+Example response:
```json
{
@@ -307,26 +332,28 @@ Example Response:
}
```
-### Delete an award emoji
+### Delete an award emoji from a comment
-Sometimes its just not meant to be, and you'll have to remove your award. Only available to
-admins or the author of the award.
+Sometimes it's just not meant to be and you'll have to remove the award.
-```
+NOTE: **Note:**
+Only available to administrators or the author of the award.
+
+```text
DELETE /projects/:id/issues/:issue_iid/notes/:note_id/award_emoji/:award_id
```
Parameters:
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `issue_iid` | integer | yes | The internal ID of an issue |
-| `note_id` | integer | yes | The ID of a note |
-| `award_id` | integer | yes | The ID of an award_emoji |
+| Attribute | Type | Required | Description |
+|:------------|:---------------|:---------|:-----------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding). |
+| `issue_iid` | integer | yes | Internal ID of an issue. |
+| `note_id` | integer | yes | ID of a comment (note). |
+| `award_id` | integer | yes | ID of an award_emoji. |
+
+Example request:
-```bash
+```sh
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/1/issues/80/award_emoji/345
```
-
-[ce-4575]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4575
diff --git a/doc/api/branches.md b/doc/api/branches.md
index 3b55154887d..8d5f333ba77 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -1,21 +1,31 @@
# Branches API
+This API operates on [repository branches](../user/project/repository/branches/index.md).
+
+TIP: **Tip:**
+See also [Protected branches API](protected_branches.md).
+
## List repository branches
Get a list of repository branches from a project, sorted by name alphabetically.
-This endpoint can be accessed without authentication if the repository is
-publicly accessible.
-```
+NOTE: **Note:**
+This endpoint can be accessed without authentication if the repository is publicly accessible.
+
+```text
GET /projects/:id/repository/branches
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `search` | string | no | Return list of branches matching the search criteria. |
+Parameters:
+
+| Attribute | Type | Required | Description |
+|:----------|:---------------|:---------|:-------------------------------------------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `search` | string | no | Return list of branches matching the search criteria. |
+
+Example request:
-```bash
+```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/branches
```
@@ -53,19 +63,25 @@ Example response:
## Get single repository branch
-Get a single project repository branch. This endpoint can be accessed without
-authentication if the repository is publicly accessible.
+Get a single project repository branch.
-```
+NOTE: **Note:**
+This endpoint can be accessed without authentication if the repository is publicly accessible.
+
+```text
GET /projects/:id/repository/branches/:branch
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `branch` | string | yes | The name of the branch |
+Parameters:
+
+| Attribute | Type | Required | Description |
+|:----------|:---------------|:---------|:-------------------------------------------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `branch` | string | yes | Name of the branch. |
-```bash
+Example request:
+
+```sh
curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/branches/master
```
@@ -100,120 +116,34 @@ Example response:
## Protect repository branch
->**Note:** This API endpoint is deprecated in favor of `POST /projects/:id/protected_branches`.
-
-Protects a single project repository branch. This is an idempotent function,
-protecting an already protected repository branch still returns a `200 OK`
-status code.
-
-```
-PUT /projects/:id/repository/branches/:branch/protect
-```
-
-```bash
-curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/branches/master/protect?developers_can_push=true&developers_can_merge=true
-```
-
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `branch` | string | yes | The name of the branch |
-| `developers_can_push` | boolean | no | Flag if developers can push to the branch |
-| `developers_can_merge` | boolean | no | Flag if developers can merge to the branch |
-
-Example response:
-
-```json
-{
- "commit": {
- "author_email": "john@example.com",
- "author_name": "John Smith",
- "authored_date": "2012-06-27T05:51:39-07:00",
- "committed_date": "2012-06-28T03:44:20-07:00",
- "committer_email": "john@example.com",
- "committer_name": "John Smith",
- "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
- "short_id": "7b5c3cc",
- "title": "add projects API",
- "message": "add projects API",
- "parent_ids": [
- "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
- ]
- },
- "name": "master",
- "merged": false,
- "protected": true,
- "default": true,
- "developers_can_push": true,
- "developers_can_merge": true,
- "can_push": true
-}
-```
+See [`POST /projects/:id/protected_branches`](protected_branches.md#protect-repository-branches) for
+information on protecting repository branches.
## Unprotect repository branch
->**Note:** This API endpoint is deprecated in favor of `DELETE /projects/:id/protected_branches/:name`
+See [`DELETE /projects/:id/protected_branches/:name`](protected_branches.md#unprotect-repository-branches)
+for information on unprotecting repository branches.
-Unprotects a single project repository branch. This is an idempotent function,
-unprotecting an already unprotected repository branch still returns a `200 OK`
-status code.
-
-```
-PUT /projects/:id/repository/branches/:branch/unprotect
-```
-
-```bash
-curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/branches/master/unprotect
-```
-
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `branch` | string | yes | The name of the branch |
+## Create repository branch
-Example response:
+Create a new branch in the repository.
-```json
-{
- "commit": {
- "author_email": "john@example.com",
- "author_name": "John Smith",
- "authored_date": "2012-06-27T05:51:39-07:00",
- "committed_date": "2012-06-28T03:44:20-07:00",
- "committer_email": "john@example.com",
- "committer_name": "John Smith",
- "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c",
- "short_id": "7b5c3cc",
- "title": "add projects API",
- "message": "add projects API",
- "parent_ids": [
- "4ad91d3c1144c406e50c7b33bae684bd6837faf8"
- ]
- },
- "name": "master",
- "merged": false,
- "protected": false,
- "default": true,
- "developers_can_push": false,
- "developers_can_merge": false,
- "can_push": true
-}
+```text
+POST /projects/:id/repository/branches
```
-## Create repository branch
+Parameters:
-```
-POST /projects/:id/repository/branches
-```
+| Attribute | Type | Required | Description |
+|:----------|:--------|:---------|:-------------------------------------------------------------------------------------------------------------|
+| `id` | integer | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `branch` | string | yes | Name of the branch. |
+| `ref` | string | yes | Branch name or commit SHA to create branch from. |
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `branch` | string | yes | The name of the branch |
-| `ref` | string | yes | The branch name or commit SHA to create branch from |
+Example request:
-```bash
-curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/branches?branch=newbranch&ref=master"
+```sh
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/branches?branch=newbranch&ref=master
```
Example response:
@@ -247,36 +177,47 @@ Example response:
## Delete repository branch
-```
+Delete a branch from the repository.
+
+NOTE: **Note:**
+In the case of an error, an explanation message is provided.
+
+```text
DELETE /projects/:id/repository/branches/:branch
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
-| `branch` | string | yes | The name of the branch |
+Parameters:
+
+| Attribute | Type | Required | Description |
+|:----------|:---------------|:---------|:-------------------------------------------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+| `branch` | string | yes | Name of the branch. |
-In case of an error, an explaining message is provided.
+Example request:
-```bash
-curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/branches/newbranch"
+```sh
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/branches/newbranch
```
## Delete merged branches
Will delete all branches that are merged into the project's default branch.
-Protected branches will not be deleted as part of this operation.
+NOTE: **Note:**
+[Protected branches](../user/project/protected_branches.md) will not be deleted as part of this operation.
-```
+```text
DELETE /projects/:id/repository/merged_branches
```
-| Attribute | Type | Required | Description |
-| --------- | ---- | -------- | ----------- |
-| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user |
+Parameters:
+
+| Attribute | Type | Required | Description |
+|:----------|:---------------|:---------|:-------------------------------------------------------------------------------------------------------------|
+| `id` | integer/string | yes | ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user. |
+Example request:
-```bash
-curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/5/repository/merged_branches"
+```sh
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/projects/5/repository/merged_branches
```
diff --git a/doc/api/features.md b/doc/api/features.md
index 59f1005ef72..47f104e1f20 100644
--- a/doc/api/features.md
+++ b/doc/api/features.md
@@ -60,9 +60,10 @@ POST /features/:name
| `value` | integer/string | yes | `true` or `false` to enable/disable, or an integer for percentage of time |
| `feature_group` | string | no | A Feature group name |
| `user` | string | no | A GitLab username |
+| `project` | string | no | A projects path, for example 'gitlab-org/gitlab-ce' |
-Note that you can enable or disable a feature for both a `feature_group` and a
-`user` with a single API call.
+Note that you can enable or disable a feature for a `feature_group`, a `user`,
+and a `project` in a single API call.
```bash
curl --data "value=30" --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/features/new_library
diff --git a/doc/api/groups.md b/doc/api/groups.md
index 2d9114c40ea..907b443d355 100644
--- a/doc/api/groups.md
+++ b/doc/api/groups.md
@@ -357,12 +357,14 @@ Example response:
{
"group_id": 4,
"group_name": "Twitter",
+ "group_full_path": "twitter",
"group_access_level": 30,
"expires_at": null
},
{
"group_id": 3,
"group_name": "Gitlab Org",
+ "group_full_path": "gitlab-org",
"group_access_level": 10,
"expires_at": "2018-08-14"
}
diff --git a/doc/api/import.md b/doc/api/import.md
new file mode 100644
index 00000000000..9f8e0d232c6
--- /dev/null
+++ b/doc/api/import.md
@@ -0,0 +1,33 @@
+# Import API
+
+## Import repository from GitHub
+
+Import your projects from GitHub to GitLab via the API.
+
+```
+POST /import/github
+```
+
+| Attribute | Type | Required | Description |
+|------------|---------|----------|---------------------|
+| `personal_access_token` | string | yes | GitHub personal access token |
+| `repo_id` | integer | yes | GitHub repository ID |
+| `new_name` | string | no | New repo name |
+| `target_namespace` | string | yes | Namespace to import repo into |
+
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "personal_access_token=abc123&repo_id=12345&target_namespace=root" https://gitlab.example.com/api/v4/import/github
+```
+
+Example response:
+
+```json
+{
+ "id": 27,
+ "name": "my-repo",
+ "full_path": "/root/my-repo",
+ "full_name": "Administrator / my-repo"
+}
+```
+
diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md
index 6786c0c5b5c..6e156a14b25 100644
--- a/doc/api/oauth2.md
+++ b/doc/api/oauth2.md
@@ -1,126 +1,165 @@
# GitLab as an OAuth2 provider
-This document covers using the [OAuth2](https://oauth.net/2/) protocol to allow other services access GitLab resources on user's behalf.
+This document covers using the [OAuth2](https://oauth.net/2/) protocol to allow
+other services to access GitLab resources on user's behalf.
-If you want GitLab to be an OAuth authentication service provider to sign into other services please see the [OAuth2 provider](../integration/oauth_provider.md)
-documentation.
+If you want GitLab to be an OAuth authentication service provider to sign into
+other services, see the [OAuth2 provider](../integration/oauth_provider.md)
+documentation. This functionality is based on the
+[doorkeeper Ruby gem](https://github.com/doorkeeper-gem/doorkeeper).
-This functionality is based on [doorkeeper gem](https://github.com/doorkeeper-gem/doorkeeper).
+## Supported OAuth2 flows
-## Supported OAuth2 Flows
+GitLab currently supports the following authorization flows:
-GitLab currently supports following authorization flows:
+- **Web application flow:** Most secure and common type of flow, designed for
+ applications with secure server-side.
+- **Implicit grant flow:** This flow is designed for user-agent only apps (e.g., single
+ page web application running on GitLab Pages).
+- **Resource owner password credentials flow:** To be used **only** for securely
+ hosted, first-party services.
-- *Web Application Flow* - Most secure and common type of flow, designed for the applications with secure server-side.
-- *Implicit Flow* - This flow is designed for user-agent only apps (e.g. single page web application running on GitLab Pages).
-- *Resource Owner Password Credentials Flow* - To be used **only** for securely hosted, first-party services.
+Refer to the [OAuth RFC](https://tools.ietf.org/html/rfc6749) to find out
+how all those flows work and pick the right one for your use case.
-Please refer to [OAuth RFC](https://tools.ietf.org/html/rfc6749) to find out in details how all those flows work and pick the right one for your use case.
+Both **web application** and **implicit grant** flows require `application` to be
+registered first via the `/profile/applications` page in your user's account.
+During registration, by enabling proper scopes, you can limit the range of
+resources which the `application` can access. Upon creation, you'll obtain the
+`application` credentials: _Application ID_ and _Client Secret_ - **keep them secure**.
-Both *web application* and *implicit* flows require `application` to be registered first via `/profile/applications` page
-in your user's account. During registration, by enabling proper scopes you can limit the range of resources which the `application` can access. Upon creation
-you'll obtain `application` credentials: _Application ID_ and _Client Secret_ - **keep them secure**.
+CAUTION: **Important:**
+OAuth specification advises sending the `state` parameter with each request to
+`/oauth/authorize`. We highly recommended sending a unique value with each request
+and validate it against the one in the redirect request. This is important in
+order to prevent [CSRF attacks](https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)).
+The `state` parameter really should have been a requirement in the standard!
->**Important:** OAuth specification advises sending `state` parameter with each request to `/oauth/authorize`. We highly recommended to send a unique
-value with each request and validate it against the one in redirect request. This is important to prevent [CSRF attacks]. The `state` param really should
-have been a requirement in the standard!
+In the following sections you will find detailed instructions on how to obtain
+authorization with each flow.
-In the following sections you will find detailed instructions on how to obtain authorization with each flow.
+### Web application flow
-### Web Application Flow
+NOTE: **Note:**
+Check the [RFC spec](https://tools.ietf.org/html/rfc6749#section-4.1) for a
+detailed flow description.
-Check [RFC spec](http://tools.ietf.org/html/rfc6749#section-4.1) for a detailed flow description
+The web application flow is:
-#### 1. Requesting authorization code
+1. Request authorization code. To do that, you should redirect the user to the
+ `/oauth/authorize` endpoint with the following GET parameters:
-To request the authorization code, you should redirect the user to the `/oauth/authorize` endpoint with following GET parameters:
+ ```
+ https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=YOUR_UNIQUE_STATE_HASH
+ ```
-```
-https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code&state=YOUR_UNIQUE_STATE_HASH
-```
+ This will ask the user to approve the applications access to their account and
+ then redirect back to the `REDIRECT_URI` you provided. The redirect will
+ include the GET `code` parameter, for example:
-This will ask the user to approve the applications access to their account and then redirect back to the `REDIRECT_URI` you provided. The redirect will
-include the GET `code` parameter, for example:
+ ```
+ http://myapp.com/oauth/redirect?code=1234567890&state=YOUR_UNIQUE_STATE_HASH
+ ```
-`http://myapp.com/oauth/redirect?code=1234567890&state=YOUR_UNIQUE_STATE_HASH`
+ You should then use `code` to request an access token.
-You should then use the `code` to request an access token.
+1. Once you have the authorization code you can request an `access_token` using the
+ code. You can do that by using any HTTP client. In the following example,
+ we are using Ruby's `rest-client`:
-#### 2. Requesting access token
+ ```ruby
+ parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI'
+ RestClient.post 'http://gitlab.example.com/oauth/token', parameters
+ ```
-Once you have the authorization code you can request an `access_token` using the code, to do that you can use any HTTP client. In the following example,
-we are using Ruby's `rest-client`:
+ Example response:
-```
-parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI'
-RestClient.post 'http://gitlab.example.com/oauth/token', parameters
+ ```json
+ {
+ "access_token": "de6780bc506a0446309bd9362820ba8aed28aa506c71eedbe1c5c4f9dd350e54",
+ "token_type": "bearer",
+ "expires_in": 7200,
+ "refresh_token": "8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1"
+ }
+ ```
-# The response will be
-{
- "access_token": "de6780bc506a0446309bd9362820ba8aed28aa506c71eedbe1c5c4f9dd350e54",
- "token_type": "bearer",
- "expires_in": 7200,
- "refresh_token": "8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1"
-}
-```
->**Note:**
-The `redirect_uri` must match the `redirect_uri` used in the original authorization request.
+NOTE: **Note:**
+The `redirect_uri` must match the `redirect_uri` used in the original
+authorization request.
You can now make requests to the API with the access token returned.
+### Implicit grant flow
-### Implicit Grant
-
-Check [RFC spec](http://tools.ietf.org/html/rfc6749#section-4.2) for a detailed flow description.
-
-Unlike the web flow, the client receives an `access token` immediately as a result of the authorization request. The flow does not use client secret
-or authorization code because all of the application code and storage is easily accessible, therefore __secrets__ can leak easily.
+NOTE: **Note:**
+Check the [RFC spec](https://tools.ietf.org/html/rfc6749#section-4.2) for a
+detailed flow description.
->**Important:** Avoid using this flow for applications that store data outside of the GitLab instance. If you do, make sure to verify `application id`
-associated with access token before granting access to the data
+CAUTION: **Important:**
+Avoid using this flow for applications that store data outside of the GitLab
+instance. If you do, make sure to verify `application id` associated with the
+access token before granting access to the data
(see [/oauth/token/info](https://github.com/doorkeeper-gem/doorkeeper/wiki/API-endpoint-descriptions-and-examples#get----oauthtokeninfo)).
+Unlike the web flow, the client receives an `access token` immediately as a
+result of the authorization request. The flow does not use the client secret
+or the authorization code because all of the application code and storage is
+easily accessible, therefore secrets can leak easily.
-#### 1. Requesting access token
-
-To request the access token, you should redirect the user to the `/oauth/authorize` endpoint using `token` response type:
+To request the access token, you should redirect the user to the
+`/oauth/authorize` endpoint using `token` response type:
```
https://gitlab.example.com/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=token&state=YOUR_UNIQUE_STATE_HASH
```
-This will ask the user to approve the application's access to their account and then redirect back to the `REDIRECT_URI` you provided. The redirect
-will include a fragment with `access_token` as well as token details in GET parameters, for example:
+This will ask the user to approve the application's access to their account and
+then redirect them back to the `REDIRECT_URI` you provided. The redirect
+will include a fragment with `access_token` as well as token details in GET
+parameters, for example:
```
http://myapp.com/oauth/redirect#access_token=ABCDExyz123&state=YOUR_UNIQUE_STATE_HASH&token_type=bearer&expires_in=3600
```
-### Resource Owner Password Credentials
+### Resource owner password credentials flow
-Check [RFC spec](http://tools.ietf.org/html/rfc6749#section-4.3) for a detailed flow description.
+NOTE: **Note:**
+Check the [RFC spec](https://tools.ietf.org/html/rfc6749#section-4.3) for a
+detailed flow description.
-> **Deprecation notice:** Starting in GitLab 8.11, the Resource Owner Password Credentials has been *disabled* for users with two-factor authentication
-turned on. These users can access the API using [personal access tokens] instead.
+NOTE: **Note:**
+The Resource Owner Password Credentials is disabled for users with [two-factor
+authentication](../user/profile/account/two_factor_authentication.md) turned on.
+These users can access the API using [personal access tokens](../user/profile/personal_access_tokens.md)
+instead.
-In this flow, a token is requested in exchange for the resource owner credentials (username and password).
-The credentials should only be used when there is a high degree of trust between the resource owner and the client (e.g. the
-client is part of the device operating system or a highly privileged application), and when other authorization grant types are not
-available (such as an authorization code).
+In this flow, a token is requested in exchange for the resource owner credentials
+(username and password).
->**Important:**
-Never store the user's credentials and only use this grant type when your client is deployed to a trusted environment, in 99% of cases [personal access tokens]
-are a better choice.
+The credentials should only be used when:
-Even though this grant type requires direct client access to the resource owner credentials, the resource owner credentials are used
-for a single request and are exchanged for an access token. This grant type can eliminate the need for the client to store the
-resource owner credentials for future use, by exchanging the credentials with a long-lived access token or refresh token.
+- There is a high degree of trust between the resource owner and the client. For
+ example, the client is part of the device operating system or a highly
+ privileged application.
+- Other authorization grant types are not available (such as an authorization code).
-#### 1. Requesting access token
+CAUTION: **Important:**
+Never store the user's credentials and only use this grant type when your client
+is deployed to a trusted environment, in 99% of cases
+[personal access tokens](../user/profile/personal_access_tokens.md) are a better
+choice.
-POST request to `/oauth/token` with parameters:
+Even though this grant type requires direct client access to the resource owner
+credentials, the resource owner credentials are used for a single request and
+are exchanged for an access token. This grant type can eliminate the need for
+the client to store the resource owner credentials for future use, by exchanging
+the credentials with a long-lived access token or refresh token.
-```
+To request an access token, you must make a POST request to `/oauth/token` with
+the following parameters:
+
+```json
{
"grant_type" : "password",
"username" : "user@example.com",
@@ -128,6 +167,13 @@ POST request to `/oauth/token` with parameters:
}
```
+Example cURL request:
+
+```sh
+echo 'grant_type=password&username=<your_username>&password=<your_password>' > auth.txt
+curl --data "@auth.txt" --request POST https://gitlab.example.com/oauth/token
+```
+
Then, you'll receive the access token back in the response:
```
@@ -138,7 +184,7 @@ Then, you'll receive the access token back in the response:
}
```
-For testing you can use the oauth2 ruby gem:
+For testing, you can use the `oauth2` Ruby gem:
```
client = OAuth2::Client.new('the_client_id', 'the_client_secret', :site => "http://example.com")
@@ -148,7 +194,9 @@ puts access_token.token
## Access GitLab API with `access token`
-The `access token` allows you to make requests to the API on a behalf of a user. You can pass the token either as GET parameter
+The `access token` allows you to make requests to the API on behalf of a user.
+You can pass the token either as GET parameter:
+
```
GET https://gitlab.example.com/api/v4/user?access_token=OAUTH-TOKEN
```
@@ -159,5 +207,3 @@ or you can put the token to the Authorization header:
curl --header "Authorization: Bearer OAUTH-TOKEN" https://gitlab.example.com/api/v4/user
```
-[personal access tokens]: ../user/profile/personal_access_tokens.md
-[CSRF attacks]: http://www.oauthsecurity.com/#user-content-authorization-code-flow
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 538cd34de43..1296b435792 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -525,11 +525,13 @@ GET /projects/:id
{
"group_id": 4,
"group_name": "Twitter",
+ "group_full_path": "twitter",
"group_access_level": 30
},
{
"group_id": 3,
"group_name": "Gitlab Org",
+ "group_full_path": "gitlab-org",
"group_access_level": 10
}
],
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index efee2852eb8..d7ab6fc506d 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -57,6 +57,7 @@ A job is defined by a list of parameters that define the job behavior.
|---------------|----------|-------------|
| [script](#script) | yes | Defines a shell script which is executed by Runner |
| [extends](#extends) | no | Defines a configuration entry that this job is going to inherit from |
+| [include](#include) | no | Defines a configuration entry that allows this job to include external YAML files |
| [image](#image-and-services) | no | Use docker image, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
| [services](#image-and-services) | no | Use docker services, covered in [Using Docker Images](../docker/using_docker_images.md#define-image-and-services-from-gitlab-ciyml) |
| [stage](#stage) | no | Defines a job stage (default: `test`) |
@@ -77,118 +78,6 @@ A job is defined by a list of parameters that define the job behavior.
| [retry](#retry) | no | Define when and how many times a job can be auto-retried in case of a failure |
| [parallel](#parallel) | no | Defines how many instances of a job should be run in parallel |
-### `extends`
-
-> Introduced in GitLab 11.3.
-
-`extends` defines an entry name that a job that uses `extends` is going to
-inherit from.
-
-It is an alternative to using [YAML anchors](#anchors) and is a little
-more flexible and readable:
-
-```yaml
-.tests:
- script: rake test
- stage: test
- only:
- refs:
- - branches
-
-rspec:
- extends: .tests
- script: rake rspec
- only:
- variables:
- - $RSPEC
-```
-
-In the example above, the `rspec` job inherits from the `.tests` template job.
-GitLab will perform a reverse deep merge based on the keys. GitLab will:
-
-- Merge the `rspec` contents into `.tests` recursively.
-- Not merge the values of the keys.
-
-This results in the following `rspec` job:
-
-```yaml
-rspec:
- script: rake rspec
- stage: test
- only:
- refs:
- - branches
- variables:
- - $RSPEC
-```
-
-NOTE: **Note:**
-Note that `script: rake test` has been overwritten by `script: rake rspec`.
-
-If you do want to include the `rake test`, have a look at [before_script-and-after_script](#before_script-and-after_script).
-
-`.tests` in this example is a [hidden key](#hidden-keys-jobs), but it's
-possible to inherit from regular jobs as well.
-
-`extends` supports multi-level inheritance, however it is not recommended to
-use more than three levels. The maximum nesting level that is supported is 10.
-The following example has two levels of inheritance:
-
-```yaml
-.tests:
- only:
- - pushes
-
-.rspec:
- extends: .tests
- script: rake rspec
-
-rspec 1:
- variables:
- RSPEC_SUITE: '1'
- extends: .rspec
-
-rspec 2:
- variables:
- RSPEC_SUITE: '2'
- extends: .rspec
-
-spinach:
- extends: .tests
- script: rake spinach
-```
-
-`extends` works across configuration files combined with [`include`](#include).
-
-### `pages`
-
-`pages` is a special job that is used to upload static content to GitLab that
-can be used to serve your website. It has a special syntax, so the two
-requirements below must be met:
-
-1. Any static content must be placed under a `public/` directory
-1. `artifacts` with a path to the `public/` directory must be defined
-
-The example below simply moves all files from the root of the project to the
-`public/` directory. The `.public` workaround is so `cp` doesn't also copy
-`public/` to itself in an infinite loop:
-
-```yaml
-pages:
- stage: deploy
- script:
- - mkdir .public
- - cp -r * .public
- - mv .public public
- artifacts:
- paths:
- - public
- only:
- - master
-```
-
-Read more on [GitLab Pages user documentation](../../user/project/pages/index.md).
-
## `image` and `services`
This allows to specify a custom Docker image and a list of services that can be
@@ -259,7 +148,7 @@ There are also two edge cases worth mentioning:
1. If no `stages` are defined in `.gitlab-ci.yml`, then the `build`,
`test` and `deploy` are allowed to be used as job's stage by default.
-2. If a job doesn't specify a `stage`, the job is assigned the `test` stage.
+1. If a job doesn't specify a `stage`, the job is assigned the `test` stage.
## `stage`
@@ -327,7 +216,7 @@ a "key: value" pair. Be careful when using special characters:
jobs are created:
1. `only` defines the names of branches and tags for which the job will run.
-2. `except` defines the names of branches and tags for which the job will
+1. `except` defines the names of branches and tags for which the job will
**not** run.
There are a few rules that apply to the usage of job policy:
@@ -676,9 +565,9 @@ cleanup_job:
The above script will:
1. Execute `cleanup_build_job` only when `build_job` fails.
-2. Always execute `cleanup_job` as the last step in pipeline regardless of
+1. Always execute `cleanup_job` as the last step in pipeline regardless of
success or failure.
-3. Allow you to manually execute `deploy_job` from GitLab's UI.
+1. Allow you to manually execute `deploy_job` from GitLab's UI.
### `when:manual`
@@ -1621,7 +1510,6 @@ Possible values for `when` are:
- `missing_dependency_failure`: Retry if a dependency was missing.
- `runner_unsupported`: Retry if the runner was unsupported.
-
## `parallel`
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22631) in GitLab 11.5.
@@ -1644,193 +1532,213 @@ test:
## `include`
-> Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 10.5.
-> Available for Starter, Premium and Ultimate since 10.6.
-> Behaviour expanded in GitLab 10.8 to allow more flexible overriding.
-> [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21603)
-to GitLab Core in 11.4
-> In GitLab 11.7, support for [including GitLab-supplied templates directly](https://gitlab.com/gitlab-org/gitlab-ce/issues/53445) and support for [including templates from another repository](https://gitlab.com/gitlab-org/gitlab-ce/issues/53903) was added.
+> - Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 10.5.
+> - Available for Starter, Premium and Ultimate since 10.6.
+> - [Moved](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21603) to GitLab Core in 11.4.
Using the `include` keyword, you can allow the inclusion of external YAML files.
+`include` requires the external YAML file to have the extensions `.yml` or `.yaml`,
+otherwise the external file will not be included.
-In the following example, the content of `.before-script-template.yml` will be
-automatically fetched and evaluated along with the content of `.gitlab-ci.yml`:
+The files defined in `include` are:
-```yaml
-# Content of https://gitlab.com/awesome-project/raw/master/.before-script-template.yml
+- Deep merged with those in `.gitlab-ci.yml`.
+- Always evaluated first and merged with the content of `.gitlab-ci.yml`,
+ regardless of the position of the `include` keyword.
-before_script:
- - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
- - gem install bundler --no-document
- - bundle install --jobs $(nproc) "${FLAGS[@]}"
-```
+TIP: **Tip:**
+Use merging to customize and override included CI/CD configurations with local
+definitions.
-```yaml
-# Content of .gitlab-ci.yml
+Recursive includes are not supported. Your external files should not use the
+`include` keyword as it will be ignored.
-include: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+NOTE: **Note:**
+Using YAML aliases across different YAML files sourced by `include` is not
+supported. You must only refer to aliases in the same file. Instead
+of using YAML anchors, you can use the [`extends` keyword](#extends).
-rspec:
- script:
- - bundle exec rspec
-```
+`include` supports four include methods:
-NOTE: **Note:**
-`include` requires the external YAML files to have the extensions `.yml` or `.yaml`.
-The external file will not be included if the extension is missing.
+- [`local`](#includelocal)
+- [`file`](#includefile)
+- [`template`](#includetemplate)
+- [`remote`](#includeremote)
-You can include your extra YAML file either as a single string or
-as an array of multiple values. You can also use full paths or
-relative URLs. The following examples are both valid:
+See [usage examples](#include-examples).
-```yaml
-# Single string
+### `include:local`
-include: '/templates/.after-script-template.yml'
-```
+`include:local` includes a file from the same repository as `.gitlab-ci.yml`.
+It's referenced using full paths relative to the root directory (`/`).
-```yaml
-# Single string
+You can only use files that are currently tracked by Git on the same branch
+your configuration file is on. In other words, when using a `include:local`, make
+sure that both `.gitlab-ci.yml` and the local file are on the same branch.
+
+NOTE: **Note:**
+Including local files through Git submodules paths is not supported.
+Example:
+
+```yaml
include:
- file: '/templates/.after-script-template.yml'
+ - local: '/templates/.gitlab-ci-template.yml'
```
-```yaml
-# Array
+### `include:file`
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53903) in GitLab 11.7.
+
+To include files from another private project under the same GitLab instance,
+use `include:file`. This file is referenced using full paths relative to the
+root directory (`/`). For example:
+```yaml
include:
- - 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
- - '/templates/.after-script-template.yml'
+ - project: 'my-group/my-project'
+ file: '/templates/.gitlab-ci-template.yml'
```
-```yaml
-# Array mixed syntax
+You can also specify `ref`, with the default being the `HEAD` of the project:
+```yaml
include:
- - 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
- - '/templates/.after-script-template.yml'
- - template: Auto-DevOps.gitlab-ci.yml
+ - project: 'my-group/my-project'
+ ref: master
+ file: '/templates/.gitlab-ci-template.yml'
+
+ - project: 'my-group/my-project'
+ ref: v1.0.0
+ file: '/templates/.gitlab-ci-template.yml'
+
+ - project: 'my-group/my-project'
+ ref: 787123b47f14b552955ca2786bc9542ae66fee5b # Git SHA
+ file: '/templates/.gitlab-ci-template.yml'
```
-```yaml
-# Array
+### `include:template`
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/53445) in GitLab 11.7.
+`include:template` can be used to include `.gitlab-ci.yml` templates that are
+[shipped with GitLab](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/ci/templates).
+
+For example:
+
+```yaml
+# File sourced from GitLab's template collection
include:
- - remote: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
- - local: '/templates/.after-script-template.yml'
- template: Auto-DevOps.gitlab-ci.yml
```
----
+### `include:remote`
+
+`include:remote` can be used to include a file from a different location,
+using HTTP/HTTPS, referenced by using the full URL. The remote file must be
+publicly accessible through a simple GET request as authentication schemas
+in the remote URL is not supported. For example:
-`include` supports four types of files:
+```yaml
+include:
+ - remote: 'https://gitlab.com/awesome-project/raw/master/.gitlab-ci-template.yml'
+```
-- **local** to the same repository, referenced by using full paths in the same
- repository, with `/` being the root directory. For example:
+NOTE: **Note for GitLab admins:**
+In order to include files from another repository inside your local network,
+you may need to enable the **Allow requests to the local network from hooks and services** checkbox
+located in the **Admin area > Settings > Network > Outbound requests** section.
- ```yaml
- # Within the repository
- include: '/templates/.gitlab-ci-template.yml'
- ```
+### `include` examples
- Or using:
+Here are a few more `include` examples.
- ```yaml
- # Within the repository
- include:
- local: '/templates/.gitlab-ci-template.yml'
- ```
+#### Single string or array of multiple values
- NOTE: **Note:**
- You can only use files that are currently tracked by Git on the same branch
- your configuration file is. In other words, when using a **local file**, make
- sure that both `.gitlab-ci.yml` and the local file are on the same branch.
+You can include your extra YAML file(s) either as a single string or
+an array of multiple values. The following examples are all valid.
- NOTE: **Note:**
- We don't support the inclusion of local files through Git submodules paths.
+Single string with the `include:local` method implied:
-- **file** from another repository, referenced by using full paths in the same
- repository, with `/` being the root directory. For example:
+```yaml
+include: '/templates/.after-script-template.yml'
+```
- ```yaml
- include:
- project: 'my-group/my-project'
- file: '/templates/.gitlab-ci-template.yml'
- ```
+Array with `include` method implied:
- You can also specify `ref:`. The default `ref:` is the `HEAD` of the project:
+```yaml
+include:
+ - 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+ - '/templates/.after-script-template.yml'
+```
- ```yaml
- include:
- - project: 'my-group/my-project'
- ref: master
- file: '/templates/.gitlab-ci-template.yml'
+Single string with `include` method specified explicitly:
- - project: 'my-group/my-project'
- ref: v1.0.0
- file: '/templates/.gitlab-ci-template.yml'
+```yaml
+include:
+ remote: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+```
- - project: 'my-group/my-project'
- ref: 787123b47f14b552955ca2786bc9542ae66fee5b # git sha
- file: '/templates/.gitlab-ci-template.yml'
- ```
+Array with `include:remote` being the single item:
-- **remote** in a different location, accessed using HTTP/HTTPS, referenced
- using the full URL. For example:
+```yaml
+include:
+ - remote: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+```
- ```yaml
- # File sourced from outside repository
- include: 'https://gitlab.com/awesome-project/raw/master/.gitlab-ci-template.yml'
- ```
+Array with multiple `include` methods specified explicitly:
- Or using:
+```yaml
+include:
+ - remote: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+ - local: '/templates/.after-script-template.yml'
+ - template: Auto-DevOps.gitlab-ci.yml
+```
- ```yaml
- # File sourced from outside repository
- include:
- remote: 'https://gitlab.com/awesome-project/raw/master/.gitlab-ci-template.yml'
- ```
+Array mixed syntax:
- NOTE: **Note:**
- The remote file must be publicly accessible through a simple GET request, as we don't support authentication schemas in the remote URL.
+```yaml
+include:
+ - 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
+ - '/templates/.after-script-template.yml'
+ - template: Auto-DevOps.gitlab-ci.yml
+ - project: 'my-group/my-project'
+ ref: master
+ file: '/templates/.gitlab-ci-template.yml'
+```
- NOTE: **Note:**
- In order to include files from another repository inside your local network,
- you may need to enable the **Allow requests to the local network from hooks and services** checkbox
- located in the **Settings > Network > Outbound requests** section within the **Admin area**.
+#### Re-using a `before_script` template
-- **template** included with GitLab. For example:
+In the following example, the content of `.before-script-template.yml` will be
+automatically fetched and evaluated along with the content of `.gitlab-ci.yml`.
- ```yaml
- # File sourced from GitLab's template collection
- include:
- template: Auto-DevOps.gitlab-ci.yml
- ```
+Content of `https://gitlab.com/awesome-project/raw/master/.before-script-template.yml`:
- NOTE: **Note:**
- Templates included this way are sourced from [lib/gitlab/ci/templates](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/lib/gitlab/ci/templates).
+```yaml
+before_script:
+ - apt-get update -qq && apt-get install -y -qq sqlite3 libsqlite3-dev nodejs
+ - gem install bundler --no-document
+ - bundle install --jobs $(nproc) "${FLAGS[@]}"
+```
----
+Content of `.gitlab-ci.yml`:
+```yaml
+include: 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml'
-Since GitLab 10.8 we are now deep merging the files defined in `include`
-with those in `.gitlab-ci.yml`. Files defined by `include` are always
-evaluated first and merged with the content of `.gitlab-ci.yml`, no
-matter the position of the `include` keyword. You can take advantage of
-merging to customize and override details in included CI
-configurations with local definitions.
+rspec:
+ script:
+ - bundle exec rspec
+```
-NOTE: **Note:**
-The recursive includes are not supported, meaning your external files
-should not use the `include` keyword, as it will be ignored.
+#### Overriding external template values
The following example shows specific YAML-defined variables and details of the
`production` job from an include file being customized in `.gitlab-ci.yml`.
-```yaml
-# Content of https://company.com/autodevops-template.yml
+Content of `https://company.com/autodevops-template.yml`:
+```yaml
variables:
POSTGRES_USER: user
POSTGRES_PASSWORD: testing_password
@@ -1848,9 +1756,9 @@ production:
- master
```
-```yaml
-# Content of .gitlab-ci.yml
+Content of `.gitlab-ci.yml`:
+```yaml
include: 'https://company.com/autodevops-template.yml'
image: alpine:latest
@@ -1877,11 +1785,11 @@ with the environment url of the `production` job defined in
The merging lets you extend and override dictionary mappings, but
you cannot add or modify items to an included array. For example, to add
an additional item to the production job script, you must repeat the
-existing script items.
+existing script items:
-```yaml
-# Content of https://company.com/autodevops-template.yml
+Content of `https://company.com/autodevops-template.yml`:
+```yaml
production:
stage: production
script:
@@ -1889,9 +1797,9 @@ production:
- deploy
```
-```yaml
-# Content of .gitlab-ci.yml
+Content of `.gitlab-ci.yml`:
+```yaml
include: 'https://company.com/autodevops-template.yml'
stages:
@@ -1908,10 +1816,140 @@ In this case, if `install_dependencies` and `deploy` were not repeated in
`.gitlab-ci.yml`, they would not be part of the script for the `production`
job in the combined CI configuration.
+## `extends`
+
+> Introduced in GitLab 11.3.
+
+`extends` defines an entry name that a job that uses `extends` is going to
+inherit from.
+
+It is an alternative to using [YAML anchors](#anchors) and is a little
+more flexible and readable:
+
+```yaml
+.tests:
+ script: rake test
+ stage: test
+ only:
+ refs:
+ - branches
+
+rspec:
+ extends: .tests
+ script: rake rspec
+ only:
+ variables:
+ - $RSPEC
+```
+
+In the example above, the `rspec` job inherits from the `.tests` template job.
+GitLab will perform a reverse deep merge based on the keys. GitLab will:
+
+- Merge the `rspec` contents into `.tests` recursively.
+- Not merge the values of the keys.
+
+This results in the following `rspec` job:
+
+```yaml
+rspec:
+ script: rake rspec
+ stage: test
+ only:
+ refs:
+ - branches
+ variables:
+ - $RSPEC
+```
+
NOTE: **Note:**
-We currently do not support using YAML aliases across different YAML files
-sourced by `include`. You must only refer to aliases in the same file. Instead
-of using YAML anchors you can use [`extends` keyword](#extends).
+Note that `script: rake test` has been overwritten by `script: rake rspec`.
+
+If you do want to include the `rake test`, see [`before_script` and `after_script`](#before_script-and-after_script).
+
+`.tests` in this example is a [hidden key](#hidden-keys-jobs), but it's
+possible to inherit from regular jobs as well.
+
+`extends` supports multi-level inheritance, however it is not recommended to
+use more than three levels. The maximum nesting level that is supported is 10.
+The following example has two levels of inheritance:
+
+```yaml
+.tests:
+ only:
+ - pushes
+
+.rspec:
+ extends: .tests
+ script: rake rspec
+
+rspec 1:
+ variables:
+ RSPEC_SUITE: '1'
+ extends: .rspec
+
+rspec 2:
+ variables:
+ RSPEC_SUITE: '2'
+ extends: .rspec
+
+spinach:
+ extends: .tests
+ script: rake spinach
+```
+
+## Using `extends` and `include` together
+
+`extends` works across configuration files combined with `include`.
+
+For example, if you have a local `included.yml` file:
+
+```yaml
+.template:
+ script:
+ - echo Hello!
+```
+
+Then, in `.gitlab-ci.yml` you can use it like this:
+
+```yaml
+include: included.yml
+
+useTemplate:
+ image: alpine
+ extends: .template
+```
+
+This will run a job called `useTemplate` that runs `echo Hello!` as defined in
+the `.template` job, and uses the `alpine` Docker image as defined in the local job.
+
+## `pages`
+
+`pages` is a special job that is used to upload static content to GitLab that
+can be used to serve your website. It has a special syntax, so the two
+requirements below must be met:
+
+- Any static content must be placed under a `public/` directory.
+- `artifacts` with a path to the `public/` directory must be defined.
+
+The example below simply moves all files from the root of the project to the
+`public/` directory. The `.public` workaround is so `cp` doesn't also copy
+`public/` to itself in an infinite loop:
+
+```yaml
+pages:
+ stage: deploy
+ script:
+ - mkdir .public
+ - cp -r * .public
+ - mv .public public
+ artifacts:
+ paths:
+ - public
+ only:
+ - master
+```
+
+Read more on [GitLab Pages user documentation](../../user/project/pages/index.md).
## `variables`
@@ -1950,9 +1988,9 @@ which can be set in GitLab's UI.
### Git strategy
-> Introduced in GitLab 8.9 as an experimental feature. May change or be removed
- completely in future releases. `GIT_STRATEGY=none` requires GitLab Runner
- v1.7+.
+> Introduced in GitLab 8.9 as an experimental feature. May change or be removed
+> completely in future releases. `GIT_STRATEGY=none` requires GitLab Runner
+> v1.7+.
You can set the `GIT_STRATEGY` used for getting recent application code, either
globally or per-job in the [`variables`](#variables) section. If left
@@ -2282,8 +2320,9 @@ capitalization, the commit will be created but the pipeline will be skipped.
Alternatively, one can pass the `ci.skip` [Git push option][push-option] if
using Git 2.10 or newer:
-```
-$ git push -o ci.skip
+
+```sh
+git push -o ci.skip
```
## Validate the .gitlab-ci.yml
diff --git a/doc/development/README.md b/doc/development/README.md
index f22dde32de9..05715274a81 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -47,6 +47,7 @@ description: 'Learn how to contribute to GitLab.'
- [Avoid modules with instance variables](module_with_instance_variables.md) if possible
- [How to dump production data to staging](db_dump.md)
- [Working with the GitHub importer](github_importer.md)
+- [Import/Export development documentation](import_export.md)
- [Working with Merge Request diffs](diffs.md)
- [Permissions](permissions.md)
- [Prometheus metrics](prometheus_metrics.md)
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index 7c7da50a149..6d9149004fe 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -6,7 +6,7 @@ scheduling into milestones. Labelling is a task for everyone.
Most issues will have labels for at least one of the following:
-- Type: ~"feature proposal", ~bug, ~customer, etc.
+- Type: ~feature, ~bug, ~customer, etc.
- Subject: ~wiki, ~"container registry", ~ldap, ~api, ~frontend, etc.
- Team: ~Plan, ~Manage, ~Quality, etc.
- Stage: ~"devops:plan", ~"devops:create", etc.
@@ -27,8 +27,8 @@ labels, you can _always_ add the team and type, and often also the subject.
Type labels are very important. They define what kind of issue this is. Every
issue should have one or more.
-Examples of type labels are ~"feature proposal", ~bug, ~customer, ~security,
-and ~"direction".
+Examples of type labels are ~feature, ~bug, ~customer, ~security,
+and ~direction.
A number of type labels have a priority assigned to them, which automatically
makes them float to the top, depending on their importance.
@@ -200,7 +200,7 @@ We add the ~"Accepting merge requests" label to:
- Low priority ~bug issues (i.e. we do not add it to the bugs that we want to
solve in the ~"Next Patch Release")
-- Small ~"feature proposal"
+- Small ~feature
- Small ~"technical debt" issues
After adding the ~"Accepting merge requests" label, we try to estimate the
@@ -259,10 +259,10 @@ For feature proposals for EE, open an issue on the
[issue tracker of EE][ee-tracker].
In order to help track the feature proposals, we have created a
-[`feature proposal`][fpl] label. For the time being, users that are not members
+[`feature`][fl] label. For the time being, users that are not members
of the project cannot add labels. You can instead ask one of the [core team]
-members to add the label ~"feature proposal" to the issue or add the following
-code snippet right after your description in a new line: `~"feature proposal"`.
+members to add the label ~feature to the issue or add the following
+code snippet right after your description in a new line: `~feature`.
Please keep feature proposals as small and simple as possible, complex ones
might be edited to make them small and simple.
@@ -276,7 +276,7 @@ need to ask one of the [core team] members to add the label, if you do not have
If you want to create something yourself, consider opening an issue first to
discuss whether it is interesting to include this in GitLab.
-[fpl]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=feature+proposal
+[fl]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=feature
## Issue tracker guidelines
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index 721e26f2de9..c188819560e 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -272,20 +272,26 @@ Inside the document:
- For regular code blocks, always use a highlighting class corresponding to the
language for better readability. Examples:
- ```md
- ```ruby
- Ruby code
- ```
-
- ```js
- JavaScript code
- ```
-
- ```md
- Markdown code
- ```
- ```
-
+ ````md
+ ```ruby
+ Ruby code
+ ```
+
+ ```js
+ JavaScript code
+ ```
+
+ ```md
+ Markdown code
+ ```
+
+ ```text
+ Code for which no specific highlighting class is available.
+ ```
+ ````
+
+- To display raw markdown instead of rendered markdown, use four backticks on their own lines around the
+ markdown to display. See [example](https://gitlab.com/gitlab-org/gitlab-ce/blob/8c1991b9bb7e3b8d606481fdea316d633cfa5eb7/doc/development/documentation/styleguide.md#L275-287).
- For a complete reference on code blocks, check the [Kramdown guide](https://about.gitlab.com/handbook/product/technical-writing/markdown-guide/#code-blocks).
## Alert boxes
@@ -383,7 +389,7 @@ Which renders to:
> ### This is an `h3`
>{:.no_toc}
-## Specific sections and terms
+## Terms
To maintain consistency through GitLab documentation, the following guides documentation authors
on agreed styles and usage of terms.
@@ -412,7 +418,7 @@ The following are recommended verbs for specific uses.
|:------------|:--------------------------------|:-------------------|
| "go" | making a browser go to location | "navigate", "open" |
-### GitLab versions and tiers
+## GitLab versions and tiers
- Every piece of documentation that comes with a new feature should declare the
GitLab version that feature got introduced. Right below the heading add a
@@ -437,7 +443,7 @@ The following are recommended verbs for specific uses.
> [Introduced](<link-to-issue>) in [GitLab Starter](https://about.gitlab.com/pricing/) 10.3.
```
-#### Early versions of EE
+### Early versions of EE
If the feature was created before GitLab 9.2 (before [different EE tiers were introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/1851)):
@@ -450,7 +456,7 @@ For example:
> [Introduced](<link-to-issue>) in GitLab Enterprise Edition 9.0. Available in [GitLab Premium](https://about.gitlab.com/pricing/).
```
-### Product badges
+## Product badges
When a feature is available in EE-only tiers, add the corresponding tier according to the
feature availability:
@@ -471,12 +477,16 @@ keyword "only":
The tier should be ideally added to headers, so that the full badge will be displayed.
However, it can be also mentioned from paragraphs, list items, and table cells. For these cases,
the tier mention will be represented by an orange question mark that will show the tiers on hover.
-E.g., `**[STARTER]**` renders **[STARTER]**, `**[STARTER ONLY]**` renders **[STARTER ONLY]**.
+
+For example:
+
+- `**[STARTER]**` renders as **[STARTER]**
+- `**[STARTER ONLY]**` renders as **[STARTER ONLY]**
The absence of tiers' mentions mean that the feature is available in GitLab Core,
GitLab.com Free, and all higher tiers.
-#### How it works
+### How it works
Introduced by [!244](https://gitlab.com/gitlab-com/gitlab-docs/merge_requests/244),
the special markup `**[STARTER]**` will generate a `span` element to trigger the
diff --git a/doc/development/fe_guide/index.md b/doc/development/fe_guide/index.md
index ad87ecf1b87..3a3cb77f592 100644
--- a/doc/development/fe_guide/index.md
+++ b/doc/development/fe_guide/index.md
@@ -21,8 +21,8 @@ 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 (v6.0 or greater) and Yarn
-(v1.2 or greater). You can find information on how to install these on our
+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].
### Browser Support
diff --git a/doc/development/import_export.md b/doc/development/import_export.md
new file mode 100644
index 00000000000..71db1abb201
--- /dev/null
+++ b/doc/development/import_export.md
@@ -0,0 +1,352 @@
+# Import/Export development documentation
+
+Troubleshooing and general development guidelines and tips for the [Import/Export feature](../user/project/settings/import_export.md).
+
+<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> This document is originally based on the [Import/Export 201 presentation available on YouTube](https://www.youtube.com/watch?v=V3i1OfExotE).
+
+## Troubleshooting commands
+
+Finds information about the status of the import and further logs using the JID:
+
+```ruby
+# Rails console
+Project.find_by_full_path('group/project').import_state.slice(:jid, :status, :last_error)
+> {"jid"=>"414dec93f941a593ea1a6894", "status"=>"finished", "last_error"=>nil}
+```
+
+```bash
+# Logs
+grep JID /var/log/gitlab/sidekiq/current
+grep "Import/Export error" /var/log/gitlab/sidekiq/current
+grep "Import/Export backtrace" /var/log/gitlab/sidekiq/current
+```
+
+## Troubleshooting performance issues
+
+Read through the current performance problems using the Import/Export below.
+
+### OOM errors
+
+Out of memory (OOM) errors are normally caused by the [Sidekiq Memory Killer](https://docs.gitlab.com/ee/administration/operations/sidekiq_memory_killer.html):
+
+```bash
+SIDEKIQ_MEMORY_KILLER_MAX_RSS = 2GB in GitLab.com
+```
+
+An import status `started`, and the following sidekiq logs will signal a memory issue:
+
+```bash
+WARN: Work still in progress <struct with JID>
+```
+
+### Timeouts
+
+Timeout errors occur due to the `StuckImportJobsWorker` marking the process as failed:
+
+```ruby
+class StuckImportJobsWorker
+ include ApplicationWorker
+ include CronjobQueue
+
+ IMPORT_JOBS_EXPIRATION = 15.hours.to_i
+
+ def perform
+ import_state_without_jid_count = mark_import_states_without_jid_as_failed!
+ import_state_with_jid_count = mark_import_states_with_jid_as_failed!
+ ...
+```
+
+```bash
+Marked stuck import jobs as failed. JIDs: xyz
+```
+
+```
+ +-----------+ +-----------------------------------+
+ |Export Job |--->| Calls ActiveRecord `as_json` and |
+ +-----------+ | `to_json` on all project models |
+ +-----------------------------------+
+
+ +-----------+ +-----------------------------------+
+ |Import Job |--->| Loads all JSON in memory, then |
+ +-----------+ | inserts into the DB in batches |
+ +-----------------------------------+
+```
+
+### Problems and solutions
+
+| Problem | Possible solutions |
+| -------- | -------- |
+| [Slow JSON](https://gitlab.com/gitlab-org/gitlab-ce/issues/54084) loading/dumping models from the database | [split the worker](https://gitlab.com/gitlab-org/gitlab-ce/issues/54085) |
+| | Batch export
+| | Optimize SQL
+| | Move away from `ActiveRecord` callbacks (difficult)
+| High memory usage (see also some [analysis](https://gitlab.com/gitlab-org/gitlab-ce/issues/35389) | DB Commit sweet spot that uses less memory |
+| | [Netflix Fast JSON API](https://github.com/Netflix/fast_jsonapi) may help |
+| | Batch reading/writing to disk and any SQL
+
+### Temporary solutions
+
+While the performance problems are not tackled, there is a process to workaround
+importing big projects, using a foreground import:
+
+[Foreground import](https://gitlab.com/gitlab-com/gl-infra/infrastructure/issues/5384) of big projects for customers.
+(Using the import template in the [infrastructure tracker](https://gitlab.com/gitlab-com/gl-infra/infrastructure/))
+
+## Security
+
+The Import/Export feature is constantly updated (adding new things to export), however
+the code hasn't been refactored in a long time. We should perform a [code audit](https://gitlab.com/gitlab-org/gitlab-ce/issues/42135)
+to make sure its dynamic nature does not increase the number of security concerns.
+
+### Security in the code
+
+Some of these classes provide a layer of security to the Import/Export.
+
+The `AttributeCleaner` removes any prohibited keys:
+
+```ruby
+# AttributeCleaner
+# Removes all `_ids` and other prohibited keys
+ class AttributeCleaner
+ ALLOWED_REFERENCES = RelationFactory::PROJECT_REFERENCES + RelationFactory::USER_REFERENCES + ['group_id']
+
+ def clean
+ @relation_hash.reject do |key, _value|
+ prohibited_key?(key) || !@relation_class.attribute_method?(key) || excluded_key?(key)
+ end.except('id')
+ end
+
+ ...
+
+```
+
+The `AttributeConfigurationSpec` checks and confirms the addition of new columns:
+
+```ruby
+# AttributeConfigurationSpec
+<<-MSG
+ It looks like #{relation_class}, which is exported using the project Import/Export, has new attributes:
+
+ Please add the attribute(s) to SAFE_MODEL_ATTRIBUTES if you consider this can be exported.
+ Otherwise, please blacklist the attribute(s) in IMPORT_EXPORT_CONFIG by adding it to its correspondent
+ model in the +excluded_attributes+ section.
+
+ SAFE_MODEL_ATTRIBUTES: #{File.expand_path(safe_attributes_file)}
+ IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file}
+MSG
+```
+
+The `ModelConfigurationSpec` checks and confirms the addition of new models:
+
+```ruby
+# ModelConfigurationSpec
+<<-MSG
+ New model(s) <#{new_models.join(',')}> have been added, related to #{parent_model_name}, which is exported by
+ the Import/Export feature.
+
+ If you think this model should be included in the export, please add it to `#{Gitlab::ImportExport.config_file}`.
+
+ Definitely add it to `#{File.expand_path(ce_models_yml)}`
+ #{"or `#{File.expand_path(ee_models_yml)}` if the model/associations are EE-specific\n" if ee_models_hash.any?}
+ to signal that you've handled this error and to prevent it from showing up in the future.
+MSG
+```
+
+The `ExportFileSpec` detects encrypted or sensitive columns:
+
+```ruby
+# ExportFileSpec
+<<-MSG
+ Found a new sensitive word <#{key_found}>, which is part of the hash #{parent.inspect}
+ If you think this information shouldn't get exported, please exclude the model or attribute in
+ IMPORT_EXPORT_CONFIG.
+
+ Otherwise, please add the exception to +safe_list+ in CURRENT_SPEC using #{sensitive_word} as the
+ key and the correspondent hash or model as the value.
+
+ Also, if the attribute is a generated unique token, please add it to RelationFactory::TOKEN_RESET_MODELS
+ if it needs to be reset (to prevent duplicate column problems while importing to the same instance).
+
+ IMPORT_EXPORT_CONFIG: #{Gitlab::ImportExport.config_file}
+ CURRENT_SPEC: #{__FILE__}
+MSG
+```
+
+## Versioning
+
+Import/Export does not use strict SemVer, since it has frequent constant changes
+during a single GitLab release. It does require an update when there is a breaking change.
+
+```ruby
+# ImportExport
+module Gitlab
+ module ImportExport
+ extend self
+
+ # For every version update, the version history in import_export.md has to be kept up to date.
+ VERSION = '0.2.4'
+```
+
+## Version history
+
+The [current version history](../user/project/settings/import_export.md) also displays the equivalent GitLab version
+and it is useful for knowing which versions won't be compatible between them.
+
+| GitLab version | Import/Export version |
+| ---------------- | --------------------- |
+| 11.1 to current | 0.2.4 |
+| 10.8 | 0.2.3 |
+| 10.4 | 0.2.2 |
+| ... | ... |
+| 8.10.3 | 0.1.3 |
+| 8.10.0 | 0.1.2 |
+| 8.9.5 | 0.1.1 |
+| 8.9.0 | 0.1.0 |
+
+### When to bump the version up
+
+We will have to bump the verision if we rename model/columns or perform any format
+modifications in the JSON structure or the file structure of the archive file.
+
+We do not need to bump the version up in any of the following cases:
+
+- Add a new column or a model
+- Remove a column or model (unless there is a DB constraint)
+- Export new things (such as a new type of upload)
+
+
+Every time we bump the version, the integration specs will fail and can be fixed with:
+
+```bash
+bundle exec rake gitlab:import_export:bump_version
+```
+
+### Renaming columns or models
+
+This is a relatively common occurence that will require a version bump.
+
+There is also the _RC problem_ - GitLab.com runs an RC, prior to any customers,
+meaning that we want to bump the version up in the next version (or patch release).
+
+For example:
+
+1. Add rename to `RelationRenameService` in X.Y
+2. Remove it from `RelationRenameService` in X.Y + 1
+3. Bump Import/Export version in X.Y + 1
+
+```ruby
+module Gitlab
+ module ImportExport
+ class RelationRenameService
+ RENAMES = {
+ 'pipelines' => 'ci_pipelines' # Added in 11.6, remove in 11.7
+ }.freeze
+```
+
+## A quick dive into the code
+
+### Import/Export configuration (`import_export.yml`)
+
+The main configuration `import_export.yml` defines what models can be exported/imported.
+
+Model relationships to be included in the project import/export:
+
+```yaml
+project_tree:
+ - labels:
+ :priorities
+ - milestones:
+ - events:
+ - :push_event_payload
+ - issues:
+ - events:
+ - ...
+```
+
+Only include the following attributes for the models specified:
+
+```yaml
+included_attributes:
+ user:
+ - :id
+ - :email
+ ...
+
+```
+
+Do not include the following attributes for the models specified:
+
+```yaml
+excluded_attributes:
+ project:
+ - :name
+ - :path
+ - ...
+```
+
+Extra methods to be called by the export:
+
+```yaml
+# Methods
+methods:
+ labels:
+ - :type
+ label:
+ - :type
+```
+
+### Import
+
+The import job status moves from `none` to `finished` or `failed` into different states:
+
+_import\_status_: none -> scheduled -> started -> finished/failed
+
+While the status is `started` the `Importer` code processes each step required for the import.
+
+```ruby
+# ImportExport::Importer
+module Gitlab
+ module ImportExport
+ class Importer
+ def execute
+ if import_file && check_version! && restorers.all?(&:restore) && overwrite_project
+ project_tree.restored_project
+ else
+ raise Projects::ImportService::Error.new(@shared.errors.join(', '))
+ end
+ rescue => e
+ raise Projects::ImportService::Error.new(e.message)
+ ensure
+ remove_import_file
+ end
+
+ def restorers
+ [repo_restorer, wiki_restorer, project_tree, avatar_restorer,
+ uploads_restorer, lfs_restorer, statistics_restorer]
+ end
+```
+
+The export service, is similar to the `Importer`, restoring data instead of saving it.
+
+### Export
+
+```ruby
+# ImportExport::ExportService
+module Projects
+ module ImportExport
+ class ExportService < BaseService
+
+ def save_all!
+ if save_services
+ Gitlab::ImportExport::Saver.save(project: project, shared: @shared)
+ notify_success
+ else
+ cleanup_and_notify_error!
+ end
+ end
+
+ def save_services
+ [version_saver, avatar_saver, project_tree_saver, uploads_saver, repo_saver,
+ wiki_repo_saver, lfs_saver].all?(&:save)
+ end
+```
diff --git a/doc/development/testing_guide/flaky_tests.md b/doc/development/testing_guide/flaky_tests.md
index bbb2313ea7b..f6bc9fb0979 100644
--- a/doc/development/testing_guide/flaky_tests.md
+++ b/doc/development/testing_guide/flaky_tests.md
@@ -5,6 +5,18 @@
It's a test that sometimes fails, but if you retry it enough times, it passes,
eventually.
+## Quarantined tests
+
+Tests can be put in quarantine by assigning `:quarantine` metadata. This means
+they will be skipped unless run with `--tag quarantine`. This can be used for
+tests that are expected to fail while a fix is in progress (similar to how
+[`skip` or `pending`](https://relishapp.com/rspec/rspec-core/v/3-8/docs/pending-and-skipped-examples)
+ can be used).
+
+```
+bin/rspec --tag quarantine
+```
+
## Automatic retries and flaky tests detection
On our CI, we use [rspec-retry] to automatically retry a failing example a few
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 5653c59f576..2eba2cc4847 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -50,62 +50,85 @@ The GitLab installation consists of setting up the following components:
`sudo` is not installed on Debian by default. Make sure your system is
up-to-date and install it.
- # run as root!
- apt-get update -y
- apt-get upgrade -y
- apt-get install sudo -y
+```sh
+# run as root!
+apt-get update -y
+apt-get upgrade -y
+apt-get install sudo -y
+```
**Note:** During this installation some files will need to be edited manually. If you are familiar with vim set it as default editor with the commands below. If you are not familiar with vim please skip this and keep using the default editor.
- # Install vim and set as default editor
- sudo apt-get install -y vim
- sudo update-alternatives --set editor /usr/bin/vim.basic
+```sh
+# Install vim and set as default editor
+sudo apt-get install -y vim
+sudo update-alternatives --set editor /usr/bin/vim.basic
+```
Install the required packages (needed to compile Ruby and native extensions to Ruby gems):
- sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libre2-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate rsync python-docutils pkg-config cmake
+```sh
+sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libre2-dev \
+ libreadline-dev libncurses5-dev libffi-dev curl openssh-server checkinstall libxml2-dev \
+ libxslt-dev libcurl4-openssl-dev libicu-dev logrotate rsync python-docutils pkg-config cmake
+```
Ubuntu 14.04 (Trusty Tahr) doesn't have the `libre2-dev` package available, but
you can [install re2 manually](https://github.com/google/re2/wiki/Install).
If you want to use Kerberos for user authentication, then install libkrb5-dev:
- sudo apt-get install libkrb5-dev
+```sh
+sudo apt-get install libkrb5-dev
+```
**Note:** If you don't know what Kerberos is, you can assume you don't need it.
Make sure you have the right version of Git installed
- # Install Git
- sudo apt-get install -y git-core
+```sh
+# Install Git
+sudo apt-get install -y git-core
- # Make sure Git is version 2.18.0 or higher
- git --version
+# Make sure Git is version 2.18.0 or higher
+git --version
+```
Is the system packaged Git too old? Remove it and compile from source.
- # Remove packaged Git
- sudo apt-get remove git-core
+```sh
+# Remove packaged Git
+sudo apt-get remove git-core
+
+# Install dependencies
+sudo apt-get install -y libcurl4-openssl-dev libexpat1-dev gettext libz-dev libssl-dev build-essential
+
+# Download and compile from source
+cd /tmp
+curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.18.0.tar.gz
+echo '94faf2c0b02a7920b0b46f4961d8e9cad08e81418614102898a55f980fa3e7e4 git-2.18.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.18.0.tar.gz
+cd git-2.18.0/
+./configure
+make prefix=/usr/local all
- # Install dependencies
- sudo apt-get install -y libcurl4-openssl-dev libexpat1-dev gettext libz-dev libssl-dev build-essential
+# Install into /usr/local/bin
+sudo make prefix=/usr/local install
- # Download and compile from source
- cd /tmp
- curl --remote-name --progress https://www.kernel.org/pub/software/scm/git/git-2.18.0.tar.gz
- echo '94faf2c0b02a7920b0b46f4961d8e9cad08e81418614102898a55f980fa3e7e4 git-2.18.0.tar.gz' | shasum -a256 -c - && tar -xzf git-2.18.0.tar.gz
- cd git-2.18.0/
- ./configure
- make prefix=/usr/local all
+# When editing config/gitlab.yml (Step 5), change the git -> bin_path to /usr/local/bin/git
+```
- # Install into /usr/local/bin
- sudo make prefix=/usr/local install
+For the [Custom Favicon](../customization/favicon.md) to work, graphicsmagick
+needs to be installed.
- # When editing config/gitlab.yml (Step 5), change the git -> bin_path to /usr/local/bin/git
+```sh
+sudo apt-get install -y graphicsmagick
+```
**Note:** In order to receive mail notifications, make sure to install a mail server. By default, Debian is shipped with exim4 but this [has problems](https://gitlab.com/gitlab-org/gitlab-ce/issues/12754) while Ubuntu does not ship with one. The recommended mail server is postfix and you can install it with:
- sudo apt-get install -y postfix
+```sh
+sudo apt-get install -y postfix
+```
Then select 'Internet Site' and press enter to confirm the hostname.
@@ -127,22 +150,28 @@ instructions are designed to install Ruby from the official source code.
Remove the old Ruby 1.8 if present:
- sudo apt-get remove ruby1.8
+```sh
+sudo apt-get remove ruby1.8
+```
Download Ruby and compile it:
- mkdir /tmp/ruby && cd /tmp/ruby
- curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.3.tar.gz
- echo 'f919a9fbcdb7abecd887157b49833663c5c15fda ruby-2.5.3.tar.gz' | shasum -c - && tar xzf ruby-2.5.3.tar.gz
- cd ruby-2.5.3
-
- ./configure --disable-install-rdoc
- make
- sudo make install
+```sh
+mkdir /tmp/ruby && cd /tmp/ruby
+curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.3.tar.gz
+echo 'f919a9fbcdb7abecd887157b49833663c5c15fda ruby-2.5.3.tar.gz' | shasum -c - && tar xzf ruby-2.5.3.tar.gz
+cd ruby-2.5.3
+
+./configure --disable-install-rdoc
+make
+sudo make install
+```
Then install the Bundler Gem:
- sudo gem install bundler --no-document
+```sh
+sudo gem install bundler --no-document
+```
## 3. Go
@@ -151,31 +180,35 @@ GitLab we need a Go compiler. The instructions below assume you use 64-bit
Linux. You can find downloads for other platforms at the [Go download
page](https://golang.org/dl).
- # Remove former Go installation folder
- sudo rm -rf /usr/local/go
-
- curl --remote-name --progress https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz
- echo 'fa1b0e45d3b647c252f51f5e1204aba049cde4af177ef9f2181f43004f901035 go1.10.3.linux-amd64.tar.gz' | shasum -a256 -c - && \
- sudo tar -C /usr/local -xzf go1.10.3.linux-amd64.tar.gz
- sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
- rm go1.10.3.linux-amd64.tar.gz
+```sh
+# Remove former Go installation folder
+sudo rm -rf /usr/local/go
+
+curl --remote-name --progress https://dl.google.com/go/go1.10.3.linux-amd64.tar.gz
+echo 'fa1b0e45d3b647c252f51f5e1204aba049cde4af177ef9f2181f43004f901035 go1.10.3.linux-amd64.tar.gz' | shasum -a256 -c - && \
+ sudo tar -C /usr/local -xzf go1.10.3.linux-amd64.tar.gz
+sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
+rm go1.10.3.linux-amd64.tar.gz
+```
## 4. Node
Since GitLab 8.17, GitLab requires the use of Node to compile javascript
assets, and Yarn to manage javascript dependencies. The current minimum
-requirements for these are node >= v6.0.0 and yarn >= v1.2.0. In many distros
+requirements for these are node >= v8.10.0 and yarn >= v1.10.0. In many distros
the versions provided by the official package repositories are out of date, so
we'll need to install through the following commands:
- # install node v8.x
- curl --location https://deb.nodesource.com/setup_8.x | sudo bash -
- sudo apt-get install -y nodejs
-
- curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
- echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
- sudo apt-get update
- sudo apt-get install yarn
+```sh
+# install node v8.x
+curl --location https://deb.nodesource.com/setup_8.x | sudo bash -
+sudo apt-get install -y nodejs
+
+curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+sudo apt-get update
+sudo apt-get install yarn
+```
Visit the official websites for [node](https://nodejs.org/en/download/package-manager/) and [yarn](https://yarnpkg.com/en/docs/install/) if you have any trouble with these steps.
@@ -183,7 +216,9 @@ Visit the official websites for [node](https://nodejs.org/en/download/package-ma
Create a `git` user for GitLab:
- sudo adduser --disabled-login --gecos 'GitLab' git
+```sh
+sudo adduser --disabled-login --gecos 'GitLab' git
+```
## 6. Database
@@ -195,37 +230,37 @@ you need at least PostgreSQL 9.2.
1. Install the database packages:
- ```bash
+ ```sh
sudo apt-get install -y postgresql postgresql-client libpq-dev postgresql-contrib
```
1. Create a database user for GitLab:
- ```bash
+ ```sh
sudo -u postgres psql -d template1 -c "CREATE USER git CREATEDB;"
```
1. Create the `pg_trgm` extension (required for GitLab 8.6+):
- ```bash
+ ```sh
sudo -u postgres psql -d template1 -c "CREATE EXTENSION IF NOT EXISTS pg_trgm;"
```
1. Create the GitLab production database and grant all privileges on database:
- ```bash
+ ```sh
sudo -u postgres psql -d template1 -c "CREATE DATABASE gitlabhq_production OWNER git;"
```
1. Try connecting to the new database with the new user:
- ```bash
+ ```sh
sudo -u git -H psql -d gitlabhq_production
```
1. Check if the `pg_trgm` extension is enabled:
- ```bash
+ ```sh
SELECT true AS enabled
FROM pg_available_extensions
WHERE name = 'pg_trgm'
@@ -243,7 +278,7 @@ you need at least PostgreSQL 9.2.
1. Quit the database session:
- ```bash
+ ```sh
gitlabhq_production> \q
```
@@ -262,7 +297,7 @@ If you are using Debian 7 or Ubuntu 12.04, follow the special documentation
on [an alternate Redis installation](redis.md). Once done, follow the rest of
the guide here.
-```
+```sh
# Configure redis to use sockets
sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig
@@ -294,89 +329,95 @@ sudo usermod -aG redis git
## 8. GitLab
- # We'll install GitLab into home directory of the user "git"
- cd /home/git
+```sh
+# We'll install GitLab into home directory of the user "git"
+cd /home/git
+```
### Clone the Source
- # Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 11-7-stable gitlab
+```sh
+# Clone GitLab repository
+sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 11-7-stable gitlab
+```
**Note:** You can change `11-7-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
- # Go to GitLab installation folder
- cd /home/git/gitlab
+```sh
+# Go to GitLab installation folder
+cd /home/git/gitlab
- # Copy the example GitLab config
- sudo -u git -H cp config/gitlab.yml.example config/gitlab.yml
+# Copy the example GitLab config
+sudo -u git -H cp config/gitlab.yml.example config/gitlab.yml
- # Update GitLab config file, follow the directions at top of file
- sudo -u git -H editor config/gitlab.yml
+# Update GitLab config file, follow the directions at top of file
+sudo -u git -H editor config/gitlab.yml
- # Copy the example secrets file
- sudo -u git -H cp config/secrets.yml.example config/secrets.yml
- sudo -u git -H chmod 0600 config/secrets.yml
+# Copy the example secrets file
+sudo -u git -H cp config/secrets.yml.example config/secrets.yml
+sudo -u git -H chmod 0600 config/secrets.yml
- # Make sure GitLab can write to the log/ and tmp/ directories
- sudo chown -R git log/
- sudo chown -R git tmp/
- sudo chmod -R u+rwX,go-w log/
- sudo chmod -R u+rwX tmp/
+# Make sure GitLab can write to the log/ and tmp/ directories
+sudo chown -R git log/
+sudo chown -R git tmp/
+sudo chmod -R u+rwX,go-w log/
+sudo chmod -R u+rwX tmp/
- # Make sure GitLab can write to the tmp/pids/ and tmp/sockets/ directories
- sudo chmod -R u+rwX tmp/pids/
- sudo chmod -R u+rwX tmp/sockets/
+# Make sure GitLab can write to the tmp/pids/ and tmp/sockets/ directories
+sudo chmod -R u+rwX tmp/pids/
+sudo chmod -R u+rwX tmp/sockets/
- # Create the public/uploads/ directory
- sudo -u git -H mkdir public/uploads/
+# Create the public/uploads/ directory
+sudo -u git -H mkdir public/uploads/
- # Make sure only the GitLab user has access to the public/uploads/ directory
- # now that files in public/uploads are served by gitlab-workhorse
- sudo chmod 0700 public/uploads
+# Make sure only the GitLab user has access to the public/uploads/ directory
+# now that files in public/uploads are served by gitlab-workhorse
+sudo chmod 0700 public/uploads
- # Change the permissions of the directory where CI job traces are stored
- sudo chmod -R u+rwX builds/
+# Change the permissions of the directory where CI job traces are stored
+sudo chmod -R u+rwX builds/
- # Change the permissions of the directory where CI artifacts are stored
- sudo chmod -R u+rwX shared/artifacts/
+# Change the permissions of the directory where CI artifacts are stored
+sudo chmod -R u+rwX shared/artifacts/
- # Change the permissions of the directory where GitLab Pages are stored
- sudo chmod -R ug+rwX shared/pages/
+# Change the permissions of the directory where GitLab Pages are stored
+sudo chmod -R ug+rwX shared/pages/
- # Copy the example Unicorn config
- sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb
+# Copy the example Unicorn config
+sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb
- # Find number of cores
- nproc
+# Find number of cores
+nproc
- # Enable cluster mode if you expect to have a high load instance
- # Set the number of workers to at least the number of cores
- # Ex. change amount of workers to 3 for 2GB RAM server
- sudo -u git -H editor config/unicorn.rb
+# Enable cluster mode if you expect to have a high load instance
+# Set the number of workers to at least the number of cores
+# Ex. change amount of workers to 3 for 2GB RAM server
+sudo -u git -H editor config/unicorn.rb
- # Copy the example Rack attack config
- sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb
+# Copy the example Rack attack config
+sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb
- # Configure Git global settings for git user
- # 'autocrlf' is needed for the web editor
- sudo -u git -H git config --global core.autocrlf input
+# Configure Git global settings for git user
+# 'autocrlf' is needed for the web editor
+sudo -u git -H git config --global core.autocrlf input
- # Disable 'git gc --auto' because GitLab already runs 'git gc' when needed
- sudo -u git -H git config --global gc.auto 0
+# Disable 'git gc --auto' because GitLab already runs 'git gc' when needed
+sudo -u git -H git config --global gc.auto 0
- # Enable packfile bitmaps
- sudo -u git -H git config --global repack.writeBitmaps true
+# Enable packfile bitmaps
+sudo -u git -H git config --global repack.writeBitmaps true
- # Enable push options
- sudo -u git -H git config --global receive.advertisePushOptions true
+# Enable push options
+sudo -u git -H git config --global receive.advertisePushOptions true
- # Configure Redis connection settings
- sudo -u git -H cp config/resque.yml.example config/resque.yml
+# Configure Redis connection settings
+sudo -u git -H cp config/resque.yml.example config/resque.yml
- # Change the Redis socket path if you are not using the default Debian / Ubuntu configuration
- sudo -u git -H editor config/resque.yml
+# Change the Redis socket path if you are not using the default Debian / Ubuntu configuration
+sudo -u git -H editor config/resque.yml
+```
**Important Note:** Make sure to edit both `gitlab.yml` and `unicorn.rb` to match your setup.
@@ -384,33 +425,37 @@ sudo usermod -aG redis git
### Configure GitLab DB Settings
- # PostgreSQL only:
- sudo -u git cp config/database.yml.postgresql config/database.yml
-
- # MySQL only:
- sudo -u git cp config/database.yml.mysql config/database.yml
-
- # MySQL and remote PostgreSQL only:
- # Update username/password in config/database.yml.
- # You only need to adapt the production settings (first part).
- # If you followed the database guide then please do as follows:
- # Change 'secure password' with the value you have given to $password
- # You can keep the double quotes around the password
- sudo -u git -H editor config/database.yml
-
- # PostgreSQL and MySQL:
- # Make config/database.yml readable to git only
- sudo -u git -H chmod o-rwx config/database.yml
+```sh
+# PostgreSQL only:
+sudo -u git cp config/database.yml.postgresql config/database.yml
+
+# MySQL only:
+sudo -u git cp config/database.yml.mysql config/database.yml
+
+# MySQL and remote PostgreSQL only:
+# Update username/password in config/database.yml.
+# You only need to adapt the production settings (first part).
+# If you followed the database guide then please do as follows:
+# Change 'secure password' with the value you have given to $password
+# You can keep the double quotes around the password
+sudo -u git -H editor config/database.yml
+
+# PostgreSQL and MySQL:
+# Make config/database.yml readable to git only
+sudo -u git -H chmod o-rwx config/database.yml
+```
### Install Gems
**Note:** As of bundler 1.5.2, you can invoke `bundle install -jN` (where `N` the number of your processor cores) and enjoy the parallel gems installation with measurable difference in completion time (~60% faster). Check the number of your cores with `nproc`. For more information check this [post](https://robots.thoughtbot.com/parallel-gem-installing-using-bundler). First make sure you have bundler >= 1.5.2 (run `bundle -v`) as it addresses some [issues](https://devcenter.heroku.com/changelog-items/411) that were [fixed](https://github.com/bundler/bundler/pull/2817) in 1.5.2.
- # For PostgreSQL (note, the option says "without ... mysql")
- sudo -u git -H bundle install --deployment --without development test mysql aws kerberos
+```sh
+# For PostgreSQL (note, the option says "without ... mysql")
+sudo -u git -H bundle install --deployment --without development test mysql aws kerberos
- # Or if you use MySQL (note, the option says "without ... postgres")
- sudo -u git -H bundle install --deployment --without development test postgres aws kerberos
+# Or if you use MySQL (note, the option says "without ... postgres")
+sudo -u git -H bundle install --deployment --without development test postgres aws kerberos
+```
**Note:** If you want to use Kerberos for user authentication, then omit `kerberos` in the `--without` option above.
@@ -418,12 +463,14 @@ sudo usermod -aG redis git
GitLab Shell is an SSH access and repository management software developed specially for GitLab.
- # Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
- sudo -u git -H bundle exec rake gitlab:shell:install REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production SKIP_STORAGE_VALIDATION=true
+```sh
+# Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
+sudo -u git -H bundle exec rake gitlab:shell:install REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production SKIP_STORAGE_VALIDATION=true
- # By default, the gitlab-shell config is generated from your main GitLab config.
- # You can review (and modify) the gitlab-shell config as follows:
- sudo -u git -H editor /home/git/gitlab-shell/config.yml
+# By default, the gitlab-shell config is generated from your main GitLab config.
+# You can review (and modify) the gitlab-shell config as follows:
+sudo -u git -H editor /home/git/gitlab-shell/config.yml
+```
**Note:** If you want to use HTTPS, see [Using HTTPS](#using-https) for the additional steps.
@@ -441,57 +488,73 @@ GitLab-Workhorse uses [GNU Make](https://www.gnu.org/software/make/). The
following command-line will install GitLab-Workhorse in `/home/git/gitlab-workhorse`
which is the recommended location.
- sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production
+```sh
+sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse]" RAILS_ENV=production
+```
You can specify a different Git repository by providing it as an extra parameter:
- sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse,https://example.com/gitlab-workhorse.git]" RAILS_ENV=production
+```sh
+sudo -u git -H bundle exec rake "gitlab:workhorse:install[/home/git/gitlab-workhorse,https://example.com/gitlab-workhorse.git]" RAILS_ENV=production
+```
### Install gitlab-pages
GitLab-Pages uses [GNU Make](https://www.gnu.org/software/make/). This step is optional and only needed if you wish to host static sites from within GitLab. The following commands will install GitLab-Pages in `/home/git/gitlab-pages`. For additional setup steps, please consult the [administration guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/administration/pages/source.md) for your version of GitLab as the GitLab Pages daemon can be ran several different ways.
- cd /home/git
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
- cd gitlab-pages
- sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
- sudo -u git -H make
+```sh
+cd /home/git
+sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-pages.git
+cd gitlab-pages
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
+sudo -u git -H make
+```
### Install Gitaly
- # Fetch Gitaly source with Git and compile with Go
- sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories]" RAILS_ENV=production
+```sh
+# Fetch Gitaly source with Git and compile with Go
+sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories]" RAILS_ENV=production
+```
You can specify a different Git repository by providing it as an extra parameter:
- sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories,https://example.com/gitaly.git]" RAILS_ENV=production
+```sh
+sudo -u git -H bundle exec rake "gitlab:gitaly:install[/home/git/gitaly,/home/git/repositories,https://example.com/gitaly.git]" RAILS_ENV=production
+```
Next, make sure gitaly configured:
- # Restrict Gitaly socket access
- sudo chmod 0700 /home/git/gitlab/tmp/sockets/private
- sudo chown git /home/git/gitlab/tmp/sockets/private
+```sh
+# Restrict Gitaly socket access
+sudo chmod 0700 /home/git/gitlab/tmp/sockets/private
+sudo chown git /home/git/gitlab/tmp/sockets/private
- # If you are using non-default settings you need to update config.toml
- cd /home/git/gitaly
- sudo -u git -H editor config.toml
+# If you are using non-default settings you need to update config.toml
+cd /home/git/gitaly
+sudo -u git -H editor config.toml
+```
For more information about configuring Gitaly see
[doc/administration/gitaly](../administration/gitaly).
### Initialize Database and Activate Advanced Features
- sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
- # Type 'yes' to create the database tables.
+```sh
+sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production
+# Type 'yes' to create the database tables.
- # or you can skip the question by adding force=yes
- sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production force=yes
+# or you can skip the question by adding force=yes
+sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production force=yes
- # When done you see 'Administrator account created:'
+# When done you see 'Administrator account created:'
+```
**Note:** You can set the Administrator/root password and e-mail by supplying them in environmental variables, `GITLAB_ROOT_PASSWORD` and `GITLAB_ROOT_EMAIL` respectively, as seen below. If you don't set the password (and it is set to the default one) please wait with exposing GitLab to the public internet until the installation is done and you've logged into the server the first time. During the first login you'll be forced to change the default password.
- sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=yourpassword GITLAB_ROOT_EMAIL=youremail
+```sh
+sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=yourpassword GITLAB_ROOT_EMAIL=youremail
+```
### Secure secrets.yml
@@ -503,43 +566,58 @@ Otherwise your secrets are exposed if one of your backups is compromised.
Download the init script (will be `/etc/init.d/gitlab`):
- sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```sh
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
And if you are installing with a non-default folder or user copy and edit the defaults file:
- sudo cp lib/support/init.d/gitlab.default.example /etc/default/gitlab
+```sh
+sudo cp lib/support/init.d/gitlab.default.example /etc/default/gitlab
+```
If you installed GitLab in another directory or as a user other than the default you should change these settings in `/etc/default/gitlab`. Do not edit `/etc/init.d/gitlab` as it will be changed on upgrade.
Make GitLab start on boot:
- sudo update-rc.d gitlab defaults 21
+```sh
+sudo update-rc.d gitlab defaults 21
+```
### Set up Logrotate
- sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
+```sh
+sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
+```
### Check Application Status
Check if GitLab and its environment are configured correctly:
- sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
-
+```sh
+sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+```
### Compile GetText PO files
- sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production
+```sh
+sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production
+```
### Compile Assets
- sudo -u git -H yarn install --production --pure-lockfile
- sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
+```sh
+sudo -u git -H yarn install --production --pure-lockfile
+sudo -u git -H bundle exec rake gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
+```
### Start Your GitLab Instance
- sudo service gitlab start
- # or
- sudo /etc/init.d/gitlab restart
+```sh
+sudo service gitlab start
+# or
+sudo /etc/init.d/gitlab restart
+```
## 9. Nginx
@@ -547,27 +625,33 @@ Check if GitLab and its environment are configured correctly:
### Installation
- sudo apt-get install -y nginx
+```sh
+sudo apt-get install -y nginx
+```
### Site Configuration
Copy the example site config:
- sudo cp lib/support/nginx/gitlab /etc/nginx/sites-available/gitlab
- sudo ln -s /etc/nginx/sites-available/gitlab /etc/nginx/sites-enabled/gitlab
+```sh
+sudo cp lib/support/nginx/gitlab /etc/nginx/sites-available/gitlab
+sudo ln -s /etc/nginx/sites-available/gitlab /etc/nginx/sites-enabled/gitlab
+```
Make sure to edit the config file to match your setup. Also, ensure that you match your paths to GitLab, especially if installing for a user other than the 'git' user:
- # Change YOUR_SERVER_FQDN to the fully-qualified
- # domain name of your host serving GitLab.
- #
- # Remember to match your paths to GitLab, especially
- # if installing for a user other than 'git'.
- #
- # If using Ubuntu default nginx install:
- # either remove the default_server from the listen line
- # or else sudo rm -f /etc/nginx/sites-enabled/default
- sudo editor /etc/nginx/sites-available/gitlab
+```sh
+# Change YOUR_SERVER_FQDN to the fully-qualified
+# domain name of your host serving GitLab.
+#
+# Remember to match your paths to GitLab, especially
+# if installing for a user other than 'git'.
+#
+# If using Ubuntu default nginx install:
+# either remove the default_server from the listen line
+# or else sudo rm -f /etc/nginx/sites-enabled/default
+sudo editor /etc/nginx/sites-available/gitlab
+```
If you intend to enable GitLab pages, there is a separate Nginx config you need
to use. Read all about the needed configuration at the
@@ -579,13 +663,17 @@ to use. Read all about the needed configuration at the
Validate your `gitlab` or `gitlab-ssl` Nginx config file with the following command:
- sudo nginx -t
+```sh
+sudo nginx -t
+```
You should receive `syntax is okay` and `test is successful` messages. If you receive errors check your `gitlab` or `gitlab-ssl` Nginx config file for typos, etc. as indicated in the error message given.
### Restart
- sudo service nginx restart
+```sh
+sudo service nginx restart
+```
## Done!
@@ -593,7 +681,9 @@ You should receive `syntax is okay` and `test is successful` messages. If you re
To make sure you didn't miss anything run a more thorough check with:
- sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```sh
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
If all items are green, then congratulations on successfully installing GitLab!
@@ -680,31 +770,39 @@ for the changes to take effect.
If you'd like to connect to a Redis server on a non-standard port or on a different host, you can configure its connection string via the `config/resque.yml` file.
- # example
- production:
- url: redis://redis.example.tld:6379
+```
+# example
+production:
+ url: redis://redis.example.tld:6379
+```
If you want to connect the Redis server via socket, then use the "unix:" URL scheme and the path to the Redis socket file in the `config/resque.yml` file.
- # example
- production:
- url: unix:/path/to/redis/socket
+```
+# example
+production:
+ url: unix:/path/to/redis/socket
+```
Also you can use environment variables in the `config/resque.yml` file:
- # example
- production:
- url: <%= ENV.fetch('GITLAB_REDIS_URL') %>
+```
+# example
+production:
+ url: <%= ENV.fetch('GITLAB_REDIS_URL') %>
+```
### Custom SSH Connection
If you are running SSH on a non-standard port, you must change the GitLab user's SSH config.
- # Add to /home/git/.ssh/config
- host localhost # Give your setup a name (here: override localhost)
- user git # Your remote git user
- port 2222 # Your port number
- hostname 127.0.0.1; # Your server name or IP
+```
+# Add to /home/git/.ssh/config
+host localhost # Give your setup a name (here: override localhost)
+ user git # Your remote git user
+ port 2222 # Your port number
+ hostname 127.0.0.1; # Your server name or IP
+```
You also need to change the corresponding options (e.g. `ssh_user`, `ssh_host`, `admin_uri`) in the `config\gitlab.yml` file.
diff --git a/doc/integration/oauth_provider.md b/doc/integration/oauth_provider.md
index acc9db15826..c02a29dffb4 100644
--- a/doc/integration/oauth_provider.md
+++ b/doc/integration/oauth_provider.md
@@ -3,8 +3,11 @@
This document is about using GitLab as an OAuth authentication service provider
to sign in to other services.
-If you want to use other OAuth authentication service providers to sign in to
-GitLab, please see the [OAuth2 client documentation](../api/oauth2.md).
+If you want to use:
+
+- Other OAuth authentication service providers to sign in to
+ GitLab, see the [OAuth2 client documentation](omniauth.md).
+- The related API, see [Applications API](../api/applications.md).
## Introduction to OAuth
@@ -28,7 +31,7 @@ GitLab supports two ways of adding a new OAuth2 application to an instance. You
can either add an application as a regular user or add it in the admin area.
What this means is that GitLab can actually have instance-wide and a user-wide
applications. There is no difference between them except for the different
-permission levels they are set (user/admin). The default callback URL is
+permission levels they are set (user/admin). The default callback URL is
`http://your-gitlab.example.com/users/auth/gitlab/callback`
## Adding an application through the profile
diff --git a/doc/security/webhooks.md b/doc/security/webhooks.md
index b17b0a4bc4a..fb2b6768f0a 100644
--- a/doc/security/webhooks.md
+++ b/doc/security/webhooks.md
@@ -12,7 +12,7 @@ If a web service does not require authentication, Webhooks can be used to trigge
To prevent this type of exploitation from happening, starting with GitLab 10.6, all Webhook requests to the current GitLab instance server address and/or in a private network will be forbidden by default. That means that all requests made to 127.0.0.1, ::1 and 0.0.0.0, as well as IPv4 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16 and IPv6 site-local (ffc0::/10) addresses won't be allowed.
-This behavior can be overridden by enabling the option *"Allow requests to the local network from hooks and services"* in the *"Outbound requests"* section inside the Admin area under **Settings** (`/admin/application_settings`):
+This behavior can be overridden by enabling the option *"Allow requests to the local network from hooks and services"* in the *"Outbound requests"* section inside the Admin area under **Settings** (`/admin/application_settings/network`):
![Outbound requests admin settings](img/outbound_requests_section.png)
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 780e9b8783e..68e50a61151 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -683,6 +683,8 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| `PRODUCTION_REPLICAS` | The number of replicas to deploy in the production environment. This takes precedence over `REPLICAS`; defaults to 1. |
| `CANARY_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html); defaults to 1 |
| `CANARY_PRODUCTION_REPLICAS` | The number of canary replicas to deploy for [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html) in the production environment. This takes precedence over `CANARY_REPLICAS`; defaults to 1 |
+| `ADDITIONAL_HOSTS` | Fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. |
+| `<ENVIRONMENT>_ADDITIONAL_HOSTS` | For a specific environment, the fully qualified domain names specified as a comma-separated list that are added to the ingress hosts. This takes precedence over `ADDITIONAL_HOSTS`. |
| `POSTGRES_ENABLED` | Whether PostgreSQL is enabled; defaults to `"true"`. Set to `false` to disable the automatic deployment of PostgreSQL. |
| `POSTGRES_USER` | The PostgreSQL user; defaults to `user`. Set it to use a custom username. |
| `POSTGRES_PASSWORD` | The PostgreSQL password; defaults to `testing-password`. Set it to use a custom password. |
diff --git a/doc/update/11.7-to-11.8.md b/doc/update/11.7-to-11.8.md
new file mode 100644
index 00000000000..1587c310876
--- /dev/null
+++ b/doc/update/11.7-to-11.8.md
@@ -0,0 +1,393 @@
+---
+comments: false
+---
+
+# From 11.7 to 11.8
+
+Make sure you view this update guide from the branch (version) of GitLab you would
+like to install (e.g., `11-8-stable`. You can select the branch in the version
+dropdown at the top left corner of GitLab (below the menu bar).
+
+If the highest number stable branch is unclear please check the
+[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation
+guide links by version.
+
+### 1. Stop server
+
+```bash
+sudo service gitlab stop
+```
+
+### 2. Backup
+
+NOTE: If you installed GitLab from source, make sure `rsync` is installed.
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Update Ruby
+
+NOTE: Beginning in GitLab 11.0, we only support Ruby 2.4 or higher, and dropped
+support for Ruby 2.3. Be sure to upgrade if necessary.
+
+You can check which version you are running with `ruby -v`.
+
+Download Ruby and compile it:
+
+```bash
+mkdir /tmp/ruby && cd /tmp/ruby
+curl --remote-name --progress https://cache.ruby-lang.org/pub/ruby/2.5/ruby-2.5.3.tar.gz
+echo 'f919a9fbcdb7abecd887157b49833663c5c15fda ruby-2.5.3.tar.gz' | shasum -c - && tar xzf ruby-2.5.3.tar.gz
+cd ruby-2.5.3
+
+./configure --disable-install-rdoc
+make
+sudo make install
+```
+
+Install Bundler:
+
+```bash
+sudo gem install bundler --no-document
+```
+
+### 4. Update Node
+
+NOTE: Beginning in GitLab 11.8, we only support node 8 or higher, and dropped
+support for node 6. Be sure to upgrade if necessary.
+
+GitLab utilizes [webpack](http://webpack.js.org) to compile frontend assets.
+This requires a minimum version of node v8.10.0.
+
+You can check which version you are running with `node -v`. If you are running
+a version older than `v8.10.0` you will need to update to a newer version. You
+can find instructions to install from community maintained packages or compile
+from source at the nodejs.org website.
+
+<https://nodejs.org/en/download/>
+
+GitLab also requires the use of yarn `>= v1.10.0` to manage JavaScript
+dependencies.
+
+```bash
+curl --silent --show-error https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
+echo "deb https://dl.yarnpkg.com/debian/ stable main" | sudo tee /etc/apt/sources.list.d/yarn.list
+sudo apt-get update
+sudo apt-get install yarn
+```
+
+More information can be found on the [yarn website](https://yarnpkg.com/en/docs/install).
+
+### 5. Update Go
+
+NOTE: GitLab 11.4 and higher only supports Go 1.10.x and newer, and dropped support for Go
+1.9.x. Be sure to upgrade your installation if necessary.
+
+You can check which version you are running with `go version`.
+
+Download and install Go:
+
+```bash
+# Remove former Go installation folder
+sudo rm -rf /usr/local/go
+
+curl --remote-name --progress https://dl.google.com/go/go1.10.5.linux-amd64.tar.gz
+echo 'a035d9beda8341b645d3f45a1b620cf2d8fb0c5eb409be36b389c0fd384ecc3a go1.10.5.linux-amd64.tar.gz' | shasum -a256 -c - && \
+ sudo tar -C /usr/local -xzf go1.10.5.linux-amd64.tar.gz
+sudo ln -sf /usr/local/go/bin/{go,godoc,gofmt} /usr/local/bin/
+rm go1.10.5.linux-amd64.tar.gz
+```
+
+### 6. Get latest code
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git fetch --all --prune
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+sudo -u git -H git checkout -- locale
+```
+
+For GitLab Community Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 11-8-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H git checkout 11-8-stable-ee
+```
+
+### 7. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_SHELL_VERSION)
+sudo -u git -H bin/compile
+```
+
+### 8. Update gitlab-workhorse
+
+Install and compile gitlab-workhorse. GitLab-Workhorse uses
+[GNU Make](https://www.gnu.org/software/make/).
+If you are not using Linux you may have to run `gmake` instead of
+`make` below.
+
+```bash
+cd /home/git/gitlab-workhorse
+
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_WORKHORSE_VERSION)
+sudo -u git -H make
+```
+
+### 9. Update Gitaly
+
+#### Check Gitaly configuration
+
+Due to a bug in the `rake gitlab:gitaly:install` script your Gitaly
+configuration file may contain syntax errors. The block name
+`[[storages]]`, which may occur more than once in your `config.toml`
+file, should be `[[storage]]` instead.
+
+```shell
+sudo -u git -H sed -i.pre-10.1 's/\[\[storages\]\]/[[storage]]/' /home/git/gitaly/config.toml
+```
+
+#### Compile Gitaly
+
+```shell
+cd /home/git/gitaly
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITALY_SERVER_VERSION)
+sudo -u git -H make
+```
+
+### 10. Update gitlab-pages
+
+#### Only needed if you use GitLab Pages
+
+Install and compile gitlab-pages. GitLab-Pages uses
+[GNU Make](https://www.gnu.org/software/make/).
+If you are not using Linux you may have to run `gmake` instead of
+`make` below.
+
+```bash
+cd /home/git/gitlab-pages
+
+sudo -u git -H git fetch --all --tags --prune
+sudo -u git -H git checkout v$(</home/git/gitlab/GITLAB_PAGES_VERSION)
+sudo -u git -H make
+```
+
+### 11. Update MySQL permissions
+
+If you are using MySQL you need to grant the GitLab user the necessary
+permissions on the database:
+
+```bash
+mysql -u root -p -e "GRANT TRIGGER ON \`gitlabhq_production\`.* TO 'git'@'localhost';"
+```
+
+If you use MySQL with replication, or just have MySQL configured with binary logging,
+you will need to also run the following on all of your MySQL servers:
+
+```bash
+mysql -u root -p -e "SET GLOBAL log_bin_trust_function_creators = 1;"
+```
+
+You can make this setting permanent by adding it to your `my.cnf`:
+
+```
+log_bin_trust_function_creators=1
+```
+
+### 12. Update configuration files
+
+#### New `unicorn.rb` configuration
+
+We have made [changes](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22372) to `unicorn.rb` to allow GitLab run with both Unicorn and Puma in future.
+
+Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/11-8-stable/config/unicorn.rb.example but with your settings.
+In particular, make sure that `require_relative "/home/git/gitlab/lib/gitlab/cluster/lifecycle_events"` line exists and the `before_exec`, `before_fork`, and `after_fork` handlers are configured as shown below:
+
+```ruby
+require_relative "/home/git/gitlab/lib/gitlab/cluster/lifecycle_events"
+
+before_exec do |server|
+ # Signal application hooks that we're about to restart
+ Gitlab::Cluster::LifecycleEvents.do_master_restart
+end
+
+before_fork do |server, worker|
+ # Signal application hooks that we're about to fork
+ Gitlab::Cluster::LifecycleEvents.do_before_fork
+end
+
+after_fork do |server, worker|
+ # Signal application hooks of worker start
+ Gitlab::Cluster::LifecycleEvents.do_worker_start
+end
+```
+
+#### New configuration options for `gitlab.yml`
+
+There might be configuration options available for [`gitlab.yml`][yaml]. View them with the command below and apply them manually to your current `gitlab.yml`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/11-7-stable:config/gitlab.yml.example origin/11-8-stable:config/gitlab.yml.example
+```
+
+#### Nginx configuration
+
+Ensure you're still up-to-date with the latest NGINX configuration changes:
+
+```sh
+cd /home/git/gitlab
+
+# For HTTPS configurations
+git diff origin/11-7-stable:lib/support/nginx/gitlab-ssl origin/11-8-stable:lib/support/nginx/gitlab-ssl
+
+# For HTTP configurations
+git diff origin/11-7-stable:lib/support/nginx/gitlab origin/11-8-stable:lib/support/nginx/gitlab
+```
+
+If you are using Strict-Transport-Security in your installation to continue using it you must enable it in your Nginx
+configuration as GitLab application no longer handles setting it.
+
+If you are using Apache instead of NGINX please see the updated [Apache templates].
+Also note that because Apache does not support upstreams behind Unix sockets you
+will need to let gitlab-workhorse listen on a TCP port. You can do this
+via [/etc/default/gitlab].
+
+[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache
+[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-8-stable/lib/support/init.d/gitlab.default.example#L38
+
+#### SMTP configuration
+
+If you're installing from source and use SMTP to deliver mail, you will need to add the following line
+to config/initializers/smtp_settings.rb:
+
+```ruby
+ActionMailer::Base.delivery_method = :smtp
+```
+
+See [smtp_settings.rb.sample] as an example.
+
+[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-8-stable/config/initializers/smtp_settings.rb.sample#L13
+
+#### Init script
+
+There might be new configuration options available for [`gitlab.default.example`][gl-example]. View them with the command below and apply them manually to your current `/etc/default/gitlab`:
+
+```sh
+cd /home/git/gitlab
+
+git diff origin/11-7-stable:lib/support/init.d/gitlab.default.example origin/11-8-stable:lib/support/init.d/gitlab.default.example
+```
+
+Ensure you're still up-to-date with the latest init script changes:
+
+```bash
+cd /home/git/gitlab
+
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+For Ubuntu 16.04.1 LTS:
+
+```bash
+sudo systemctl daemon-reload
+```
+
+### 13. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without postgres')
+sudo -u git -H bundle install --without postgres development test --deployment
+
+# PostgreSQL installations (note: the line below states '--without mysql')
+sudo -u git -H bundle install --without mysql development test --deployment
+
+# Optional: clean up old gems
+sudo -u git -H bundle clean
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Compile GetText PO files
+
+sudo -u git -H bundle exec rake gettext:compile RAILS_ENV=production
+
+# Update node dependencies and recompile assets
+sudo -u git -H bundle exec rake yarn:install gitlab:assets:clean gitlab:assets:compile RAILS_ENV=production NODE_ENV=production
+
+# Clean up cache
+sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
+```
+
+**MySQL installations**: Run through the `MySQL strings limits` and `Tables and data conversion to utf8mb4` [tasks](../install/database_mysql.md).
+
+### 14. Start application
+
+```bash
+sudo service gitlab start
+sudo service nginx restart
+```
+
+### 15. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+```
+
+To make sure you didn't miss anything run a more thorough check:
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (11.7)
+
+### 1. Revert the code to the previous version
+
+Follow the [upgrade guide from 11.6 to 11.7](11.6-to-11.7.md), except for the
+database migration (the backup is already migrated to the previous version).
+
+### 2. Restore from the backup
+
+```bash
+cd /home/git/gitlab
+
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+
+If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above.
+
+[yaml]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-8-stable/config/gitlab.yml.example
+[gl-example]: https://gitlab.com/gitlab-org/gitlab-ce/blob/11-8-stable/lib/support/init.d/gitlab.default.example
diff --git a/doc/user/award_emojis.md b/doc/user/award_emojis.md
index 93be3da44d4..e4fd08a582c 100644
--- a/doc/user/award_emojis.md
+++ b/doc/user/award_emojis.md
@@ -1,24 +1,24 @@
# Award emoji
-> **Notes:**
-> - First [introduced][1825] in GitLab 8.2.
-> - GitLab 9.0 [introduced][ce-9570] the usage of native emojis if the platform
+> - First [introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1825) in GitLab 8.2.
+> - GitLab 9.0 [introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9570) the usage of native emoji if the platform
> supports them and falls back to images or CSS sprites. This change greatly
-> improved the award emoji performance overall.
+> improved award emoji performance overall.
When you're collaborating online, you get fewer opportunities for high-fives
-and thumbs-ups. Emoji can be awarded to issues, merge requests, snippets, and
-virtually everywhere where you can have a discussion.
+and thumbs-ups. Emoji can be awarded to [issues](project/issues/index.md), [merge requests](project/merge_requests/index.md),
+[snippets](snippets.md), and anywhere you can have a discussion.
![Award emoji](img/award_emoji_select.png)
Award emoji make it much easier to give and receive feedback without a long
-comment thread. Comments that are only emoji will automatically become
-award emoji.
+comment thread.
+
+For information on the relevant API, see [Award Emoji API](../api/award_emoji.md).
## Sort issues and merge requests on vote count
-> [Introduced][2871] in GitLab 8.5.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2781) in GitLab 8.5.
You can quickly sort issues and merge requests by the number of votes they
have received. The sort options can be found in the dropdown menu as "Most
@@ -32,20 +32,16 @@ downvotes.
## Award emoji for comments
-> [Introduced][4291] in GitLab 8.9.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4291) in GitLab 8.9.
Award emoji can also be applied to individual comments when you want to
celebrate an accomplishment or agree with an opinion.
-To add an award emoji, click the smile in the top right of the comment and pick
-an emoji from the dropdown. If you want to remove an award emoji, just click
-the emoji again and the vote will be removed.
+To:
+
+- Add an award emoji, click the smile in the top right of the comment and pick an emoji from the dropdown.
+- Remove an award emoji, click the emoji again and the vote will be removed.
![Picking an emoji for a comment](img/award_emoji_comment_picker.png)
![An award emoji has been applied to a comment](img/award_emoji_comment_awarded.png)
-
-[2871]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2781
-[1825]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/1825
-[4291]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4291
-[ce-9570]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9570
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index 4d56b33f684..b6f8f55978b 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -164,9 +164,11 @@ and you can choose the group of people to be notified.
Here's a list of what you can't do with subgroups:
-- [GitLab Pages](../../project/pages/index.md) are not currently working for
- projects hosted under a subgroup. That means that only projects hosted under
- the first parent group will work.
+- [GitLab Pages](../../project/pages/index.md) supports projects hosted under
+ a subgroup, but not subgroup websites.
+ That means that only the highest-level group supports
+ [group websites](../../project/pages/introduction.html#user-or-group-pages),
+ although you can have project websites under a subgroup.
- It is not possible to share a project with a group that's an ancestor of
the group the project is in. That means you can only share as you walk down
the hierarchy. For example, `group/subgroup01/project` **cannot** be shared
diff --git a/doc/user/markdown.md b/doc/user/markdown.md
index 893658290e5..f2448f240ca 100644
--- a/doc/user/markdown.md
+++ b/doc/user/markdown.md
@@ -300,7 +300,7 @@ You can use it to point out a <img src="https://gitlab.com/gitlab-org/gitlab-ce/
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.
-Consult the [Emoji Cheat Sheet](https://www.emojicopy.com) 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/app/assets/images/emoji/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/project/index.md b/doc/user/project/index.md
index d46ae31580a..ce8bd2de61f 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -65,8 +65,8 @@ common actions on issues or merge requests
browse, and download job artifacts
- [Pipeline settings](pipelines/settings.md): Set up Git strategy (choose the default way your repository is fetched from GitLab in a job),
timeout (defines the maximum amount of time in minutes that a job is able run), custom path for `.gitlab-ci.yml`, test coverage parsing, pipeline's visibility, and much more
- - [GKE cluster integration](clusters/index.md): Connecting your GitLab project
- with Google Kubernetes Engine
+ - [Kubernetes cluster integration](clusters/index.md): Connecting your GitLab project
+ with a Kubernetes cluster
- [GitLab Pages](pages/index.md): Build, test, and deploy your static
website with GitLab Pages
diff --git a/doc/user/project/members/index.md b/doc/user/project/members/index.md
index 2c2e8e2d556..2a490e3fd7f 100644
--- a/doc/user/project/members/index.md
+++ b/doc/user/project/members/index.md
@@ -77,7 +77,7 @@ GitLab users to the project.
Once done, hit **Add users to project** and watch that there is a new member
with the e-mail address we used above. From there on, you can resend the
-invitation, change their access level or even delete them.
+invitation, change their access level, or even delete them.
![Invite user members list](img/add_user_email_accept.png)
diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md
index 290dfa5af84..595241b2cba 100644
--- a/doc/user/project/pages/getting_started_part_one.md
+++ b/doc/user/project/pages/getting_started_part_one.md
@@ -85,6 +85,12 @@ and a project within this group is called `blog`. Your project
URL is `https://gitlab.com/websites/blog/`. Once you enable
GitLab Pages for this project, the site will live under
`https://websites.gitlab.io/blog/`.
+- You created a group for your engineering department called `engineering`,
+a subgroup for all your documentation websites called `docs`,
+and a project within this subgroup is called `workflows`. Your project
+URL is `https://gitlab.com/engineering/docs/workflows/`. Once you enable
+GitLab Pages for this project, the site will live under
+`https://engineering.gitlab.io/docs/workflows`.
#### User and Group Websites
@@ -97,9 +103,7 @@ will be published under `https://john.gitlab.io`.
Once you enable GitLab Pages for your project,
your website will be published under `https://websites.gitlab.io`.
->**Note:**
-GitLab Pages [does **not** support subgroups](../../group/subgroups/index.md#limitations).
-You can only create the highest level group website.
+> Support for subgroup project's websites was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/30548) in GitLab 11.8.
**General example:**
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index ed049e2e648..a7846b1ee18 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -38,6 +38,7 @@ be served on.
| Group pages | `groupname.example.io` | `http(s)://groupname.example.io` |
| Project pages owned by a user | `projectname` | `http(s)://username.example.io/projectname` |
| Project pages owned by a group | `projectname` | `http(s)://groupname.example.io/projectname`|
+| Project pages owned by a subgroup | `subgroup/projectname` | `http(s)://groupname.example.io/subgroup/projectname`|
> **Warning:**
> There are some known [limitations](#limitations) regarding namespaces served
@@ -494,8 +495,8 @@ don't redirect HTTP to HTTPS.
[rfc]: https://tools.ietf.org/html/rfc2818#section-3.1 "HTTP Over TLS RFC"
-GitLab Pages [does **not** support subgroups](../../group/subgroups/index.md#limitations).
-You can only create the highest level group website.
+GitLab Pages [does **not** support group websites for subgroups](../../group/subgroups/index.md#limitations).
+You can only create the highest-level group website.
## Redirects in GitLab Pages
diff --git a/doc/user/project/pipelines/schedules.md b/doc/user/project/pipelines/schedules.md
index 051277dfe02..ec8b8444d99 100644
--- a/doc/user/project/pipelines/schedules.md
+++ b/doc/user/project/pipelines/schedules.md
@@ -3,7 +3,7 @@
> **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 [Rufus-Scheduler](https://github.com/jmettraux/rufus-scheduler).
+> - Cron notation is parsed by [Fugit](https://github.com/floraison/fugit).
Pipeline schedules can be used to run a pipeline at specific intervals, for example every
month on the 22nd for a certain branch.
diff --git a/doc/user/project/repository/branches/index.md b/doc/user/project/repository/branches/index.md
index 783081cec26..f05554ffc5b 100644
--- a/doc/user/project/repository/branches/index.md
+++ b/doc/user/project/repository/branches/index.md
@@ -2,16 +2,17 @@
Read through GiLab's branching documentation:
-- [Create a branch](../web_editor.md#create-a-new-branch)
-- [Default branch](#default-branch)
-- [Protected branches](../../protected_branches.md#protected-branches)
-- [Delete merged branches](#delete-merged-branches)
-- [Branch filter search box](#branch-filter-search-box)
+- [Create a branch](../web_editor.md#create-a-new-branch).
+- [Default branch](#default-branch).
+- [Protected branches](../../protected_branches.md#protected-branches).
+- [Delete merged branches](#delete-merged-branches).
+- [Branch filter search box](#branch-filter-search-box).
See also:
-- [GitLab Flow](../../../../university/training/gitlab_flow.md#gitlab-flow): use the best of GitLab for your branching strategies
-- [Getting started with Git](../../../../topics/git/index.md) and GitLab
+- [Branches API](../../../../api/branches.md), for information on operating on repository branches using the GitLab API.
+- [GitLab Flow](../../../../university/training/gitlab_flow.md#gitlab-flow). Use the best of GitLab for your branching strategies.
+- [Getting started with Git](../../../../topics/git/index.md) and GitLab.
## Default branch
@@ -41,7 +42,6 @@ this operation.
It's particularly useful to clean up old branches that were not deleted
automatically when a merge request was merged.
-
## Branch filter search box
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/22166) in GitLab 11.5.
diff --git a/doc/user/project/settings/import_export.md b/doc/user/project/settings/import_export.md
index 3bbfa74f4b7..89008fd15b9 100644
--- a/doc/user/project/settings/import_export.md
+++ b/doc/user/project/settings/import_export.md
@@ -61,7 +61,7 @@ The following items will be exported:
- Project and wiki repositories
- Project uploads
-- Project configuration including web hooks and services
+- Project configuration, including services
- Issues with comments, merge requests with diffs and comments, labels, milestones, snippets,
and other project entities
- LFS objects
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 59b67c67f9d..a768b78cda5 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -110,6 +110,7 @@ module API
mount ::API::GroupMilestones
mount ::API::Groups
mount ::API::GroupVariables
+ mount ::API::ImportGithub
mount ::API::Internal
mount ::API::Issues
mount ::API::JobArtifacts
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index a2a3c0a16d7..57a48d0c0fa 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -115,6 +115,9 @@ module API
expose :group_name do |group_link, options|
group_link.group.name
end
+ expose :group_full_path do |group_link, options|
+ group_link.group.full_path
+ end
expose :group_access, as: :group_access_level
expose :expires_at
end
@@ -277,7 +280,7 @@ module API
# N+1 is solved then by using `subject.tags.map(&:name)`
# MR describing the solution: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/20555
super(projects_relation).preload(:group)
- .preload(project_group_links: :group,
+ .preload(project_group_links: { group: :route },
fork_network: :root_project,
fork_network_member: :forked_from_project,
forked_from_project: [:route, :forks, :tags, namespace: :route])
diff --git a/lib/api/features.rb b/lib/api/features.rb
index 1331248699f..835aac05905 100644
--- a/lib/api/features.rb
+++ b/lib/api/features.rb
@@ -16,15 +16,13 @@ module API
end
end
- # rubocop: disable CodeReuse/ActiveRecord
def gate_targets(params)
- targets = []
- targets << Feature.group(params[:feature_group]) if params[:feature_group]
- targets << UserFinder.new(params[:user]).find_by_username if params[:user]
+ Feature::Target.new(params).targets
+ end
- targets
+ def gate_specified?(params)
+ Feature::Target.new(params).gate_specified?
end
- # rubocop: enable CodeReuse/ActiveRecord
end
resource :features do
@@ -44,6 +42,7 @@ module API
requires :value, type: String, desc: '`true` or `false` to enable/disable, an integer for percentage of time'
optional :feature_group, type: String, desc: 'A Feature group name'
optional :user, type: String, desc: 'A GitLab username'
+ optional :project, type: String, desc: 'A projects path, like gitlab-org/gitlab-ce'
end
post ':name' do
feature = Feature.get(params[:name])
@@ -52,13 +51,13 @@ module API
case value
when true
- if targets.present?
+ if gate_specified?(params)
targets.each { |target| feature.enable(target) }
else
feature.enable
end
when false
- if targets.present?
+ if gate_specified?(params)
targets.each { |target| feature.disable(target) }
else
feature.disable
diff --git a/lib/api/import_github.rb b/lib/api/import_github.rb
new file mode 100644
index 00000000000..bb4e536cf57
--- /dev/null
+++ b/lib/api/import_github.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+module API
+ class ImportGithub < Grape::API
+ rescue_from Octokit::Unauthorized, with: :provider_unauthorized
+
+ helpers do
+ def client
+ @client ||= Gitlab::LegacyGithubImport::Client.new(params[:personal_access_token], client_options)
+ end
+
+ def access_params
+ { github_access_token: params[:personal_access_token] }
+ end
+
+ def client_options
+ {}
+ end
+
+ def provider
+ :github
+ end
+ end
+
+ desc 'Import a GitHub project' do
+ detail 'This feature was introduced in GitLab 11.3.4.'
+ success Entities::ProjectEntity
+ end
+ params do
+ requires :personal_access_token, type: String, desc: 'GitHub personal access token'
+ requires :repo_id, type: Integer, desc: 'GitHub repository ID'
+ optional :new_name, type: String, desc: 'New repo name'
+ requires :target_namespace, type: String, desc: 'Namespace to import repo into'
+ end
+ post 'import/github' do
+ result = Import::GithubService.new(client, current_user, params).execute(access_params, provider)
+
+ if result[:status] == :success
+ present ProjectSerializer.new.represent(result[:project])
+ else
+ status result[:http_status]
+ { errors: result[:message] }
+ end
+ end
+ end
+end
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index f53ba0ab761..95371961398 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -35,7 +35,7 @@ module API
end
optional :container_registry_token_expire_delay, type: Integer, desc: 'Authorization token duration (minutes)'
optional :default_artifacts_expire_in, type: String, desc: "Set the default expiration time for each job's artifacts"
- optional :default_branch_protection, type: Integer, values: [0, 1, 2], desc: 'Determine if developers can push to master'
+ optional :default_branch_protection, type: Integer, values: Gitlab::Access.protection_values, desc: 'Determine if developers can push to master'
optional :default_group_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default group visibility'
optional :default_project_visibility, type: String, values: Gitlab::VisibilityLevel.string_values, desc: 'The default project visibility'
optional :default_projects_limit, type: Integer, desc: 'The maximum number of personal projects'
diff --git a/lib/api/wikis.rb b/lib/api/wikis.rb
index 302b2797a34..ef0e3decc2c 100644
--- a/lib/api/wikis.rb
+++ b/lib/api/wikis.rb
@@ -22,7 +22,9 @@ module API
end
end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ WIKI_ENDPOINT_REQUIREMENTS = API::NAMESPACE_OR_PROJECT_REQUIREMENTS.merge(slug: API::NO_SLASH_URL_PART_REGEX)
+
+ resource :projects, requirements: WIKI_ENDPOINT_REQUIREMENTS do
desc 'Get a list of wiki pages' do
success Entities::WikiPageBasic
end
@@ -103,7 +105,7 @@ module API
requires :file, type: ::API::Validations::Types::SafeFile, desc: 'The attachment file to be uploaded'
optional :branch, type: String, desc: 'The name of the branch'
end
- post ":id/wikis/attachments", requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ post ":id/wikis/attachments" do
authorize! :create_wiki, user_project
result = ::Wikis::CreateAttachmentService.new(user_project,
diff --git a/lib/bitbucket_server/client.rb b/lib/bitbucket_server/client.rb
index 83e8808db07..6a608058813 100644
--- a/lib/bitbucket_server/client.rb
+++ b/lib/bitbucket_server/client.rb
@@ -4,18 +4,6 @@ module BitbucketServer
class Client
attr_reader :connection
- ServerError = Class.new(StandardError)
-
- SERVER_ERRORS = [SocketError,
- OpenSSL::SSL::SSLError,
- Errno::ECONNRESET,
- Errno::ECONNREFUSED,
- Errno::EHOSTUNREACH,
- Net::OpenTimeout,
- Net::ReadTimeout,
- Gitlab::HTTP::BlockedUrlError,
- BitbucketServer::Connection::ConnectionError].freeze
-
def initialize(options = {})
@connection = Connection.new(options)
end
@@ -64,8 +52,6 @@ module BitbucketServer
def get_collection(path, type, page_offset: 0, limit: nil)
paginator = BitbucketServer::Paginator.new(connection, Addressable::URI.escape(path), type, page_offset: page_offset, limit: limit)
BitbucketServer::Collection.new(paginator)
- rescue *SERVER_ERRORS => e
- raise ServerError, e
end
end
end
diff --git a/lib/bitbucket_server/connection.rb b/lib/bitbucket_server/connection.rb
index 7efcdcf8619..9c14b26c65a 100644
--- a/lib/bitbucket_server/connection.rb
+++ b/lib/bitbucket_server/connection.rb
@@ -7,6 +7,17 @@ module BitbucketServer
DEFAULT_API_VERSION = '1.0'
SEPARATOR = '/'
+ NETWORK_ERRORS = [
+ SocketError,
+ OpenSSL::SSL::SSLError,
+ Errno::ECONNRESET,
+ Errno::ECONNREFUSED,
+ Errno::EHOSTUNREACH,
+ Net::OpenTimeout,
+ Net::ReadTimeout,
+ Gitlab::HTTP::BlockedUrlError
+ ].freeze
+
attr_reader :api_version, :base_uri, :username, :token
ConnectionError = Class.new(StandardError)
@@ -27,6 +38,8 @@ module BitbucketServer
check_errors!(response)
response.parsed_response
+ rescue *NETWORK_ERRORS => e
+ raise ConnectionError, e
end
def post(path, body)
@@ -38,6 +51,8 @@ module BitbucketServer
check_errors!(response)
response.parsed_response
+ rescue *NETWORK_ERRORS => e
+ raise ConnectionError, e
end
# We need to support two different APIs for deletion:
@@ -55,6 +70,8 @@ module BitbucketServer
check_errors!(response)
response.parsed_response
+ rescue *NETWORK_ERRORS => e
+ raise ConnectionError, e
end
private
diff --git a/lib/feature.rb b/lib/feature.rb
index e048a443abc..e59cd70f822 100644
--- a/lib/feature.rb
+++ b/lib/feature.rb
@@ -102,4 +102,42 @@ class Feature
expires_in: 1.hour)
end
end
+
+ class Target
+ attr_reader :params
+
+ def initialize(params)
+ @params = params
+ end
+
+ def gate_specified?
+ %i(user project feature_group).any? { |key| params.key?(key) }
+ end
+
+ def targets
+ [feature_group, user, project].compact
+ end
+
+ private
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def feature_group
+ return unless params.key?(:feature_group)
+
+ Feature.group(params[:feature_group])
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+
+ def user
+ return unless params.key?(:user)
+
+ UserFinder.new(params[:user]).find_by_username!
+ end
+
+ def project
+ return unless params.key?(:project)
+
+ Project.find_by_full_path(params[:project])
+ end
+ end
end
diff --git a/lib/gitlab.rb b/lib/gitlab.rb
index 2ef54658a11..b91394f7f58 100644
--- a/lib/gitlab.rb
+++ b/lib/gitlab.rb
@@ -27,12 +27,52 @@ module Gitlab
end
end
+ def self.version_info
+ Gitlab::VersionInfo.parse(Gitlab::VERSION)
+ end
+
COM_URL = 'https://gitlab.com'.freeze
APP_DIRS_PATTERN = %r{^/?(app|config|ee|lib|spec|\(\w*\))}
SUBDOMAIN_REGEX = %r{\Ahttps://[a-z0-9]+\.gitlab\.com\z}
VERSION = File.read(root.join("VERSION")).strip.freeze
INSTALLATION_TYPE = File.read(root.join("INSTALLATION_TYPE")).strip.freeze
+ def self.pre_release?
+ VERSION.include?('pre')
+ end
+
+ def self.final_release?
+ !VERSION.include?('rc') && !pre_release?
+ end
+
+ def self.minor_release
+ "#{version_info.major}.#{version_info.minor}"
+ end
+
+ def self.prev_minor_release
+ "#{version_info.major}.#{version_info.minor - 1}"
+ end
+
+ def self.prev_major_release
+ "#{version_info.major.to_i - 1}"
+ end
+
+ def self.new_major_release?
+ version_info.minor.to_i.zero?
+ end
+
+ def self.previous_release
+ if version_info.minor_version?
+ if version_info.patch_version?
+ minor_release
+ else
+ prev_minor_release
+ end
+ else
+ prev_major_release
+ end
+ end
+
def self.com?
# Check `gl_subdomain?` as well to keep parity with gitlab.com
Gitlab.config.gitlab.url == COM_URL || gl_subdomain?
@@ -50,11 +90,11 @@ module Gitlab
Rails.env.development? || org? || com?
end
- def self.pre_release?
- VERSION.include?('pre')
- end
+ def self.process_name
+ return 'sidekiq' if Sidekiq.server?
+ return 'console' if defined?(Rails::Console)
+ return 'test' if Rails.env.test?
- def self.version_info
- Gitlab::VersionInfo.parse(Gitlab::VERSION)
+ 'web'
end
end
diff --git a/lib/gitlab/access/branch_protection.rb b/lib/gitlab/access/branch_protection.rb
new file mode 100644
index 00000000000..f039e5c011f
--- /dev/null
+++ b/lib/gitlab/access/branch_protection.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Access
+ # A wrapper around Integer based branch protection levels.
+ #
+ # This wrapper can be used to work with branch protection levels without
+ # having to directly refer to the constants. For example, instead of this:
+ #
+ # if access_level == Gitlab::Access::PROTECTION_DEV_CAN_PUSH
+ # ...
+ # end
+ #
+ # You can write this instead:
+ #
+ # protection = BranchProtection.new(access_level)
+ #
+ # if protection.developer_can_push?
+ # ...
+ # end
+ class BranchProtection
+ attr_reader :level
+
+ # @param [Integer] level The branch protection level as an Integer.
+ def initialize(level)
+ @level = level
+ end
+
+ def any?
+ level != PROTECTION_NONE
+ end
+
+ def developer_can_push?
+ level == PROTECTION_DEV_CAN_PUSH
+ end
+
+ def developer_can_merge?
+ level == PROTECTION_DEV_CAN_MERGE
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/auth/ldap/person.rb b/lib/gitlab/auth/ldap/person.rb
index a0244a3cea1..48d134f91b0 100644
--- a/lib/gitlab/auth/ldap/person.rb
+++ b/lib/gitlab/auth/ldap/person.rb
@@ -98,9 +98,7 @@ module Gitlab
private
- def entry
- @entry
- end
+ attr_reader :entry
def config
@config ||= Gitlab::Auth::LDAP::Config.new(provider)
diff --git a/lib/gitlab/background_migration/backfill_project_repositories.rb b/lib/gitlab/background_migration/backfill_project_repositories.rb
index aaf520d70f6..c8d83cc1803 100644
--- a/lib/gitlab/background_migration/backfill_project_repositories.rb
+++ b/lib/gitlab/background_migration/backfill_project_repositories.rb
@@ -83,7 +83,7 @@ module Gitlab
extend ActiveSupport::Concern
def full_path
- @full_path ||= build_full_path
+ route&.path || build_full_path
end
def build_full_path
@@ -99,7 +99,12 @@ module Gitlab
end
end
- # Namespace model.
+ # Route model
+ class Route < ActiveRecord::Base
+ belongs_to :source, inverse_of: :route, polymorphic: true
+ end
+
+ # Namespace model
class Namespace < ActiveRecord::Base
self.table_name = 'namespaces'
self.inheritance_column = nil
@@ -108,6 +113,8 @@ module Gitlab
belongs_to :parent, class_name: 'Namespace', inverse_of: 'namespaces'
+ has_one :route, -> { where(source_type: 'Namespace') }, inverse_of: :source, foreign_key: :source_id
+
has_many :projects, inverse_of: :parent
has_many :namespaces, inverse_of: :parent
end
@@ -134,6 +141,7 @@ module Gitlab
belongs_to :parent, class_name: 'Namespace', foreign_key: :namespace_id, inverse_of: 'projects'
+ has_one :route, -> { where(source_type: 'Project') }, inverse_of: :source, foreign_key: :source_id
has_one :project_repository, inverse_of: :project
delegate :disk_path, to: :storage
@@ -194,6 +202,8 @@ module Gitlab
def project_repositories(start_id, stop_id)
projects
.without_project_repository
+ .includes(:route, parent: [:route]).references(:routes)
+ .includes(:parent).references(:namespaces)
.where(id: start_id..stop_id)
.map { |project| build_attributes_for_project(project) }
.compact
diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb
index 3239743bfff..1d8904f7b29 100644
--- a/lib/gitlab/ci/config/entry/job.rb
+++ b/lib/gitlab/ci/config/entry/job.rb
@@ -16,13 +16,6 @@ module Gitlab
dependencies before_script after_script variables
environment coverage retry parallel extends].freeze
- DEFAULT_ONLY_POLICY = {
- refs: %w(branches tags)
- }.freeze
-
- DEFAULT_EXCEPT_POLICY = {
- }.freeze
-
validations do
validates :config, allowed_keys: ALLOWED_KEYS
validates :config, presence: true
@@ -73,7 +66,8 @@ module Gitlab
description: 'Services that will be used to execute this job.'
entry :only, Entry::Policy,
- description: 'Refs policy this job will be executed for.'
+ description: 'Refs policy this job will be executed for.',
+ default: { refs: %w[branches tags] }
entry :except, Entry::Policy,
description: 'Refs policy this job will be executed for.'
@@ -156,8 +150,8 @@ module Gitlab
services: services_value,
stage: stage_value,
cache: cache_value,
- only: DEFAULT_ONLY_POLICY.deep_merge(only_value.to_h),
- except: DEFAULT_EXCEPT_POLICY.deep_merge(except_value.to_h),
+ only: only_value,
+ except: except_value,
variables: variables_defined? ? variables_value : nil,
environment: environment_defined? ? environment_value : nil,
environment_name: environment_defined? ? environment_value[:name] : nil,
diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb
index 998da1f6837..9c677bf6617 100644
--- a/lib/gitlab/ci/config/entry/policy.rb
+++ b/lib/gitlab/ci/config/entry/policy.rb
@@ -64,7 +64,8 @@ module Gitlab
end
end
- def self.default
+ def value
+ default.to_h.deep_merge(subject.value.to_h)
end
end
end
diff --git a/lib/gitlab/ci/config/entry/retry.rb b/lib/gitlab/ci/config/entry/retry.rb
index eaf8b38aa3c..e9cbcb31e21 100644
--- a/lib/gitlab/ci/config/entry/retry.rb
+++ b/lib/gitlab/ci/config/entry/retry.rb
@@ -82,9 +82,6 @@ module Gitlab
'retry config'
end
end
-
- def self.default
- end
end
end
end
diff --git a/lib/gitlab/ci/config/entry/variables.rb b/lib/gitlab/ci/config/entry/variables.rb
index 89d790ebfa6..c9d0c7cb568 100644
--- a/lib/gitlab/ci/config/entry/variables.rb
+++ b/lib/gitlab/ci/config/entry/variables.rb
@@ -14,7 +14,7 @@ module Gitlab
validates :config, variables: true
end
- def self.default
+ def self.default(**)
{}
end
diff --git a/lib/gitlab/ci/cron_parser.rb b/lib/gitlab/ci/cron_parser.rb
index b1db9084662..94f4a4e36c9 100644
--- a/lib/gitlab/ci/cron_parser.rb
+++ b/lib/gitlab/ci/cron_parser.rb
@@ -35,7 +35,7 @@ module Gitlab
# NOTE:
# cron_timezone can only accept timezones listed in TZInfo::Timezone.
# Aliases of Timezones from ActiveSupport::TimeZone are NOT accepted,
- # because Rufus::Scheduler only supports TZInfo::Timezone.
+ # because Fugit::Cron only supports TZInfo::Timezone.
#
# For example, those codes have the same effect.
# Time.zone = 'Pacific Time (US & Canada)' (ActiveSupport::TimeZone)
@@ -47,10 +47,7 @@ module Gitlab
# If you want to know more, please take a look
# https://github.com/rails/rails/blob/master/activesupport/lib/active_support/values/time_zone.rb
def try_parse_cron(cron, cron_timezone)
- cron_line = Rufus::Scheduler.parse("#{cron} #{cron_timezone}")
- cron_line if cron_line.is_a?(Rufus::Scheduler::CronLine)
- rescue
- # noop
+ Fugit::Cron.parse("#{cron} #{cron_timezone}")
end
end
end
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index e292641da2b..95160e1432f 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -49,7 +49,7 @@ variables:
POSTGRES_ENABLED: "true"
POSTGRES_DB: $CI_ENVIRONMENT_SLUG
- KUBERNETES_VERSION: 1.10.9
+ KUBERNETES_VERSION: 1.11.6
HELM_VERSION: 2.11.0
DOCKER_DRIVER: overlay2
@@ -667,6 +667,14 @@ rollout 100%:
create_application_secret "$track"
+ env_slug=$(echo ${CI_ENVIRONMENT_SLUG//-/_} | tr -s '[:lower:]' '[:upper:]')
+ eval env_ADDITIONAL_HOSTS=\$${env_slug}_ADDITIONAL_HOSTS
+ if [ -n "$env_ADDITIONAL_HOSTS" ]; then
+ additional_hosts="{$env_ADDITIONAL_HOSTS}"
+ elif [ -n "$ADDITIONAL_HOSTS" ]; then
+ additional_hosts="{$ADDITIONAL_HOSTS}"
+ fi
+
if [[ -n "$DB_INITIALIZE" && -z "$(helm ls -q "^$name$")" ]]; then
echo "Deploying first release with database initialization..."
helm upgrade --install \
@@ -681,6 +689,7 @@ rollout 100%:
--set application.database_url="$DATABASE_URL" \
--set application.secretName="$APPLICATION_SECRET_NAME" \
--set service.url="$CI_ENVIRONMENT_URL" \
+ --set service.additionalHosts="$additional_hosts" \
--set replicaCount="$replicas" \
--set postgresql.enabled="$postgres_enabled" \
--set postgresql.nameOverride="postgres" \
@@ -714,6 +723,7 @@ rollout 100%:
--set application.database_url="$DATABASE_URL" \
--set application.secretName="$APPLICATION_SECRET_NAME" \
--set service.url="$CI_ENVIRONMENT_URL" \
+ --set service.additionalHosts="$additional_hosts" \
--set replicaCount="$replicas" \
--set postgresql.enabled="$postgres_enabled" \
--set postgresql.nameOverride="postgres" \
diff --git a/lib/gitlab/config/entry/configurable.rb b/lib/gitlab/config/entry/configurable.rb
index afdb60b2cd5..37ba16dba25 100644
--- a/lib/gitlab/config/entry/configurable.rb
+++ b/lib/gitlab/config/entry/configurable.rb
@@ -56,6 +56,7 @@ module Gitlab
def entry(key, entry, metadata)
factory = ::Gitlab::Config::Entry::Factory.new(entry)
.with(description: metadata[:description])
+ .with(default: metadata[:default])
(@nodes ||= {}).merge!(key.to_sym => factory)
end
diff --git a/lib/gitlab/config/entry/factory.rb b/lib/gitlab/config/entry/factory.rb
index 30d43c9f9a1..79f9ff32514 100644
--- a/lib/gitlab/config/entry/factory.rb
+++ b/lib/gitlab/config/entry/factory.rb
@@ -12,7 +12,7 @@ module Gitlab
def initialize(entry)
@entry = entry
@metadata = {}
- @attributes = {}
+ @attributes = { default: entry.default }
end
def value(value)
@@ -21,12 +21,12 @@ module Gitlab
end
def metadata(metadata)
- @metadata.merge!(metadata)
+ @metadata.merge!(metadata.compact)
self
end
def with(attributes)
- @attributes.merge!(attributes)
+ @attributes.merge!(attributes.compact)
self
end
@@ -38,9 +38,7 @@ module Gitlab
# See issue #18775.
#
if @value.nil?
- Entry::Unspecified.new(
- fabricate_unspecified
- )
+ Entry::Unspecified.new(fabricate_unspecified)
else
fabricate(@entry, @value)
end
@@ -53,10 +51,12 @@ module Gitlab
# If entry has a default value we fabricate concrete node
# with default value.
#
- if @entry.default.nil?
+ default = @attributes.fetch(:default)
+
+ if default.nil?
fabricate(Entry::Undefined)
else
- fabricate(@entry, @entry.default)
+ fabricate(@entry, default)
end
end
@@ -64,6 +64,7 @@ module Gitlab
entry.new(value, @metadata).tap do |node|
node.key = @attributes[:key]
node.parent = @attributes[:parent]
+ node.default = @attributes[:default]
node.description = @attributes[:description]
end
end
diff --git a/lib/gitlab/config/entry/node.rb b/lib/gitlab/config/entry/node.rb
index 30357b2c95b..9999ab4ff95 100644
--- a/lib/gitlab/config/entry/node.rb
+++ b/lib/gitlab/config/entry/node.rb
@@ -10,7 +10,7 @@ module Gitlab
InvalidError = Class.new(StandardError)
attr_reader :config, :metadata
- attr_accessor :key, :parent, :description
+ attr_accessor :key, :parent, :default, :description
def initialize(config, **metadata)
@config = config
@@ -85,7 +85,7 @@ module Gitlab
"#<#{self.class.name} #{unspecified}{#{key}: #{val.inspect}}>"
end
- def self.default
+ def self.default(**)
end
def self.aspects
diff --git a/lib/gitlab/config/entry/simplifiable.rb b/lib/gitlab/config/entry/simplifiable.rb
index 3e148fe2e91..5fbf7565e2a 100644
--- a/lib/gitlab/config/entry/simplifiable.rb
+++ b/lib/gitlab/config/entry/simplifiable.rb
@@ -6,6 +6,8 @@ module Gitlab
class Simplifiable < SimpleDelegator
EntryStrategy = Struct.new(:name, :condition)
+ attr_reader :subject
+
def initialize(config, **metadata)
unless self.class.const_defined?(:UnknownStrategy)
raise ArgumentError, 'UndefinedStrategy not available!'
@@ -17,7 +19,7 @@ module Gitlab
entry = self.class.entry_class(strategy)
- super(entry.new(config, metadata))
+ super(@subject = entry.new(config, metadata))
end
def self.strategy(name, **opts)
@@ -37,6 +39,9 @@ module Gitlab
self::UnknownStrategy
end
end
+
+ def self.default
+ end
end
end
end
diff --git a/lib/gitlab/git/bundle_file.rb b/lib/gitlab/git/bundle_file.rb
new file mode 100644
index 00000000000..8384a436fcc
--- /dev/null
+++ b/lib/gitlab/git/bundle_file.rb
@@ -0,0 +1,30 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Git
+ class BundleFile
+ # All git bundle files start with this string
+ #
+ # https://github.com/git/git/blob/v2.20.1/bundle.c#L15
+ MAGIC = "# v2 git bundle\n"
+
+ InvalidBundleError = Class.new(StandardError)
+
+ attr_reader :filename
+
+ def self.check!(filename)
+ new(filename).check!
+ end
+
+ def initialize(filename)
+ @filename = filename
+ end
+
+ def check!
+ data = File.open(filename, 'r') { |f| f.read(MAGIC.size) }
+
+ raise InvalidBundleError, 'Invalid bundle file' unless data == MAGIC
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 5bbedc9d5e3..786c90f9272 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -789,6 +789,11 @@ module Gitlab
end
def create_from_bundle(bundle_path)
+ # It's important to check that the linked-to file is actually a valid
+ # .bundle file as it is passed to `git clone`, which may otherwise
+ # interpret it as a pointer to another repository
+ ::Gitlab::Git::BundleFile.check!(bundle_path)
+
gitaly_repository_client.create_from_bundle(bundle_path)
end
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 15137140639..9b1794eec91 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -8,10 +8,7 @@ module Gitlab
def add_gon_variables
gon.api_version = 'v4'
- gon.default_avatar_url =
- Gitlab::Utils.append_path(
- Gitlab.config.gitlab.url,
- ActionController::Base.helpers.image_path('no_avatar.png'))
+ gon.default_avatar_url = default_avatar_url
gon.max_file_size = Gitlab::CurrentSettings.max_attachment_size
gon.asset_host = ActionController::Base.asset_host
gon.webpack_public_path = webpack_public_path
@@ -50,5 +47,15 @@ module Gitlab
# use this method to push multiple feature flags.
gon.push({ features: { var_name => enabled } }, true)
end
+
+ def default_avatar_url
+ # We can't use ActionController::Base.helpers.image_url because it
+ # doesn't return an actual URL because request is nil for some reason.
+ #
+ # We also can't use Gitlab::Utils.append_path because the image path
+ # may be an absolute URL.
+ URI.join(Gitlab.config.gitlab.url,
+ ActionController::Base.helpers.image_path('no_avatar.png')).to_s
+ end
end
end
diff --git a/lib/gitlab/middleware/read_only/controller.rb b/lib/gitlab/middleware/read_only/controller.rb
index f142f9da43d..817db12ac55 100644
--- a/lib/gitlab/middleware/read_only/controller.rb
+++ b/lib/gitlab/middleware/read_only/controller.rb
@@ -71,12 +71,16 @@ module Gitlab
@route_hash ||= Rails.application.routes.recognize_path(request.url, { method: request.request_method }) rescue {}
end
+ def relative_url
+ File.join('', Gitlab.config.gitlab.relative_url_root).chomp('/')
+ end
+
# Overridden in EE module
def whitelisted_routes
- grack_route || ReadOnly.internal_routes.any? { |path| request.path.include?(path) } || lfs_route || sidekiq_route
+ grack_route? || internal_route? || lfs_route? || sidekiq_route?
end
- def grack_route
+ def grack_route?
# Calling route_hash may be expensive. Only do it if we think there's a possible match
return false unless
request.path.end_with?('.git/git-upload-pack', '.git/git-receive-pack')
@@ -84,7 +88,11 @@ module Gitlab
WHITELISTED_GIT_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
end
- def lfs_route
+ def internal_route?
+ ReadOnly.internal_routes.any? { |path| request.path.include?(path) }
+ end
+
+ def lfs_route?
# Calling route_hash may be expensive. Only do it if we think there's a possible match
unless request.path.end_with?('/info/lfs/objects/batch',
'/info/lfs/locks', '/info/lfs/locks/verify') ||
@@ -95,8 +103,8 @@ module Gitlab
WHITELISTED_GIT_LFS_ROUTES[route_hash[:controller]]&.include?(route_hash[:action])
end
- def sidekiq_route
- request.path.start_with?('/admin/sidekiq')
+ def sidekiq_route?
+ request.path.start_with?("#{relative_url}/admin/sidekiq")
end
end
end
diff --git a/lib/gitlab/release_blog_post.rb b/lib/gitlab/release_blog_post.rb
new file mode 100644
index 00000000000..639aee61464
--- /dev/null
+++ b/lib/gitlab/release_blog_post.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+require 'singleton'
+
+module Gitlab
+ class ReleaseBlogPost
+ include Singleton
+
+ RELEASE_RSS_URL = 'https://about.gitlab.com/releases.xml'
+
+ def blog_post_url
+ @url ||= fetch_blog_post_url
+ end
+
+ private
+
+ def fetch_blog_post_url
+ installed_version = Gitlab.final_release? ? Gitlab.minor_release : Gitlab.previous_release
+ response = Gitlab::HTTP.get(RELEASE_RSS_URL, verify: false)
+
+ return unless response.code == 200
+
+ blog_entry = find_installed_blog_entry(response, installed_version)
+ blog_entry['id'] if blog_entry
+ end
+
+ def find_installed_blog_entry(response, installed_version)
+ response['feed']['entry'].find do |entry|
+ entry['release'] == installed_version || matches_previous_release_post(entry['release'], installed_version)
+ end
+ end
+
+ def should_match_previous_release_post?
+ Gitlab.new_major_release? && !Gitlab.final_release?
+ end
+
+ def matches_previous_release_post(rss_release_version, installed_version)
+ should_match_previous_release_post? && rss_release_version[/\d+/] == installed_version
+ end
+ end
+end
diff --git a/lib/gitlab/sentry.rb b/lib/gitlab/sentry.rb
index 46d01964eac..956c16117f5 100644
--- a/lib/gitlab/sentry.rb
+++ b/lib/gitlab/sentry.rb
@@ -52,14 +52,6 @@ module Gitlab
end
end
- def self.program_context
- if Sidekiq.server?
- 'sidekiq'
- else
- 'rails'
- end
- end
-
def self.should_raise_for_dev?
Rails.env.development? || Rails.env.test?
end
diff --git a/lib/gitlab/tracing.rb b/lib/gitlab/tracing.rb
new file mode 100644
index 00000000000..3c4db42ac06
--- /dev/null
+++ b/lib/gitlab/tracing.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Tracing
+ # Only enable tracing when the `GITLAB_TRACING` env var is configured. Note that we avoid using ApplicationSettings since
+ # the same environment variable needs to be configured for Workhorse, Gitaly and any other components which
+ # emit tracing. Since other components may start before Rails, and may not have access to ApplicationSettings,
+ # an env var makes more sense.
+ def self.enabled?
+ connection_string.present?
+ end
+
+ def self.connection_string
+ ENV['GITLAB_TRACING']
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/factory.rb b/lib/gitlab/tracing/factory.rb
new file mode 100644
index 00000000000..fc714164353
--- /dev/null
+++ b/lib/gitlab/tracing/factory.rb
@@ -0,0 +1,61 @@
+# frozen_string_literal: true
+
+require "cgi"
+
+module Gitlab
+ module Tracing
+ class Factory
+ OPENTRACING_SCHEME = "opentracing"
+
+ def self.create_tracer(service_name, connection_string)
+ return unless connection_string.present?
+
+ begin
+ opentracing_details = parse_connection_string(connection_string)
+ driver_name = opentracing_details[:driver_name]
+
+ case driver_name
+ when "jaeger"
+ JaegerFactory.create_tracer(service_name, opentracing_details[:options])
+ else
+ raise "Unknown driver: #{driver_name}"
+ end
+ rescue => e
+ # Can't create the tracer? Warn and continue sans tracer
+ warn "Unable to instantiate tracer: #{e}"
+ nil
+ end
+ end
+
+ def self.parse_connection_string(connection_string)
+ parsed = URI.parse(connection_string)
+
+ unless valid_uri?(parsed)
+ raise "Invalid tracing connection string"
+ end
+
+ {
+ driver_name: parsed.host,
+ options: parse_query(parsed.query)
+ }
+ end
+ private_class_method :parse_connection_string
+
+ def self.parse_query(query)
+ return {} unless query
+
+ CGI.parse(query).symbolize_keys.transform_values(&:first)
+ end
+ private_class_method :parse_query
+
+ def self.valid_uri?(uri)
+ return false unless uri
+
+ uri.scheme == OPENTRACING_SCHEME &&
+ uri.host.to_s =~ /^[a-z0-9_]+$/ &&
+ uri.path.empty?
+ end
+ private_class_method :valid_uri?
+ end
+ end
+end
diff --git a/lib/gitlab/tracing/jaeger_factory.rb b/lib/gitlab/tracing/jaeger_factory.rb
new file mode 100644
index 00000000000..0726f6b67f4
--- /dev/null
+++ b/lib/gitlab/tracing/jaeger_factory.rb
@@ -0,0 +1,97 @@
+# frozen_string_literal: true
+
+require 'jaeger/client'
+
+module Gitlab
+ module Tracing
+ class JaegerFactory
+ # When the probabilistic sampler is used, by default 0.1% of requests will be traced
+ DEFAULT_PROBABILISTIC_RATE = 0.001
+
+ # The default port for the Jaeger agent UDP listener
+ DEFAULT_UDP_PORT = 6831
+
+ # Reduce this from default of 10 seconds as the Ruby jaeger
+ # client doesn't have overflow control, leading to very large
+ # messages which fail to send over UDP (max packet = 64k)
+ # Flush more often, with smaller packets
+ FLUSH_INTERVAL = 5
+
+ def self.create_tracer(service_name, options)
+ kwargs = {
+ service_name: service_name,
+ sampler: get_sampler(options[:sampler], options[:sampler_param]),
+ reporter: get_reporter(service_name, options[:http_endpoint], options[:udp_endpoint])
+ }
+
+ extra_params = options.except(:sampler, :sampler_param, :http_endpoint, :udp_endpoint, :strict_parsing, :debug) # rubocop: disable CodeReuse/ActiveRecord
+ if extra_params.present?
+ message = "jaeger tracer: invalid option: #{extra_params.keys.join(", ")}"
+
+ if options[:strict_parsing]
+ raise message
+ else
+ warn message
+ end
+ end
+
+ Jaeger::Client.build(kwargs)
+ end
+
+ def self.get_sampler(sampler_type, sampler_param)
+ case sampler_type
+ when "probabilistic"
+ sampler_rate = sampler_param ? sampler_param.to_f : DEFAULT_PROBABILISTIC_RATE
+ Jaeger::Samplers::Probabilistic.new(rate: sampler_rate)
+ when "const"
+ const_value = sampler_param == "1"
+ Jaeger::Samplers::Const.new(const_value)
+ else
+ nil
+ end
+ end
+ private_class_method :get_sampler
+
+ def self.get_reporter(service_name, http_endpoint, udp_endpoint)
+ encoder = Jaeger::Encoders::ThriftEncoder.new(service_name: service_name)
+
+ if http_endpoint.present?
+ sender = get_http_sender(encoder, http_endpoint)
+ elsif udp_endpoint.present?
+ sender = get_udp_sender(encoder, udp_endpoint)
+ else
+ return nil
+ end
+
+ Jaeger::Reporters::RemoteReporter.new(
+ sender: sender,
+ flush_interval: FLUSH_INTERVAL
+ )
+ end
+ private_class_method :get_reporter
+
+ def self.get_http_sender(encoder, address)
+ Jaeger::HttpSender.new(
+ url: address,
+ encoder: encoder,
+ logger: Logger.new(STDOUT)
+ )
+ end
+ private_class_method :get_http_sender
+
+ def self.get_udp_sender(encoder, address)
+ pair = address.split(":", 2)
+ host = pair[0]
+ port = pair[1] ? pair[1].to_i : DEFAULT_UDP_PORT
+
+ Jaeger::UdpSender.new(
+ host: host,
+ port: port,
+ encoder: encoder,
+ logger: Logger.new(STDOUT)
+ )
+ end
+ private_class_method :get_udp_sender
+ end
+ end
+end
diff --git a/lib/gitlab/version_info.rb b/lib/gitlab/version_info.rb
index aa6d5310161..142ead12c08 100644
--- a/lib/gitlab/version_info.rb
+++ b/lib/gitlab/version_info.rb
@@ -20,6 +20,14 @@ module Gitlab
@patch = patch
end
+ def minor_version?
+ minor.to_i > 0
+ end
+
+ def patch_version?
+ patch.to_i > 0
+ end
+
def <=>(other)
return unless other.is_a? VersionInfo
return unless valid? && other.valid?
diff --git a/lib/system_check/base_check.rb b/lib/system_check/base_check.rb
index e06245294c4..46aad8aa885 100644
--- a/lib/system_check/base_check.rb
+++ b/lib/system_check/base_check.rb
@@ -70,18 +70,14 @@ module SystemCheck
# multiple reasons why a check can fail
#
# @param [String] reason to be displayed
- def skip_reason=(reason)
- @skip_reason = reason
- end
+ attr_writer :skip_reason
# Skip reason defined during runtime
#
# This value have precedence over the one defined in the subclass
#
# @return [String] the reason
- def skip_reason
- @skip_reason
- end
+ attr_reader :skip_reason
# Does the check support automatically repair routine?
#
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index d3f751c7796..795a46382a7 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -129,6 +129,9 @@ msgstr ""
msgid "%{issuableType} will be removed! Are you sure?"
msgstr ""
+msgid "%{link_start}Read more%{link_end} about role permissions"
+msgstr ""
+
msgid "%{loadingIcon} Started"
msgstr ""
@@ -399,6 +402,9 @@ msgstr ""
msgid "Add reaction"
msgstr ""
+msgid "Add to project"
+msgstr ""
+
msgid "Add todo"
msgstr ""
@@ -1305,6 +1311,9 @@ msgstr ""
msgid "Choose a file"
msgstr ""
+msgid "Choose a role permission"
+msgstr ""
+
msgid "Choose a template..."
msgstr ""
@@ -1464,6 +1473,9 @@ msgstr ""
msgid "Closed"
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 ""
+
msgid "ClusterIntegration|%{appList} was successfully installed on your Kubernetes cluster"
msgstr ""
@@ -1890,6 +1902,9 @@ msgstr ""
msgid "ClusterIntegration|sign up"
msgstr ""
+msgid "Code"
+msgstr ""
+
msgid "Cohorts"
msgstr ""
@@ -1928,9 +1943,15 @@ msgid_plural "Commits"
msgstr[0] ""
msgstr[1] ""
+msgid "Commit %{commit_id}"
+msgstr ""
+
msgid "Commit Message"
msgstr ""
+msgid "Commit deleted"
+msgstr ""
+
msgid "Commit duration in minutes for last 30 commits"
msgstr ""
@@ -2749,6 +2770,9 @@ msgstr ""
msgid "Enable and configure Prometheus metrics."
msgstr ""
+msgid "Enable error tracking"
+msgstr ""
+
msgid "Enable for this project"
msgstr ""
@@ -2767,6 +2791,9 @@ msgstr ""
msgid "Enable the Performance Bar for a given group."
msgstr ""
+msgid "Enable two-factor authentication"
+msgstr ""
+
msgid "Enable usage ping"
msgstr ""
@@ -2944,6 +2971,9 @@ msgstr ""
msgid "Error occurred when toggling the notification subscription"
msgstr ""
+msgid "Error rendering markdown preview"
+msgstr ""
+
msgid "Error saving label update."
msgstr ""
@@ -2980,6 +3010,9 @@ msgstr ""
msgid "EventFilterBy|Filter by team"
msgstr ""
+msgid "Events"
+msgstr ""
+
msgid "Every %{action} attempt has failed: %{job_error_message}. Please try again."
msgstr ""
@@ -3004,6 +3037,9 @@ msgstr ""
msgid "Existing folder"
msgstr ""
+msgid "Existing members and groups"
+msgstr ""
+
msgid "Expand"
msgstr ""
@@ -3067,6 +3103,9 @@ msgstr ""
msgid "Failed to load emoji list."
msgstr ""
+msgid "Failed to load errors from Sentry"
+msgstr ""
+
msgid "Failed to remove issue from board, please try again."
msgstr ""
@@ -3127,6 +3166,12 @@ msgstr ""
msgid "Filter by two-factor authentication"
msgstr ""
+msgid "Filter results by group"
+msgstr ""
+
+msgid "Filter results by project"
+msgstr ""
+
msgid "Filter..."
msgstr ""
@@ -3136,6 +3181,9 @@ msgstr ""
msgid "Find by path"
msgstr ""
+msgid "Find existing members by name"
+msgstr ""
+
msgid "Find file"
msgstr ""
@@ -3250,6 +3298,9 @@ msgstr ""
msgid "Geo"
msgstr ""
+msgid "Get started with error tracking"
+msgstr ""
+
msgid "Getting started with releases"
msgstr ""
@@ -3385,6 +3436,9 @@ msgstr ""
msgid "Group name"
msgstr ""
+msgid "Group:"
+msgstr ""
+
msgid "Group: %{group_name}"
msgstr ""
@@ -3424,6 +3478,9 @@ msgstr ""
msgid "Groups can also be nested by creating %{subgroup_docs_link_start}subgroups%{subgroup_docs_link_end}."
msgstr ""
+msgid "Groups with access to <strong>%{project_name}</strong>"
+msgstr ""
+
msgid "GroupsDropdown|Frequently visited"
msgstr ""
@@ -3651,12 +3708,21 @@ msgstr ""
msgid "Import issues"
msgstr ""
+msgid "Import members"
+msgstr ""
+
+msgid "Import members from another project"
+msgstr ""
+
msgid "Import multiple repositories by uploading a manifest file."
msgstr ""
msgid "Import project"
msgstr ""
+msgid "Import project members"
+msgstr ""
+
msgid "Import projects from Bitbucket"
msgstr ""
@@ -3762,6 +3828,12 @@ msgstr ""
msgid "Invite"
msgstr ""
+msgid "Invite group"
+msgstr ""
+
+msgid "Invite member"
+msgstr ""
+
msgid "Invoke Count"
msgstr ""
@@ -3956,6 +4028,9 @@ msgstr ""
msgid "Last reply by"
msgstr ""
+msgid "Last seen"
+msgstr ""
+
msgid "Last update"
msgstr ""
@@ -4099,6 +4174,9 @@ msgstr ""
msgid "Manage project labels"
msgstr ""
+msgid "Manage two-factor authentication"
+msgstr ""
+
msgid "Manifest"
msgstr ""
@@ -4180,6 +4258,12 @@ msgstr ""
msgid "Members"
msgstr ""
+msgid "Members can be added by project <i>Maintainers</i> or <i>Owners</i>"
+msgstr ""
+
+msgid "Members of <strong>%{project_name}</strong>"
+msgstr ""
+
msgid "Merge Request"
msgstr ""
@@ -4345,6 +4429,9 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Monitor your errors by integrating with Sentry"
+msgstr ""
+
msgid "Monitoring"
msgstr ""
@@ -4509,9 +4596,15 @@ msgstr ""
msgid "No contributions were found"
msgstr ""
+msgid "No details available"
+msgstr ""
+
msgid "No due date"
msgstr ""
+msgid "No errors to display"
+msgstr ""
+
msgid "No estimate or time spent"
msgstr ""
@@ -4718,6 +4811,9 @@ msgstr ""
msgid "Only project members can comment."
msgstr ""
+msgid "Only project members will be imported. Group members will be skipped."
+msgstr ""
+
msgid "Oops, are you sure?"
msgstr ""
@@ -4730,6 +4826,9 @@ msgstr ""
msgid "Open comment type dropdown"
msgstr ""
+msgid "Open errors"
+msgstr ""
+
msgid "Open in Xcode"
msgstr ""
@@ -4838,6 +4937,9 @@ msgstr ""
msgid "Personal Access Token"
msgstr ""
+msgid "Personal project creation is not allowed. Please contact your administrator with questions"
+msgstr ""
+
msgid "Pick a name"
msgstr ""
@@ -5117,6 +5219,12 @@ msgstr ""
msgid "Profiles|Account scheduled for removal."
msgstr ""
+msgid "Profiles|Activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Active"
+msgstr ""
+
msgid "Profiles|Add key"
msgstr ""
@@ -5132,6 +5240,9 @@ msgstr ""
msgid "Profiles|Change username"
msgstr ""
+msgid "Profiles|Changing your username can have unintended side effects."
+msgstr ""
+
msgid "Profiles|Choose file..."
msgstr ""
@@ -5144,6 +5255,15 @@ msgstr ""
msgid "Profiles|Clear status"
msgstr ""
+msgid "Profiles|Click on icon to activate signin with one of the following services"
+msgstr ""
+
+msgid "Profiles|Connect"
+msgstr ""
+
+msgid "Profiles|Connected Accounts"
+msgstr ""
+
msgid "Profiles|Current path: %{path}"
msgstr ""
@@ -5162,6 +5282,9 @@ msgstr ""
msgid "Profiles|Deleting an account has the following effects:"
msgstr ""
+msgid "Profiles|Disconnect"
+msgstr ""
+
msgid "Profiles|Do not show on profile"
msgstr ""
@@ -5174,6 +5297,9 @@ msgstr ""
msgid "Profiles|Enter your name, so people you know can recognize you"
msgstr ""
+msgid "Profiles|Increase your account's security by enabling Two-Factor Authentication (2FA)"
+msgstr ""
+
msgid "Profiles|Invalid password"
msgstr ""
@@ -5210,6 +5336,9 @@ msgstr ""
msgid "Profiles|Set new profile picture"
msgstr ""
+msgid "Profiles|Social sign-in"
+msgstr ""
+
msgid "Profiles|Some options are unavailable for LDAP accounts"
msgstr ""
@@ -5237,6 +5366,9 @@ msgstr ""
msgid "Profiles|This information will appear on your profile"
msgstr ""
+msgid "Profiles|Two-Factor Authentication"
+msgstr ""
+
msgid "Profiles|Type your %{confirmationValue} to confirm:"
msgstr ""
@@ -5369,12 +5501,18 @@ msgstr ""
msgid "Project export started. A download link will be sent by email."
msgstr ""
+msgid "Project members"
+msgstr ""
+
msgid "Project name"
msgstr ""
msgid "Project slug"
msgstr ""
+msgid "Project:"
+msgstr ""
+
msgid "ProjectActivityRSS|Subscribe"
msgstr ""
@@ -5937,6 +6075,9 @@ msgstr ""
msgid "Search for projects, issues, etc."
msgstr ""
+msgid "Search groups"
+msgstr ""
+
msgid "Search merge requests"
msgstr ""
@@ -5952,6 +6093,9 @@ msgstr ""
msgid "Search project"
msgstr ""
+msgid "Search projects"
+msgstr ""
+
msgid "Search users"
msgstr ""
@@ -6012,6 +6156,9 @@ msgstr ""
msgid "Select branch/tag"
msgstr ""
+msgid "Select members to invite"
+msgstr ""
+
msgid "Select project"
msgstr ""
@@ -6245,6 +6392,9 @@ msgstr ""
msgid "Slower but makes sure the project workspace is pristine as it clones the repository from scratch for every job"
msgstr ""
+msgid "Snippet Contents"
+msgstr ""
+
msgid "Snippets"
msgstr ""
@@ -7191,6 +7341,9 @@ msgstr ""
msgid "Title"
msgstr ""
+msgid "Titles and Filenames"
+msgstr ""
+
msgid "To GitLab"
msgstr ""
@@ -7614,6 +7767,9 @@ msgstr ""
msgid "Want to see the data? Please ask an administrator for access."
msgstr ""
+msgid "We couldn't find any results matching"
+msgstr ""
+
msgid "We detected potential spam in the %{humanized_resource_name}. Please solve the reCAPTCHA to proceed."
msgstr ""
@@ -7632,6 +7788,9 @@ msgstr ""
msgid "Web terminal"
msgstr ""
+msgid "What's new?"
+msgstr ""
+
msgid "When a runner is locked, it cannot be assigned to other projects"
msgstr ""
@@ -7836,6 +7995,9 @@ msgstr ""
msgid "You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}"
msgstr ""
+msgid "You can invite a new member to <strong>%{project_name}</strong> or invite another group."
+msgstr ""
+
msgid "You can move around the graph by using the arrow keys."
msgstr ""
@@ -7989,6 +8151,9 @@ msgstr ""
msgid "Your name"
msgstr ""
+msgid "Your project limit is %{limit} projects! Please contact your administrator to increase it"
+msgstr ""
+
msgid "Your projects"
msgstr ""
@@ -8013,6 +8178,9 @@ msgstr ""
msgid "command line instructions"
msgstr ""
+msgid "commented on %{link_to_project}"
+msgstr ""
+
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 ""
@@ -8071,7 +8239,7 @@ msgstr ""
msgid "here"
msgstr ""
-msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/issues/"
+msgid "http://<sentry-host>/api/0/projects/{organization_slug}/{project_slug}/"
msgstr ""
msgid "https://your-bitbucket-server"
@@ -8086,6 +8254,12 @@ msgstr ""
msgid "importing"
msgstr ""
+msgid "in group %{link_to_group}"
+msgstr ""
+
+msgid "in project %{link_to_project}"
+msgstr ""
+
msgid "issue boards"
msgstr ""
@@ -8338,6 +8512,9 @@ msgstr ""
msgid "personal access token"
msgstr ""
+msgid "private"
+msgstr ""
+
msgid "project"
msgstr ""
diff --git a/package.json b/package.json
index 8c6fdcbad06..75df0ec3ff6 100644
--- a/package.json
+++ b/package.json
@@ -7,6 +7,7 @@
"eslint-fix": "eslint --max-warnings 0 --report-unused-disable-directives --ext .js,.vue --fix .",
"eslint-report": "eslint --max-warnings 0 --ext .js,.vue --format html --output-file ./eslint-report.html --no-inline-config .",
"jest": "BABEL_ENV=jest jest",
+ "jsdoc": "jsdoc -c config/jsdocs.config.js",
"karma": "BABEL_ENV=${BABEL_ENV:=karma} karma start --single-run true config/karma.config.js",
"karma-coverage": "BABEL_ENV=coverage karma start --single-run true config/karma.config.js",
"karma-start": "BABEL_ENV=karma karma start config/karma.config.js",
@@ -55,7 +56,7 @@
"d3-time": "^1.0.8",
"d3-time-format": "^2.1.1",
"dateformat": "^3.0.3",
- "deckar01-task_list": "^2.0.0",
+ "deckar01-task_list": "^2.0.1",
"diff": "^3.4.0",
"document-register-element": "1.3.0",
"dropzone": "^4.2.0",
@@ -132,6 +133,7 @@
"babel-types": "^6.26.0",
"chalk": "^2.4.1",
"commander": "^2.18.0",
+ "docdash": "^1.0.2",
"eslint": "~5.9.0",
"eslint-import-resolver-jest": "^2.1.1",
"eslint-import-resolver-webpack": "^0.10.1",
@@ -148,6 +150,8 @@
"jasmine-jquery": "^2.1.1",
"jest": "^23.6.0",
"jest-junit": "^5.2.0",
+ "jsdoc": "^3.5.5",
+ "jsdoc-vue": "^1.0.0",
"karma": "^3.0.0",
"karma-chrome-launcher": "^2.2.0",
"karma-coverage-istanbul-reporter": "^2.0.4",
@@ -157,12 +161,13 @@
"karma-sourcemap-loader": "^0.3.7",
"karma-webpack": "^4.0.0-beta.0",
"nodemon": "^1.18.4",
- "prettier": "1.15.2",
+ "prettier": "1.15.3",
"vue-jest": "^3.0.2",
"webpack-dev-server": "^3.1.14",
"yarn-deduplicate": "^1.0.5"
},
"engines": {
+ "node": ">=8.10.0",
"yarn": "^1.10.0"
}
}
diff --git a/qa/Rakefile b/qa/Rakefile
new file mode 100644
index 00000000000..8df1cfdc174
--- /dev/null
+++ b/qa/Rakefile
@@ -0,0 +1,6 @@
+require_relative 'qa/tools/revoke_all_personal_access_tokens'
+
+desc "Revokes all personal access tokens"
+task :revoke_personal_access_tokens do
+ QA::Tools::RevokeAllPersonalAccessTokens.new.run
+end
diff --git a/qa/qa.rb b/qa/qa.rb
index ef6a92f9768..2cbd74121f1 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -158,6 +158,10 @@ module QA
autoload :Activity, 'qa/page/project/activity'
autoload :Menu, 'qa/page/project/menu'
+ module Branches
+ autoload :Show, 'qa/page/project/branches/show'
+ end
+
module Commit
autoload :Show, 'qa/page/project/commit/show'
end
@@ -191,6 +195,11 @@ module QA
autoload :MirroringRepositories, 'qa/page/project/settings/mirroring_repositories'
end
+ module SubMenus
+ autoload :Common, 'qa/page/project/sub_menus/common'
+ autoload :Repository, 'qa/page/project/sub_menus/repository'
+ end
+
module Issue
autoload :New, 'qa/page/project/issue/new'
autoload :Show, 'qa/page/project/issue/show'
diff --git a/qa/qa/git/repository.rb b/qa/qa/git/repository.rb
index 7f959441dac..86e00cdbb9c 100644
--- a/qa/qa/git/repository.rb
+++ b/qa/qa/git/repository.rb
@@ -46,12 +46,9 @@ module QA
run("git clone #{opts} #{uri} ./")
end
- def checkout(branch_name)
- run(%Q{git checkout "#{branch_name}"})
- end
-
- def checkout_new_branch(branch_name)
- run(%Q{git checkout -b "#{branch_name}"})
+ def checkout(branch_name, new_branch: false)
+ opts = new_branch ? '-b' : ''
+ run(%Q{git checkout #{opts} "#{branch_name}"}).to_s
end
def shallow_clone
@@ -84,6 +81,10 @@ module QA
run("git push #{uri} #{branch}")
end
+ def merge(branch)
+ run("git merge #{branch}")
+ end
+
def commits
run('git log --oneline').split("\n")
end
diff --git a/qa/qa/page/profile/personal_access_tokens.rb b/qa/qa/page/profile/personal_access_tokens.rb
index 9191dbe9cf3..8c12eff5cf1 100644
--- a/qa/qa/page/profile/personal_access_tokens.rb
+++ b/qa/qa/page/profile/personal_access_tokens.rb
@@ -3,29 +3,51 @@ module QA
module Profile
class PersonalAccessTokens < Page::Base
view 'app/views/shared/_personal_access_tokens_form.html.haml' do
- element :personal_access_token_name_field, 'text_field :name' # rubocop:disable QA/ElementWithPattern
- element :create_token_button, 'submit "Create #{type} token"' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
- element :scopes_api_radios, "label :scopes" # rubocop:disable QA/ElementWithPattern
+ element :personal_access_token_name_field
+ element :create_token_button
+ end
+
+ view 'app/views/shared/tokens/_scopes_form.html.haml' do
+ element :api_radio, 'qa-#{scope}-radio' # rubocop:disable QA/ElementWithPattern, Lint/InterpolationCheck
end
view 'app/views/shared/_personal_access_tokens_created_container.html.haml' do
- element :create_token_field, "text_field_tag 'created-personal-access-token'" # rubocop:disable QA/ElementWithPattern
+ element :created_personal_access_token
+ end
+ view 'app/views/shared/_personal_access_tokens_table.html.haml' do
+ element :revoke_button
end
def fill_token_name(name)
- fill_in 'personal_access_token_name', with: name
+ fill_element(:personal_access_token_name_field, name)
end
def check_api
- check 'personal_access_token_scopes_api'
+ check_element(:api_radio)
end
def create_token
- click_on 'Create personal access token'
+ click_element(:create_token_button)
end
def created_access_token
- page.find('#created-personal-access-token').value
+ find_element(:created_personal_access_token, wait: 30).value
+ end
+
+ def has_token_row_for_name?(token_name)
+ page.has_css?('tr', text: token_name, wait: 1.0)
+ end
+
+ def first_token_row_for_name(token_name)
+ page.find('tr', text: token_name, match: :first, wait: 1.0)
+ end
+
+ def revoke_first_token_with_name(token_name)
+ within first_token_row_for_name(token_name) do
+ accept_confirm do
+ click_element(:revoke_button)
+ end
+ end
end
end
end
diff --git a/qa/qa/page/project/branches/show.rb b/qa/qa/page/project/branches/show.rb
new file mode 100644
index 00000000000..762a97e2088
--- /dev/null
+++ b/qa/qa/page/project/branches/show.rb
@@ -0,0 +1,62 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module Branches
+ class Show < Page::Base
+ view 'app/views/projects/branches/_branch.html.haml' do
+ element :remove_btn
+ end
+ view 'app/views/projects/branches/_panel.html.haml' do
+ element :all_branches
+ end
+ view 'app/views/projects/branches/index.html.haml' do
+ element :delete_merged_branches
+ end
+
+ def delete_branch(branch_name)
+ within_element(:all_branches) do
+ within(".js-branch-#{branch_name}") do
+ accept_alert do
+ find_element(:remove_btn).click
+ end
+ end
+ end
+ end
+
+ def has_branch_title?(branch_title)
+ within_element(:all_branches) do
+ within(".item-title") do
+ has_text?(branch_title)
+ end
+ end
+ end
+
+ def has_branch_with_badge?(branch_name, badge)
+ within_element(:all_branches) do
+ within(".js-branch-#{branch_name} .badge") do
+ has_text?(badge)
+ end
+ end
+ end
+
+ def delete_merged_branches
+ accept_alert do
+ click_element(:delete_merged_branches)
+ end
+ end
+
+ def wait_for_texts_not_to_be_visible(texts)
+ text_not_visible = wait do
+ texts.all? do |text|
+ has_no_text?(text)
+ end
+ end
+ raise "Expected text(s) #{texts} not to be visible" unless text_not_visible
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb
index 835e1ed00b5..eb3426b1ac0 100644
--- a/qa/qa/page/project/menu.rb
+++ b/qa/qa/page/project/menu.rb
@@ -4,11 +4,14 @@ module QA
module Page
module Project
class Menu < Page::Base
+ include SubMenus::Common
+ include SubMenus::Repository
+
view 'app/views/layouts/nav/sidebar/_project.html.haml' do
element :settings_item
element :settings_link, 'link_to edit_project_path' # rubocop:disable QA/ElementWithPattern
- element :repository_link, "title: _('Repository')" # rubocop:disable QA/ElementWithPattern
element :link_pipelines
+ element :link_operations
element :link_members_settings
element :pipelines_settings_link, "title: _('CI / CD')" # rubocop:disable QA/ElementWithPattern
element :operations_kubernetes_link, "title: _('Kubernetes')" # rubocop:disable QA/ElementWithPattern
@@ -18,7 +21,6 @@ module QA
element :merge_requests_link, /link_to.*shortcuts-merge_requests/ # rubocop:disable QA/ElementWithPattern
element :merge_requests_link_text, "Merge Requests" # rubocop:disable QA/ElementWithPattern
element :top_level_items, '.sidebar-top-level-items' # rubocop:disable QA/ElementWithPattern
- element :operations_section, "class: 'shortcuts-operations'" # rubocop:disable QA/ElementWithPattern
element :activity_link, "title: _('Activity')" # rubocop:disable QA/ElementWithPattern
element :wiki_link_text, "Wiki" # rubocop:disable QA/ElementWithPattern
element :milestones_link
@@ -85,12 +87,6 @@ module QA
end
end
- def click_repository
- within_sidebar do
- click_link('Repository')
- end
- end
-
def click_repository_settings
hover_settings do
within_submenu do
@@ -129,6 +125,7 @@ module QA
def hover_issues
within_sidebar do
+ scroll_to_element(:issues_item)
find_element(:issues_item).hover
yield
@@ -137,7 +134,8 @@ module QA
def hover_operations
within_sidebar do
- find('.shortcuts-operations').hover
+ scroll_to_element(:link_operations)
+ find_element(:link_operations).hover
yield
end
@@ -145,20 +143,9 @@ module QA
def hover_settings
within_sidebar do
- find('.qa-settings-item').hover
-
- yield
- end
- end
-
- def within_sidebar
- page.within('.sidebar-top-level-items') do
- yield
- end
- end
+ scroll_to_element(:settings_item)
+ find_element(:settings_item).hover
- def within_submenu
- page.within('.fly-out-list') do
yield
end
end
diff --git a/qa/qa/page/project/settings/deploy_keys.rb b/qa/qa/page/project/settings/deploy_keys.rb
index 3c8c0cbdf7c..5da8d352e74 100644
--- a/qa/qa/page/project/settings/deploy_keys.rb
+++ b/qa/qa/page/project/settings/deploy_keys.rb
@@ -14,8 +14,13 @@ module QA
end
view 'app/assets/javascripts/deploy_keys/components/key.vue' do
- element :key_title, /class=".*qa-key-title.*"/ # rubocop:disable QA/ElementWithPattern
- element :key_fingerprint, /class=".*qa-key-fingerprint.*"/ # rubocop:disable QA/ElementWithPattern
+ element :key
+ element :key_title
+ element :key_fingerprint
+ end
+
+ def add_key
+ click_on 'Add key'
end
def fill_key_title(title)
@@ -26,31 +31,29 @@ module QA
fill_in 'deploy_key_key', with: key
end
- def add_key
- click_on 'Add key'
- end
-
- def key_title
+ def find_fingerprint(title)
within_project_deploy_keys do
- find_element(:key_title).text
+ find_element(:key, title)
+ .find(element_selector_css(:key_fingerprint)).text
end
end
- def key_fingerprint
+ def has_key?(title, fingerprint)
within_project_deploy_keys do
- find_element(:key_fingerprint).text
+ find_element(:key, title)
+ .has_css?(element_selector_css(:key_fingerprint), text: fingerprint)
end
end
- def key_titles
+ def key_title
within_project_deploy_keys do
- all_elements(:key_title)
+ find_element(:key_title).text
end
end
- def key_fingerprints
+ def key_fingerprint
within_project_deploy_keys do
- all_elements(:key_fingerprint)
+ find_element(:key_fingerprint).text
end
end
@@ -58,7 +61,7 @@ module QA
def within_project_deploy_keys
wait(reload: false) do
- has_css?(element_selector_css(:project_deploy_keys))
+ has_element?(:project_deploy_keys)
end
within_element(:project_deploy_keys) do
diff --git a/qa/qa/page/project/sub_menus/common.rb b/qa/qa/page/project/sub_menus/common.rb
new file mode 100644
index 00000000000..c94e1e85256
--- /dev/null
+++ b/qa/qa/page/project/sub_menus/common.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module SubMenus
+ module Common
+ def within_sidebar
+ within('.sidebar-top-level-items') do
+ yield
+ end
+ end
+
+ def within_submenu
+ within('.fly-out-list') do
+ yield
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/sub_menus/repository.rb b/qa/qa/page/project/sub_menus/repository.rb
new file mode 100644
index 00000000000..29eaa9a74de
--- /dev/null
+++ b/qa/qa/page/project/sub_menus/repository.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+module QA
+ module Page
+ module Project
+ module SubMenus
+ module Repository
+ def self.included(base)
+ base.class_eval do
+ view 'app/views/layouts/nav/sidebar/_project.html.haml' do
+ element :project_menu_repo
+ element :branches_link
+ end
+ end
+ end
+
+ def click_repository
+ within_sidebar do
+ click_element(:project_menu_repo)
+ end
+ end
+
+ def click_repository_branches
+ hover_repository do
+ within_submenu do
+ click_element(:branches_link)
+ end
+ end
+ end
+
+ private
+
+ def hover_repository
+ within_sidebar do
+ find_element(:project_menu_repo).hover
+
+ yield
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/resource/deploy_key.rb b/qa/qa/resource/deploy_key.rb
index 9ed8fb7726e..9565598efb0 100644
--- a/qa/qa/resource/deploy_key.rb
+++ b/qa/qa/resource/deploy_key.rb
@@ -8,11 +8,7 @@ module QA
attribute :fingerprint do
Page::Project::Settings::Repository.perform do |setting|
setting.expand_deploy_keys do |key|
- key_offset = key.key_titles.index do |key_title|
- key_title.text == title
- end
-
- key.key_fingerprints[key_offset].text
+ key.find_fingerprint(title)
end
end
end
diff --git a/qa/qa/resource/repository/push.rb b/qa/qa/resource/repository/push.rb
index c14d97ff7fb..f33aa0acef0 100644
--- a/qa/qa/resource/repository/push.rb
+++ b/qa/qa/resource/repository/push.rb
@@ -64,7 +64,7 @@ module QA
repository.configure_identity(username, email)
if new_branch
- repository.checkout_new_branch(branch_name)
+ repository.checkout(branch_name, new_branch: true)
else
repository.checkout(branch_name)
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb
new file mode 100644
index 00000000000..0f0c627d79a
--- /dev/null
+++ b/qa/qa/specs/features/browser_ui/3_create/repository/add_list_delete_branches_spec.rb
@@ -0,0 +1,92 @@
+# frozen_string_literal: true
+
+module QA
+ context 'Create' do
+ describe 'Create, list, and delete branches via web' do
+ master_branch = 'master'
+ second_branch = 'second-branch'
+ third_branch = 'third-branch'
+ file_1_master = 'file.txt'
+ file_2_master = 'other-file.txt'
+ file_second_branch = 'file-2.txt'
+ file_third_branch = 'file-3.txt'
+ first_commit_message_of_master_branch = "Add #{file_1_master}"
+ second_commit_message_of_master_branch = "Add #{file_2_master}"
+ commit_message_of_second_branch = "Add #{file_second_branch}"
+ commit_message_of_third_branch = "Add #{file_third_branch}"
+
+ before do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+
+ project = Resource::Project.fabricate! do |proj|
+ proj.name = 'project-qa-test'
+ proj.description = 'project for qa test'
+ end
+ project.visit!
+
+ Git::Repository.perform do |repository|
+ repository.uri = project.repository_http_location.uri
+ repository.use_default_credentials
+
+ repository.act do
+ clone
+ configure_identity('GitLab QA', 'root@gitlab.com')
+ commit_file(file_1_master, 'Test file content', first_commit_message_of_master_branch)
+ push_changes
+ checkout(second_branch, new_branch: true)
+ commit_file(file_second_branch, 'File 2 content', commit_message_of_second_branch)
+ push_changes(second_branch)
+ checkout(master_branch)
+ # This second commit on master is needed for the master branch to be ahead
+ # of the second branch, and when the second branch is merged to master it will
+ # show the 'merged' badge on it.
+ # Refer to the below issue note:
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/55524#note_126100848
+ commit_file(file_2_master, 'Other test file content', second_commit_message_of_master_branch)
+ push_changes
+ merge(second_branch)
+ push_changes
+ checkout(third_branch, new_branch: true)
+ commit_file(file_third_branch, 'File 3 content', commit_message_of_third_branch)
+ push_changes(third_branch)
+ end
+ end
+ Page::Project::Show.perform(&:wait_for_push)
+ end
+
+ it 'branches are correctly listed after CRUD operations' do
+ Page::Project::Menu.perform(&:click_repository_branches)
+
+ expect(page).to have_content(master_branch)
+ expect(page).to have_content(second_branch)
+ expect(page).to have_content(third_branch)
+ expect(page).to have_content("Merge branch 'second-branch'")
+ expect(page).to have_content(commit_message_of_second_branch)
+ expect(page).to have_content(commit_message_of_third_branch)
+
+ Page::Project::Branches::Show.perform do |branches|
+ expect(branches).to have_branch_with_badge(second_branch, 'merged')
+ end
+
+ Page::Project::Branches::Show.perform do |branches_view|
+ branches_view.delete_branch(third_branch)
+ end
+
+ expect(page).not_to have_content(third_branch)
+
+ Page::Project::Branches::Show.perform(&:delete_merged_branches)
+
+ expect(page).to have_content(
+ 'Merged branches are being deleted. This can take some time depending on the number of branches. Please refresh the page to see changes.'
+ )
+
+ page.refresh
+ Page::Project::Branches::Show.perform do |branches_view|
+ branches_view.wait_for_texts_not_to_be_visible([commit_message_of_second_branch])
+ expect(branches_view).not_to have_branch_title(second_branch)
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
index 84757f25379..6f39a755392 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb
@@ -5,7 +5,7 @@ module QA
describe 'Deploy key creation' do
it 'user adds a deploy key' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
- Page::Main::Login.act { sign_in_using_credentials }
+ Page::Main::Login.perform(&:sign_in_using_credentials)
key = Runtime::Key::RSA.new
deploy_key_title = 'deploy key title'
@@ -16,7 +16,13 @@ module QA
resource.key = deploy_key_value
end
- expect(deploy_key.fingerprint).to eq(key.fingerprint)
+ expect(deploy_key.fingerprint).to eq key.fingerprint
+
+ Page::Project::Settings::Repository.perform do |setting|
+ setting.expand_deploy_keys do |keys|
+ expect(keys).to have_key(deploy_key_title, key.fingerprint)
+ end
+ end
end
end
end
diff --git a/qa/qa/tools/revoke_all_personal_access_tokens.rb b/qa/qa/tools/revoke_all_personal_access_tokens.rb
new file mode 100644
index 00000000000..7484b633bf6
--- /dev/null
+++ b/qa/qa/tools/revoke_all_personal_access_tokens.rb
@@ -0,0 +1,44 @@
+# frozen_string_literal: true
+
+require_relative '../../qa'
+require 'net/protocol.rb'
+# This script revokes all personal access tokens with the name of 'api-test-token' on the host specified by GITLAB_ADDRESS
+# Required environment variables: GITLAB_USERNAME, GITLAB_PASSWORD and GITLAB_ADDRESS
+# Run `rake revoke_personal_access_tokens`
+
+module QA
+ module Tools
+ class RevokeAllPersonalAccessTokens
+ def run
+ do_run
+ rescue Net::ReadTimeout
+ STDOUT.puts 'Net::ReadTimeout during run. Trying again'
+ run
+ end
+
+ private
+
+ def do_run
+ raise ArgumentError, "Please provide GITLAB_USERNAME" unless ENV['GITLAB_USERNAME']
+ raise ArgumentError, "Please provide GITLAB_PASSWORD" unless ENV['GITLAB_PASSWORD']
+ raise ArgumentError, "Please provide GITLAB_ADDRESS" unless ENV['GITLAB_ADDRESS']
+
+ STDOUT.puts 'Running...'
+
+ Runtime::Browser.visit(ENV['GITLAB_ADDRESS'], Page::Main::Login)
+ Page::Main::Login.perform(&:sign_in_using_credentials)
+ Page::Main::Menu.perform(&:go_to_profile_settings)
+ Page::Profile::Menu.perform(&:click_access_tokens)
+
+ token_name = 'api-test-token'
+
+ Page::Profile::PersonalAccessTokens.perform do |page|
+ while page.has_token_row_for_name?(token_name)
+ page.revoke_first_token_with_name(token_name)
+ print "\e[32m.\e[0m"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/inject_enterprise_edition_module.rb b/rubocop/cop/inject_enterprise_edition_module.rb
index c8b8aca51ab..1d37b1bd12d 100644
--- a/rubocop/cop/inject_enterprise_edition_module.rb
+++ b/rubocop/cop/inject_enterprise_edition_module.rb
@@ -11,9 +11,13 @@ module RuboCop
METHODS = Set.new(%i[include extend prepend]).freeze
- def_node_matcher :ee_const?, <<~PATTERN
- (const (const _ :EE) _)
- PATTERN
+ def ee_const?(node)
+ line = node.location.expression.source_line
+
+ # We use `match?` here instead of RuboCop's AST matching, as this makes
+ # it far easier to handle nested constants such as `EE::Foo::Bar::Baz`.
+ line.match?(/(\s|\()(::)?EE::/)
+ end
def on_send(node)
return unless METHODS.include?(node.children[1])
diff --git a/rubocop/spec_helpers.rb b/rubocop/spec_helpers.rb
index 9bf5f1e3b18..63c1b975a65 100644
--- a/rubocop/spec_helpers.rb
+++ b/rubocop/spec_helpers.rb
@@ -1,6 +1,7 @@
module RuboCop
module SpecHelpers
SPEC_HELPERS = %w[fast_spec_helper.rb rails_helper.rb spec_helper.rb].freeze
+ MIGRATION_SPEC_DIRECTORIES = ['spec/migrations', 'spec/lib/gitlab/background_migration'].freeze
# Returns true if the given node originated from the spec directory.
def in_spec?(node)
@@ -10,14 +11,18 @@ module RuboCop
path.start_with?(File.join(Dir.pwd, 'spec'), File.join(Dir.pwd, 'ee', 'spec'))
end
+ def migration_directories
+ @migration_directories ||= MIGRATION_SPEC_DIRECTORIES.map do |dir|
+ [File.join(Dir.pwd, dir), File.join(Dir.pwd, 'ee', dir)]
+ end.flatten
+ end
+
# Returns true if the given node originated from a migration spec.
def in_migration_spec?(node)
path = node.location.expression.source_buffer.name
in_spec?(node) &&
- path.start_with?(
- File.join(Dir.pwd, 'spec', 'migrations'),
- File.join(Dir.pwd, 'ee', 'spec', 'migrations'))
+ path.start_with?(*migration_directories)
end
end
end
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 43f561f7a25..c290acb72aa 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -519,12 +519,14 @@ describe ApplicationController do
get :index
expect(response).to have_gitlab_http_status(404)
+ expect(response).to render_template('errors/not_found')
end
it 'renders a 403 when a message is passed to access denied' do
get :index, params: { message: 'None shall pass' }
expect(response).to have_gitlab_http_status(403)
+ expect(response).to render_template('errors/access_denied')
end
it 'renders a status passed to access denied' do
diff --git a/spec/controllers/import/bitbucket_server_controller_spec.rb b/spec/controllers/import/bitbucket_server_controller_spec.rb
index 73195463a50..bb282db5a41 100644
--- a/spec/controllers/import/bitbucket_server_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_server_controller_spec.rb
@@ -78,7 +78,7 @@ describe Import::BitbucketServerController do
end
it "returns an error when the server can't be contacted" do
- expect(client).to receive(:repo).with(project_key, repo_slug).and_raise(BitbucketServer::Client::ServerError)
+ expect(client).to receive(:repo).with(project_key, repo_slug).and_raise(::BitbucketServer::Connection::ConnectionError)
post :create, params: { project: project_key, repository: repo_slug }, format: :json
diff --git a/spec/controllers/omniauth_callbacks_controller_spec.rb b/spec/controllers/omniauth_callbacks_controller_spec.rb
index 21936491ffc..59463462e5a 100644
--- a/spec/controllers/omniauth_callbacks_controller_spec.rb
+++ b/spec/controllers/omniauth_callbacks_controller_spec.rb
@@ -55,7 +55,7 @@ describe OmniauthCallbacksController, type: :controller do
context 'when a redirect url is stored' do
it 'redirects with fragment' do
- post provider, nil, { user_return_to: '/fake/url' }
+ post provider, session: { user_return_to: '/fake/url' }
expect(response).to redirect_to('/fake/url#L101')
end
@@ -63,7 +63,7 @@ describe OmniauthCallbacksController, type: :controller do
context 'when a redirect url with a fragment is stored' do
it 'redirects with the new fragment' do
- post provider, nil, { user_return_to: '/fake/url#replaceme' }
+ post provider, session: { user_return_to: '/fake/url#replaceme' }
expect(response).to redirect_to('/fake/url#L101')
end
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index df21dc7bc85..5b3256bf409 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -1030,19 +1030,6 @@ describe Projects::IssuesController do
let(:project) { create(:project, :public) }
let(:file) { fixture_file_upload('spec/fixtures/csv_comma.csv') }
- context 'feature disabled' do
- it 'returns 404' do
- sign_in(user)
- project.add_maintainer(user)
-
- stub_feature_flags(issues_import_csv: false)
-
- import_csv
-
- expect(response).to have_gitlab_http_status :not_found
- end
- end
-
context 'unauthorized' do
it 'returns 404 for guests' do
sign_out(:user)
@@ -1086,9 +1073,9 @@ describe Projects::IssuesController do
end
def import_csv
- post :import_csv, namespace_id: project.namespace.to_param,
- project_id: project.to_param,
- file: file
+ post :import_csv, params: { namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ file: file }
end
end
diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb
index 7f65fe551e9..d8a331b3cf0 100644
--- a/spec/controllers/projects/jobs_controller_spec.rb
+++ b/spec/controllers/projects/jobs_controller_spec.rb
@@ -770,50 +770,6 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do
end
end
- describe 'POST cancel_all' do
- before do
- project.add_developer(user)
- sign_in(user)
- end
-
- context 'when jobs are cancelable' do
- before do
- create_list(:ci_build, 2, :cancelable, pipeline: pipeline)
-
- post_cancel_all
- end
-
- it 'redirects to a index page' do
- expect(response).to have_gitlab_http_status(:found)
- expect(response).to redirect_to(namespace_project_jobs_path)
- end
-
- it 'transits to canceled' do
- expect(Ci::Build.all).to all(be_canceled)
- end
- end
-
- context 'when jobs are not cancelable' do
- before do
- create_list(:ci_build, 2, :canceled, pipeline: pipeline)
-
- post_cancel_all
- end
-
- it 'redirects to a index page' do
- expect(response).to have_gitlab_http_status(:found)
- expect(response).to redirect_to(namespace_project_jobs_path)
- end
- end
-
- def post_cancel_all
- post :cancel_all, params: {
- namespace_id: project.namespace,
- project_id: project
- }
- end
- end
-
describe 'POST erase' do
let(:role) { :maintainer }
diff --git a/spec/controllers/projects/pages_controller_spec.rb b/spec/controllers/projects/pages_controller_spec.rb
index 382c1b5d124..4b742a5d427 100644
--- a/spec/controllers/projects/pages_controller_spec.rb
+++ b/spec/controllers/projects/pages_controller_spec.rb
@@ -28,10 +28,10 @@ describe Projects::PagesController do
let(:group) { create(:group, :nested) }
let(:project) { create(:project, namespace: group) }
- it 'returns a 404 status code' do
+ it 'returns a 200 status code' do
get :show, params: request_params
- expect(response).to have_gitlab_http_status(404)
+ expect(response).to have_gitlab_http_status(200)
end
end
end
diff --git a/spec/factories/wiki_pages.rb b/spec/factories/wiki_pages.rb
index 2335b5118dd..ae257d769e8 100644
--- a/spec/factories/wiki_pages.rb
+++ b/spec/factories/wiki_pages.rb
@@ -5,7 +5,7 @@ FactoryBot.define do
transient do
attrs do
{
- title: 'Title',
+ title: 'Title.with.dot',
content: 'Content for wiki page',
format: 'markdown'
}
diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb
index baa2b1d8af5..08c27354bd2 100644
--- a/spec/features/boards/boards_spec.rb
+++ b/spec/features/boards/boards_spec.rb
@@ -97,7 +97,7 @@ describe 'Issue Boards', :js do
expect(find('.board:nth-child(4)')).to have_selector('.board-card')
end
- it 'shows description tooltip on list title' do
+ it 'shows description tooltip on list title', :quarantine do
page.within('.board:nth-child(2)') do
expect(find('.board-title span.has-tooltip')[:title]).to eq('Test')
end
@@ -411,7 +411,7 @@ describe 'Issue Boards', :js do
wait_for_empty_boards((2..4))
end
- it 'filters by label with space after reload' do
+ it 'filters by label with space after reload', :quarantine do
set_filter("label", "\"#{accepting.title}")
click_filter_link(accepting.title)
submit_filter
@@ -477,7 +477,7 @@ describe 'Issue Boards', :js do
end
end
- it 'filters by multiple labels' do
+ it 'filters by multiple labels', :quarantine do
set_filter("label", testing.title)
click_filter_link(testing.title)
diff --git a/spec/features/dashboard/help_spec.rb b/spec/features/dashboard/help_spec.rb
index 68bfbf22736..fa12cecc984 100644
--- a/spec/features/dashboard/help_spec.rb
+++ b/spec/features/dashboard/help_spec.rb
@@ -5,13 +5,23 @@ RSpec.describe 'Dashboard Help' do
sign_in(create(:user))
end
- it 'renders correctly markdown' do
- visit help_page_path("administration/raketasks/maintenance")
+ context 'help dropdown' do
+ it 'shows the "What\'s new?" menu item' do
+ visit root_dashboard_path
- expect(page).to have_content('Gather information about GitLab and the system it runs on')
+ expect(page.find('.header-help .dropdown-menu')).to have_text("What's new?")
+ end
+ end
+
+ context 'documentation' do
+ it 'renders correctly markdown' do
+ visit help_page_path("administration/raketasks/maintenance")
+
+ expect(page).to have_content('Gather information about GitLab and the system it runs on')
- node = find('.documentation h2 a#user-content-check-gitlab-configuration')
- expect(node[:href]).to eq '#check-gitlab-configuration'
- expect(find(:xpath, "#{node.path}/..").text).to eq 'Check GitLab configuration'
+ node = find('.documentation h2 a#user-content-check-gitlab-configuration')
+ expect(node[:href]).to eq '#check-gitlab-configuration'
+ expect(find(:xpath, "#{node.path}/..").text).to eq 'Check GitLab configuration'
+ end
end
end
diff --git a/spec/features/dashboard/projects_spec.rb b/spec/features/dashboard/projects_spec.rb
index 975b7944741..edca8f9df08 100644
--- a/spec/features/dashboard/projects_spec.rb
+++ b/spec/features/dashboard/projects_spec.rb
@@ -91,6 +91,7 @@ describe 'Dashboard Projects' do
visit dashboard_projects_path
expect(page).to have_content(project.name)
+ expect(find('.nav-links li:nth-child(1) .badge-pill')).to have_content(1)
end
it 'shows personal projects on personal projects tab', :js do
@@ -121,6 +122,8 @@ describe 'Dashboard Projects' do
expect(page).not_to have_content(project.name)
expect(page).to have_content(project2.name)
+ expect(find('.nav-links li:nth-child(1) .badge-pill')).to have_content(1)
+ expect(find('.nav-links li:nth-child(2) .badge-pill')).to have_content(1)
end
end
diff --git a/spec/features/dashboard/todos/todos_spec.rb b/spec/features/dashboard/todos/todos_spec.rb
index 96b22a0f64b..2284ee925a0 100644
--- a/spec/features/dashboard/todos/todos_spec.rb
+++ b/spec/features/dashboard/todos/todos_spec.rb
@@ -332,7 +332,7 @@ describe 'Dashboard Todos' do
it 'links to the pipelines for the merge request' do
href = pipelines_project_merge_request_path(project, todo.target)
- expect(page).to have_link "merge request #{todo.target.to_reference(full: true)}", href
+ expect(page).to have_link "merge request #{todo.target.to_reference(full: true)}", href: href
end
end
end
diff --git a/spec/features/merge_request/user_merges_immediately_spec.rb b/spec/features/merge_request/user_merges_immediately_spec.rb
index ea61f9675bc..84636ae355c 100644
--- a/spec/features/merge_request/user_merges_immediately_spec.rb
+++ b/spec/features/merge_request/user_merges_immediately_spec.rb
@@ -25,6 +25,8 @@ describe 'Merge requests > User merges immediately', :js do
end
it 'enables merge immediately' do
+ wait_for_requests
+
page.within '.mr-widget-body' do
find('.dropdown-toggle').click
diff --git a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
index 9ebbbaea911..5f630c9ffa4 100644
--- a/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
+++ b/spec/features/projects/artifacts/user_browses_artifacts_spec.rb
@@ -25,8 +25,8 @@ describe "User browses artifacts" do
page.within(".tree-table") do
expect(page).to have_no_content("..")
.and have_content("other_artifacts_0.1.2")
- .and have_content("ci_artifacts.txt")
- .and have_content("rails_sample.jpg")
+ .and have_content("ci_artifacts.txt 27 Bytes")
+ .and have_content("rails_sample.jpg 34.4 KB")
end
page.within(".build-header") do
diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb
index 60f37f4b74a..8230396a4cc 100644
--- a/spec/features/projects/jobs_spec.rb
+++ b/spec/features/projects/jobs_spec.rb
@@ -28,7 +28,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end
it "shows Pending tab jobs" do
- expect(page).to have_link 'Cancel running'
expect(page).to have_selector('.nav-links li.active', text: 'Pending')
expect(page).to have_content job.short_sha
expect(page).to have_content job.ref
@@ -44,7 +43,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it "shows Running tab jobs" do
expect(page).to have_selector('.nav-links li.active', text: 'Running')
- expect(page).to have_link 'Cancel running'
expect(page).to have_content job.short_sha
expect(page).to have_content job.ref
expect(page).to have_content job.name
@@ -60,7 +58,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
it "shows Finished tab jobs" do
expect(page).to have_selector('.nav-links li.active', text: 'Finished')
expect(page).to have_content 'No jobs to show'
- expect(page).to have_link 'Cancel running'
end
end
@@ -75,7 +72,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
expect(page).to have_content job.short_sha
expect(page).to have_content job.ref
expect(page).to have_content job.name
- expect(page).not_to have_link 'Cancel running'
end
end
@@ -94,23 +90,6 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
end
end
- describe "POST /:project/jobs/:id/cancel_all" do
- before do
- job.run!
- visit project_jobs_path(project)
- click_link "Cancel running"
- end
-
- it 'shows all necessary content' do
- expect(page).to have_selector('.nav-links li.active', text: 'All')
- expect(page).to have_content 'canceled'
- expect(page).to have_content job.short_sha
- expect(page).to have_content job.ref
- expect(page).to have_content job.name
- expect(page).not_to have_link 'Cancel running'
- end
- end
-
describe "GET /:project/jobs/:id" do
context "Job from project" do
let(:job) { create(:ci_build, :success, :trace_live, pipeline: pipeline) }
@@ -191,7 +170,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do
href = new_project_issue_path(project, options)
- page.within('.header-action-buttons') do
+ page.within('.build-sidebar') do
expect(find('.js-new-issue')['href']).to include(href)
end
end
diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb
index 4706c28bb3d..3192c9ffad4 100644
--- a/spec/features/projects/pipelines/pipeline_spec.rb
+++ b/spec/features/projects/pipelines/pipeline_spec.rb
@@ -477,10 +477,11 @@ describe 'Pipeline', :js do
end
context 'when accessing failed jobs page' do
- it 'fails to access the page' do
- subject
+ it 'renders a 404 page' do
+ requests = inspect_requests { subject }
- expect(page).to have_title('Access Denied')
+ expect(page).to have_title('Not Found')
+ expect(requests.first.status_code).to eq(404)
end
end
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index b56bb272b46..eb70a3c41c1 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -99,6 +99,30 @@ describe 'Project' do
end
end
+ describe 'project topics' do
+ let(:project) { create(:project, :repository) }
+ let(:path) { project_path(project) }
+
+ before do
+ sign_in(create(:admin))
+ visit path
+ end
+
+ it 'shows project topics' do
+ project.update_attribute(:tag_list, 'topic1')
+ visit path
+ expect(page).to have_css('.project-topic-list')
+ expect(page).to have_content('topic1')
+ end
+
+ it 'shows up to 3 project tags' do
+ project.update_attribute(:tag_list, 'topic1, topic2, topic3, topic4')
+ visit path
+ expect(page).to have_css('.project-topic-list')
+ expect(page).to have_content('topic1, topic2, topic3 + 1 more')
+ end
+ end
+
describe 'copy clone URL to clipboard', :js do
let(:project) { create(:project, :repository) }
let(:path) { project_path(project) }
diff --git a/spec/fixtures/malicious.bundle b/spec/fixtures/malicious.bundle
new file mode 100644
index 00000000000..7ba47932906
--- /dev/null
+++ b/spec/fixtures/malicious.bundle
@@ -0,0 +1 @@
+gitdir: foo.git
diff --git a/spec/helpers/projects/error_tracking_helper_spec.rb b/spec/helpers/projects/error_tracking_helper_spec.rb
new file mode 100644
index 00000000000..7516a636c93
--- /dev/null
+++ b/spec/helpers/projects/error_tracking_helper_spec.rb
@@ -0,0 +1,58 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::ErrorTrackingHelper do
+ include Gitlab::Routing.url_helpers
+
+ set(:project) { create(:project) }
+
+ describe '#error_tracking_data' do
+ let(:setting_path) { project_settings_operations_path(project) }
+
+ let(:index_path) do
+ project_error_tracking_index_path(project, format: :json)
+ end
+
+ context 'without error_tracking_setting' do
+ it 'returns frontend configuration' do
+ expect(error_tracking_data(project)).to eq(
+ 'index-path' => index_path,
+ 'enable-error-tracking-link' => setting_path,
+ 'error-tracking-enabled' => 'false',
+ "illustration-path" => "/images/illustrations/cluster_popover.svg"
+ )
+ end
+ end
+
+ context 'with error_tracking_setting' do
+ let(:error_tracking_setting) do
+ create(:project_error_tracking_setting, project: project)
+ end
+
+ context 'when enabled' do
+ before do
+ error_tracking_setting.update!(enabled: true)
+ end
+
+ it 'show error tracking enabled' do
+ expect(error_tracking_data(project)).to include(
+ 'error-tracking-enabled' => 'true'
+ )
+ end
+ end
+
+ context 'when disabled' do
+ before do
+ error_tracking_setting.update!(enabled: false)
+ end
+
+ it 'show error tracking not enabled' do
+ expect(error_tracking_data(project)).to include(
+ 'error-tracking-enabled' => 'false'
+ )
+ end
+ end
+ end
+ end
+end
diff --git a/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js b/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js
index 7237274eb43..53b9ac22fc0 100644
--- a/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js
+++ b/spec/javascripts/diffs/components/compare_versions_dropdown_spec.js
@@ -1 +1,34 @@
// TODO: https://gitlab.com/gitlab-org/gitlab-ce/issues/48034
+import { shallowMount, createLocalVue } from '@vue/test-utils';
+import CompareVersionsDropdown from '~/diffs/components/compare_versions_dropdown.vue';
+import diffsMockData from '../mock_data/merge_request_diffs';
+
+describe('CompareVersionsDropdown', () => {
+ let wrapper;
+ const targetBranch = { branchName: 'tmp-wine-dev', versionIndex: -1 };
+
+ const factory = (options = {}) => {
+ const localVue = createLocalVue();
+
+ wrapper = shallowMount(CompareVersionsDropdown, { localVue, ...options });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ it('should render a correct base version link', () => {
+ factory({
+ propsData: {
+ baseVersionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37',
+ otherVersions: diffsMockData.slice(1),
+ targetBranch,
+ },
+ });
+
+ const links = wrapper.findAll('a');
+ const lastLink = links.wrappers[links.length - 1];
+
+ expect(lastLink.attributes('href')).toEqual(wrapper.props('baseVersionPath'));
+ });
+});
diff --git a/spec/javascripts/diffs/components/compare_versions_spec.js b/spec/javascripts/diffs/components/compare_versions_spec.js
index 75c66e9ca82..50135b0cf86 100644
--- a/spec/javascripts/diffs/components/compare_versions_spec.js
+++ b/spec/javascripts/diffs/components/compare_versions_spec.js
@@ -100,6 +100,12 @@ describe('CompareVersions', () => {
});
});
+ describe('baseVersionPath', () => {
+ it('should be set correctly from mergeRequestDiff', () => {
+ expect(vm.baseVersionPath).toEqual(vm.mergeRequestDiff.base_version_path);
+ });
+ });
+
describe('isWhitespaceVisible', () => {
const originalHref = window.location.href;
diff --git a/spec/javascripts/diffs/mock_data/merge_request_diffs.js b/spec/javascripts/diffs/mock_data/merge_request_diffs.js
index d72ad7818dd..4bbef146336 100644
--- a/spec/javascripts/diffs/mock_data/merge_request_diffs.js
+++ b/spec/javascripts/diffs/mock_data/merge_request_diffs.js
@@ -1,42 +1,46 @@
export default [
{
- versionIndex: 4,
- createdAt: '2018-10-23T11:49:16.611Z',
- commitsCount: 4,
+ base_version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37',
+ version_index: 4,
+ created_at: '2018-10-23T11:49:16.611Z',
+ commits_count: 4,
latest: true,
- shortCommitSha: 'de7a8f7f',
- versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37',
- comparePath:
+ short_commit_sha: 'de7a8f7f',
+ version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=37',
+ compare_path:
'/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=de7a8f7f20c3ea2e0bef3ba01cfd41c21f6b4995',
},
{
- versionIndex: 3,
- createdAt: '2018-10-23T11:46:40.617Z',
- commitsCount: 3,
+ base_version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=36',
+ version_index: 3,
+ created_at: '2018-10-23T11:46:40.617Z',
+ commits_count: 3,
latest: false,
- shortCommitSha: 'e78fc18f',
- versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=36',
- comparePath:
+ short_commit_sha: 'e78fc18f',
+ version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=36',
+ compare_path:
'/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=e78fc18fa37acb2185c59ca94d4a964464feb50e',
},
{
- versionIndex: 2,
- createdAt: '2018-10-04T09:57:39.648Z',
- commitsCount: 2,
+ base_version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=35',
+ version_index: 2,
+ created_at: '2018-10-04T09:57:39.648Z',
+ commits_count: 2,
latest: false,
- shortCommitSha: '48da7e7e',
- versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=35',
- comparePath:
+ short_commit_sha: '48da7e7e',
+ version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=35',
+ compare_path:
'/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=48da7e7e9a99d41c852578bd9cb541ca4d864b3e',
},
{
- versionIndex: 1,
- createdAt: '2018-09-25T20:30:39.493Z',
- commitsCount: 1,
+ base_version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=20',
+ version_index: 1,
+ created_at: '2018-09-25T20:30:39.493Z',
+ commits_count: 1,
latest: false,
- shortCommitSha: '47bac2ed',
- versionPath: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=20',
- comparePath:
+ short_commit_sha: '47bac2ed',
+ version_path: '/gnuwget/wget2/merge_requests/6/diffs?diff_id=20',
+ compare_path:
'/gnuwget/wget2/merge_requests/6/diffs?diff_id=37&start_sha=47bac2ed972c5bee344c1cea159a22cd7f711dc0',
},
];
diff --git a/spec/javascripts/error_tracking/components/error_tracking_list_spec.js b/spec/javascripts/error_tracking/components/error_tracking_list_spec.js
new file mode 100644
index 00000000000..08bbb390993
--- /dev/null
+++ b/spec/javascripts/error_tracking/components/error_tracking_list_spec.js
@@ -0,0 +1,100 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import Vuex from 'vuex';
+import ErrorTrackingList from '~/error_tracking/components/error_tracking_list.vue';
+import { GlButton, GlEmptyState, GlLoadingIcon, GlTable } from '@gitlab/ui';
+
+const localVue = createLocalVue();
+localVue.use(Vuex);
+
+describe('ErrorTrackingList', () => {
+ let store;
+ let wrapper;
+
+ function mountComponent({ errorTrackingEnabled = true } = {}) {
+ wrapper = shallowMount(ErrorTrackingList, {
+ localVue,
+ store,
+ propsData: {
+ indexPath: '/path',
+ enableErrorTrackingLink: '/link',
+ errorTrackingEnabled,
+ illustrationPath: 'illustration/path',
+ },
+ });
+ }
+
+ beforeEach(() => {
+ const actions = {
+ getErrorList: () => {},
+ };
+
+ const state = {
+ errors: [],
+ loading: true,
+ };
+
+ store = new Vuex.Store({
+ actions,
+ state,
+ });
+ });
+
+ afterEach(() => {
+ if (wrapper) {
+ wrapper.destroy();
+ }
+ });
+
+ describe('loading', () => {
+ beforeEach(() => {
+ mountComponent();
+ });
+
+ it('shows spinner', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBeTruthy();
+ expect(wrapper.find(GlTable).exists()).toBeFalsy();
+ expect(wrapper.find(GlButton).exists()).toBeFalsy();
+ });
+ });
+
+ describe('results', () => {
+ beforeEach(() => {
+ store.state.loading = false;
+
+ mountComponent();
+ });
+
+ it('shows table', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy();
+ expect(wrapper.find(GlTable).exists()).toBeTruthy();
+ expect(wrapper.find(GlButton).exists()).toBeTruthy();
+ });
+ });
+
+ describe('no results', () => {
+ beforeEach(() => {
+ store.state.loading = false;
+
+ mountComponent();
+ });
+
+ it('shows empty table', () => {
+ expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy();
+ expect(wrapper.find(GlTable).exists()).toBeTruthy();
+ expect(wrapper.find(GlButton).exists()).toBeTruthy();
+ });
+ });
+
+ describe('error tracking feature disabled', () => {
+ beforeEach(() => {
+ mountComponent({ errorTrackingEnabled: false });
+ });
+
+ it('shows empty state', () => {
+ expect(wrapper.find(GlEmptyState).exists()).toBeTruthy();
+ expect(wrapper.find(GlLoadingIcon).exists()).toBeFalsy();
+ expect(wrapper.find(GlTable).exists()).toBeFalsy();
+ expect(wrapper.find(GlButton).exists()).toBeFalsy();
+ });
+ });
+});
diff --git a/spec/javascripts/error_tracking/store/mutation_spec.js b/spec/javascripts/error_tracking/store/mutation_spec.js
new file mode 100644
index 00000000000..8117104bdbc
--- /dev/null
+++ b/spec/javascripts/error_tracking/store/mutation_spec.js
@@ -0,0 +1,36 @@
+import mutations from '~/error_tracking/store/mutations';
+import * as types from '~/error_tracking/store/mutation_types';
+
+describe('Error tracking mutations', () => {
+ describe('SET_ERRORS', () => {
+ let state;
+
+ beforeEach(() => {
+ state = { errors: [] };
+ });
+
+ it('camelizes response', () => {
+ const errors = [
+ {
+ title: 'the title',
+ external_url: 'localhost:3456',
+ count: 100,
+ userCount: 10,
+ },
+ ];
+
+ mutations[types.SET_ERRORS](state, errors);
+
+ expect(state).toEqual({
+ errors: [
+ {
+ title: 'the title',
+ externalUrl: 'localhost:3456',
+ count: 100,
+ userCount: 10,
+ },
+ ],
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/jobs/components/sidebar_spec.js b/spec/javascripts/jobs/components/sidebar_spec.js
index b0bc16d7c64..3a02351460c 100644
--- a/spec/javascripts/jobs/components/sidebar_spec.js
+++ b/spec/javascripts/jobs/components/sidebar_spec.js
@@ -28,7 +28,7 @@ describe('Sidebar details block', () => {
store,
});
- expect(vm.$el.querySelector('.js-retry-job')).toBeNull();
+ expect(vm.$el.querySelector('.js-retry-button')).toBeNull();
});
});
@@ -70,7 +70,7 @@ describe('Sidebar details block', () => {
});
it('should render link to retry job', () => {
- expect(vm.$el.querySelector('.js-retry-job').getAttribute('href')).toEqual(job.retry_path);
+ expect(vm.$el.querySelector('.js-retry-button').getAttribute('href')).toEqual(job.retry_path);
});
it('should render link to cancel job', () => {
diff --git a/spec/javascripts/jobs/store/getters_spec.js b/spec/javascripts/jobs/store/getters_spec.js
index 4195d9d3680..7931b2af79f 100644
--- a/spec/javascripts/jobs/store/getters_spec.js
+++ b/spec/javascripts/jobs/store/getters_spec.js
@@ -8,30 +8,6 @@ describe('Job Store Getters', () => {
localState = state();
});
- describe('headerActions', () => {
- describe('with new issue path', () => {
- it('returns an array with action to create a new issue', () => {
- localState.job.new_issue_path = 'issues/new';
-
- expect(getters.headerActions(localState)).toEqual([
- {
- label: 'New issue',
- path: localState.job.new_issue_path,
- cssClass:
- 'js-new-issue btn btn-success btn-inverted d-none d-md-block d-lg-block d-xl-block',
- type: 'link',
- },
- ]);
- });
- });
-
- describe('without new issue path', () => {
- it('returns an empty array', () => {
- expect(getters.headerActions(localState)).toEqual([]);
- });
- });
- });
-
describe('headerTime', () => {
describe('when the job has started key', () => {
it('returns started key', () => {
diff --git a/spec/javascripts/lib/utils/common_utils_spec.js b/spec/javascripts/lib/utils/common_utils_spec.js
index f320f232687..0dc7e93539a 100644
--- a/spec/javascripts/lib/utils/common_utils_spec.js
+++ b/spec/javascripts/lib/utils/common_utils_spec.js
@@ -347,20 +347,31 @@ describe('common_utils', () => {
});
describe('parseBoolean', () => {
+ const { parseBoolean } = commonUtils;
+
it('returns true for "true"', () => {
- expect(commonUtils.parseBoolean('true')).toEqual(true);
+ expect(parseBoolean('true')).toEqual(true);
});
it('returns false for "false"', () => {
- expect(commonUtils.parseBoolean('false')).toEqual(false);
+ expect(parseBoolean('false')).toEqual(false);
});
it('returns false for "something"', () => {
- expect(commonUtils.parseBoolean('something')).toEqual(false);
+ expect(parseBoolean('something')).toEqual(false);
});
it('returns false for null', () => {
- expect(commonUtils.parseBoolean(null)).toEqual(false);
+ expect(parseBoolean(null)).toEqual(false);
+ });
+
+ it('is idempotent', () => {
+ const input = ['true', 'false', 'something', null];
+ input.forEach(value => {
+ const result = parseBoolean(value);
+
+ expect(parseBoolean(result)).toBe(result);
+ });
});
});
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 2119a3b927a..e387367d1a2 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
@@ -1,11 +1,12 @@
import Vue from 'vue';
import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue';
+import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
+import { createLocalVue, shallowMount } from '@vue/test-utils';
const commitMessage = 'This is the commit message';
const commitMessageWithDescription = 'This is the commit message description';
-const createComponent = (customConfig = {}) => {
- const Component = Vue.extend(ReadyToMerge);
+const createTestMr = customConfig => {
const mr = {
isPipelineActive: false,
pipeline: null,
@@ -16,6 +17,7 @@ const createComponent = (customConfig = {}) => {
hasCI: false,
ciStatus: null,
sha: '12345678',
+ squash: false,
commitMessage,
commitMessageWithDescription,
shouldRemoveSourceBranch: true,
@@ -24,14 +26,23 @@ const createComponent = (customConfig = {}) => {
Object.assign(mr, customConfig.mr);
- const service = {
- merge() {},
- poll() {},
- };
+ return mr;
+};
+
+const createTestService = () => ({
+ merge() {},
+ poll() {},
+});
+
+const createComponent = (customConfig = {}) => {
+ const Component = Vue.extend(ReadyToMerge);
return new Component({
el: document.createElement('div'),
- propsData: { mr, service },
+ propsData: {
+ mr: createTestMr(customConfig),
+ service: createTestService(),
+ },
});
};
@@ -612,6 +623,47 @@ describe('ReadyToMerge', () => {
});
});
+ describe('Squash checkbox component', () => {
+ let wrapper;
+ const localVue = createLocalVue();
+
+ const createLocalComponent = (customConfig = {}) => {
+ wrapper = shallowMount(localVue.extend(ReadyToMerge), {
+ localVue,
+ propsData: {
+ mr: createTestMr(customConfig),
+ service: createTestService(),
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findCheckboxElement = () => wrapper.find(SquashBeforeMerge);
+
+ it('should be rendered when squash before merge is enabled and there is more than 1 commit', () => {
+ createLocalComponent({
+ mr: { commitsCount: 2, enableSquashBeforeMerge: true },
+ });
+
+ expect(findCheckboxElement().exists()).toBeTruthy();
+ });
+
+ it('should not be rendered when squash before merge is disabled', () => {
+ createLocalComponent({ mr: { commitsCount: 2, enableSquashBeforeMerge: false } });
+
+ expect(findCheckboxElement().exists()).toBeFalsy();
+ });
+
+ it('should not be rendered when there is only 1 commit', () => {
+ createLocalComponent({ mr: { commitsCount: 1, enableSquashBeforeMerge: true } });
+
+ expect(findCheckboxElement().exists()).toBeFalsy();
+ });
+ });
+
describe('Merge controls', () => {
describe('when allowed to merge', () => {
beforeEach(() => {
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
new file mode 100644
index 00000000000..d6d8eecfcb9
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_squash_before_merge_spec.js
@@ -0,0 +1,100 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue';
+
+const localVue = createLocalVue();
+
+describe('Squash before merge component', () => {
+ let wrapper;
+
+ const createComponent = props => {
+ wrapper = shallowMount(localVue.extend(SquashBeforeMerge), {
+ localVue,
+ sync: false,
+ propsData: {
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ describe('checkbox', () => {
+ const findCheckbox = () => wrapper.find('.qa-squash-checkbox');
+
+ it('is unchecked if passed value prop is false', () => {
+ createComponent({
+ value: false,
+ });
+
+ expect(findCheckbox().element.checked).toBeFalsy();
+ });
+
+ it('is checked if passed value prop is true', () => {
+ createComponent({
+ value: true,
+ });
+
+ expect(findCheckbox().element.checked).toBeTruthy();
+ });
+
+ it('changes value on click', done => {
+ createComponent({
+ value: false,
+ });
+
+ findCheckbox().element.checked = true;
+
+ findCheckbox().trigger('change');
+
+ wrapper.vm.$nextTick(() => {
+ expect(findCheckbox().element.checked).toBeTruthy();
+ done();
+ });
+ });
+
+ it('is disabled if isDisabled prop is true', () => {
+ createComponent({
+ value: false,
+ isDisabled: true,
+ });
+
+ expect(findCheckbox().attributes('disabled')).toBeTruthy();
+ });
+ });
+
+ describe('about link', () => {
+ it('is not rendered if no help path is passed', () => {
+ createComponent({
+ value: false,
+ });
+
+ const aboutLink = wrapper.find('a');
+
+ expect(aboutLink.exists()).toBeFalsy();
+ });
+
+ it('is rendered if help path is passed', () => {
+ createComponent({
+ value: false,
+ helpPath: 'test-path',
+ });
+
+ const aboutLink = wrapper.find('a');
+
+ expect(aboutLink.exists()).toBeTruthy();
+ });
+
+ it('should have a correct help path if passed', () => {
+ createComponent({
+ value: false,
+ helpPath: 'test-path',
+ });
+
+ const aboutLink = wrapper.find('a');
+
+ expect(aboutLink.attributes('href')).toEqual('test-path');
+ });
+ });
+});
diff --git a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js
index 8187b3204b1..12ee804f668 100644
--- a/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js
+++ b/spec/javascripts/vue_shared/components/markdown/suggestion_diff_header_spec.js
@@ -31,6 +31,12 @@ describe('Suggestion Diff component', () => {
expect(header.innerHTML.includes('Suggested change')).toBe(true);
});
+ it('renders a help button', () => {
+ const helpBtn = vm.$el.querySelector('.js-help-btn');
+
+ expect(helpBtn).not.toBeNull();
+ });
+
it('renders an apply button', () => {
const applyBtn = vm.$el.querySelector('.qa-apply-btn');
diff --git a/spec/lib/bitbucket_server/client_spec.rb b/spec/lib/bitbucket_server/client_spec.rb
index 5de0a9a65b5..b021e69800a 100644
--- a/spec/lib/bitbucket_server/client_spec.rb
+++ b/spec/lib/bitbucket_server/client_spec.rb
@@ -17,12 +17,6 @@ describe BitbucketServer::Client do
subject.pull_requests(project, repo_slug)
end
-
- it 'throws an exception when connection fails' do
- allow(BitbucketServer::Collection).to receive(:new).and_raise(OpenSSL::SSL::SSLError)
-
- expect { subject.pull_requests(project, repo_slug) }.to raise_error(described_class::ServerError)
- end
end
describe '#activities' do
diff --git a/spec/lib/bitbucket_server/connection_spec.rb b/spec/lib/bitbucket_server/connection_spec.rb
index b5da4cb1a49..ab8a5b89608 100644
--- a/spec/lib/bitbucket_server/connection_spec.rb
+++ b/spec/lib/bitbucket_server/connection_spec.rb
@@ -26,6 +26,12 @@ describe BitbucketServer::Connection do
expect { subject.get(url) }.to raise_error(described_class::ConnectionError)
end
+
+ it 'throws an exception upon a network error' do
+ WebMock.stub_request(:get, url).with(headers: { 'Accept' => 'application/json' }).to_raise(OpenSSL::SSL::SSLError)
+
+ expect { subject.get(url) }.to raise_error(described_class::ConnectionError)
+ end
end
describe '#post' do
@@ -42,6 +48,12 @@ describe BitbucketServer::Connection do
expect { subject.post(url, payload) }.to raise_error(described_class::ConnectionError)
end
+
+ it 'throws an exception upon a network error' do
+ WebMock.stub_request(:post, url).with(headers: { 'Accept' => 'application/json' }).to_raise(OpenSSL::SSL::SSLError)
+
+ expect { subject.post(url, payload) }.to raise_error(described_class::ConnectionError)
+ end
end
describe '#delete' do
@@ -63,6 +75,12 @@ describe BitbucketServer::Connection do
expect { subject.delete(:branches, branch_path, payload) }.to raise_error(described_class::ConnectionError)
end
+
+ it 'throws an exception upon a network error' do
+ WebMock.stub_request(:delete, branch_url).with(headers: headers).to_raise(OpenSSL::SSL::SSLError)
+
+ expect { subject.delete(:branches, branch_path, payload) }.to raise_error(described_class::ConnectionError)
+ end
end
end
end
diff --git a/spec/lib/feature_spec.rb b/spec/lib/feature_spec.rb
index 9d56c62ae57..630732614b2 100644
--- a/spec/lib/feature_spec.rb
+++ b/spec/lib/feature_spec.rb
@@ -182,4 +182,18 @@ describe Feature do
expect(described_class.disabled?(:enabled_feature_flag)).to be_falsey
end
end
+
+ describe Feature::Target do
+ describe '#targets' do
+ let(:project) { create(:project) }
+ let(:user_name) { project.owner.username }
+
+ subject { described_class.new(user: user_name, project: project.full_path) }
+
+ it 'returns all found targets' do
+ expect(subject.targets).to be_an(Array)
+ expect(subject.targets).to eq([project.owner, project])
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/access/branch_protection_spec.rb b/spec/lib/gitlab/access/branch_protection_spec.rb
new file mode 100644
index 00000000000..7f2979e8e28
--- /dev/null
+++ b/spec/lib/gitlab/access/branch_protection_spec.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Access::BranchProtection do
+ describe '#any?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:level, :result) do
+ Gitlab::Access::PROTECTION_NONE | false
+ Gitlab::Access::PROTECTION_DEV_CAN_PUSH | true
+ Gitlab::Access::PROTECTION_DEV_CAN_MERGE | true
+ Gitlab::Access::PROTECTION_FULL | true
+ end
+
+ with_them do
+ it { expect(described_class.new(level).any?).to eq(result) }
+ end
+ end
+
+ describe '#developer_can_push?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:level, :result) do
+ Gitlab::Access::PROTECTION_NONE | false
+ Gitlab::Access::PROTECTION_DEV_CAN_PUSH | true
+ Gitlab::Access::PROTECTION_DEV_CAN_MERGE | false
+ Gitlab::Access::PROTECTION_FULL | false
+ end
+
+ with_them do
+ it do
+ expect(described_class.new(level).developer_can_push?).to eq(result)
+ end
+ end
+ end
+
+ describe '#developer_can_merge?' do
+ using RSpec::Parameterized::TableSyntax
+
+ where(:level, :result) do
+ Gitlab::Access::PROTECTION_NONE | false
+ Gitlab::Access::PROTECTION_DEV_CAN_PUSH | false
+ Gitlab::Access::PROTECTION_DEV_CAN_MERGE | true
+ Gitlab::Access::PROTECTION_FULL | false
+ end
+
+ with_them do
+ it do
+ expect(described_class.new(level).developer_can_merge?).to eq(result)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
index 53c071f0268..510a0074554 100644
--- a/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
+++ b/spec/lib/gitlab/background_migration/backfill_project_repositories_spec.rb
@@ -2,6 +2,7 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
let(:group) { create(:group, name: 'foo', path: 'foo') }
@@ -34,6 +35,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
let!(:project_hashed_storage_2) { create(:project, name: 'bar', path: 'bar', namespace: group, storage_version: 2) }
let!(:project_legacy_storage_3) { create(:project, name: 'baz', path: 'baz', namespace: group, storage_version: 0) }
let!(:project_legacy_storage_4) { create(:project, name: 'zoo', path: 'zoo', namespace: group, storage_version: nil) }
+ let!(:project_legacy_storage_5) { create(:project, name: 'test', path: 'test', namespace: group, storage_version: nil) }
describe '.on_hashed_storage' do
it 'finds projects with repository on hashed storage' do
@@ -47,7 +49,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
it 'finds projects with repository on legacy storage' do
projects = described_class.on_legacy_storage.pluck(:id)
- expect(projects).to match_array([project_legacy_storage_3.id, project_legacy_storage_4.id])
+ expect(projects).to match_array([project_legacy_storage_3.id, project_legacy_storage_4.id, project_legacy_storage_5.id])
end
end
@@ -58,7 +60,7 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
projects = described_class.without_project_repository.pluck(:id)
- expect(projects).to contain_exactly(project_hashed_storage_2.id, project_legacy_storage_4.id)
+ expect(projects).to contain_exactly(project_hashed_storage_2.id, project_legacy_storage_4.id, project_legacy_storage_5.id)
end
end
@@ -78,17 +80,27 @@ describe Gitlab::BackgroundMigration::BackfillProjectRepositories do
expect(project.disk_path).to eq(project_legacy_storage_3.disk_path)
end
+ it 'returns the correct disk_path using the route entry' do
+ project_legacy_storage_5.route.update(path: 'zoo/new-test')
+ project = described_class.find(project_legacy_storage_5.id)
+
+ expect(project.disk_path).to eq('zoo/new-test')
+ end
+
it 'raises OrphanedNamespaceError when any parent namespace does not exist' do
subgroup = create(:group, parent: group)
project_orphaned_namespace = create(:project, name: 'baz', path: 'baz', namespace: subgroup, storage_version: nil)
subgroup.update_column(:parent_id, Namespace.maximum(:id).succ)
project = described_class.find(project_orphaned_namespace.id)
+ project.route.destroy
+ subgroup.route.destroy
- expect { project.disk_path }
+ expect { project.reload.disk_path }
.to raise_error(Gitlab::BackgroundMigration::BackfillProjectRepositories::OrphanedNamespaceError)
end
end
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb b/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb
index f92acf61682..f974dc8fda2 100644
--- a/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb
+++ b/spec/lib/gitlab/background_migration/create_gpg_key_subkeys_from_gpg_keys_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::BackgroundMigration::CreateGpgKeySubkeysFromGpgKeys, :migration, schema: 20171005130944 do
context 'when GpgKey exists' do
- let!(:gpg_key) { create(:gpg_key, key: GpgHelpers::User3.public_key) }
+ let!(:gpg_key) { create(:gpg_key, key: GpgHelpers::User3.public_key) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
before do
GpgKeySubkey.destroy_all # rubocop: disable DestroyAll
diff --git a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb
index 1969aed51da..27281333348 100644
--- a/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb
+++ b/spec/lib/gitlab/background_migration/delete_diff_files_spec.rb
@@ -1,5 +1,6 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, schema: 20180619121030 do
describe '#perform' do
context 'when diff files can be deleted' do
@@ -71,3 +72,4 @@ describe Gitlab::BackgroundMigration::DeleteDiffFiles, :migration, :sidekiq, sch
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
index 5dce3fcbcb6..bc71a90605a 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
@@ -1,5 +1,6 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :migration, schema: 20171114162227 do
include GitHelpers
@@ -324,3 +325,4 @@ describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :m
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
index 5432d270555..188969951a6 100644
--- a/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_events_to_push_event_payloads_spec.rb
@@ -1,5 +1,6 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads::Event, :migration, schema: 20170608152748 do
describe '#commit_title' do
it 'returns nil when there are no commits' do
@@ -429,3 +430,4 @@ describe Gitlab::BackgroundMigration::MigrateEventsToPushEventPayloads, :migrati
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb
index 021e1d14b18..ea8bdd48e72 100644
--- a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb
@@ -1,5 +1,6 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder, :delete do
let(:migration) { described_class.new }
@@ -17,3 +18,4 @@ describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder, :delete d
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb b/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb
index 2e77e80ee46..593486fc56c 100644
--- a/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb
+++ b/spec/lib/gitlab/background_migration/move_personal_snippet_files_spec.rb
@@ -1,5 +1,6 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::MovePersonalSnippetFiles do
let(:test_dir) { File.join(Rails.root, 'tmp', 'tests', 'move_snippet_files_test') }
let(:old_uploads_dir) { File.join('uploads', 'system', 'personal_snippet') }
@@ -70,3 +71,4 @@ describe Gitlab::BackgroundMigration::MovePersonalSnippetFiles do
"[an upload](#{path})"
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
index 4f1b01eed41..8e3cb36d313 100644
--- a/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_cluster_kubernetes_namespace_table_spec.rb
@@ -2,6 +2,7 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, :migration, schema: 20181022173835 do
let(:migration) { described_class.new }
let(:clusters) { create_list(:cluster, 10, :project, :provided_by_gcp) }
@@ -95,3 +96,4 @@ describe Gitlab::BackgroundMigration::PopulateClusterKubernetesNamespaceTable, :
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb
index 6ab126ad39a..3e009fed0f1 100644
--- a/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_external_pipeline_source_spec.rb
@@ -2,6 +2,7 @@
require 'spec_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::PopulateExternalPipelineSource, :migration, schema: 20180916011959 do
let(:migration) { described_class.new }
@@ -65,3 +66,4 @@ describe Gitlab::BackgroundMigration::PopulateExternalPipelineSource, :migration
it_behaves_like 'no changes'
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
index e99257e3481..ff1bd9f7850 100644
--- a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
@@ -1,5 +1,6 @@
require 'rails_helper'
+# rubocop:disable RSpec/FactoriesInMigrationSpecs
describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData, :migration, schema: 20171128214150 do
# commits_count attribute is added in a next migration
before do
@@ -128,3 +129,4 @@ describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData,
end
end
end
+# rubocop:enable RSpec/FactoriesInMigrationSpecs
diff --git a/spec/lib/gitlab/ci/config/entry/global_spec.rb b/spec/lib/gitlab/ci/config/entry/global_spec.rb
index 941ef33c8a4..7651f594a4c 100644
--- a/spec/lib/gitlab/ci/config/entry/global_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/global_spec.rb
@@ -160,8 +160,7 @@ describe Gitlab::Ci::Config::Entry::Global do
variables: { 'VAR' => 'value' },
ignore: false,
after_script: ['make clean'],
- only: { refs: %w[branches tags] },
- except: {} },
+ only: { refs: %w[branches tags] } },
spinach: { name: :spinach,
before_script: [],
script: %w[spinach],
@@ -172,8 +171,7 @@ describe Gitlab::Ci::Config::Entry::Global do
variables: {},
ignore: false,
after_script: ['make clean'],
- only: { refs: %w[branches tags] },
- except: {} }
+ only: { refs: %w[branches tags] } }
)
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb
index 3d0b98eb238..0560eb42e4d 100644
--- a/spec/lib/gitlab/ci/config/entry/job_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb
@@ -258,8 +258,7 @@ describe Gitlab::Ci::Config::Entry::Job do
stage: 'test',
ignore: false,
after_script: %w[cleanup],
- only: { refs: %w[branches tags] },
- except: {})
+ only: { refs: %w[branches tags] })
end
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
index d97be76f0e0..271ee30df3c 100644
--- a/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/jobs_spec.rb
@@ -67,14 +67,12 @@ describe Gitlab::Ci::Config::Entry::Jobs do
script: %w[rspec],
ignore: false,
stage: 'test',
- only: { refs: %w[branches tags] },
- except: {} },
+ only: { refs: %w[branches tags] } },
spinach: { name: :spinach,
script: %w[spinach],
ignore: false,
stage: 'test',
- only: { refs: %w[branches tags] },
- except: {} })
+ only: { refs: %w[branches tags] } })
end
end
diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
index 83001b7fdd8..1c987e13a9a 100644
--- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb
+++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb
@@ -168,8 +168,33 @@ describe Gitlab::Ci::Config::Entry::Policy do
end
end
+ describe '#value' do
+ context 'when default value has been provided' do
+ before do
+ entry.default = { refs: %w[branches tags] }
+ end
+
+ context 'when user overrides default values' do
+ let(:config) { { refs: %w[feature], variables: %w[$VARIABLE] } }
+
+ it 'does not include default values' do
+ expect(entry.value).to eq config
+ end
+ end
+
+ context 'when default value has not been defined' do
+ let(:config) { { variables: %w[$VARIABLE] } }
+
+ it 'includes default values' do
+ expect(entry.value).to eq(refs: %w[branches tags],
+ variables: %w[$VARIABLE])
+ end
+ end
+ end
+ end
+
describe '.default' do
- it 'does not have a default value' do
+ it 'does not have default policy' do
expect(described_class.default).to be_nil
end
end
diff --git a/spec/lib/gitlab/config/entry/configurable_spec.rb b/spec/lib/gitlab/config/entry/configurable_spec.rb
index 85a7cf1d241..37e38e49c0d 100644
--- a/spec/lib/gitlab/config/entry/configurable_spec.rb
+++ b/spec/lib/gitlab/config/entry/configurable_spec.rb
@@ -7,6 +7,10 @@ describe Gitlab::Config::Entry::Configurable do
end
end
+ before do
+ allow(entry).to receive(:default)
+ end
+
describe 'validations' do
context 'when entry is a hash' do
let(:instance) { entry.new(key: 'value') }
@@ -26,9 +30,11 @@ describe Gitlab::Config::Entry::Configurable do
end
describe 'configured entries' do
+ let(:entry_class) { double('entry_class', default: nil) }
+
before do
- entry.class_eval do
- entry :object, Object, description: 'test object'
+ entry.class_exec(entry_class) do |entry_class|
+ entry :object, entry_class, description: 'test object'
end
end
diff --git a/spec/lib/gitlab/git/bundle_file_spec.rb b/spec/lib/gitlab/git/bundle_file_spec.rb
new file mode 100644
index 00000000000..ff7c981dadd
--- /dev/null
+++ b/spec/lib/gitlab/git/bundle_file_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe Gitlab::Git::BundleFile do
+ describe '.check!' do
+ let(:valid_bundle) { Tempfile.new }
+ let(:valid_bundle_path) { valid_bundle.path }
+ let(:invalid_bundle_path) { Rails.root.join('spec/fixtures/malicious.bundle') }
+
+ after do
+ valid_bundle.close!
+ end
+
+ it 'returns nil for a valid bundle' do
+ valid_bundle.write("# v2 git bundle\nfoo bar baz\n")
+ valid_bundle.close
+
+ expect(described_class.check!(valid_bundle_path)).to be_nil
+ end
+
+ it 'raises an exception for an invalid bundle' do
+ expect do
+ described_class.check!(invalid_bundle_path)
+ end.to raise_error(described_class::InvalidBundleError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index 852ee9c96af..a19e3e84f83 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1753,22 +1753,23 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#create_from_bundle' do
- let(:bundle_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") }
+ let(:valid_bundle_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") }
+ let(:malicious_bundle_path) { Rails.root.join('spec/fixtures/malicious.bundle') }
let(:project) { create(:project) }
let(:imported_repo) { project.repository.raw }
before do
- expect(repository.bundle_to_disk(bundle_path)).to be_truthy
+ expect(repository.bundle_to_disk(valid_bundle_path)).to be_truthy
end
after do
- FileUtils.rm_rf(bundle_path)
+ FileUtils.rm_rf(valid_bundle_path)
end
it 'creates a repo from a bundle file' do
expect(imported_repo).not_to exist
- result = imported_repo.create_from_bundle(bundle_path)
+ result = imported_repo.create_from_bundle(valid_bundle_path)
expect(result).to be_truthy
expect(imported_repo).to exist
@@ -1776,11 +1777,17 @@ describe Gitlab::Git::Repository, :seed_helper do
end
it 'creates a symlink to the global hooks dir' do
- imported_repo.create_from_bundle(bundle_path)
+ imported_repo.create_from_bundle(valid_bundle_path)
hooks_path = Gitlab::GitalyClient::StorageSettings.allow_disk_access { File.join(imported_repo.path, 'hooks') }
expect(File.readlink(hooks_path)).to eq(Gitlab.config.gitlab_shell.hooks_path)
end
+
+ it 'raises an error if the bundle is an attempted malicious payload' do
+ expect do
+ imported_repo.create_from_bundle(malicious_bundle_path)
+ end.to raise_error(::Gitlab::Git::BundleFile::InvalidBundleError)
+ end
end
describe '#checksum' do
diff --git a/spec/lib/gitlab/gon_helper_spec.rb b/spec/lib/gitlab/gon_helper_spec.rb
index c6f09ca2112..1ff2334bacf 100644
--- a/spec/lib/gitlab/gon_helper_spec.rb
+++ b/spec/lib/gitlab/gon_helper_spec.rb
@@ -29,4 +29,13 @@ describe Gitlab::GonHelper do
helper.push_frontend_feature_flag(:my_feature_flag, 10)
end
end
+
+ describe '#default_avatar_url' do
+ it 'returns an absolute URL' do
+ url = helper.default_avatar_url
+
+ expect(url).to match(/^http/)
+ expect(url).to match(/no_avatar.*png$/)
+ end
+ end
end
diff --git a/spec/lib/gitlab/middleware/read_only_spec.rb b/spec/lib/gitlab/middleware/read_only_spec.rb
index bdb1f34d2f6..24d49a049b6 100644
--- a/spec/lib/gitlab/middleware/read_only_spec.rb
+++ b/spec/lib/gitlab/middleware/read_only_spec.rb
@@ -101,16 +101,36 @@ describe Gitlab::Middleware::ReadOnly do
expect(subject).not_to disallow_request
end
- it 'expects requests to sidekiq admin to be allowed' do
- response = request.post('/admin/sidekiq')
+ context 'sidekiq admin requests' do
+ where(:mounted_at) do
+ [
+ '',
+ '/',
+ '/gitlab',
+ '/gitlab/',
+ '/gitlab/gitlab',
+ '/gitlab/gitlab/'
+ ]
+ end
- expect(response).not_to be_redirect
- expect(subject).not_to disallow_request
+ with_them do
+ before do
+ stub_config_setting(relative_url_root: mounted_at)
+ end
- response = request.get('/admin/sidekiq')
+ it 'allows requests' do
+ path = File.join(mounted_at, 'admin/sidekiq')
+ response = request.post(path)
- expect(response).not_to be_redirect
- expect(subject).not_to disallow_request
+ expect(response).not_to be_redirect
+ expect(subject).not_to disallow_request
+
+ response = request.get(path)
+
+ expect(response).not_to be_redirect
+ expect(subject).not_to disallow_request
+ end
+ end
end
where(:description, :path) do
diff --git a/spec/lib/gitlab/release_blog_post_spec.rb b/spec/lib/gitlab/release_blog_post_spec.rb
new file mode 100644
index 00000000000..2c987df3767
--- /dev/null
+++ b/spec/lib/gitlab/release_blog_post_spec.rb
@@ -0,0 +1,97 @@
+require 'spec_helper'
+
+describe Gitlab::ReleaseBlogPost do
+ describe '.blog_post_url' do
+ let(:releases_xml) do
+ <<~EOS
+ <?xml version='1.0' encoding='utf-8' ?>
+ <feed xmlns='http://www.w3.org/2005/Atom'>
+ <entry>
+ <release>11.2</release>
+ <id>https://about.gitlab.com/2018/08/22/gitlab-11-2-released/</id>
+ </entry>
+ <entry>
+ <release>11.1</release>
+ <id>https://about.gitlab.com/2018/07/22/gitlab-11-1-released/</id>
+ </entry>
+ <entry>
+ <release>11.0</release>
+ <id>https://about.gitlab.com/2018/06/22/gitlab-11-0-released/</id>
+ </entry>
+ <entry>
+ <release>10.8</release>
+ <id>https://about.gitlab.com/2018/05/22/gitlab-10-8-released/</id>
+ </entry>
+ </feed>
+ EOS
+ end
+
+ subject { described_class.send(:new).blog_post_url }
+
+ before do
+ stub_request(:get, 'https://about.gitlab.com/releases.xml')
+ .to_return(status: 200, headers: { 'content-type' => ['text/xml'] }, body: releases_xml)
+ end
+
+ context 'matches GitLab version to blog post url' do
+ it 'returns the correct url for major pre release' do
+ stub_const('Gitlab::VERSION', '11.0.0-pre')
+
+ expect(subject).to eql('https://about.gitlab.com/2018/05/22/gitlab-10-8-released/')
+ end
+
+ it 'returns the correct url for major release candidate' do
+ stub_const('Gitlab::VERSION', '11.0.0-rc3')
+
+ expect(subject).to eql('https://about.gitlab.com/2018/05/22/gitlab-10-8-released/')
+ end
+
+ it 'returns the correct url for major release' do
+ stub_const('Gitlab::VERSION', '11.0.0')
+
+ expect(subject).to eql('https://about.gitlab.com/2018/06/22/gitlab-11-0-released/')
+ end
+
+ it 'returns the correct url for minor pre release' do
+ stub_const('Gitlab::VERSION', '11.2.0-pre')
+
+ expect(subject).to eql('https://about.gitlab.com/2018/07/22/gitlab-11-1-released/')
+ end
+
+ it 'returns the correct url for minor release candidate' do
+ stub_const('Gitlab::VERSION', '11.2.0-rc3')
+
+ expect(subject).to eql('https://about.gitlab.com/2018/07/22/gitlab-11-1-released/')
+ end
+
+ it 'returns the correct url for minor release' do
+ stub_const('Gitlab::VERSION', '11.2.0')
+
+ expect(subject).to eql('https://about.gitlab.com/2018/08/22/gitlab-11-2-released/')
+ end
+
+ it 'returns the correct url for patch pre release' do
+ stub_const('Gitlab::VERSION', '11.2.1-pre')
+ expect(subject).to eql('https://about.gitlab.com/2018/08/22/gitlab-11-2-released/')
+ end
+
+ it 'returns the correct url for patch release candidate' do
+ stub_const('Gitlab::VERSION', '11.2.1-rc3')
+
+ expect(subject).to eql('https://about.gitlab.com/2018/08/22/gitlab-11-2-released/')
+ end
+
+ it 'returns the correct url for patch release' do
+ stub_const('Gitlab::VERSION', '11.2.1')
+
+ expect(subject).to eql('https://about.gitlab.com/2018/08/22/gitlab-11-2-released/')
+ end
+
+ it 'returns nil when no blog post is matched' do
+ stub_const('Gitlab::VERSION', '9.0.0')
+
+ expect(subject).to be(nil)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tracing/factory_spec.rb b/spec/lib/gitlab/tracing/factory_spec.rb
new file mode 100644
index 00000000000..945490f0988
--- /dev/null
+++ b/spec/lib/gitlab/tracing/factory_spec.rb
@@ -0,0 +1,43 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Tracing::Factory do
+ describe '.create_tracer' do
+ let(:service_name) { 'rspec' }
+
+ context "when tracing is not configured" do
+ it 'ignores null connection strings' do
+ expect(described_class.create_tracer(service_name, nil)).to be_nil
+ end
+
+ it 'ignores empty connection strings' do
+ expect(described_class.create_tracer(service_name, '')).to be_nil
+ end
+
+ it 'ignores unknown implementations' do
+ expect(described_class.create_tracer(service_name, 'opentracing://invalid_driver')).to be_nil
+ end
+
+ it 'ignores invalid connection strings' do
+ expect(described_class.create_tracer(service_name, 'open?tracing')).to be_nil
+ end
+ end
+
+ context "when tracing is configured with jaeger" do
+ let(:mock_tracer) { double('tracer') }
+
+ it 'processes default connections' do
+ expect(Gitlab::Tracing::JaegerFactory).to receive(:create_tracer).with(service_name, {}).and_return(mock_tracer)
+
+ expect(described_class.create_tracer(service_name, 'opentracing://jaeger')).to be(mock_tracer)
+ end
+
+ it 'processes connections with parameters' do
+ expect(Gitlab::Tracing::JaegerFactory).to receive(:create_tracer).with(service_name, { a: '1', b: '2', c: '3' }).and_return(mock_tracer)
+
+ expect(described_class.create_tracer(service_name, 'opentracing://jaeger?a=1&b=2&c=3')).to be(mock_tracer)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/tracing/jaeger_factory_spec.rb b/spec/lib/gitlab/tracing/jaeger_factory_spec.rb
new file mode 100644
index 00000000000..3bffeb28830
--- /dev/null
+++ b/spec/lib/gitlab/tracing/jaeger_factory_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Tracing::JaegerFactory do
+ describe '.create_tracer' do
+ let(:service_name) { 'rspec' }
+
+ it 'processes default connections' do
+ expect(described_class.create_tracer(service_name, {})).to respond_to(:active_span)
+ end
+
+ it 'handles debug options' do
+ expect(described_class.create_tracer(service_name, { debug: "1" })).to respond_to(:active_span)
+ end
+
+ it 'handles const sampler' do
+ expect(described_class.create_tracer(service_name, { sampler: "const", sampler_param: "1" })).to respond_to(:active_span)
+ end
+
+ it 'handles probabilistic sampler' do
+ expect(described_class.create_tracer(service_name, { sampler: "probabilistic", sampler_param: "0.5" })).to respond_to(:active_span)
+ end
+
+ it 'handles http_endpoint configurations' do
+ expect(described_class.create_tracer(service_name, { http_endpoint: "http://localhost:1234" })).to respond_to(:active_span)
+ end
+
+ it 'handles udp_endpoint configurations' do
+ expect(described_class.create_tracer(service_name, { udp_endpoint: "localhost:4321" })).to respond_to(:active_span)
+ end
+
+ it 'ignores invalid parameters' do
+ expect(described_class.create_tracer(service_name, { invalid: "true" })).to respond_to(:active_span)
+ end
+
+ it 'accepts the debug parameter when strict_parser is set' do
+ expect(described_class.create_tracer(service_name, { debug: "1", strict_parsing: "1" })).to respond_to(:active_span)
+ end
+
+ it 'rejects invalid parameters when strict_parser is set' do
+ expect { described_class.create_tracer(service_name, { invalid: "true", strict_parsing: "1" }) }.to raise_error(StandardError)
+ end
+ end
+end
diff --git a/spec/lib/gitlab_spec.rb b/spec/lib/gitlab_spec.rb
index d63f448883b..6ac3d115bc6 100644
--- a/spec/lib/gitlab_spec.rb
+++ b/spec/lib/gitlab_spec.rb
@@ -8,6 +8,7 @@ describe Gitlab do
expect(described_class.root).to eq(Pathname.new(File.expand_path('../..', __dir__)))
end
end
+
describe '.revision' do
let(:cmd) { %W[#{described_class.config.git.bin_path} log --pretty=format:%h -n 1] }
@@ -69,6 +70,82 @@ describe Gitlab do
end
end
+ describe '.final_release?' do
+ subject { described_class.final_release? }
+
+ context 'returns the corrent boolean value' do
+ it 'is false for a pre release' do
+ stub_const('Gitlab::VERSION', '11.0.0-pre')
+
+ expect(subject).to be false
+ end
+
+ it 'is false for a release candidate' do
+ stub_const('Gitlab::VERSION', '11.0.0-rc2')
+
+ expect(subject).to be false
+ end
+
+ it 'is true for a final release' do
+ stub_const('Gitlab::VERSION', '11.0.2')
+
+ expect(subject).to be true
+ end
+ end
+ end
+
+ describe '.minor_release' do
+ subject { described_class.minor_release }
+
+ it 'returns the minor release of the full GitLab version' do
+ stub_const('Gitlab::VERSION', '11.0.1-rc3')
+
+ expect(subject).to eql '11.0'
+ end
+ end
+
+ describe '.previous_release' do
+ subject { described_class.previous_release }
+
+ context 'it should return the previous release' do
+ it 'returns the previous major version when GitLab major version is not final' do
+ stub_const('Gitlab::VERSION', '11.0.1-pre')
+
+ expect(subject).to eql '10'
+ end
+
+ it 'returns the current minor version when the GitLab patch version is RC and > 0' do
+ stub_const('Gitlab::VERSION', '11.2.1-rc3')
+
+ expect(subject).to eql '11.2'
+ end
+
+ it 'returns the previous minor version when the GitLab patch version is RC and 0' do
+ stub_const('Gitlab::VERSION', '11.2.0-rc3')
+
+ expect(subject).to eql '11.1'
+ end
+ end
+ end
+
+ describe '.new_major_release?' do
+ subject { described_class.new_major_release? }
+
+ context 'returns the corrent boolean value' do
+ it 'is true when the minor version is 0 and the patch is a pre release' do
+ stub_const('Gitlab::VERSION', '11.0.1-pre')
+
+ expect(subject).to be true
+ end
+
+ it 'is false when the minor version is above 0' do
+ stub_const('Gitlab::VERSION', '11.2.1-rc3')
+
+ expect(subject).to be false
+ end
+ end
+ end
+
describe '.com?' do
it 'is true when on GitLab.com' do
stub_config_setting(url: 'https://gitlab.com')
diff --git a/spec/migrations/add_foreign_keys_to_todos_spec.rb b/spec/migrations/add_foreign_keys_to_todos_spec.rb
index bf2fa5c0f56..efd87173b9c 100644
--- a/spec/migrations/add_foreign_keys_to_todos_spec.rb
+++ b/spec/migrations/add_foreign_keys_to_todos_spec.rb
@@ -3,9 +3,11 @@ require Rails.root.join('db', 'migrate', '20180201110056_add_foreign_keys_to_tod
describe AddForeignKeysToTodos, :migration do
let(:todos) { table(:todos) }
+ let(:users) { table(:users) }
+ let(:projects) { table(:projects) }
- let(:project) { create(:project) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
- let(:user) { create(:user) } # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ let(:project) { projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1) }
+ let(:user) { users.create!(email: 'email@email.com', name: 'foo', username: 'foo', projects_limit: 0) }
context 'add foreign key on user_id' do
let!(:todo_with_user) { create_todo(user_id: user.id) }
diff --git a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
index b5980cb9ddb..651341906c2 100644
--- a/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
+++ b/spec/migrations/cleanup_namespaceless_pending_delete_projects_spec.rb
@@ -2,6 +2,8 @@ require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170502101023_cleanup_namespaceless_pending_delete_projects.rb')
describe CleanupNamespacelessPendingDeleteProjects, :migration, schema: 20180222043024 do
+ let(:projects) { table(:projects) }
+
before do
# Stub after_save callbacks that will fail when Project has no namespace
allow_any_instance_of(Project).to receive(:ensure_storage_path_exists).and_return(nil)
@@ -10,9 +12,9 @@ describe CleanupNamespacelessPendingDeleteProjects, :migration, schema: 20180222
describe '#up' do
it 'only cleans up pending delete projects' do
- create(:project) # rubocop:disable RSpec/FactoriesInMigrationSpecs
- create(:project, pending_delete: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs
- project = build(:project, pending_delete: true, namespace_id: nil) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1)
+ projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ee', namespace_id: 2, pending_delete: true)
+ project = Project.new(pending_delete: true, namespace_id: nil)
project.save(validate: false)
expect(NamespacelessProjectDestroyWorker).to receive(:bulk_perform_async).with([[project.id]])
@@ -21,8 +23,8 @@ describe CleanupNamespacelessPendingDeleteProjects, :migration, schema: 20180222
end
it 'does nothing when no pending delete projects without namespace found' do
- create(:project) # rubocop:disable RSpec/FactoriesInMigrationSpecs
- create(:project, pending_delete: true) # rubocop:disable RSpec/FactoriesInMigrationSpecs
+ projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ce', namespace_id: 1)
+ projects.create!(name: 'gitlab', path: 'gitlab-org/gitlab-ee', namespace_id: 2, pending_delete: true)
expect(NamespacelessProjectDestroyWorker).not_to receive(:bulk_perform_async)
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index de313a8ca36..52c347229c6 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -35,7 +35,7 @@ describe Clusters::Applications::Ingress do
let(:application) { create(:clusters_applications_ingress, :scheduled, version: '0.22.0') }
it 'updates the application version' do
- expect(application.reload.version).to eq('0.23.0')
+ expect(application.reload.version).to eq('1.1.2')
end
end
end
@@ -90,7 +90,7 @@ describe Clusters::Applications::Ingress do
it 'should be initialized with ingress arguments' do
expect(subject.name).to eq('ingress')
expect(subject.chart).to eq('stable/nginx-ingress')
- expect(subject.version).to eq('0.23.0')
+ expect(subject.version).to eq('1.1.2')
expect(subject).to be_rbac
expect(subject.files).to eq(ingress.files)
end
@@ -107,7 +107,7 @@ describe Clusters::Applications::Ingress do
let(:ingress) { create(:clusters_applications_ingress, :errored, version: 'nginx') }
it 'should be initialized with the locked version' do
- expect(subject.version).to eq('0.23.0')
+ expect(subject.version).to eq('1.1.2')
end
end
end
diff --git a/spec/models/concerns/manual_inverse_association_spec.rb b/spec/models/concerns/manual_inverse_association_spec.rb
index aad40883854..ff4a04ea573 100644
--- a/spec/models/concerns/manual_inverse_association_spec.rb
+++ b/spec/models/concerns/manual_inverse_association_spec.rb
@@ -32,10 +32,10 @@ describe ManualInverseAssociation do
.not_to exceed_query_limit(0)
end
- it 'passes arguments to the default association method, to allow reloading' do
+ it 'allows reloading the relation' do
query_count = ActiveRecord::QueryRecorder.new do
instance.manual_association
- instance.manual_association(true)
+ instance.reload_manual_association
end.count
expect(query_count).to eq(2)
diff --git a/spec/models/gpg_key_spec.rb b/spec/models/gpg_key_spec.rb
index 33e6f1de3d1..58a1d2e4ea2 100644
--- a/spec/models/gpg_key_spec.rb
+++ b/spec/models/gpg_key_spec.rb
@@ -199,7 +199,7 @@ describe GpgKey do
gpg_key.revoke
- expect(gpg_key.subkeys(true)).to be_blank
+ expect(gpg_key.subkeys.reload).to be_blank
end
it 'invalidates all signatures associated to the subkeys' do
diff --git a/spec/models/project_import_data_spec.rb b/spec/models/project_import_data_spec.rb
index e9910c0a5d1..fe47811f074 100644
--- a/spec/models/project_import_data_spec.rb
+++ b/spec/models/project_import_data_spec.rb
@@ -39,4 +39,15 @@ describe ProjectImportData do
expect(row.credentials).to eq({ 'number' => 10, 'foo' => 'bar' })
end
end
+
+ describe '#clear_credentials' do
+ it 'clears out the Hash' do
+ row = described_class.new
+
+ row.merge_credentials('number' => 10)
+ row.clear_credentials
+
+ expect(row.credentials).to eq({})
+ end
+ end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 397b4d7c61f..7a8dc59039e 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -209,9 +209,14 @@ describe Project do
it 'does not allow new projects beyond user limits' do
project2 = build(:project)
- allow(project2).to receive(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object)
+
+ allow(project2)
+ .to receive(:creator)
+ .and_return(
+ double(can_create_project?: false, projects_limit: 0).as_null_object
+ )
+
expect(project2).not_to be_valid
- expect(project2.errors[:limit_reached].first).to match(/Personal project creation is not allowed/)
end
describe 'wiki path conflict' do
@@ -3087,7 +3092,7 @@ describe Project do
context 'when the project is in a subgroup' do
let(:group) { create(:group, :nested) }
- it { is_expected.to be(false) }
+ it { is_expected.to be(true) }
end
end
@@ -4431,6 +4436,75 @@ describe Project do
end
end
+ describe '#check_personal_projects_limit' do
+ context 'when creating a project for a group' do
+ it 'does nothing' do
+ creator = build(:user)
+ project = build(:project, namespace: build(:group), creator: creator)
+
+ allow(creator)
+ .to receive(:can_create_project?)
+ .and_return(false)
+
+ project.check_personal_projects_limit
+
+ expect(project.errors).to be_empty
+ end
+ end
+
+ context 'when the user is not allowed to create a personal project' do
+ let(:user) { build(:user) }
+ let(:project) { build(:project, creator: user) }
+
+ before do
+ allow(user)
+ .to receive(:can_create_project?)
+ .and_return(false)
+ end
+
+ context 'when the project limit is zero' do
+ it 'adds a validation error' do
+ allow(user)
+ .to receive(:projects_limit)
+ .and_return(0)
+
+ project.check_personal_projects_limit
+
+ expect(project.errors[:limit_reached].first)
+ .to match(/Personal project creation is not allowed/)
+ end
+ end
+
+ context 'when the project limit is greater than zero' do
+ it 'adds a validation error' do
+ allow(user)
+ .to receive(:projects_limit)
+ .and_return(5)
+
+ project.check_personal_projects_limit
+
+ expect(project.errors[:limit_reached].first)
+ .to match(/Your project limit is 5 projects/)
+ end
+ end
+ end
+
+ context 'when the user is allowed to create personal projects' do
+ it 'does nothing' do
+ user = build(:user)
+ project = build(:project, creator: user)
+
+ allow(user)
+ .to receive(:can_create_project?)
+ .and_return(true)
+
+ project.check_personal_projects_limit
+
+ expect(project.errors).to be_empty
+ end
+ end
+ end
+
def rugged_config
rugged_repo(project.repository).config
end
diff --git a/spec/models/remote_mirror_spec.rb b/spec/models/remote_mirror_spec.rb
index 224bc9ed935..c06e9a08ab4 100644
--- a/spec/models/remote_mirror_spec.rb
+++ b/spec/models/remote_mirror_spec.rb
@@ -303,6 +303,25 @@ describe RemoteMirror, :mailer do
end
end
+ context '#url=' do
+ let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first }
+
+ it 'resets all the columns when URL changes' do
+ remote_mirror.update(last_error: Time.now,
+ last_update_at: Time.now,
+ last_successful_update_at: Time.now,
+ update_status: 'started',
+ error_notification_sent: true)
+
+ expect { remote_mirror.update_attribute(:url, 'http://new.example.com') }
+ .to change { remote_mirror.last_error }.to(nil)
+ .and change { remote_mirror.last_update_at }.to(nil)
+ .and change { remote_mirror.last_successful_update_at }.to(nil)
+ .and change { remote_mirror.update_status }.to('finished')
+ .and change { remote_mirror.error_notification_sent }.to(false)
+ end
+ end
+
context '#updated_since?' do
let(:remote_mirror) { create(:project, :repository, :remote_mirror).remote_mirrors.first }
let(:timestamp) { Time.now - 5.minutes }
diff --git a/spec/requests/api/features_spec.rb b/spec/requests/api/features_spec.rb
index 7d3eff7d32d..22a9e36ca31 100644
--- a/spec/requests/api/features_spec.rb
+++ b/spec/requests/api/features_spec.rb
@@ -129,6 +129,40 @@ describe API::Features do
end
end
+ context 'when enabling for a project by path' do
+ context 'when the project exists' do
+ let!(:project) { create(:project) }
+
+ it 'sets the feature gate' do
+ post api("/features/#{feature_name}", admin), params: { value: 'true', project: project.full_path }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response).to eq(
+ 'name' => 'my_feature',
+ 'state' => 'conditional',
+ 'gates' => [
+ { 'key' => 'boolean', 'value' => false },
+ { 'key' => 'actors', 'value' => ["Project:#{project.id}"] }
+ ])
+ end
+ end
+
+ context 'when the project does not exist' do
+ it 'sets no new values' do
+ post api("/features/#{feature_name}", admin), params: { value: 'true', project: 'mep/to/the/mep/mep' }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response).to eq(
+ "name" => "my_feature",
+ "state" => "off",
+ "gates" => [
+ { "key" => "boolean", "value" => false }
+ ]
+ )
+ end
+ end
+ end
+
it 'creates a feature with the given percentage if passed an integer' do
post api("/features/#{feature_name}", admin), params: { value: '50' }
diff --git a/spec/requests/api/import_github_spec.rb b/spec/requests/api/import_github_spec.rb
new file mode 100644
index 00000000000..aceff9b4aa6
--- /dev/null
+++ b/spec/requests/api/import_github_spec.rb
@@ -0,0 +1,56 @@
+require 'spec_helper'
+
+describe API::ImportGithub do
+ include ApiHelpers
+
+ let(:token) { "asdasd12345" }
+ let(:provider) { :github }
+ let(:access_params) { { github_access_token: token } }
+
+ describe "POST /import/github" do
+ let(:user) { create(:user) }
+ let(:project) { create(:project) }
+ let(:provider_username) { user.username }
+ let(:provider_user) { OpenStruct.new(login: provider_username) }
+ let(:provider_repo) do
+ OpenStruct.new(
+ name: 'vim',
+ full_name: "#{provider_username}/vim",
+ owner: OpenStruct.new(login: provider_username)
+ )
+ end
+
+ before do
+ Grape::Endpoint.before_each do |endpoint|
+ allow(endpoint).to receive(:client).and_return(double('client', user: provider_user, repo: provider_repo).as_null_object)
+ end
+ end
+
+ it 'returns 201 response when the project is imported successfully' do
+ allow(Gitlab::LegacyGithubImport::ProjectCreator)
+ .to receive(:new).with(provider_repo, provider_repo.name, user.namespace, user, access_params, type: provider)
+ .and_return(double(execute: project))
+
+ post api("/import/github", user), params: {
+ target_namespace: user.namespace_path,
+ personal_access_token: token,
+ repo_id: 1234
+ }
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response).to be_a Hash
+ expect(json_response['name']).to eq(project.name)
+ end
+
+ it 'returns 422 response when user can not create projects in the chosen namespace' do
+ other_namespace = create(:group, name: 'other_namespace')
+
+ post api("/import/github", user), params: {
+ target_namespace: other_namespace.name,
+ personal_access_token: token,
+ repo_id: 1234
+ }
+
+ expect(response).to have_gitlab_http_status(422)
+ end
+ end
+end
diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb
index 0fd465da4f8..ba7930f6c9d 100644
--- a/spec/requests/api/issues_spec.rb
+++ b/spec/requests/api/issues_spec.rb
@@ -92,12 +92,10 @@ describe API::Issues do
end
context "when authenticated" do
- let(:first_issue) { json_response.first }
-
it "returns an array of issues" do
get api("/issues", user)
- expect_paginated_array_response(size: 2)
+ expect_paginated_array_response([issue.id, closed_issue.id])
expect(json_response.first['title']).to eq(issue.title)
expect(json_response.last).to have_key('web_url')
end
@@ -105,23 +103,19 @@ describe API::Issues do
it 'returns an array of closed issues' do
get api('/issues', user), params: { state: :closed }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(closed_issue.id)
+ expect_paginated_array_response(closed_issue.id)
end
it 'returns an array of opened issues' do
get api('/issues', user), params: { state: :opened }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(issue.id)
+ expect_paginated_array_response(issue.id)
end
it 'returns an array of all issues' do
get api('/issues', user), params: { state: :all }
- expect_paginated_array_response(size: 2)
- expect(first_issue['id']).to eq(issue.id)
- expect(json_response.second['id']).to eq(closed_issue.id)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'returns issues assigned to me' do
@@ -129,8 +123,7 @@ describe API::Issues do
get api('/issues', user2), params: { scope: 'assigned_to_me' }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
it 'returns issues assigned to me (kebab-case)' do
@@ -138,8 +131,7 @@ describe API::Issues do
get api('/issues', user2), params: { scope: 'assigned-to-me' }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
it 'returns issues authored by the given author id' do
@@ -147,8 +139,7 @@ describe API::Issues do
get api('/issues', user), params: { author_id: user2.id, scope: 'all' }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
it 'returns issues assigned to the given assignee id' do
@@ -156,8 +147,7 @@ describe API::Issues do
get api('/issues', user), params: { assignee_id: user2.id, scope: 'all' }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
it 'returns issues authored by the given author id and assigned to the given assignee id' do
@@ -165,8 +155,7 @@ describe API::Issues do
get api('/issues', user), params: { author_id: user2.id, assignee_id: user2.id, scope: 'all' }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
it 'returns issues with no assignee' do
@@ -174,8 +163,7 @@ describe API::Issues do
get api('/issues', user), params: { assignee_id: 0, scope: 'all' }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
it 'returns issues with no assignee' do
@@ -183,8 +171,7 @@ describe API::Issues do
get api('/issues', user), params: { assignee_id: 'None', scope: 'all' }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
it 'returns issues with any assignee' do
@@ -193,18 +180,17 @@ describe API::Issues do
get api('/issues', user), params: { assignee_id: 'Any', scope: 'all' }
- expect_paginated_array_response(size: 3)
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end
it 'returns issues reacted by the authenticated user' do
issue2 = create(:issue, project: project, author: user, assignees: [user])
create(:award_emoji, awardable: issue2, user: user2, name: 'star')
-
create(:award_emoji, awardable: issue, user: user2, name: 'thumbsup')
get api('/issues', user2), params: { my_reaction_emoji: 'Any', scope: 'all' }
- expect_paginated_array_response(size: 2)
+ expect_paginated_array_response([issue2.id, issue.id])
end
it 'returns issues not reacted by the authenticated user' do
@@ -213,21 +199,19 @@ describe API::Issues do
get api('/issues', user2), params: { my_reaction_emoji: 'None', scope: 'all' }
- expect_paginated_array_response(size: 2)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'returns issues matching given search string for title' do
get api("/issues", user), params: { search: issue.title }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(issue.id)
+ expect_paginated_array_response(issue.id)
end
it 'returns issues matching given search string for description' do
get api("/issues", user), params: { search: issue.description }
- expect_paginated_array_response(size: 1)
- expect(first_issue['id']).to eq(issue.id)
+ expect_paginated_array_response(issue.id)
end
context 'filtering before a specific date' do
@@ -236,15 +220,13 @@ describe API::Issues do
it 'returns issues created before a specific date' do
get api('/issues?created_before=2000-01-02T00:00:00.060Z', user)
- expect(json_response.size).to eq(1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
it 'returns issues updated before a specific date' do
get api('/issues?updated_before=2000-01-02T00:00:00.060Z', user)
- expect(json_response.size).to eq(1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
end
@@ -254,23 +236,21 @@ describe API::Issues do
it 'returns issues created after a specific date' do
get api("/issues?created_after=#{issue2.created_at}", user)
- expect(json_response.size).to eq(1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
it 'returns issues updated after a specific date' do
get api("/issues?updated_after=#{issue2.updated_at}", user)
- expect(json_response.size).to eq(1)
- expect(first_issue['id']).to eq(issue2.id)
+ expect_paginated_array_response(issue2.id)
end
end
it 'returns an array of labeled issues' do
get api("/issues", user), params: { labels: label.title }
- expect_paginated_array_response(size: 1)
- expect(first_issue['labels']).to eq([label.title])
+ expect_paginated_array_response(issue.id)
+ expect(json_response.first['labels']).to eq([label.title])
end
it 'returns an array of labeled issues when all labels matches' do
@@ -282,20 +262,20 @@ describe API::Issues do
get api("/issues", user), params: { labels: "#{label.title},#{label_b.title},#{label_c.title}" }
- expect_paginated_array_response(size: 1)
+ expect_paginated_array_response(issue.id)
expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title])
end
it 'returns an empty array if no issue matches labels' do
get api('/issues', user), params: { labels: 'foo,bar' }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an array of labeled issues matching given state' do
get api("/issues", user), params: { labels: label.title, state: :opened }
- expect_paginated_array_response(size: 1)
+ expect_paginated_array_response(issue.id)
expect(json_response.first['labels']).to eq([label.title])
expect(json_response.first['state']).to eq('opened')
end
@@ -303,112 +283,96 @@ describe API::Issues do
it 'returns an empty array if no issue matches labels and state filters' do
get api("/issues", user), params: { labels: label.title, state: :closed }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an array of issues with any label' do
get api("/issues", user), params: { labels: IssuesFinder::FILTER_ANY }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(issue.id)
+ expect_paginated_array_response(issue.id)
end
it 'returns an array of issues with no label' do
get api("/issues", user), params: { labels: IssuesFinder::FILTER_NONE }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(closed_issue.id)
+ expect_paginated_array_response(closed_issue.id)
end
it 'returns an array of issues with no label when using the legacy No+Label filter' do
get api("/issues", user), params: { labels: "No Label" }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(closed_issue.id)
+ expect_paginated_array_response(closed_issue.id)
end
it 'returns an empty array if no issue matches milestone' do
get api("/issues?milestone=#{empty_milestone.title}", user)
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an empty array if milestone does not exist' do
get api("/issues?milestone=foo", user)
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an array of issues in given milestone' do
get api("/issues?milestone=#{milestone.title}", user)
- expect_paginated_array_response(size: 2)
- expect(json_response.first['id']).to eq(issue.id)
- expect(json_response.second['id']).to eq(closed_issue.id)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'returns an array of issues matching state in milestone' do
get api("/issues?milestone=#{milestone.title}"\
'&state=closed', user)
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(closed_issue.id)
+ expect_paginated_array_response(closed_issue.id)
end
it 'returns an array of issues with no milestone' do
get api("/issues?milestone=#{no_milestone_title}", author)
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(confidential_issue.id)
+ expect_paginated_array_response(confidential_issue.id)
end
it 'returns an array of issues found by iids' do
get api('/issues', user), params: { iids: [closed_issue.iid] }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(closed_issue.id)
+ expect_paginated_array_response(closed_issue.id)
end
it 'returns an empty array if iid does not exist' do
get api("/issues", user), params: { iids: [99999] }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'sorts by created_at descending by default' do
get api('/issues', user)
- response_dates = json_response.map { |issue| issue['created_at'] }
-
- expect_paginated_array_response(size: 2)
- expect(response_dates).to eq(response_dates.sort.reverse)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'sorts ascending when requested' do
get api('/issues?sort=asc', user)
- response_dates = json_response.map { |issue| issue['created_at'] }
-
- expect_paginated_array_response(size: 2)
- expect(response_dates).to eq(response_dates.sort)
+ expect_paginated_array_response([closed_issue.id, issue.id])
end
it 'sorts by updated_at descending when requested' do
get api('/issues?order_by=updated_at', user)
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ issue.touch(:updated_at)
- expect_paginated_array_response(size: 2)
- expect(response_dates).to eq(response_dates.sort.reverse)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'sorts by updated_at ascending when requested' do
get api('/issues?order_by=updated_at&sort=asc', user)
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ issue.touch(:updated_at)
- expect_paginated_array_response(size: 2)
- expect(response_dates).to eq(response_dates.sort)
+ expect_paginated_array_response([closed_issue.id, issue.id])
end
it 'matches V4 response schema' do
@@ -430,7 +394,8 @@ describe API::Issues do
project: group_project,
state: :closed,
milestone: group_milestone,
- updated_at: 3.hours.ago
+ updated_at: 3.hours.ago,
+ created_at: 1.day.ago
end
let!(:group_confidential_issue) do
create :issue,
@@ -438,7 +403,8 @@ describe API::Issues do
project: group_project,
author: author,
assignees: [assignee],
- updated_at: 2.hours.ago
+ updated_at: 2.hours.ago,
+ created_at: 2.days.ago
end
let!(:group_issue) do
create :issue,
@@ -448,7 +414,8 @@ describe API::Issues do
milestone: group_milestone,
updated_at: 1.hour.ago,
title: issue_title,
- description: issue_description
+ description: issue_description,
+ created_at: 5.days.ago
end
let!(:group_label) do
create(:label, title: 'group_lbl', color: '#FFAABB', project: group_project)
@@ -479,10 +446,7 @@ describe API::Issues do
it 'also returns subgroups projects issues' do
get api(base_url, user)
- issue_ids = json_response.map { |issue| issue['id'] }
-
- expect_paginated_array_response(size: 5)
- expect(issue_ids).to include(issue_1.id, issue_2.id)
+ expect_paginated_array_response([issue_2.id, issue_1.id, group_closed_issue.id, group_confidential_issue.id, group_issue.id])
end
end
@@ -490,7 +454,7 @@ describe API::Issues do
it 'lists all issues in public projects' do
get api(base_url)
- expect_paginated_array_response(size: 2)
+ expect_paginated_array_response([group_closed_issue.id, group_issue.id])
end
end
@@ -502,65 +466,62 @@ describe API::Issues do
it 'returns all group issues (including opened and closed)' do
get api(base_url, admin)
- expect_paginated_array_response(size: 3)
+ expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
end
it 'returns group issues without confidential issues for non project members' do
get api(base_url, non_member), params: { state: :opened }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['title']).to eq(group_issue.title)
+ expect_paginated_array_response(group_issue.id)
end
it 'returns group confidential issues for author' do
get api(base_url, author), params: { state: :opened }
- expect_paginated_array_response(size: 2)
+ expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
end
it 'returns group confidential issues for assignee' do
get api(base_url, assignee), params: { state: :opened }
- expect_paginated_array_response(size: 2)
+ expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
end
it 'returns group issues with confidential issues for project members' do
get api(base_url, user), params: { state: :opened }
- expect_paginated_array_response(size: 2)
+ expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
end
it 'returns group confidential issues for admin' do
get api(base_url, admin), params: { state: :opened }
- expect_paginated_array_response(size: 2)
+ expect_paginated_array_response([group_confidential_issue.id, group_issue.id])
end
it 'returns an array of labeled group issues' do
get api(base_url, user), params: { labels: group_label.title }
- expect_paginated_array_response(size: 1)
+ expect_paginated_array_response(group_issue.id)
expect(json_response.first['labels']).to eq([group_label.title])
end
it 'returns an array of labeled group issues where all labels match' do
get api(base_url, user), params: { labels: "#{group_label.title},foo,bar" }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns issues matching given search string for title' do
get api(base_url, user), params: { search: group_issue.title }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(group_issue.id)
+ expect_paginated_array_response(group_issue.id)
end
it 'returns issues matching given search string for description' do
get api(base_url, user), params: { search: group_issue.description }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(group_issue.id)
+ expect_paginated_array_response(group_issue.id)
end
it 'returns an array of labeled issues when all labels matches' do
@@ -572,69 +533,64 @@ describe API::Issues do
get api(base_url, user), params: { labels: "#{group_label.title},#{label_b.title},#{label_c.title}" }
- expect_paginated_array_response(size: 1)
+ expect_paginated_array_response(group_issue.id)
expect(json_response.first['labels']).to eq([label_c.title, label_b.title, group_label.title])
end
it 'returns an array of issues found by iids' do
get api(base_url, user), params: { iids: [group_issue.iid] }
- expect_paginated_array_response(size: 1)
+ expect_paginated_array_response(group_issue.id)
expect(json_response.first['id']).to eq(group_issue.id)
end
it 'returns an empty array if iid does not exist' do
get api(base_url, user), params: { iids: [99999] }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an empty array if no group issue matches labels' do
get api(base_url, user), params: { labels: 'foo,bar' }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an array of group issues with any label' do
get api(base_url, user), params: { labels: IssuesFinder::FILTER_ANY }
- expect_paginated_array_response(size: 1)
+ expect_paginated_array_response(group_issue.id)
expect(json_response.first['id']).to eq(group_issue.id)
end
it 'returns an array of group issues with no label' do
get api(base_url, user), params: { labels: IssuesFinder::FILTER_NONE }
- response_ids = json_response.map { |issue| issue['id'] }
-
- expect_paginated_array_response(size: 2)
- expect(response_ids).to contain_exactly(group_closed_issue.id, group_confidential_issue.id)
+ expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id])
end
it 'returns an empty array if no issue matches milestone' do
get api(base_url, user), params: { milestone: group_empty_milestone.title }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an empty array if milestone does not exist' do
get api(base_url, user), params: { milestone: 'foo' }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an array of issues in given milestone' do
get api(base_url, user), params: { state: :opened, milestone: group_milestone.title }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(group_issue.id)
+ expect_paginated_array_response(group_issue.id)
end
it 'returns an array of issues matching state in milestone' do
get api(base_url, user), params: { milestone: group_milestone.title, state: :closed }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(group_closed_issue.id)
+ expect_paginated_array_response(group_closed_issue.id)
end
it 'returns an array of issues with no milestone' do
@@ -642,44 +598,33 @@ describe API::Issues do
expect(response).to have_gitlab_http_status(200)
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(group_confidential_issue.id)
+ expect_paginated_array_response(group_confidential_issue.id)
end
it 'sorts by created_at descending by default' do
get api(base_url, user)
- response_dates = json_response.map { |issue| issue['created_at'] }
-
- expect_paginated_array_response(size: 3)
- expect(response_dates).to eq(response_dates.sort.reverse)
+ expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
end
it 'sorts ascending when requested' do
get api("#{base_url}?sort=asc", user)
- response_dates = json_response.map { |issue| issue['created_at'] }
-
- expect_paginated_array_response(size: 3)
- expect(response_dates).to eq(response_dates.sort)
+ expect_paginated_array_response([group_issue.id, group_confidential_issue.id, group_closed_issue.id])
end
it 'sorts by updated_at descending when requested' do
get api("#{base_url}?order_by=updated_at", user)
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ group_issue.touch(:updated_at)
- expect_paginated_array_response(size: 3)
- expect(response_dates).to eq(response_dates.sort.reverse)
+ expect_paginated_array_response([group_issue.id, group_confidential_issue.id, group_closed_issue.id])
end
it 'sorts by updated_at ascending when requested' do
get api(base_url, user), params: { order_by: :updated_at, sort: :asc }
- response_dates = json_response.map { |issue| issue['updated_at'] }
-
- expect_paginated_array_response(size: 3)
- expect(response_dates).to eq(response_dates.sort)
+ expect_paginated_array_response([group_closed_issue.id, group_confidential_issue.id, group_issue.id])
end
end
end
@@ -691,8 +636,7 @@ describe API::Issues do
it 'returns public project issues' do
get api("/projects/#{project.id}/issues")
- expect_paginated_array_response(size: 2)
- expect(json_response.first['title']).to eq(issue.title)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
end
@@ -731,56 +675,49 @@ describe API::Issues do
get api("/projects/#{restricted_project.id}/issues", non_member)
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns project issues without confidential issues for non project members' do
get api("#{base_url}/issues", non_member)
- expect_paginated_array_response(size: 2)
- expect(json_response.first['title']).to eq(issue.title)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'returns project issues without confidential issues for project members with guest role' do
get api("#{base_url}/issues", guest)
- expect_paginated_array_response(size: 2)
- expect(json_response.first['title']).to eq(issue.title)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'returns project confidential issues for author' do
get api("#{base_url}/issues", author)
- expect_paginated_array_response(size: 3)
- expect(json_response.first['title']).to eq(issue.title)
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end
it 'returns project confidential issues for assignee' do
get api("#{base_url}/issues", assignee)
- expect_paginated_array_response(size: 3)
- expect(json_response.first['title']).to eq(issue.title)
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end
it 'returns project issues with confidential issues for project members' do
get api("#{base_url}/issues", user)
- expect_paginated_array_response(size: 3)
- expect(json_response.first['title']).to eq(issue.title)
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end
it 'returns project confidential issues for admin' do
get api("#{base_url}/issues", admin)
- expect_paginated_array_response(size: 3)
- expect(json_response.first['title']).to eq(issue.title)
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end
it 'returns an array of labeled project issues' do
get api("#{base_url}/issues", user), params: { labels: label.title }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['labels']).to eq([label.title])
+ expect_paginated_array_response(issue.id)
end
it 'returns an array of labeled issues when all labels matches' do
@@ -792,142 +729,117 @@ describe API::Issues do
get api("#{base_url}/issues", user), params: { labels: "#{label.title},#{label_b.title},#{label_c.title}" }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['labels']).to eq([label_c.title, label_b.title, label.title])
+ expect_paginated_array_response(issue.id)
end
it 'returns issues matching given search string for title' do
get api("#{base_url}/issues?search=#{issue.title}", user)
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(issue.id)
+ expect_paginated_array_response(issue.id)
end
it 'returns issues matching given search string for description' do
get api("#{base_url}/issues?search=#{issue.description}", user)
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(issue.id)
+ expect_paginated_array_response(issue.id)
end
it 'returns an array of issues found by iids' do
get api("#{base_url}/issues", user), params: { iids: [issue.iid] }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(issue.id)
+ expect_paginated_array_response(issue.id)
end
it 'returns an empty array if iid does not exist' do
get api("#{base_url}/issues", user), params: { iids: [99999] }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an empty array if not all labels matches' do
get api("#{base_url}/issues?labels=#{label.title},foo", user)
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an array of project issues with any label' do
get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_ANY }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(issue.id)
+ expect_paginated_array_response(issue.id)
end
it 'returns an array of project issues with no label' do
get api("#{base_url}/issues", user), params: { labels: IssuesFinder::FILTER_NONE }
- response_ids = json_response.map { |issue| issue['id'] }
-
- expect_paginated_array_response(size: 2)
- expect(response_ids).to contain_exactly(closed_issue.id, confidential_issue.id)
+ expect_paginated_array_response([confidential_issue.id, closed_issue.id])
end
it 'returns an empty array if no project issue matches labels' do
get api("#{base_url}/issues", user), params: { labels: 'foo,bar' }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an empty array if no issue matches milestone' do
get api("#{base_url}/issues", user), params: { milestone: empty_milestone.title }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an empty array if milestone does not exist' do
get api("#{base_url}/issues", user), params: { milestone: :foo }
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
it 'returns an array of issues in given milestone' do
get api("#{base_url}/issues", user), params: { milestone: milestone.title }
- expect_paginated_array_response(size: 2)
- expect(json_response.first['id']).to eq(issue.id)
- expect(json_response.second['id']).to eq(closed_issue.id)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'returns an array of issues matching state in milestone' do
get api("#{base_url}/issues", user), params: { milestone: milestone.title, state: :closed }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(closed_issue.id)
+ expect_paginated_array_response(closed_issue.id)
end
it 'returns an array of issues with no milestone' do
get api("#{base_url}/issues", user), params: { milestone: no_milestone_title }
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(confidential_issue.id)
+ expect_paginated_array_response(confidential_issue.id)
end
it 'returns an array of issues with any milestone' do
get api("#{base_url}/issues", user), params: { milestone: any_milestone_title }
- response_ids = json_response.map { |issue| issue['id'] }
-
- expect_paginated_array_response(size: 2)
- expect(response_ids).to contain_exactly(closed_issue.id, issue.id)
+ expect_paginated_array_response([issue.id, closed_issue.id])
end
it 'sorts by created_at descending by default' do
get api("#{base_url}/issues", user)
- response_dates = json_response.map { |issue| issue['created_at'] }
-
- expect_paginated_array_response(size: 3)
- expect(response_dates).to eq(response_dates.sort.reverse)
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end
it 'sorts ascending when requested' do
get api("#{base_url}/issues", user), params: { sort: :asc }
- response_dates = json_response.map { |issue| issue['created_at'] }
-
- expect_paginated_array_response(size: 3)
- expect(response_dates).to eq(response_dates.sort)
+ expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id])
end
it 'sorts by updated_at descending when requested' do
get api("#{base_url}/issues", user), params: { order_by: :updated_at }
- response_dates = json_response.map { |issue| issue['updated_at'] }
+ issue.touch(:updated_at)
- expect_paginated_array_response(size: 3)
- expect(response_dates).to eq(response_dates.sort.reverse)
+ expect_paginated_array_response([issue.id, confidential_issue.id, closed_issue.id])
end
it 'sorts by updated_at ascending when requested' do
get api("#{base_url}/issues", user), params: { order_by: :updated_at, sort: :asc }
- response_dates = json_response.map { |issue| issue['updated_at'] }
-
- expect_paginated_array_response(size: 3)
- expect(response_dates).to eq(response_dates.sort)
+ expect_paginated_array_response([closed_issue.id, confidential_issue.id, issue.id])
end
end
@@ -1828,21 +1740,21 @@ describe API::Issues do
it 'return public project issues' do
get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by")
- expect_paginated_array_response(size: 1)
+ expect_paginated_array_response(merge_request.id)
end
end
it 'returns merge requests that will close issue on merge' do
get api("/projects/#{project.id}/issues/#{issue.iid}/closed_by", user)
- expect_paginated_array_response(size: 1)
+ expect_paginated_array_response(merge_request.id)
end
context 'when no merge requests will close issue' do
it 'returns empty array' do
get api("/projects/#{project.id}/issues/#{closed_issue.iid}/closed_by", user)
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
end
@@ -1878,7 +1790,7 @@ describe API::Issues do
it 'return list of referenced merge requests from issue' do
get_related_merge_requests(project.id, issue.iid)
- expect_paginated_array_response(size: 1)
+ expect_paginated_array_response(related_mr.id)
end
it 'renders 404 if project is not visible' do
@@ -1902,15 +1814,14 @@ describe API::Issues do
get_related_merge_requests(project.id, issue.iid, user)
- expect_paginated_array_response(size: 1)
- expect(json_response.first['id']).to eq(related_mr.id)
+ expect_paginated_array_response(related_mr.id)
end
context 'no merge request mentioned a issue' do
it 'returns empty array' do
get_related_merge_requests(project.id, closed_issue.iid, user)
- expect_paginated_array_response(size: 0)
+ expect_paginated_array_response([])
end
end
@@ -1948,13 +1859,6 @@ describe API::Issues do
end
end
- def expect_paginated_array_response(size: nil)
- 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(size) if size
- end
-
describe 'GET projects/:id/issues/:issue_iid/participants' do
it_behaves_like 'issuable participants endpoint' do
let(:entity) { issue }
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index ffe4512fa6f..a01b494f615 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -948,6 +948,7 @@ describe API::Projects do
expect(json_response['shared_with_groups'].length).to eq(1)
expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
+ expect(json_response['shared_with_groups'][0]['group_full_path']).to eq(group.full_path)
expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
expect(json_response['shared_with_groups'][0]['expires_at']).to be_nil
expect(json_response['only_allow_merge_if_pipeline_succeeds']).to eq(project.only_allow_merge_if_pipeline_succeeds)
@@ -967,6 +968,7 @@ describe API::Projects do
expect(json_response['shared_with_groups'].length).to eq(1)
expect(json_response['shared_with_groups'][0]['group_id']).to eq(group.id)
expect(json_response['shared_with_groups'][0]['group_name']).to eq(group.name)
+ expect(json_response['shared_with_groups'][0]['group_full_path']).to eq(group.full_path)
expect(json_response['shared_with_groups'][0]['group_access_level']).to eq(link.group_access)
expect(json_response['shared_with_groups'][0]['expires_at']).to eq(expires_at.to_s)
end
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index cfbda63bb30..45fb1562e84 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -63,7 +63,8 @@ describe API::Settings, 'Settings' do
terms: 'Hello world!',
performance_bar_allowed_group_path: group.full_path,
instance_statistics_visibility_private: true,
- diff_max_patch_bytes: 150_000
+ diff_max_patch_bytes: 150_000,
+ default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE
}
expect(response).to have_gitlab_http_status(200)
@@ -88,6 +89,7 @@ describe API::Settings, 'Settings' do
expect(json_response['performance_bar_allowed_group_id']).to eq(group.id)
expect(json_response['instance_statistics_visibility_private']).to be(true)
expect(json_response['diff_max_patch_bytes']).to eq(150_000)
+ expect(json_response['default_branch_protection']).to eq(Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
end
end
diff --git a/spec/requests/api/wikis_spec.rb b/spec/requests/api/wikis_spec.rb
index f5092e8e2b5..6109829aad1 100644
--- a/spec/requests/api/wikis_spec.rb
+++ b/spec/requests/api/wikis_spec.rb
@@ -22,7 +22,7 @@ describe API::Wikis do
context 'when wiki has pages' do
let!(:pages) do
[create(:wiki_page, wiki: project_wiki, attrs: { title: 'page1', content: 'content of page1' }),
- create(:wiki_page, wiki: project_wiki, attrs: { title: 'page2', content: 'content of page2' })]
+ create(:wiki_page, wiki: project_wiki, attrs: { title: 'page2.with.dot', content: 'content of page2' })]
end
it 'returns the list of wiki pages without content' do
diff --git a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
index 08ffc3c3a53..0ff777388e5 100644
--- a/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
+++ b/spec/rubocop/cop/inject_enterprise_edition_module_spec.rb
@@ -19,6 +19,41 @@ describe RuboCop::Cop::InjectEnterpriseEditionModule do
SOURCE
end
+ it 'does not flag the use of `prepend EEFoo` in the middle of a file' do
+ expect_no_offenses(<<~SOURCE)
+ class Foo
+ prepend EEFoo
+ end
+ SOURCE
+ end
+
+ it 'flags the use of `prepend EE::Foo::Bar` in the middle of a file' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ prepend EE::Foo::Bar
+ ^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ end
+ SOURCE
+ end
+
+ it 'flags the use of `prepend(EE::Foo::Bar)` in the middle of a file' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ prepend(EE::Foo::Bar)
+ ^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ end
+ SOURCE
+ end
+
+ it 'flags the use of `prepend EE::Foo::Bar::Baz` in the middle of a file' do
+ expect_offense(<<~SOURCE)
+ class Foo
+ prepend EE::Foo::Bar::Baz
+ ^^^^^^^^^^^^^^^^^^^^^^^^^ Injecting EE modules must be done on the last line of this file, outside of any class or module definitions
+ end
+ SOURCE
+ end
+
it 'flags the use of `prepend ::EE` in the middle of a file' do
expect_offense(<<~SOURCE)
class Foo
diff --git a/spec/services/ci/process_pipeline_service_spec.rb b/spec/services/ci/process_pipeline_service_spec.rb
index 7ce7d2d882a..6674d89518e 100644
--- a/spec/services/ci/process_pipeline_service_spec.rb
+++ b/spec/services/ci/process_pipeline_service_spec.rb
@@ -762,7 +762,7 @@ describe Ci::ProcessPipelineService, '#execute' do
end
def manual_actions
- pipeline.manual_actions(true)
+ pipeline.manual_actions.reload
end
def create_build(name, **opts)
diff --git a/spec/services/projects/protect_default_branch_service_spec.rb b/spec/services/projects/protect_default_branch_service_spec.rb
new file mode 100644
index 00000000000..c145b2c06c6
--- /dev/null
+++ b/spec/services/projects/protect_default_branch_service_spec.rb
@@ -0,0 +1,242 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Projects::ProtectDefaultBranchService do
+ let(:service) { described_class.new(project) }
+ let(:project) { instance_spy(Project) }
+
+ describe '#execute' do
+ before do
+ allow(service)
+ .to receive(:protect_default_branch)
+ end
+
+ context 'without a default branch' do
+ it 'does nothing' do
+ allow(service)
+ .to receive(:default_branch)
+ .and_return(nil)
+
+ service.execute
+
+ expect(service)
+ .not_to have_received(:protect_default_branch)
+ end
+ end
+
+ context 'with a default branch' do
+ it 'protects the default branch' do
+ allow(service)
+ .to receive(:default_branch)
+ .and_return('master')
+
+ service.execute
+
+ expect(service)
+ .to have_received(:protect_default_branch)
+ end
+ end
+ end
+
+ describe '#protect_default_branch' do
+ before do
+ allow(service)
+ .to receive(:default_branch)
+ .and_return('master')
+
+ allow(project)
+ .to receive(:change_head)
+ .with('master')
+
+ allow(service)
+ .to receive(:create_protected_branch)
+ end
+
+ context 'when branch protection is needed' do
+ before do
+ allow(service)
+ .to receive(:protect_branch?)
+ .and_return(true)
+
+ allow(service)
+ .to receive(:create_protected_branch)
+ end
+
+ it 'changes the HEAD of the project' do
+ service.protect_default_branch
+
+ expect(project)
+ .to have_received(:change_head)
+ end
+
+ it 'protects the default branch' do
+ service.protect_default_branch
+
+ expect(service)
+ .to have_received(:create_protected_branch)
+ end
+ end
+
+ context 'when branch protection is not needed' do
+ before do
+ allow(service)
+ .to receive(:protect_branch?)
+ .and_return(false)
+ end
+
+ it 'changes the HEAD of the project' do
+ service.protect_default_branch
+
+ expect(project)
+ .to have_received(:change_head)
+ end
+
+ it 'does not protect the default branch' do
+ service.protect_default_branch
+
+ expect(service)
+ .not_to have_received(:create_protected_branch)
+ end
+ end
+ end
+
+ describe '#create_protected_branch' do
+ it 'creates the protected branch' do
+ creator = instance_spy(User)
+ create_service = instance_spy(ProtectedBranches::CreateService)
+ access_level = Gitlab::Access::DEVELOPER
+ params = {
+ name: 'master',
+ push_access_levels_attributes: [{ access_level: access_level }],
+ merge_access_levels_attributes: [{ access_level: access_level }]
+ }
+
+ allow(project)
+ .to receive(:creator)
+ .and_return(creator)
+
+ allow(ProtectedBranches::CreateService)
+ .to receive(:new)
+ .with(project, creator, params)
+ .and_return(create_service)
+
+ allow(service)
+ .to receive(:push_access_level)
+ .and_return(access_level)
+
+ allow(service)
+ .to receive(:merge_access_level)
+ .and_return(access_level)
+
+ allow(service)
+ .to receive(:default_branch)
+ .and_return('master')
+
+ allow(create_service)
+ .to receive(:execute)
+ .with(skip_authorization: true)
+
+ service.create_protected_branch
+
+ expect(create_service)
+ .to have_received(:execute)
+ end
+ end
+
+ describe '#protect_branch?' do
+ context 'when default branch protection is disabled' do
+ it 'returns false' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_protection)
+ .and_return(Gitlab::Access::PROTECTION_NONE)
+
+ expect(service.protect_branch?).to eq(false)
+ end
+ end
+
+ context 'when default branch protection is enabled' do
+ before do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_protection)
+ .and_return(Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
+
+ allow(service)
+ .to receive(:default_branch)
+ .and_return('master')
+ end
+
+ it 'returns false if the branch is already protected' do
+ allow(ProtectedBranch)
+ .to receive(:protected?)
+ .with(project, 'master')
+ .and_return(true)
+
+ expect(service.protect_branch?).to eq(false)
+ end
+
+ it 'returns true if the branch is not yet protected' do
+ allow(ProtectedBranch)
+ .to receive(:protected?)
+ .with(project, 'master')
+ .and_return(false)
+
+ expect(service.protect_branch?).to eq(true)
+ end
+ end
+ end
+
+ describe '#default_branch' do
+ it 'returns the default branch of the project' do
+ allow(project)
+ .to receive(:default_branch)
+ .and_return('master')
+
+ expect(service.default_branch).to eq('master')
+ end
+ end
+
+ describe '#push_access_level' do
+ context 'when developers can push' do
+ it 'returns the DEVELOPER access level' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_protection)
+ .and_return(Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
+
+ expect(service.push_access_level).to eq(Gitlab::Access::DEVELOPER)
+ end
+ end
+
+ context 'when developers can not push' do
+ it 'returns the MAINTAINER access level' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_protection)
+ .and_return(Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
+
+ expect(service.push_access_level).to eq(Gitlab::Access::MAINTAINER)
+ end
+ end
+ end
+
+ describe '#merge_access_level' do
+ context 'when developers can merge' do
+ it 'returns the DEVELOPER access level' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_protection)
+ .and_return(Gitlab::Access::PROTECTION_DEV_CAN_MERGE)
+
+ expect(service.merge_access_level).to eq(Gitlab::Access::DEVELOPER)
+ end
+ end
+
+ context 'when developers can not merge' do
+ it 'returns the MAINTAINER access level' do
+ allow(Gitlab::CurrentSettings)
+ .to receive(:default_branch_protection)
+ .and_return(Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
+
+ expect(service.merge_access_level).to eq(Gitlab::Access::MAINTAINER)
+ end
+ end
+ end
+end
diff --git a/spec/services/suggestions/apply_service_spec.rb b/spec/services/suggestions/apply_service_spec.rb
index 3a483717756..e5ca1c155ed 100644
--- a/spec/services/suggestions/apply_service_spec.rb
+++ b/spec/services/suggestions/apply_service_spec.rb
@@ -117,13 +117,184 @@ describe Suggestions::ApplyService do
expect(commit.committer_email).to eq(user.commit_email)
expect(commit.author_name).to eq(user.name)
end
+
+ context 'when it fails to apply because the file was changed' do
+ it 'returns error message' do
+ service = instance_double(Files::UpdateService)
+
+ expect(Files::UpdateService).to receive(:new)
+ .and_return(service)
+
+ allow(service).to receive(:execute)
+ .and_raise(Files::UpdateService::FileChangedError)
+
+ result = subject.execute(suggestion)
+
+ expect(result).to eq(message: 'The file has been changed', status: :error)
+ end
+ end
+
+ context 'when diff ref from position is different from repo diff refs' do
+ it 'returns error message' do
+ outdated_refs = Gitlab::Diff::DiffRefs.new(base_sha: 'foo', start_sha: 'bar', head_sha: 'outdated')
+
+ allow(suggestion).to receive(:appliable?) { true }
+ allow(suggestion.position).to receive(:diff_refs) { outdated_refs }
+
+ result = subject.execute(suggestion)
+
+ expect(result).to eq(message: 'The file has been changed', status: :error)
+ end
+ end
+
+ context 'multiple suggestions applied' do
+ let(:expected_content) do
+ <<-CONTENT.strip_heredoc
+ require 'fileutils'
+ require 'open3'
+
+ module Popen
+ extend self
+
+
+ def popen(cmd, path=nil)
+ unless cmd.is_a?(Array)
+ # v1 change
+ end
+
+ path ||= Dir.pwd
+ # v1 change
+ vars = {
+ "PWD" => path
+ }
+
+ options = {
+ chdir: path
+ }
+ # v2 change
+ unless File.directory?(path)
+ FileUtils.mkdir_p(path)
+ end
+
+ @cmd_output = ""
+ # v2 change
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ @cmd_output << stderr.read
+ @cmd_status = wait_thr.value.exitstatus
+ end
+
+ return @cmd_output, @cmd_status
+ end
+ end
+ CONTENT
+ end
+
+ let(:merge_request) do
+ create(:merge_request, source_project: project,
+ target_project: project)
+ end
+
+ def create_suggestion(diff, old_line: nil, new_line: nil, from_content:, to_content:, path:)
+ position = Gitlab::Diff::Position.new(old_path: path,
+ new_path: path,
+ old_line: old_line,
+ new_line: new_line,
+ diff_refs: diff.diff_refs)
+
+ suggestion_note = create(:diff_note_on_merge_request, noteable: merge_request,
+ original_position: position,
+ position: position,
+ project: project)
+ create(:suggestion, note: suggestion_note,
+ from_content: from_content,
+ to_content: to_content)
+ end
+
+ def apply_suggestion(suggestion)
+ suggestion.note.reload
+ merge_request.reload
+ merge_request.clear_memoized_shas
+
+ result = subject.execute(suggestion)
+ refresh = MergeRequests::RefreshService.new(project, user)
+ refresh.execute(merge_request.diff_head_sha,
+ suggestion.commit_id,
+ merge_request.source_branch_ref)
+
+ result
+ end
+
+ def fetch_raw_diff(suggestion)
+ project.reload.commit(suggestion.commit_id).diffs.diff_files.first.diff.diff
+ end
+
+ it 'applies multiple suggestions in subsequent versions correctly' do
+ diff = merge_request.merge_request_diff
+ path = 'files/ruby/popen.rb'
+
+ suggestion_1_changes = { old_line: nil,
+ new_line: 13,
+ from_content: "\n",
+ to_content: "# v1 change\n",
+ path: path }
+
+ suggestion_2_changes = { old_line: 24,
+ new_line: 31,
+ from_content: " @cmd_output << stderr.read\n",
+ to_content: "# v2 change\n",
+ path: path }
+
+ suggestion_1 = create_suggestion(diff, suggestion_1_changes)
+ suggestion_2 = create_suggestion(diff, suggestion_2_changes)
+
+ apply_suggestion(suggestion_1)
+
+ suggestion_1_diff = fetch_raw_diff(suggestion_1)
+
+ # rubocop: disable Layout/TrailingWhitespace
+ expected_suggestion_1_diff = <<-CONTENT.strip_heredoc
+ @@ -10,7 +10,7 @@ module Popen
+ end
+
+ path ||= Dir.pwd
+ -
+ +# v1 change
+ vars = {
+ "PWD" => path
+ }
+ CONTENT
+ # rubocop: enable Layout/TrailingWhitespace
+
+ apply_suggestion(suggestion_2)
+
+ suggestion_2_diff = fetch_raw_diff(suggestion_2)
+
+ # rubocop: disable Layout/TrailingWhitespace
+ expected_suggestion_2_diff = <<-CONTENT.strip_heredoc
+ @@ -28,7 +28,7 @@ module Popen
+
+ Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
+ @cmd_output << stdout.read
+ - @cmd_output << stderr.read
+ +# v2 change
+ @cmd_status = wait_thr.value.exitstatus
+ end
+ CONTENT
+ # rubocop: enable Layout/TrailingWhitespace
+
+ expect(suggestion_1_diff.strip).to eq(expected_suggestion_1_diff.strip)
+ expect(suggestion_2_diff.strip).to eq(expected_suggestion_2_diff.strip)
+ end
+ end
end
context 'fork-project' do
let(:project) { create(:project, :public, :repository) }
let(:forked_project) do
- fork_project_with_submodules(project, user)
+ fork_project_with_submodules(project, user, repository: project.repository)
end
let(:merge_request) do
diff --git a/spec/sidekiq/cron/job_gem_dependency_spec.rb b/spec/sidekiq/cron/job_gem_dependency_spec.rb
index 2e30cf025b0..2e7de75fd08 100644
--- a/spec/sidekiq/cron/job_gem_dependency_spec.rb
+++ b/spec/sidekiq/cron/job_gem_dependency_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Sidekiq::Cron::Job do
describe 'cron jobs' do
- context 'when rufus-scheduler depends on ZoTime or EoTime' do
+ context 'when Fugit depends on ZoTime or EoTime' do
before do
described_class
.create(name: 'TestCronWorker',
@@ -10,7 +10,7 @@ describe Sidekiq::Cron::Job do
class: Settings.cron_jobs[:pipeline_schedule_worker]['job_class'])
end
- it 'does not get "Rufus::Scheduler::ZoTime/EtOrbi::EoTime into an exact number"' do
+ it 'does not get any errors' do
expect { described_class.all.first.should_enque?(Time.now) }.not_to raise_error
end
end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 89357056c93..72684caad32 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -22,7 +22,7 @@ if rspec_profiling_is_configured && (!ENV.key?('CI') || branch_can_be_profiled)
require 'rspec_profiling/rspec'
end
-if ENV['CI'] && !ENV['NO_KNAPSACK']
+if ENV['CI'] && ENV['KNAPSACK_GENERATE_REPORT'] && !ENV['NO_KNAPSACK']
require 'knapsack'
Knapsack::Adapters::RSpecAdapter.bind
end
@@ -127,6 +127,16 @@ RSpec.configure do |config|
.and_return(false)
end
+ config.before(:suite) do
+ # Set latest release blog post URL for "What's new?" link
+ Gitlab::ReleaseBlogPost.instance.instance_variable_set(:@url, 'https://about.gitlab.com')
+ end
+
+ config.before(:example, :quarantine) do
+ # Skip tests in quarantine unless we explicitly focus on them.
+ skip('In quarantine') unless config.inclusion_filter[:quarantine]
+ end
+
config.before(:example, :request_store) do
RequestStore.begin!
end
diff --git a/spec/support/helpers/api_helpers.rb b/spec/support/helpers/api_helpers.rb
index a57a3b2cf34..4a9ce9beb78 100644
--- a/spec/support/helpers/api_helpers.rb
+++ b/spec/support/helpers/api_helpers.rb
@@ -36,4 +36,11 @@ module ApiHelpers
full_path
end
+
+ def expect_paginated_array_response(items)
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.map { |item| item['id'] }).to eq(Array(items))
+ end
end
diff --git a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
index 1f688c0f9d3..dcf7c1a90c2 100644
--- a/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
+++ b/spec/support/shared_examples/lib/gitlab/background_migration/backfill_project_repositories_examples.rb
@@ -32,11 +32,13 @@ shared_examples 'backfill migration for project repositories' do |storage|
it 'inserts rows in a single query' do
projects.create!(name: 'foo', path: 'foo', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
+ group2 = namespaces.create!(name: 'gro', path: 'gro')
control_count = ActiveRecord::QueryRecorder.new { described_class.new.perform(1, projects.last.id) }
projects.create!(name: 'bar', path: 'bar', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
- projects.create!(name: 'zoo', path: 'zoo', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
+ projects.create!(name: 'top', path: 'top', namespace_id: group.id, storage_version: storage_version, repository_storage: shard.name)
+ projects.create!(name: 'zoo', path: 'zoo', namespace_id: group2.id, storage_version: storage_version, repository_storage: shard.name)
expect { described_class.new.perform(1, projects.last.id) }.not_to exceed_query_limit(control_count)
end
diff --git a/spec/views/help/instance_configuration.html.haml_spec.rb b/spec/views/help/instance_configuration.html.haml_spec.rb
index f30b5881fde..ceb7e34a540 100644
--- a/spec/views/help/instance_configuration.html.haml_spec.rb
+++ b/spec/views/help/instance_configuration.html.haml_spec.rb
@@ -13,9 +13,9 @@ describe 'help/instance_configuration' do
it 'has links to several sections' do
render
- expect(rendered).to have_link(nil, '#ssh-host-keys-fingerprints') if ssh_settings.any?
- expect(rendered).to have_link(nil, '#gitlab-pages')
- expect(rendered).to have_link(nil, '#gitlab-ci')
+ expect(rendered).to have_link(nil, href: '#ssh-host-keys-fingerprints') if ssh_settings.any?
+ expect(rendered).to have_link(nil, href: '#gitlab-pages')
+ expect(rendered).to have_link(nil, href: '#gitlab-ci')
end
it 'has several sections' do
diff --git a/spec/views/projects/commit/show.html.haml_spec.rb b/spec/views/projects/commit/show.html.haml_spec.rb
index a9c32122600..d07099489e5 100644
--- a/spec/views/projects/commit/show.html.haml_spec.rb
+++ b/spec/views/projects/commit/show.html.haml_spec.rb
@@ -54,9 +54,9 @@ describe 'projects/commit/show.html.haml' do
end
it 'shows that it is in the context of a merge request' do
- merge_request_url = diffs_project_merge_request_url(project, merge_request, commit_id: commit.id)
+ merge_request_url = diffs_project_merge_request_path(project, merge_request, commit_id: commit.id)
expect(rendered).to have_content("This commit is part of merge request")
- expect(rendered).to have_link(merge_request.to_reference, merge_request_url)
+ expect(rendered).to have_link(merge_request.to_reference, href: merge_request_url)
end
end
end
diff --git a/spec/workers/git_garbage_collect_worker_spec.rb b/spec/workers/git_garbage_collect_worker_spec.rb
index a159f24f876..4895a968d6e 100644
--- a/spec/workers/git_garbage_collect_worker_spec.rb
+++ b/spec/workers/git_garbage_collect_worker_spec.rb
@@ -71,6 +71,17 @@ describe GitGarbageCollectWorker do
subject.perform(project.id)
end
+
+ context 'when the repository has joined a pool' do
+ let!(:pool) { create(:pool_repository, :ready) }
+ let(:project) { pool.source_project }
+
+ it 'ensures the repositories are linked' do
+ expect_any_instance_of(PoolRepository).to receive(:link_repository).once
+
+ subject.perform(project.id)
+ end
+ end
end
context 'when no lease can be obtained' do
diff --git a/spec/workers/remote_mirror_notification_worker_spec.rb b/spec/workers/remote_mirror_notification_worker_spec.rb
new file mode 100644
index 00000000000..e3db10ed645
--- /dev/null
+++ b/spec/workers/remote_mirror_notification_worker_spec.rb
@@ -0,0 +1,39 @@
+require 'spec_helper'
+
+describe RemoteMirrorNotificationWorker, :mailer do
+ set(:project) { create(:project, :repository, :remote_mirror) }
+ set(:mirror) { project.remote_mirrors.first }
+
+ describe '#execute' do
+ it 'calls NotificationService#remote_mirror_update_failed when the mirror exists' do
+ mirror.update_column(:last_error, "There was a problem fetching")
+
+ expect(NotificationService).to receive_message_chain(:new, :remote_mirror_update_failed)
+
+ subject.perform(mirror.id)
+
+ expect(mirror.reload.error_notification_sent?).to be_truthy
+ end
+
+ it 'does nothing when the mirror has no errors' do
+ expect(NotificationService).not_to receive(:new)
+
+ subject.perform(mirror.id)
+ end
+
+ it 'does nothing when the mirror does not exist' do
+ expect(NotificationService).not_to receive(:new)
+
+ subject.perform(RemoteMirror.maximum(:id).to_i.succ)
+ end
+
+ it 'does nothing when a notification has already been sent' do
+ mirror.update_columns(last_error: "There was a problem fetching",
+ error_notification_sent: true)
+
+ expect(NotificationService).not_to receive(:new)
+
+ subject.perform(mirror.id)
+ end
+ end
+end
diff --git a/spec/workers/repository_update_remote_mirror_worker_spec.rb b/spec/workers/repository_update_remote_mirror_worker_spec.rb
index d73b0b53713..b582a3650b6 100644
--- a/spec/workers/repository_update_remote_mirror_worker_spec.rb
+++ b/spec/workers/repository_update_remote_mirror_worker_spec.rb
@@ -22,6 +22,13 @@ describe RepositoryUpdateRemoteMirrorWorker do
expect { subject.perform(remote_mirror.id, Time.now) }.to change { remote_mirror.reload.update_status }.to('finished')
end
+ it 'resets the notification flag upon success' do
+ expect_any_instance_of(Projects::UpdateRemoteMirrorService).to receive(:execute).with(remote_mirror).and_return(status: :success)
+ remote_mirror.update_column(:error_notification_sent, true)
+
+ expect { subject.perform(remote_mirror.id, Time.now) }.to change { remote_mirror.reload.error_notification_sent }.to(false)
+ end
+
it 'sets status as failed when update remote mirror service executes with errors' do
error_message = 'fail!'
diff --git a/yarn.lock b/yarn.lock
index 40a8594ee6c..fadfeb3dc49 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -29,18 +29,7 @@
semver "^5.4.1"
source-map "^0.5.0"
-"@babel/generator@^7.0.0":
- version "7.1.2"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.1.2.tgz#fde75c072575ce7abbd97322e8fef5bae67e4630"
- integrity sha512-70A9HWLS/1RHk3Ck8tNHKxOoKQuSKocYgwDN85Pyl/RBduss6AKxUR7RIZ/lzduQMSYfWEM4DDBu6A+XGbkFig==
- dependencies:
- "@babel/types" "^7.1.2"
- jsesc "^2.5.1"
- lodash "^4.17.10"
- source-map "^0.5.0"
- trim-right "^1.0.1"
-
-"@babel/generator@^7.2.2":
+"@babel/generator@^7.0.0", "@babel/generator@^7.2.2":
version "7.2.2"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.2.2.tgz#18c816c70962640eab42fe8cae5f3947a5c65ccc"
integrity sha512-I4o675J/iS8k+P38dvJ3IBGqObLXyQLTxtrR4u9cSUJOURvafeEWb/pFMOTwtNrmq73mJzyF6ueTbO1BtN0Zeg==
@@ -182,17 +171,7 @@
"@babel/traverse" "^7.1.0"
"@babel/types" "^7.0.0"
-"@babel/helper-replace-supers@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.1.0.tgz#5fc31de522ec0ef0899dc9b3e7cf6a5dd655f362"
- integrity sha512-BvcDWYZRWVuDeXTYZWxekQNO5D4kO55aArwZOTFXw6rlLQA8ZaDicJR1sO47h+HrnCiDFiww0fSPV0d713KBGQ==
- dependencies:
- "@babel/helper-member-expression-to-functions" "^7.0.0"
- "@babel/helper-optimise-call-expression" "^7.0.0"
- "@babel/traverse" "^7.1.0"
- "@babel/types" "^7.0.0"
-
-"@babel/helper-replace-supers@^7.2.3":
+"@babel/helper-replace-supers@^7.1.0", "@babel/helper-replace-supers@^7.2.3":
version "7.2.3"
resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.2.3.tgz#19970020cf22677d62b3a689561dbd9644d8c5e5"
integrity sha512-GyieIznGUfPXPWu0yLS6U55Mz67AZD9cUk0BfirOWlPrXlBcan9Gz+vHGz+cPfuoweZSnPzPIm67VtQM0OWZbA==
@@ -245,12 +224,7 @@
esutils "^2.0.2"
js-tokens "^4.0.0"
-"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.2":
- version "7.1.2"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.2.tgz#85c5c47af6d244fab77bce6b9bd830e38c978409"
- integrity sha512-x5HFsW+E/nQalGMw7hu+fvPqnBeBaIr0lWJ2SG0PPL2j+Pm9lYvCrsZJGIgauPIENx0v10INIyFjmSNUD/gSqQ==
-
-"@babel/parser@^7.2.2", "@babel/parser@^7.2.3":
+"@babel/parser@^7.0.0", "@babel/parser@^7.2.2", "@babel/parser@^7.2.3":
version "7.2.3"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.2.3.tgz#32f5df65744b70888d17872ec106b02434ba1489"
integrity sha512-0LyEcVlfCoFmci8mXx8A5oIkpkOgyo8dRHtxBnK9RRBwxO2+JZPNsqtVEZQ7mJFPxnXF9lfmU24mHOPI0qnlkA==
@@ -617,16 +591,7 @@
js-levenshtein "^1.1.3"
semver "^5.3.0"
-"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.1.2":
- version "7.1.2"
- resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644"
- integrity sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag==
- dependencies:
- "@babel/code-frame" "^7.0.0"
- "@babel/parser" "^7.1.2"
- "@babel/types" "^7.1.2"
-
-"@babel/template@^7.2.2":
+"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.1.2", "@babel/template@^7.2.2":
version "7.2.2"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.2.2.tgz#005b3fdf0ed96e88041330379e0da9a708eb2907"
integrity sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g==
@@ -635,22 +600,7 @@
"@babel/parser" "^7.2.2"
"@babel/types" "^7.2.2"
-"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0":
- version "7.1.0"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.1.0.tgz#503ec6669387efd182c3888c4eec07bcc45d91b2"
- integrity sha512-bwgln0FsMoxm3pLOgrrnGaXk18sSM9JNf1/nHC/FksmNGFbYnPWY4GYCfLxyP1KRmfsxqkRpfoa6xr6VuuSxdw==
- dependencies:
- "@babel/code-frame" "^7.0.0"
- "@babel/generator" "^7.0.0"
- "@babel/helper-function-name" "^7.1.0"
- "@babel/helper-split-export-declaration" "^7.0.0"
- "@babel/parser" "^7.1.0"
- "@babel/types" "^7.0.0"
- debug "^3.1.0"
- globals "^11.1.0"
- lodash "^4.17.10"
-
-"@babel/traverse@^7.1.5", "@babel/traverse@^7.2.2", "@babel/traverse@^7.2.3":
+"@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.2.2", "@babel/traverse@^7.2.3":
version "7.2.3"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.2.3.tgz#7ff50cefa9c7c0bd2d81231fdac122f3957748d8"
integrity sha512-Z31oUD/fJvEWVR0lNZtfgvVt512ForCTNKYcJBGbPb1QZfve4WGH8Wsy7+Mev33/45fhP/hwQtvgusNdcCMgSw==
@@ -665,16 +615,7 @@
globals "^11.1.0"
lodash "^4.17.10"
-"@babel/types@^7.0.0", "@babel/types@^7.1.2":
- version "7.1.2"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.1.2.tgz#183e7952cf6691628afdc2e2b90d03240bac80c0"
- integrity sha512-pb1I05sZEKiSlMUV9UReaqsCPUpgbHHHu2n1piRm7JkuBkm6QxcaIzKu6FMnMtCbih/cEYTR+RGYYC96Yk9HAg==
- dependencies:
- esutils "^2.0.2"
- lodash "^4.17.10"
- to-fast-properties "^2.0.0"
-
-"@babel/types@^7.2.0", "@babel/types@^7.2.2":
+"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2":
version "7.2.2"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.2.2.tgz#44e10fc24e33af524488b716cdaee5360ea8ed1e"
integrity sha512-fKCuD6UFUMkR541eDWL+2ih/xFZBXPOg/7EQFeTluMDebfqR4jrpaCjLhkWlQS4hT6nRa2PMEgXKbRB5/H2fpg==
@@ -1654,6 +1595,11 @@ babel-types@^6.0.0, babel-types@^6.18.0, babel-types@^6.24.1, babel-types@^6.26.
lodash "^4.17.4"
to-fast-properties "^1.0.3"
+babylon@7.0.0-beta.19:
+ version "7.0.0-beta.19"
+ resolved "https://registry.yarnpkg.com/babylon/-/babylon-7.0.0-beta.19.tgz#e928c7e807e970e0536b078ab3e0c48f9e052503"
+ integrity sha512-Vg0C9s/REX6/WIXN37UKpv5ZhRi6A4pjHlpkE34+8/a6c2W1Q692n3hmc+SZG5lKRnaExLUbxtJ1SVT+KaCQ/A==
+
babylon@^6.18.0:
version "6.18.0"
resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3"
@@ -1746,7 +1692,7 @@ blob@0.0.4:
resolved "https://registry.yarnpkg.com/blob/-/blob-0.0.4.tgz#bcf13052ca54463f30f9fc7e95b9a47630a94921"
integrity sha1-vPEwUspURj8w+fx+lbmkdjCpSSE=
-bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.5.1, bluebird@^3.5.3:
+bluebird@^3.1.1, bluebird@^3.3.0, bluebird@^3.5.1, bluebird@^3.5.3, bluebird@~3.5.0:
version "3.5.3"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7"
integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==
@@ -2099,6 +2045,13 @@ caseless@~0.12.0:
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
+catharsis@~0.8.9:
+ version "0.8.9"
+ resolved "https://registry.yarnpkg.com/catharsis/-/catharsis-0.8.9.tgz#98cc890ca652dd2ef0e70b37925310ff9e90fc8b"
+ integrity sha1-mMyJDKZS3S7w5ws3klMQ/56Q/Is=
+ dependencies:
+ underscore-contrib "~0.3.0"
+
chalk@1.1.3, chalk@^1.0.0, chalk@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98"
@@ -3034,10 +2987,10 @@ decamelize@^2.0.0:
dependencies:
xregexp "4.0.0"
-deckar01-task_list@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/deckar01-task_list/-/deckar01-task_list-2.0.0.tgz#7f7a595430d21b3036ed5dfbf97d6b65de18e2c9"
- integrity sha1-f3pZVDDSGzA27V37+X1rZd4Y4sk=
+deckar01-task_list@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/deckar01-task_list/-/deckar01-task_list-2.0.1.tgz#fdcfb6ab5717055a82f29e863a49990a043a06a9"
+ integrity sha512-i5fT8QxJ9iV6dfgy5U0NHW91O5cKsvDc4u8JNMnZ6efQc356bA9vKuXO3732agSry+bO6TolzTmuqSRi4tkkeA==
decode-uri-component@^0.2.0:
version "0.2.0"
@@ -3246,6 +3199,11 @@ dns-txt@^2.0.2:
dependencies:
buffer-indexof "^1.0.0"
+docdash@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/docdash/-/docdash-1.0.2.tgz#0449a8f6bb247f563020b78a5485dea95ae2e094"
+ integrity sha512-IEM57bWPLtVXpUeCKbiGvHsHtW9O9ZiiBPfeQDAZ7JdQiAF3aNWQoJ3e/+uJ63lHO009yn0tbJjtKpXJ2AURCQ==
+
doctrine@1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa"
@@ -3554,7 +3512,7 @@ escape-html@~1.0.3:
resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988"
integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=
-escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5:
+escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5, escape-string-regexp@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
@@ -4639,7 +4597,7 @@ got@^8.0.3:
url-parse-lax "^3.0.0"
url-to-options "^1.0.1"
-graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2:
+graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.9:
version "4.1.15"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00"
integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==
@@ -6128,11 +6086,41 @@ js-yaml@3.x, js-yaml@^3.12.0, js-yaml@^3.7.0:
argparse "^1.0.7"
esprima "^4.0.0"
+js2xmlparser@~3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/js2xmlparser/-/js2xmlparser-3.0.0.tgz#3fb60eaa089c5440f9319f51760ccd07e2499733"
+ integrity sha1-P7YOqgicVED5MZ9RdgzNB+JJlzM=
+ dependencies:
+ xmlcreate "^1.0.1"
+
jsbn@~0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
+jsdoc-vue@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/jsdoc-vue/-/jsdoc-vue-1.0.0.tgz#ff3ac1ba6bc4a74079bb79058a7bf0066e346235"
+ integrity sha1-/zrBumvEp0B5u3kFinvwBm40YjU=
+
+jsdoc@^3.5.5:
+ version "3.5.5"
+ resolved "https://registry.yarnpkg.com/jsdoc/-/jsdoc-3.5.5.tgz#484521b126e81904d632ff83ec9aaa096708fa4d"
+ integrity sha512-6PxB65TAU4WO0Wzyr/4/YhlGovXl0EVYfpKbpSroSj0qBxT4/xod/l40Opkm38dRHRdQgdeY836M0uVnJQG7kg==
+ dependencies:
+ babylon "7.0.0-beta.19"
+ bluebird "~3.5.0"
+ catharsis "~0.8.9"
+ escape-string-regexp "~1.0.5"
+ js2xmlparser "~3.0.0"
+ klaw "~2.0.0"
+ marked "~0.3.6"
+ mkdirp "~0.5.1"
+ requizzle "~0.2.1"
+ strip-json-comments "~2.0.1"
+ taffydb "2.6.2"
+ underscore "~1.8.3"
+
jsdom@^11.5.1:
version "11.12.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-11.12.0.tgz#1a80d40ddd378a1de59656e9e6dc5a3ba8657bc8"
@@ -6385,6 +6373,13 @@ kind-of@^6.0.0, kind-of@^6.0.2:
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051"
integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==
+klaw@~2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/klaw/-/klaw-2.0.0.tgz#59c128e0dc5ce410201151194eeb9cbf858650f6"
+ integrity sha1-WcEo4Nxc5BAgEVEZTuucv4WGUPY=
+ dependencies:
+ graceful-fs "^4.1.9"
+
kleur@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/kleur/-/kleur-2.0.2.tgz#b704f4944d95e255d038f0cb05fb8a602c55a300"
@@ -6672,10 +6667,10 @@ map-visit@^1.0.0:
dependencies:
object-visit "^1.0.0"
-marked@^0.3.12:
- version "0.3.12"
- resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.12.tgz#7cf25ff2252632f3fe2406bde258e94eee927519"
- integrity sha512-k4NaW+vS7ytQn6MgJn3fYpQt20/mOgYM5Ft9BYMfQJDz2QT6yEeS9XJ8k2Nw8JTeWK/znPPW2n3UJGzyYEiMoA==
+marked@^0.3.12, marked@~0.3.6:
+ version "0.3.19"
+ resolved "https://registry.yarnpkg.com/marked/-/marked-0.3.19.tgz#5d47f709c4c9fc3c216b6d46127280f40b39d790"
+ integrity sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==
match-at@^0.1.1:
version "0.1.1"
@@ -6916,7 +6911,7 @@ mixin-deep@^1.2.0:
for-in "^1.0.2"
is-extendable "^1.0.1"
-mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0:
+mkdirp@0.5.x, mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1:
version "0.5.1"
resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=
@@ -7879,10 +7874,10 @@ prettier@1.13.7:
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.7.tgz#850f3b8af784a49a6ea2d2eaa7ed1428a34b7281"
integrity sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w==
-prettier@1.15.2:
- version "1.15.2"
- resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.15.2.tgz#d31abe22afa4351efa14c7f8b94b58bb7452205e"
- integrity sha512-YgPLFFA0CdKL4Eg2IHtUSjzj/BWgszDHiNQAe0VAIBse34148whfdzLagRL+QiKS+YfK5ftB6X4v/MBw8yCoug==
+prettier@1.15.3:
+ version "1.15.3"
+ resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.15.3.tgz#1feaac5bdd181237b54dbe65d874e02a1472786a"
+ integrity sha512-gAU9AGAPMaKb3NNSUUuhhFAS7SCO4ALTN4nRIn6PJ075Qd28Yn2Ig2ahEJWdJwJmlEBTUfC7mMUSFy8MwsOCfg==
pretty-format@^23.6.0:
version "23.6.0"
@@ -8441,6 +8436,13 @@ requires-port@1.x.x, requires-port@^1.0.0:
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
+requizzle@~0.2.1:
+ version "0.2.1"
+ resolved "https://registry.yarnpkg.com/requizzle/-/requizzle-0.2.1.tgz#6943c3530c4d9a7e46f1cddd51c158fc670cdbde"
+ integrity sha1-aUPDUwxNmn5G8c3dUcFY/GcM294=
+ dependencies:
+ underscore "~1.6.0"
+
resolve-cwd@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
@@ -9329,6 +9331,11 @@ table@^5.0.2:
slice-ansi "2.0.0"
string-width "^2.1.1"
+taffydb@2.6.2:
+ version "2.6.2"
+ resolved "https://registry.yarnpkg.com/taffydb/-/taffydb-2.6.2.tgz#7cbcb64b5a141b6a2efc2c5d2c67b4e150b2a268"
+ integrity sha1-fLy2S1oUG2ou/CxdLGe04VCyomg=
+
tapable@^0.1.8:
version "0.1.10"
resolved "https://registry.yarnpkg.com/tapable/-/tapable-0.1.10.tgz#29c35707c2b70e50d07482b5d202e8ed446dafd4"
@@ -9651,11 +9658,28 @@ undefsafe@^2.0.2:
dependencies:
debug "^2.2.0"
+underscore-contrib@~0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/underscore-contrib/-/underscore-contrib-0.3.0.tgz#665b66c24783f8fa2b18c9f8cbb0e2c7d48c26c7"
+ integrity sha1-ZltmwkeD+PorGMn4y7Dix9SMJsc=
+ dependencies:
+ underscore "1.6.0"
+
+underscore@1.6.0, underscore@~1.6.0:
+ version "1.6.0"
+ resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.6.0.tgz#8b38b10cacdef63337b8b24e4ff86d45aea529a8"
+ integrity sha1-izixDKze9jM3uLJOT/htRa6lKag=
+
underscore@^1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.9.0.tgz#31dbb314cfcc88f169cd3692d9149d81a00a73e4"
integrity sha512-4IV1DSSxC1QK48j9ONFK1MoIAKKkbE8i7u55w2R6IqBqbT7A/iG7aZBCR2Bi8piF0Uz+i/MG1aeqLwl/5vqF+A==
+underscore@~1.8.3:
+ version "1.8.3"
+ resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022"
+ integrity sha1-Tz+1OxBuYJf8+ctBCfKl6b36UCI=
+
unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
@@ -10353,6 +10377,11 @@ xmlbuilder@8.2.2:
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-8.2.2.tgz#69248673410b4ba42e1a6136551d2922335aa773"
integrity sha1-aSSGc0ELS6QuGmE2VR0pIjNap3M=
+xmlcreate@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/xmlcreate/-/xmlcreate-1.0.2.tgz#fa6bf762a60a413fb3dd8f4b03c5b269238d308f"
+ integrity sha1-+mv3YqYKQT+z3Y9LA8WyaSONMI8=
+
xmlhttprequest-ssl@~1.5.4:
version "1.5.5"
resolved "https://registry.yarnpkg.com/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz#c2876b06168aadc40e57d97e81191ac8f4398b3e"