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:
authorYorick Peterse <yorickpeterse@gmail.com>2019-02-08 16:34:16 +0300
committerYorick Peterse <yorickpeterse@gmail.com>2019-02-08 16:34:16 +0300
commit6afe2688aafa31347eea245b8e98577c80bbd443 (patch)
tree53a7b6c57eb5560cf8ed76466762a2225cbae824
parent5b4c5e84762b09529838c466b12a13fae024457f (diff)
parent7444d7e4b3a5bf217f4e0041e4a80c63da1a645e (diff)
Merge master to 11.8 stable for the feature freeze
-rw-r--r--.gitlab-ci.yml7
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/awards_handler.js2
-rw-r--r--app/assets/javascripts/clusters/clusters_bundle.js29
-rw-r--r--app/assets/javascripts/clusters/components/application_row.vue143
-rw-r--r--app/assets/javascripts/clusters/components/applications.vue3
-rw-r--r--app/assets/javascripts/clusters/constants.js6
-rw-r--r--app/assets/javascripts/clusters/stores/clusters_store.js16
-rw-r--r--app/assets/javascripts/diffs/components/compare_versions.vue18
-rw-r--r--app/assets/javascripts/diffs/components/diff_file_header.vue3
-rw-r--r--app/assets/javascripts/diffs/components/diff_stats.vue52
-rw-r--r--app/assets/javascripts/diffs/components/tree_list.vue11
-rw-r--r--app/assets/javascripts/diffs/store/modules/diff_state.js2
-rw-r--r--app/assets/javascripts/due_date_select.js2
-rw-r--r--app/assets/javascripts/environments/components/container.vue10
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue10
-rw-r--r--app/assets/javascripts/environments/components/environments_app.vue5
-rw-r--r--app/assets/javascripts/environments/components/environments_table.vue8
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_bundle.js2
-rw-r--r--app/assets/javascripts/environments/folder/environments_folder_view.vue5
-rw-r--r--app/assets/javascripts/environments/index.js2
-rw-r--r--app/assets/javascripts/issuable_form.js1
-rw-r--r--app/assets/javascripts/issue_show/components/description.vue14
-rw-r--r--app/assets/javascripts/lib/utils/file_upload.js3
-rw-r--r--app/assets/javascripts/main.js3
-rw-r--r--app/assets/javascripts/member_expiration_date.js1
-rw-r--r--app/assets/javascripts/merge_request.js8
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue4
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue6
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue9
-rw-r--r--app/assets/javascripts/notes/components/note_awards_list.vue2
-rw-r--r--app/assets/javascripts/pages/dashboard/projects/index.js2
-rw-r--r--app/assets/javascripts/pages/explore/projects/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/environments/metrics/index.js2
-rw-r--r--app/assets/javascripts/pages/projects/graphs/charts/index.js18
-rw-r--r--app/assets/javascripts/pages/users/activity_calendar.js10
-rw-r--r--app/assets/javascripts/pages/users/user_tabs.js2
-rw-r--r--app/assets/javascripts/projects/project_new.js26
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue40
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue38
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue91
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue278
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js2
-rw-r--r--app/assets/javascripts/vue_shared/components/pikaday.vue1
-rw-r--r--app/assets/stylesheets/framework/common.scss9
-rw-r--r--app/assets/stylesheets/framework/files.scss20
-rw-r--r--app/assets/stylesheets/framework/variables.scss8
-rw-r--r--app/assets/stylesheets/pages/clusters.scss14
-rw-r--r--app/assets/stylesheets/pages/diff.scss52
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss43
-rw-r--r--app/assets/stylesheets/pages/projects.scss26
-rw-r--r--app/controllers/clusters/clusters_controller.rb2
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb6
-rw-r--r--app/controllers/profiles/preferences_controller.rb2
-rw-r--r--app/controllers/projects/environments_controller.rb5
-rw-r--r--app/controllers/projects/error_tracking_controller.rb40
-rw-r--r--app/controllers/projects/merge_requests/application_controller.rb3
-rw-r--r--app/finders/issuable_finder.rb2
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/auto_devops_helper.rb37
-rw-r--r--app/helpers/environments_helper.rb1
-rw-r--r--app/helpers/preferences_helper.rb15
-rw-r--r--app/models/application_setting.rb8
-rw-r--r--app/models/clusters/applications/prometheus.rb6
-rw-r--r--app/models/clusters/cluster.rb40
-rw-r--r--app/models/clusters/concerns/application_status.rb13
-rw-r--r--app/models/clusters/concerns/application_version.rb4
-rw-r--r--app/models/clusters/platforms/kubernetes.rb2
-rw-r--r--app/models/concerns/cache_markdown_field.rb23
-rw-r--r--app/models/pool_repository.rb4
-rw-r--r--app/models/project.rb2
-rw-r--r--app/models/project_auto_devops.rb6
-rw-r--r--app/models/project_wiki.rb4
-rw-r--r--app/models/repository.rb5
-rw-r--r--app/models/user.rb3
-rw-r--r--app/serializers/cluster_application_entity.rb1
-rw-r--r--app/serializers/deployment_entity.rb10
-rw-r--r--app/serializers/error_tracking/project_serializer.rb2
-rw-r--r--app/services/auth/container_registry_authentication_service.rb3
-rw-r--r--app/services/clusters/applications/check_installation_progress_service.rb23
-rw-r--r--app/services/clusters/applications/schedule_installation_service.rb12
-rw-r--r--app/services/clusters/applications/upgrade_service.rb28
-rw-r--r--app/services/labels/update_service.rb1
-rw-r--r--app/services/merge_requests/update_service.rb7
-rw-r--r--app/services/projects/import_service.rb2
-rw-r--r--app/services/task_list_toggle_service.rb3
-rw-r--r--app/views/admin/application_settings/_localization.html.haml11
-rw-r--r--app/views/admin/application_settings/preferences.html.haml11
-rw-r--r--app/views/award_emoji/_awards_block.html.haml4
-rw-r--r--app/views/clusters/clusters/_form.html.haml17
-rw-r--r--app/views/profiles/preferences/show.html.haml18
-rw-r--r--app/views/projects/commit/show.html.haml2
-rw-r--r--app/views/projects/compare/show.html.haml2
-rw-r--r--app/views/projects/diffs/_diffs.html.haml4
-rw-r--r--app/views/projects/diffs/_file.html.haml4
-rw-r--r--app/views/projects/environments/index.html.haml1
-rw-r--r--app/views/projects/new.html.haml3
-rw-r--r--app/views/projects/project_templates/_built_in_templates.html.haml4
-rw-r--r--app/views/projects/settings/ci_cd/_autodevops_form.html.haml19
-rw-r--r--app/views/shared/icons/_express.svg1
-rw-r--r--app/views/shared/icons/_rails.svg1
-rw-r--r--app/views/shared/icons/_spring.svg1
-rw-r--r--app/views/shared/projects/_project.html.haml41
-rw-r--r--app/workers/all_queues.yml1
-rw-r--r--app/workers/cluster_upgrade_app_worker.rb13
-rw-r--r--app/workers/repository_fork_worker.rb10
-rw-r--r--changelogs/unreleased/2105-add-setting-for-first-day-of-the-week.yml5
-rw-r--r--changelogs/unreleased/44332-add-openid-profile-scopes.yml5
-rw-r--r--changelogs/unreleased/52347-lines-changed-statistics-is-not-easily-visible-in-mr-changes-view.yml5
-rw-r--r--changelogs/unreleased/52363-ui-changes-to-cluster-and-ado-pages.yml5
-rw-r--r--changelogs/unreleased/56014-api-merge-request-squash-commit-messages.yml5
-rw-r--r--changelogs/unreleased/56014-better-squash-commit-messages.yml6
-rw-r--r--changelogs/unreleased/56543-project-lists-further-iteration-improvements.yml5
-rw-r--r--changelogs/unreleased/56788-unicorn-metric-labels.yml5
-rw-r--r--changelogs/unreleased/56938-diff-file-headers-on-compare-not-quite-right.yml5
-rw-r--r--changelogs/unreleased/adriel-remove-feature-flag.yml5
-rw-r--r--changelogs/unreleased/api-group-labels.yml5
-rw-r--r--changelogs/unreleased/fix-repo-settings-file-upload-error.yml5
-rw-r--r--changelogs/unreleased/gitaly-update-1.18.0.yml5
-rw-r--r--changelogs/unreleased/jlenny-AddPagesTemplates.yml5
-rw-r--r--changelogs/unreleased/jlenny-NewAndroidTemplate.yml5
-rw-r--r--changelogs/unreleased/local-markdown-version-bkp3.yml5
-rw-r--r--changelogs/unreleased/move-permission-check-manual-actions-on-deployments.yml5
-rw-r--r--changelogs/unreleased/support-chunking-in-client.yml5
-rw-r--r--changelogs/unreleased/tooltips-to-top.yml5
-rw-r--r--changelogs/unreleased/use_upgrade_install_for_helm_apps.yml5
-rw-r--r--config/initializers/doorkeeper_openid_connect.rb23
-rw-r--r--config/locales/doorkeeper.en.yml6
-rw-r--r--config/routes/project.rb6
-rw-r--r--db/migrate/20140502125220_migrate_repo_size.rb2
-rw-r--r--db/migrate/20181027114222_add_first_day_of_week_to_user_preferences.rb9
-rw-r--r--db/migrate/20181028120717_add_first_day_of_week_to_application_settings.rb16
-rw-r--r--db/migrate/20190130091630_add_local_cached_markdown_version.rb11
-rw-r--r--db/post_migrate/20190204115450_migrate_auto_dev_ops_domain_to_cluster_domain.rb49
-rw-r--r--db/schema.rb5
-rw-r--r--doc/README.md2
-rw-r--r--doc/administration/gitaly/index.md18
-rw-r--r--doc/administration/index.md1
-rw-r--r--doc/administration/invalidate_markdown_cache.md16
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md2
-rw-r--r--doc/administration/operations/filesystem_benchmarking.md87
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/group_labels.md201
-rw-r--r--doc/api/merge_requests.md2
-rw-r--r--doc/api/settings.md10
-rw-r--r--doc/ci/variables/README.md2
-rw-r--r--doc/development/contributing/issue_workflow.md2
-rw-r--r--doc/development/contributing/merge_request_workflow.md3
-rw-r--r--doc/development/i18n/externalization.md6
-rw-r--r--doc/development/i18n/index.md2
-rw-r--r--doc/development/i18n/merging_translations.md2
-rw-r--r--doc/install/installation.md13
-rw-r--r--doc/ssh/README.md12
-rw-r--r--doc/topics/autodevops/index.md39
-rw-r--r--doc/user/group/clusters/index.md15
-rw-r--r--doc/user/index.md44
-rw-r--r--doc/user/profile/preferences.md8
-rw-r--r--doc/user/project/clusters/index.md18
-rw-r--r--doc/user/project/merge_requests/img/squash_mr_message.pngbin0 -> 150302 bytes
-rw-r--r--doc/user/project/merge_requests/squash_and_merge.md15
-rw-r--r--doc/user/project/pages/getting_started_part_three.md19
-rw-r--r--doc/user/project/pages/getting_started_part_two.md2
-rw-r--r--doc/user/project/pipelines/job_artifacts.md5
-rw-r--r--doc/workflow/repository_mirroring.md8
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/entities.rb13
-rw-r--r--lib/api/group_labels.rb63
-rw-r--r--lib/api/helpers.rb11
-rw-r--r--lib/api/helpers/label_helpers.rb82
-rw-r--r--lib/api/labels.rb81
-rw-r--r--lib/api/merge_requests.rb2
-rw-r--r--lib/api/settings.rb1
-rw-r--r--lib/api/subscriptions.rb87
-rw-r--r--lib/backup/repository.rb2
-rw-r--r--lib/gitlab/auth.rb5
-rw-r--r--lib/gitlab/bitbucket_import/importer.rb6
-rw-r--r--lib/gitlab/bitbucket_import/wiki_formatter.rb25
-rw-r--r--lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml121
-rw-r--r--lib/gitlab/ci/templates/Android.gitlab-ci.yml2
-rw-r--r--lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml41
-rw-r--r--lib/gitlab/ci/templates/dotNET.gitlab-ci.yml1
-rw-r--r--lib/gitlab/git/object_pool.rb9
-rw-r--r--lib/gitlab/git/repository.rb7
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb33
-rw-r--r--lib/gitlab/gitaly_client/util.rb8
-rw-r--r--lib/gitlab/github_import/importer/repository_importer.rb9
-rw-r--r--lib/gitlab/gon_helper.rb1
-rw-r--r--lib/gitlab/kubernetes/helm/api.rb11
-rw-r--r--lib/gitlab/kubernetes/helm/install_command.rb34
-rw-r--r--lib/gitlab/kubernetes/helm/upgrade_command.rb65
-rw-r--r--lib/gitlab/legacy_github_import/importer.rb2
-rw-r--r--lib/gitlab/legacy_github_import/wiki_formatter.rb4
-rw-r--r--lib/gitlab/metrics/samplers/unicorn_sampler.rb8
-rw-r--r--lib/gitlab/project_template.rb19
-rw-r--r--lib/gitlab/shell.rb67
-rw-r--r--locale/gitlab.pot181
-rw-r--r--package.json2
-rw-r--r--qa/qa/page/main/login.rb14
-rw-r--r--qa/qa/page/project/settings/ci_cd.rb5
-rw-r--r--qa/qa/resource/kubernetes_cluster.rb6
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/add_deploy_key_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb3
-rw-r--r--qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb6
-rw-r--r--qa/spec/spec_helper.rb14
-rw-r--r--spec/controllers/groups/clusters_controller_spec.rb21
-rw-r--r--spec/controllers/profiles/preferences_controller_spec.rb3
-rw-r--r--spec/controllers/projects/error_tracking_controller_spec.rb114
-rw-r--r--spec/features/clusters/cluster_detail_page_spec.rb69
-rw-r--r--spec/features/merge_request/user_accepts_merge_request_spec.rb4
-rw-r--r--spec/features/merge_request/user_customizes_merge_commit_message_spec.rb10
-rw-r--r--spec/features/merge_request/user_resolves_conflicts_spec.rb2
-rw-r--r--spec/features/merge_request/user_sees_versions_spec.rb31
-rw-r--r--spec/features/projects/settings/pipelines_settings_spec.rb25
-rw-r--r--spec/features/snippets/show_spec.rb8
-rw-r--r--spec/fixtures/api/schemas/cluster_status.json3
-rw-r--r--spec/fixtures/api/schemas/public_api/v4/group_labels.json18
-rw-r--r--spec/helpers/auto_devops_helper_spec.rb35
-rw-r--r--spec/helpers/preferences_helper_spec.rb24
-rw-r--r--spec/javascripts/clusters/components/application_row_spec.js138
-rw-r--r--spec/javascripts/clusters/stores/clusters_store_spec.js3
-rw-r--r--spec/javascripts/diffs/components/compare_versions_spec.js4
-rw-r--r--spec/javascripts/diffs/components/diff_file_header_spec.js4
-rw-r--r--spec/javascripts/diffs/components/diff_stats_spec.js33
-rw-r--r--spec/javascripts/diffs/components/tree_list_spec.js6
-rw-r--r--spec/javascripts/environments/environment_item_spec.js2
-rw-r--r--spec/javascripts/environments/environment_table_spec.js1
-rw-r--r--spec/javascripts/environments/environments_app_spec.js1
-rw-r--r--spec/javascripts/environments/folder/environments_folder_view_spec.js1
-rw-r--r--spec/javascripts/helpers/vue_test_utils_helper.js19
-rw-r--r--spec/javascripts/helpers/vue_test_utils_helper_spec.js48
-rw-r--r--spec/javascripts/issue_show/components/description_spec.js10
-rw-r--r--spec/javascripts/lib/utils/file_upload_spec.js44
-rw-r--r--spec/javascripts/merge_request_spec.js53
-rw-r--r--spec/javascripts/monitoring/charts/area_spec.js220
-rw-r--r--spec/javascripts/monitoring/dashboard_spec.js16
-rw-r--r--spec/javascripts/monitoring/mock_data.js1
-rw-r--r--spec/javascripts/notes/mock_data.js1
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/commit_edit_spec.js85
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js61
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_commits_header_spec.js110
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js153
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js2
-rw-r--r--spec/lib/banzai/object_renderer_spec.rb2
-rw-r--r--spec/lib/gitlab/auth_spec.rb2
-rw-r--r--spec/lib/gitlab/bare_repository_import/repository_spec.rb2
-rw-r--r--spec/lib/gitlab/bitbucket_import/importer_spec.rb8
-rw-r--r--spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb29
-rw-r--r--spec/lib/gitlab/git/blame_spec.rb2
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb2
-rw-r--r--spec/lib/gitlab/git/branch_spec.rb4
-rw-r--r--spec/lib/gitlab/git/commit_spec.rb4
-rw-r--r--spec/lib/gitlab/git/compare_spec.rb2
-rw-r--r--spec/lib/gitlab/git/diff_spec.rb2
-rw-r--r--spec/lib/gitlab/git/remote_repository_spec.rb16
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb97
-rw-r--r--spec/lib/gitlab/git/tag_spec.rb2
-rw-r--r--spec/lib/gitlab/git/tree_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/remote_service_spec.rb2
-rw-r--r--spec/lib/gitlab/gitaly_client/util_spec.rb4
-rw-r--r--spec/lib/gitlab/github_import/importer/repository_importer_spec.rb14
-rw-r--r--spec/lib/gitlab/kubernetes/helm/api_spec.rb45
-rw-r--r--spec/lib/gitlab/kubernetes/helm/install_command_spec.rb84
-rw-r--r--spec/lib/gitlab/kubernetes/helm/upgrade_command_spec.rb140
-rw-r--r--spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb8
-rw-r--r--spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb4
-rw-r--r--spec/lib/gitlab/project_template_spec.rb7
-rw-r--r--spec/lib/gitlab/shell_spec.rb17
-rw-r--r--spec/migrations/clean_up_for_members_spec.rb4
-rw-r--r--spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb114
-rw-r--r--spec/models/application_setting_spec.rb7
-rw-r--r--spec/models/clusters/applications/cert_manager_spec.rb1
-rw-r--r--spec/models/clusters/applications/ingress_spec.rb1
-rw-r--r--spec/models/clusters/applications/jupyter_spec.rb1
-rw-r--r--spec/models/clusters/applications/knative_spec.rb1
-rw-r--r--spec/models/clusters/applications/prometheus_spec.rb5
-rw-r--r--spec/models/clusters/applications/runner_spec.rb1
-rw-r--r--spec/models/clusters/cluster_spec.rb105
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb13
-rw-r--r--spec/models/concerns/cache_markdown_field_spec.rb55
-rw-r--r--spec/models/project_spec.rb8
-rw-r--r--spec/models/project_wiki_spec.rb4
-rw-r--r--spec/models/repository_spec.rb2
-rw-r--r--spec/models/resource_label_event_spec.rb6
-rw-r--r--spec/presenters/blob_presenter_spec.rb2
-rw-r--r--spec/requests/api/group_labels_spec.rb258
-rw-r--r--spec/requests/api/merge_requests_spec.rb23
-rw-r--r--spec/requests/api/settings_spec.rb4
-rw-r--r--spec/requests/openid_connect_spec.rb44
-rw-r--r--spec/serializers/cluster_application_entity_spec.rb8
-rw-r--r--spec/serializers/deployment_entity_spec.rb30
-rw-r--r--spec/serializers/environment_serializer_spec.rb4
-rw-r--r--spec/services/clusters/applications/check_installation_progress_service_spec.rb123
-rw-r--r--spec/services/clusters/applications/create_service_spec.rb17
-rw-r--r--spec/services/clusters/applications/schedule_installation_service_spec.rb24
-rw-r--r--spec/services/clusters/applications/upgrade_service_spec.rb128
-rw-r--r--spec/services/issues/update_service_spec.rb72
-rw-r--r--spec/services/merge_requests/update_service_spec.rb2
-rw-r--r--spec/services/projects/create_service_spec.rb6
-rw-r--r--spec/services/projects/fork_service_spec.rb2
-rw-r--r--spec/services/projects/transfer_service_spec.rb2
-rw-r--r--spec/services/projects/update_service_spec.rb2
-rw-r--r--spec/services/task_list_toggle_service_spec.rb11
-rw-r--r--spec/support/shared_examples/issuable_shared_examples.rb73
-rw-r--r--spec/support/shared_examples/models/cluster_application_status_shared_examples.rb53
-rw-r--r--spec/support/shared_examples/models/cluster_application_version_shared_examples.rb20
-rw-r--r--spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb53
-rw-r--r--spec/workers/post_receive_spec.rb15
-rw-r--r--spec/workers/repository_fork_worker_spec.rb7
-rw-r--r--vendor/project_templates/gitbook.tar.gzbin0 -> 13808 bytes
-rw-r--r--vendor/project_templates/hexo.tar.gzbin0 -> 548020 bytes
-rw-r--r--vendor/project_templates/hugo.tar.gzbin0 -> 1048753 bytes
-rw-r--r--vendor/project_templates/jekyll.tar.gzbin0 -> 60703 bytes
-rw-r--r--vendor/project_templates/plainhtml.tar.gzbin0 -> 12079 bytes
-rw-r--r--yarn.lock8
321 files changed, 5107 insertions, 1524 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index b93a79de994..4e8453726a3 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -388,11 +388,13 @@ flaky-examples-check:
.assets-compile-cache: &assets-compile-cache
cache:
- key: "assets-compile:vendor_ruby:.yarn-cache:tmp_cache_assets_sprockets:v3"
+ key: "assets-compile:vendor_ruby:.yarn-cache:tmp_cache_assets_sprockets:v4"
paths:
- vendor/ruby/
- .yarn-cache/
- - tmp/cache/assets/sprockets
+ # We have disabled caching of sprockets for now, as it fails to pick up changes in SCSS:
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/57431
+ # - tmp/cache/assets/sprockets
compile-assets:
<<: *dedicated-runner
@@ -636,7 +638,6 @@ gitlab:assets:compile:
image: dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.5.3-git-2.18-chrome-71.0-node-8.x-yarn-1.12-graphicsmagick-1.3.29-docker-18.06.1
dependencies:
- setup-test-env
- - compile-assets
services:
- docker:stable-dind
variables:
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 092afa15df4..815d5ca06d5 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-1.17.0
+1.19.0
diff --git a/Gemfile b/Gemfile
index a2527b17b70..a3b01c275ce 100644
--- a/Gemfile
+++ b/Gemfile
@@ -422,7 +422,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 1.5.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 1.10.0', require: 'gitaly'
gem 'grpc', '~> 1.15.0'
gem 'google-protobuf', '~> 3.6'
diff --git a/Gemfile.lock b/Gemfile.lock
index f661da41507..0b2bd2c96bd 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -278,7 +278,7 @@ GEM
gettext_i18n_rails (>= 0.7.1)
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
- gitaly-proto (1.5.0)
+ gitaly-proto (1.10.0)
grpc (~> 1.0)
github-markup (1.7.0)
gitlab-default_value_for (3.1.1)
@@ -1020,7 +1020,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.3)
- gitaly-proto (~> 1.5.0)
+ gitaly-proto (~> 1.10.0)
github-markup (~> 1.7.0)
gitlab-default_value_for (~> 3.1.1)
gitlab-markup (~> 1.6.5)
diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js
index cace8bb9dba..73ce3e760ab 100644
--- a/app/assets/javascripts/awards_handler.js
+++ b/app/assets/javascripts/awards_handler.js
@@ -437,7 +437,7 @@ export class AwardsHandler {
createAwardButtonForVotesBlock(votesBlock, emojiName) {
const buttonHtml = `
- <button class="btn award-control js-emoji-btn has-tooltip active" title="You" data-placement="bottom">
+ <button class="btn award-control js-emoji-btn has-tooltip active" title="You">
${this.emoji.glEmojiTag(emojiName)}
<span class="award-control-text js-counter">1</span>
</button>
diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js
index fc4779632f9..6ebd1ad109e 100644
--- a/app/assets/javascripts/clusters/clusters_bundle.js
+++ b/app/assets/javascripts/clusters/clusters_bundle.js
@@ -6,7 +6,13 @@ import Flash from '../flash';
import Poll from '../lib/utils/poll';
import initSettingsPanels from '../settings_panels';
import eventHub from './event_hub';
-import { APPLICATION_STATUS, REQUEST_SUBMITTED, REQUEST_FAILURE } from './constants';
+import {
+ APPLICATION_STATUS,
+ REQUEST_SUBMITTED,
+ REQUEST_FAILURE,
+ UPGRADE_REQUESTED,
+ UPGRADE_REQUEST_FAILURE,
+} from './constants';
import ClustersService from './services/clusters_service';
import ClustersStore from './stores/clusters_store';
import Applications from './components/applications.vue';
@@ -120,11 +126,17 @@ export default class Clusters {
addListeners() {
if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken);
eventHub.$on('installApplication', this.installApplication);
+ eventHub.$on('upgradeApplication', data => this.upgradeApplication(data));
+ eventHub.$on('upgradeFailed', appId => this.upgradeFailed(appId));
+ eventHub.$on('dismissUpgradeSuccess', appId => this.dismissUpgradeSuccess(appId));
}
removeListeners() {
if (this.showTokenButton) this.showTokenButton.removeEventListener('click', this.showToken);
eventHub.$off('installApplication', this.installApplication);
+ eventHub.$off('upgradeApplication', this.upgradeApplication);
+ eventHub.$off('upgradeFailed', this.upgradeFailed);
+ eventHub.$off('dismissUpgradeSuccess', this.dismissUpgradeSuccess);
}
initPolling() {
@@ -245,6 +257,21 @@ export default class Clusters {
});
}
+ upgradeApplication(data) {
+ const appId = data.id;
+ this.store.updateAppProperty(appId, 'requestStatus', UPGRADE_REQUESTED);
+ this.store.updateAppProperty(appId, 'status', APPLICATION_STATUS.UPDATING);
+ this.service.installApplication(appId, data.params).catch(() => this.upgradeFailed(appId));
+ }
+
+ upgradeFailed(appId) {
+ this.store.updateAppProperty(appId, 'requestStatus', UPGRADE_REQUEST_FAILURE);
+ }
+
+ dismissUpgradeSuccess(appId) {
+ this.store.updateAppProperty(appId, 'requestStatus', null);
+ }
+
destroy() {
this.destroyed = true;
diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue
index 3c3ce1dec56..5952e93b9a7 100644
--- a/app/assets/javascripts/clusters/components/application_row.vue
+++ b/app/assets/javascripts/clusters/components/application_row.vue
@@ -1,15 +1,24 @@
<script>
/* eslint-disable vue/require-default-prop */
+import { GlLink } from '@gitlab/ui';
+import TimeagoTooltip from '../../vue_shared/components/time_ago_tooltip.vue';
import { s__, sprintf } from '../../locale';
import eventHub from '../event_hub';
import identicon from '../../vue_shared/components/identicon.vue';
import loadingButton from '../../vue_shared/components/loading_button.vue';
-import { APPLICATION_STATUS, REQUEST_SUBMITTED, REQUEST_FAILURE } from '../constants';
+import {
+ APPLICATION_STATUS,
+ REQUEST_SUBMITTED,
+ REQUEST_FAILURE,
+ UPGRADE_REQUESTED,
+} from '../constants';
export default {
components: {
loadingButton,
identicon,
+ TimeagoTooltip,
+ GlLink,
},
props: {
id: {
@@ -54,6 +63,18 @@ export default {
type: String,
required: false,
},
+ version: {
+ type: String,
+ required: false,
+ },
+ chartRepo: {
+ type: String,
+ required: false,
+ },
+ upgradeAvailable: {
+ type: Boolean,
+ required: false,
+ },
installApplicationRequestParams: {
type: Object,
required: false,
@@ -78,7 +99,8 @@ export default {
return (
this.status === APPLICATION_STATUS.INSTALLED ||
this.status === APPLICATION_STATUS.UPDATED ||
- this.status === APPLICATION_STATUS.UPDATING
+ this.status === APPLICATION_STATUS.UPDATING ||
+ this.status === APPLICATION_STATUS.UPDATE_ERRORED
);
},
canInstall() {
@@ -146,6 +168,69 @@ export default {
title: this.title,
});
},
+ versionLabel() {
+ if (this.upgradeFailed) {
+ return s__('ClusterIntegration|Upgrade failed');
+ } else if (this.isUpgrading) {
+ return s__('ClusterIntegration|Upgrading');
+ }
+
+ return s__('ClusterIntegration|Upgraded');
+ },
+ upgradeRequested() {
+ return this.requestStatus === UPGRADE_REQUESTED;
+ },
+ upgradeSuccessful() {
+ return this.status === APPLICATION_STATUS.UPDATED;
+ },
+ upgradeFailed() {
+ if (this.isUpgrading) {
+ return false;
+ }
+
+ return this.status === APPLICATION_STATUS.UPDATE_ERRORED;
+ },
+ upgradeFailureDescription() {
+ return sprintf(
+ s__(
+ 'ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again.',
+ ),
+ {
+ title: this.title,
+ },
+ );
+ },
+ upgradeSuccessDescription() {
+ return sprintf(s__('ClusterIntegration|%{title} upgraded successfully.'), {
+ title: this.title,
+ });
+ },
+ upgradeButtonLabel() {
+ let label;
+ if (this.upgradeAvailable && !this.upgradeFailed && !this.isUpgrading) {
+ label = s__('ClusterIntegration|Upgrade');
+ } else if (this.isUpgrading) {
+ label = s__('ClusterIntegration|Upgrading');
+ } else if (this.upgradeFailed) {
+ label = s__('ClusterIntegration|Retry upgrade');
+ }
+
+ return label;
+ },
+ isUpgrading() {
+ // Since upgrading is handled asynchronously on the backend we need this check to prevent any delay on the frontend
+ return (
+ this.status === APPLICATION_STATUS.UPDATING ||
+ (this.upgradeRequested && !this.upgradeSuccessful)
+ );
+ },
+ },
+ watch: {
+ status() {
+ if (this.status === APPLICATION_STATUS.UPDATE_ERRORED) {
+ eventHub.$emit('upgradeFailed', this.id);
+ }
+ },
},
methods: {
installClicked() {
@@ -154,6 +239,15 @@ export default {
params: this.installApplicationRequestParams,
});
},
+ upgradeClicked() {
+ eventHub.$emit('upgradeApplication', {
+ id: this.id,
+ params: this.installApplicationRequestParams,
+ });
+ },
+ dismissUpgradeSuccess() {
+ eventHub.$emit('dismissUpgradeSuccess', this.id);
+ },
},
};
</script>
@@ -207,6 +301,51 @@ export default {
</li>
</ul>
</div>
+
+ <div
+ v-if="(upgradeSuccessful || upgradeFailed) && !upgradeAvailable"
+ class="form-text text-muted label p-0 js-cluster-application-upgrade-details"
+ >
+ {{ versionLabel }}
+
+ <span v-if="upgradeSuccessful"> to</span>
+
+ <gl-link
+ v-if="upgradeSuccessful"
+ :href="chartRepo"
+ target="_blank"
+ class="js-cluster-application-upgrade-version"
+ >
+ chart v{{ version }}
+ </gl-link>
+ </div>
+
+ <div
+ v-if="upgradeFailed && !isUpgrading"
+ class="bs-callout bs-callout-danger cluster-application-banner mt-2 mb-0 js-cluster-application-upgrade-failure-message"
+ >
+ {{ upgradeFailureDescription }}
+ </div>
+
+ <div
+ v-if="upgradeRequested && upgradeSuccessful"
+ class="bs-callout bs-callout-success cluster-application-banner mt-2 mb-0 p-0 pl-3"
+ >
+ {{ upgradeSuccessDescription }}
+
+ <button class="close cluster-application-banner-close" @click="dismissUpgradeSuccess">
+ &times;
+ </button>
+ </div>
+
+ <loading-button
+ v-if="upgradeAvailable || upgradeFailed || isUpgrading"
+ class="btn btn-primary js-cluster-application-upgrade-button mt-2"
+ :loading="isUpgrading"
+ :disabled="isUpgrading"
+ :label="upgradeButtonLabel"
+ @click="upgradeClicked"
+ />
</div>
<div
:class="{ 'section-25': showManageButton, 'section-15': !showManageButton }"
diff --git a/app/assets/javascripts/clusters/components/applications.vue b/app/assets/javascripts/clusters/components/applications.vue
index 5d19c79570a..0cf187d4189 100644
--- a/app/assets/javascripts/clusters/components/applications.vue
+++ b/app/assets/javascripts/clusters/components/applications.vue
@@ -362,6 +362,9 @@ export default {
:status-reason="applications.runner.statusReason"
:request-status="applications.runner.requestStatus"
:request-reason="applications.runner.requestReason"
+ :version="applications.runner.version"
+ :chart-repo="applications.runner.chartRepo"
+ :upgrade-available="applications.runner.upgradeAvailable"
:disabled="!helmInstalled"
title-link="https://docs.gitlab.com/runner/"
>
diff --git a/app/assets/javascripts/clusters/constants.js b/app/assets/javascripts/clusters/constants.js
index 360511e8882..39022879d91 100644
--- a/app/assets/javascripts/clusters/constants.js
+++ b/app/assets/javascripts/clusters/constants.js
@@ -12,15 +12,19 @@ export const APPLICATION_STATUS = {
SCHEDULED: 'scheduled',
INSTALLING: 'installing',
INSTALLED: 'installed',
- UPDATED: 'updated',
UPDATING: 'updating',
+ UPDATED: 'updated',
+ UPDATE_ERRORED: 'update_errored',
ERROR: 'errored',
};
// These are only used client-side
export const REQUEST_SUBMITTED = 'request-submitted';
export const REQUEST_FAILURE = 'request-failure';
+export const UPGRADE_REQUESTED = 'upgrade-requested';
+export const UPGRADE_REQUEST_FAILURE = 'upgrade-request-failure';
export const INGRESS = 'ingress';
export const JUPYTER = 'jupyter';
export const KNATIVE = 'knative';
+export const RUNNER = 'runner';
export const CERT_MANAGER = 'cert_manager';
diff --git a/app/assets/javascripts/clusters/stores/clusters_store.js b/app/assets/javascripts/clusters/stores/clusters_store.js
index 8f74be4e0e6..d309678be27 100644
--- a/app/assets/javascripts/clusters/stores/clusters_store.js
+++ b/app/assets/javascripts/clusters/stores/clusters_store.js
@@ -1,6 +1,6 @@
import { s__ } from '../../locale';
import { parseBoolean } from '../../lib/utils/common_utils';
-import { INGRESS, JUPYTER, KNATIVE, CERT_MANAGER } from '../constants';
+import { INGRESS, JUPYTER, KNATIVE, CERT_MANAGER, RUNNER } from '../constants';
export default class ClusterStore {
constructor() {
@@ -40,6 +40,9 @@ export default class ClusterStore {
statusReason: null,
requestStatus: null,
requestReason: null,
+ version: null,
+ chartRepo: 'https://gitlab.com/charts/gitlab-runner',
+ upgradeAvailable: null,
},
prometheus: {
title: s__('ClusterIntegration|Prometheus'),
@@ -100,7 +103,13 @@ export default class ClusterStore {
this.state.statusReason = serverState.status_reason;
serverState.applications.forEach(serverAppEntry => {
- const { name: appId, status, status_reason: statusReason } = serverAppEntry;
+ const {
+ name: appId,
+ status,
+ status_reason: statusReason,
+ version,
+ update_available: upgradeAvailable,
+ } = serverAppEntry;
this.state.applications[appId] = {
...(this.state.applications[appId] || {}),
@@ -124,6 +133,9 @@ export default class ClusterStore {
serverAppEntry.hostname || this.state.applications.knative.hostname;
this.state.applications.knative.externalIp =
serverAppEntry.external_ip || this.state.applications.knative.externalIp;
+ } else if (appId === RUNNER) {
+ this.state.applications.runner.version = version;
+ this.state.applications.runner.upgradeAvailable = upgradeAvailable;
}
});
}
diff --git a/app/assets/javascripts/diffs/components/compare_versions.vue b/app/assets/javascripts/diffs/components/compare_versions.vue
index 3ef54752436..0bf2dde8b96 100644
--- a/app/assets/javascripts/diffs/components/compare_versions.vue
+++ b/app/assets/javascripts/diffs/components/compare_versions.vue
@@ -6,6 +6,7 @@ import { polyfillSticky } from '~/lib/utils/sticky';
import Icon from '~/vue_shared/components/icon.vue';
import CompareVersionsDropdown from './compare_versions_dropdown.vue';
import SettingsDropdown from './settings_dropdown.vue';
+import DiffStats from './diff_stats.vue';
export default {
components: {
@@ -14,6 +15,7 @@ export default {
GlLink,
GlButton,
SettingsDropdown,
+ DiffStats,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -35,8 +37,15 @@ export default {
},
},
computed: {
- ...mapState('diffs', ['commit', 'showTreeList', 'startVersion', 'latestVersionPath']),
- ...mapGetters('diffs', ['hasCollapsedFile']),
+ ...mapGetters('diffs', ['hasCollapsedFile', 'diffFilesLength']),
+ ...mapState('diffs', [
+ 'commit',
+ 'showTreeList',
+ 'startVersion',
+ 'latestVersionPath',
+ 'addedLines',
+ 'removedLines',
+ ]),
comparableDiffs() {
return this.mergeRequestDiffs.slice(1);
},
@@ -104,6 +113,11 @@ export default {
<gl-link :href="commit.commit_url" class="monospace">{{ commit.short_id }}</gl-link>
</div>
<div class="inline-parallel-buttons d-none d-md-flex ml-auto">
+ <diff-stats
+ :diff-files-length="diffFilesLength"
+ :added-lines="addedLines"
+ :removed-lines="removedLines"
+ />
<gl-button
v-if="commit || startVersion"
:href="latestVersionPath"
diff --git a/app/assets/javascripts/diffs/components/diff_file_header.vue b/app/assets/javascripts/diffs/components/diff_file_header.vue
index b58f704bebb..60586d4a607 100644
--- a/app/assets/javascripts/diffs/components/diff_file_header.vue
+++ b/app/assets/javascripts/diffs/components/diff_file_header.vue
@@ -9,6 +9,7 @@ import { GlTooltipDirective } from '@gitlab/ui';
import { truncateSha } from '~/lib/utils/text_utility';
import { __, s__, sprintf } from '~/locale';
import EditButton from './edit_button.vue';
+import DiffStats from './diff_stats.vue';
export default {
components: {
@@ -16,6 +17,7 @@ export default {
EditButton,
Icon,
FileIcon,
+ DiffStats,
},
directives: {
GlTooltip: GlTooltipDirective,
@@ -202,6 +204,7 @@ export default {
v-if="!diffFile.submodule && addMergeRequestButtons"
class="file-actions d-none d-sm-block"
>
+ <diff-stats :added-lines="diffFile.added_lines" :removed-lines="diffFile.removed_lines" />
<template v-if="diffFile.blob && diffFile.blob.readable_text">
<button
:disabled="!diffHasDiscussions(diffFile)"
diff --git a/app/assets/javascripts/diffs/components/diff_stats.vue b/app/assets/javascripts/diffs/components/diff_stats.vue
new file mode 100644
index 00000000000..2e5855380af
--- /dev/null
+++ b/app/assets/javascripts/diffs/components/diff_stats.vue
@@ -0,0 +1,52 @@
+<script>
+import Icon from '~/vue_shared/components/icon.vue';
+import { n__ } from '~/locale';
+
+export default {
+ components: { Icon },
+ props: {
+ addedLines: {
+ type: Number,
+ required: true,
+ },
+ removedLines: {
+ type: Number,
+ required: true,
+ },
+ diffFilesLength: {
+ type: Number,
+ required: false,
+ default: null,
+ },
+ },
+ computed: {
+ filesText() {
+ return n__('File', 'Files', this.diffFilesLength);
+ },
+ isCompareVersionsHeader() {
+ return Boolean(this.diffFilesLength);
+ },
+ },
+};
+</script>
+
+<template>
+ <div
+ class="diff-stats"
+ :class="{
+ 'is-compare-versions-header d-none d-lg-inline-flex': isCompareVersionsHeader,
+ 'd-inline-flex': !isCompareVersionsHeader,
+ }"
+ >
+ <div v-if="diffFilesLength !== null" class="diff-stats-group">
+ <icon name="doc-code" class="diff-stats-icon text-secondary" />
+ <strong>{{ diffFilesLength }} {{ filesText }}</strong>
+ </div>
+ <div class="diff-stats-group cgreen">
+ <icon name="file-addition" class="diff-stats-icon" /> <strong>{{ addedLines }}</strong>
+ </div>
+ <div class="diff-stats-group cred">
+ <icon name="file-deletion" class="diff-stats-icon" /> <strong>{{ removedLines }}</strong>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/diffs/components/tree_list.vue b/app/assets/javascripts/diffs/components/tree_list.vue
index a0f09932593..96ae197d8b8 100644
--- a/app/assets/javascripts/diffs/components/tree_list.vue
+++ b/app/assets/javascripts/diffs/components/tree_list.vue
@@ -14,8 +14,8 @@ export default {
FileRow,
},
computed: {
- ...mapState('diffs', ['tree', 'addedLines', 'removedLines', 'renderTreeList']),
- ...mapGetters('diffs', ['allBlobs', 'diffFilesLength']),
+ ...mapState('diffs', ['tree', 'renderTreeList']),
+ ...mapGetters('diffs', ['allBlobs']),
filteredTreeList() {
return this.renderTreeList ? this.tree : this.allBlobs;
},
@@ -64,13 +64,6 @@ export default {
{{ s__('MergeRequest|No files found') }}
</p>
</div>
- <div v-once class="pt-3 pb-3 text-center">
- {{ n__('%d changed file', '%d changed files', diffFilesLength) }}
- <div>
- <span class="cgreen"> {{ n__('%d addition', '%d additions', addedLines) }} </span>
- <span class="cred"> {{ n__('%d deleted', '%d deletions', removedLines) }} </span>
- </div>
- </div>
</div>
</template>
diff --git a/app/assets/javascripts/diffs/store/modules/diff_state.js b/app/assets/javascripts/diffs/store/modules/diff_state.js
index 6ee33d9fc6d..47f78a5db54 100644
--- a/app/assets/javascripts/diffs/store/modules/diff_state.js
+++ b/app/assets/javascripts/diffs/store/modules/diff_state.js
@@ -11,6 +11,8 @@ const storedTreeShow = localStorage.getItem(MR_TREE_SHOW_KEY);
export default () => ({
isLoading: true,
+ addedLines: null,
+ removedLines: null,
endpoint: '',
basePath: '',
commit: null,
diff --git a/app/assets/javascripts/due_date_select.js b/app/assets/javascripts/due_date_select.js
index dbfcf8cc921..cb1b1173190 100644
--- a/app/assets/javascripts/due_date_select.js
+++ b/app/assets/javascripts/due_date_select.js
@@ -64,6 +64,7 @@ class DueDateSelect {
this.saveDueDate(true);
}
},
+ firstDay: gon.first_day_of_week,
});
calendar.setDate(parsePikadayDate($dueDateInput.val()));
@@ -183,6 +184,7 @@ export default class DueDateSelectors {
onSelect(dateText) {
$datePicker.val(calendar.toString(dateText));
},
+ firstDay: gon.first_day_of_week,
});
calendar.setDate(parsePikadayDate(datePickerVal));
diff --git a/app/assets/javascripts/environments/components/container.vue b/app/assets/javascripts/environments/components/container.vue
index bd402c0eea5..6ece8b92a30 100644
--- a/app/assets/javascripts/environments/components/container.vue
+++ b/app/assets/javascripts/environments/components/container.vue
@@ -22,10 +22,6 @@ export default {
type: Object,
required: true,
},
- canCreateDeployment: {
- type: Boolean,
- required: true,
- },
canReadEnvironment: {
type: Boolean,
required: true,
@@ -51,11 +47,7 @@ export default {
<slot name="emptyState"></slot>
<div v-if="!isLoading && environments.length > 0" class="table-holder">
- <environment-table
- :environments="environments"
- :can-create-deployment="canCreateDeployment"
- :can-read-environment="canReadEnvironment"
- />
+ <environment-table :environments="environments" :can-read-environment="canReadEnvironment" />
<table-pagination
v-if="pagination && pagination.totalPages > 1"
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index f44806d82a6..503c1b38f71 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -47,12 +47,6 @@ export default {
default: () => ({}),
},
- canCreateDeployment: {
- type: Boolean,
- required: false,
- default: false,
- },
-
canReadEnvironment: {
type: Boolean,
required: false,
@@ -151,7 +145,7 @@ export default {
},
actions() {
- if (!this.model || !this.model.last_deployment || !this.canCreateDeployment) {
+ if (!this.model || !this.model.last_deployment) {
return [];
}
@@ -561,7 +555,7 @@ export default {
/>
<rollback-component
- v-if="canRetry && canCreateDeployment"
+ v-if="canRetry"
:is-last-deployment="isLastDeployment"
:retry-url="retryUrl"
/>
diff --git a/app/assets/javascripts/environments/components/environments_app.vue b/app/assets/javascripts/environments/components/environments_app.vue
index 87c1d44dd40..aa2417d3194 100644
--- a/app/assets/javascripts/environments/components/environments_app.vue
+++ b/app/assets/javascripts/environments/components/environments_app.vue
@@ -24,10 +24,6 @@ export default {
type: Boolean,
required: true,
},
- canCreateDeployment: {
- type: Boolean,
- required: true,
- },
canReadEnvironment: {
type: Boolean,
required: true,
@@ -106,7 +102,6 @@ export default {
:is-loading="isLoading"
:environments="state.environments"
:pagination="state.paginationInformation"
- :can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
@onChangePage="onChangePage"
>
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index 75bdf87137f..e2c304de00a 100644
--- a/app/assets/javascripts/environments/components/environments_table.vue
+++ b/app/assets/javascripts/environments/components/environments_table.vue
@@ -23,12 +23,6 @@ export default {
required: false,
default: false,
},
-
- canCreateDeployment: {
- type: Boolean,
- required: false,
- default: false,
- },
},
methods: {
folderUrl(model) {
@@ -64,7 +58,6 @@ export default {
is="environment-item"
:key="`environment-item-${i}`"
:model="model"
- :can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
/>
@@ -79,7 +72,6 @@ export default {
v-for="(children, index) in model.children"
:key="`env-item-${i}-${index}`"
:model="children"
- :can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
/>
diff --git a/app/assets/javascripts/environments/folder/environments_folder_bundle.js b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
index 982e550e73c..56e7f69cad6 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_bundle.js
+++ b/app/assets/javascripts/environments/folder/environments_folder_bundle.js
@@ -18,7 +18,6 @@ export default () =>
endpoint: environmentsData.environmentsDataEndpoint,
folderName: environmentsData.environmentsDataFolderName,
cssContainerClass: environmentsData.cssClass,
- canCreateDeployment: parseBoolean(environmentsData.environmentsDataCanCreateDeployment),
canReadEnvironment: parseBoolean(environmentsData.environmentsDataCanReadEnvironment),
};
},
@@ -28,7 +27,6 @@ export default () =>
endpoint: this.endpoint,
folderName: this.folderName,
cssContainerClass: this.cssContainerClass,
- canCreateDeployment: this.canCreateDeployment,
canReadEnvironment: this.canReadEnvironment,
},
});
diff --git a/app/assets/javascripts/environments/folder/environments_folder_view.vue b/app/assets/javascripts/environments/folder/environments_folder_view.vue
index d6f0b6115a6..80f0e00400b 100644
--- a/app/assets/javascripts/environments/folder/environments_folder_view.vue
+++ b/app/assets/javascripts/environments/folder/environments_folder_view.vue
@@ -23,10 +23,6 @@ export default {
type: String,
required: true,
},
- canCreateDeployment: {
- type: Boolean,
- required: true,
- },
canReadEnvironment: {
type: Boolean,
required: true,
@@ -55,7 +51,6 @@ export default {
:is-loading="isLoading"
:environments="state.environments"
:pagination="state.paginationInformation"
- :can-create-deployment="canCreateDeployment"
:can-read-environment="canReadEnvironment"
@onChangePage="onChangePage"
/>
diff --git a/app/assets/javascripts/environments/index.js b/app/assets/javascripts/environments/index.js
index d366e7550b7..6af66d0f86e 100644
--- a/app/assets/javascripts/environments/index.js
+++ b/app/assets/javascripts/environments/index.js
@@ -20,7 +20,6 @@ export default () =>
helpPagePath: environmentsData.helpPagePath,
cssContainerClass: environmentsData.cssClass,
canCreateEnvironment: parseBoolean(environmentsData.canCreateEnvironment),
- canCreateDeployment: parseBoolean(environmentsData.canCreateDeployment),
canReadEnvironment: parseBoolean(environmentsData.canReadEnvironment),
};
},
@@ -32,7 +31,6 @@ export default () =>
helpPagePath: this.helpPagePath,
cssContainerClass: this.cssContainerClass,
canCreateEnvironment: this.canCreateEnvironment,
- canCreateDeployment: this.canCreateDeployment,
canReadEnvironment: this.canReadEnvironment,
},
});
diff --git a/app/assets/javascripts/issuable_form.js b/app/assets/javascripts/issuable_form.js
index 4d2533d01f1..9336b71cfd7 100644
--- a/app/assets/javascripts/issuable_form.js
+++ b/app/assets/javascripts/issuable_form.js
@@ -44,6 +44,7 @@ export default class IssuableForm {
parse: dateString => parsePikadayDate(dateString),
toString: date => pikadayToString(date),
onSelect: dateText => $issuableDueDate.val(calendar.toString(dateText)),
+ firstDay: gon.first_day_of_week,
});
calendar.setDate(parsePikadayDate($issuableDueDate.val()));
}
diff --git a/app/assets/javascripts/issue_show/components/description.vue b/app/assets/javascripts/issue_show/components/description.vue
index e664269b199..58f14bac8c8 100644
--- a/app/assets/javascripts/issue_show/components/description.vue
+++ b/app/assets/javascripts/issue_show/components/description.vue
@@ -1,6 +1,7 @@
<script>
import $ from 'jquery';
-import { __ } from '~/locale';
+import { s__, sprintf } from '~/locale';
+import createFlash from '~/flash';
import animateMixin from '../mixins/animate';
import TaskList from '../../task_list';
import recaptchaModalImplementor from '../../vue_shared/mixins/recaptcha_modal_implementor';
@@ -91,9 +92,14 @@ export default {
},
taskListUpdateError() {
- window.Flash(
- __(
- 'Someone edited this issue at the same time you did. The description has been updated and you will need to make your changes again.',
+ createFlash(
+ sprintf(
+ s__(
+ 'Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again.',
+ ),
+ {
+ issueType: this.issuableType,
+ },
),
);
diff --git a/app/assets/javascripts/lib/utils/file_upload.js b/app/assets/javascripts/lib/utils/file_upload.js
index b41ffb44971..82ee83e4348 100644
--- a/app/assets/javascripts/lib/utils/file_upload.js
+++ b/app/assets/javascripts/lib/utils/file_upload.js
@@ -1,6 +1,9 @@
export default (buttonSelector, fileSelector) => {
const btn = document.querySelector(buttonSelector);
const fileInput = document.querySelector(fileSelector);
+
+ if (!btn || !fileInput) return;
+
const form = btn.closest('form');
btn.addEventListener('click', () => {
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 8e10b3ad912..63db4938cd7 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -124,9 +124,6 @@ function deferredInitialisation() {
selector: '.has-tooltip, [data-toggle="tooltip"]',
trigger: 'hover',
boundary: 'viewport',
- placement(tip, el) {
- return $(el).data('placement') || 'bottom';
- },
});
// Initialize popovers
diff --git a/app/assets/javascripts/member_expiration_date.js b/app/assets/javascripts/member_expiration_date.js
index 0beedcacf33..0dabb28ea66 100644
--- a/app/assets/javascripts/member_expiration_date.js
+++ b/app/assets/javascripts/member_expiration_date.js
@@ -33,6 +33,7 @@ export default function memberExpirationDate(selector = '.js-access-expiration-d
toggleClearInput.call($input);
},
+ firstDay: gon.first_day_of_week,
});
calendar.setDate(parsePikadayDate($input.val()));
diff --git a/app/assets/javascripts/merge_request.js b/app/assets/javascripts/merge_request.js
index ac3b47cd218..3b42a154af8 100644
--- a/app/assets/javascripts/merge_request.js
+++ b/app/assets/javascripts/merge_request.js
@@ -2,6 +2,7 @@
import $ from 'jquery';
import { __ } from '~/locale';
+import createFlash from '~/flash';
import TaskList from './task_list';
import MergeRequestTabs from './merge_request_tabs';
import IssuablesHelper from './helpers/issuables_helper';
@@ -40,6 +41,13 @@ function MergeRequest(opts) {
document.querySelector('#task_status').innerText = result.task_status;
document.querySelector('#task_status_short').innerText = result.task_status_short;
},
+ onError: () => {
+ createFlash(
+ __(
+ 'Someone edited this merge request at the same time you did. Please refresh the page to see changes.',
+ ),
+ );
+ },
});
}
}
diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue
index ec0e33a1927..14c02db7bcc 100644
--- a/app/assets/javascripts/monitoring/components/charts/area.vue
+++ b/app/assets/javascripts/monitoring/components/charts/area.vue
@@ -189,8 +189,8 @@ export default {
<template>
<div class="prometheus-graph col-12 col-lg-6">
<div class="prometheus-graph-header">
- <h5 class="prometheus-graph-title">{{ graphData.title }}</h5>
- <div class="prometheus-graph-widgets"><slot></slot></div>
+ <h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5>
+ <div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div>
</div>
<gl-area-chart
ref="areaChart"
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 9c5fd93f7d1..895a57785bc 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -160,7 +160,8 @@ export default {
{{ s__('Metrics|Environment') }}
<div class="dropdown prepend-left-10">
<button class="dropdown-menu-toggle" data-toggle="dropdown" type="button">
- <span> {{ currentEnvironmentName }} </span> <icon name="chevron-down" />
+ <span>{{ currentEnvironmentName }}</span>
+ <icon name="chevron-down" />
</button>
<div
v-if="store.environmentsData.length > 0"
@@ -172,9 +173,8 @@ export default {
:href="environment.metrics_path"
:class="{ 'is-active': environment.name == currentEnvironmentName }"
class="dropdown-item"
+ >{{ environment.name }}</a
>
- {{ environment.name }}
- </a>
</li>
</ul>
</div>
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index 394f2a80a67..cad0d382fa2 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -151,10 +151,9 @@ export default {
</div>
<div v-if="canAwardEmoji" class="note-actions-item">
<a
- v-gl-tooltip.bottom
+ v-gl-tooltip
:class="{ 'js-user-authored': isAuthoredByCurrentUser }"
class="note-action-button note-emoji-button js-add-award js-note-emoji"
- data-position="right"
href="#"
title="Add reaction"
>
@@ -175,7 +174,7 @@ export default {
/>
<div v-if="canEdit" class="note-actions-item">
<button
- v-gl-tooltip.bottom
+ v-gl-tooltip
type="button"
title="Edit comment"
class="note-action-button js-note-edit btn btn-transparent"
@@ -186,7 +185,7 @@ export default {
</div>
<div v-if="showDeleteAction" class="note-actions-item">
<button
- v-gl-tooltip.bottom
+ v-gl-tooltip
type="button"
title="Delete comment"
class="note-action-button js-note-delete btn btn-transparent"
@@ -197,7 +196,7 @@ export default {
</div>
<div v-else-if="shouldShowActionsDropdown" class="dropdown more-actions note-actions-item">
<button
- v-gl-tooltip.bottom
+ v-gl-tooltip
type="button"
title="More actions"
class="note-action-button more-actions-toggle btn btn-transparent"
diff --git a/app/assets/javascripts/notes/components/note_awards_list.vue b/app/assets/javascripts/notes/components/note_awards_list.vue
index 3efdd1c5c17..17e5fcab5b7 100644
--- a/app/assets/javascripts/notes/components/note_awards_list.vue
+++ b/app/assets/javascripts/notes/components/note_awards_list.vue
@@ -171,7 +171,6 @@ export default {
:class="getAwardClassBindings(awardList)"
:title="awardTitle(awardList)"
data-boundary="viewport"
- data-placement="bottom"
class="btn award-control"
type="button"
@click="handleAward(awardName)"
@@ -187,7 +186,6 @@ export default {
title="Add reaction"
aria-label="Add reaction"
data-boundary="viewport"
- data-placement="bottom"
type="button"
>
<span class="award-control-icon award-control-icon-neutral">
diff --git a/app/assets/javascripts/pages/dashboard/projects/index.js b/app/assets/javascripts/pages/dashboard/projects/index.js
index 8f98be79640..01001d4f3ff 100644
--- a/app/assets/javascripts/pages/dashboard/projects/index.js
+++ b/app/assets/javascripts/pages/dashboard/projects/index.js
@@ -1,7 +1,5 @@
import ProjectsList from '~/projects_list';
-import Star from '../../../star';
document.addEventListener('DOMContentLoaded', () => {
new ProjectsList(); // eslint-disable-line no-new
- new Star('.project-row'); // eslint-disable-line no-new
});
diff --git a/app/assets/javascripts/pages/explore/projects/index.js b/app/assets/javascripts/pages/explore/projects/index.js
index 8f98be79640..01001d4f3ff 100644
--- a/app/assets/javascripts/pages/explore/projects/index.js
+++ b/app/assets/javascripts/pages/explore/projects/index.js
@@ -1,7 +1,5 @@
import ProjectsList from '~/projects_list';
-import Star from '../../../star';
document.addEventListener('DOMContentLoaded', () => {
new ProjectsList(); // eslint-disable-line no-new
- new Star('.project-row'); // eslint-disable-line no-new
});
diff --git a/app/assets/javascripts/pages/projects/environments/metrics/index.js b/app/assets/javascripts/pages/projects/environments/metrics/index.js
index 0b644780ad4..0d69a689316 100644
--- a/app/assets/javascripts/pages/projects/environments/metrics/index.js
+++ b/app/assets/javascripts/pages/projects/environments/metrics/index.js
@@ -1,3 +1,3 @@
-import monitoringBundle from '~/monitoring/monitoring_bundle';
+import monitoringBundle from 'ee_else_ce/monitoring/monitoring_bundle';
document.addEventListener('DOMContentLoaded', monitoringBundle);
diff --git a/app/assets/javascripts/pages/projects/graphs/charts/index.js b/app/assets/javascripts/pages/projects/graphs/charts/index.js
index 3ccad513c05..26d7fa7371d 100644
--- a/app/assets/javascripts/pages/projects/graphs/charts/index.js
+++ b/app/assets/javascripts/pages/projects/graphs/charts/index.js
@@ -43,10 +43,26 @@ document.addEventListener('DOMContentLoaded', () => {
],
});
+ const reorderWeekDays = (weekDays, firstDayOfWeek = 0) => {
+ if (firstDayOfWeek === 0) {
+ return weekDays;
+ }
+
+ return Object.keys(weekDays).reduce((acc, dayName, idx, arr) => {
+ const reorderedDayName = arr[(idx + firstDayOfWeek) % arr.length];
+
+ return {
+ ...acc,
+ [reorderedDayName]: weekDays[reorderedDayName],
+ };
+ }, {});
+ };
+
const hourData = chartData(projectChartData.hour);
responsiveChart($('#hour-chart'), hourData);
- const dayData = chartData(projectChartData.weekDays);
+ const weekDays = reorderWeekDays(projectChartData.weekDays, gon.first_day_of_week);
+ const dayData = chartData(weekDays);
responsiveChart($('#weekday-chart'), dayData);
const monthData = chartData(projectChartData.month);
diff --git a/app/assets/javascripts/pages/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js
index 8a84ac37dab..afa099d0e0b 100644
--- a/app/assets/javascripts/pages/users/activity_calendar.js
+++ b/app/assets/javascripts/pages/users/activity_calendar.js
@@ -159,7 +159,7 @@ export default class ActivityCalendar {
.append('g')
.attr('transform', (group, i) => {
_.each(group, (stamp, a) => {
- if (a === 0 && stamp.day === 0) {
+ if (a === 0 && stamp.day === this.firstDayOfWeek) {
const month = stamp.date.getMonth();
const x = this.daySizeWithSpace * i + 1 + this.daySizeWithSpace;
const lastMonth = _.last(this.months);
@@ -205,6 +205,14 @@ export default class ActivityCalendar {
y: 29 + this.dayYPos(5),
},
];
+
+ if (this.firstDayOfWeek === 1) {
+ days.push({
+ text: 'S',
+ y: 29 + this.dayYPos(7),
+ });
+ }
+
this.svg
.append('g')
.selectAll('text')
diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js
index 1c3fd58ca74..39cd891c111 100644
--- a/app/assets/javascripts/pages/users/user_tabs.js
+++ b/app/assets/javascripts/pages/users/user_tabs.js
@@ -234,7 +234,7 @@ export default class UserTabs {
data,
calendarActivitiesPath,
utcOffset,
- 0,
+ gon.first_day_of_week,
monthsAgo,
);
}
diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js
index 998554d1be5..d65e73a3f9c 100644
--- a/app/assets/javascripts/projects/project_new.js
+++ b/app/assets/javascripts/projects/project_new.js
@@ -115,15 +115,35 @@ const bindEvents = () => {
const templates = {
rails: {
text: 'Ruby on Rails',
- icon: '.template-option svg.icon-rails',
+ icon: '.template-option .icon-rails',
},
express: {
text: 'NodeJS Express',
- icon: '.template-option svg.icon-node-express',
+ icon: '.template-option .icon-express',
},
spring: {
text: 'Spring',
- icon: '.template-option svg.icon-java-spring',
+ icon: '.template-option .icon-spring',
+ },
+ hugo: {
+ text: 'Pages/Hugo',
+ icon: '.template-option .icon-hugo',
+ },
+ jekyll: {
+ text: 'Pages/Jekyll',
+ icon: '.template-option .icon-jekyll',
+ },
+ plainhtml: {
+ text: 'Pages/Plain HTML',
+ icon: '.template-option .icon-plainhtml',
+ },
+ gitbook: {
+ text: 'Pages/GitBook',
+ icon: '.template-option .icon-gitbook',
+ },
+ hexo: {
+ text: 'Pages/Hexo',
+ icon: '.template-option .icon-hexo',
},
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue
new file mode 100644
index 00000000000..a38f25cce35
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_edit.vue
@@ -0,0 +1,40 @@
+<script>
+export default {
+ props: {
+ value: {
+ type: String,
+ required: true,
+ },
+ label: {
+ type: String,
+ required: true,
+ },
+ inputId: {
+ type: String,
+ required: true,
+ },
+ },
+};
+</script>
+
+<template>
+ <li>
+ <div class="commit-message-editor">
+ <div class="d-flex flex-wrap align-items-center justify-content-between">
+ <label class="col-form-label" :for="inputId">
+ <strong>{{ label }}</strong>
+ </label>
+ <slot name="header"></slot>
+ </div>
+ <textarea
+ :id="inputId"
+ :value="value"
+ class="form-control js-gfm-input append-bottom-default commit-message-edit"
+ required="required"
+ rows="7"
+ @input="$emit('input', $event.target.value)"
+ ></textarea>
+ <slot name="checkbox"></slot>
+ </div>
+ </li>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue
new file mode 100644
index 00000000000..b3c1c0e329d
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commit_message_dropdown.vue
@@ -0,0 +1,38 @@
+<script>
+import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
+
+export default {
+ components: {
+ GlDropdown,
+ GlDropdownItem,
+ },
+ props: {
+ commits: {
+ type: Array,
+ required: true,
+ default: () => [],
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <gl-dropdown
+ right
+ no-caret
+ text="Use an existing commit message"
+ variant="link"
+ class="mr-commit-dropdown"
+ >
+ <gl-dropdown-item
+ v-for="commit in commits"
+ :key="commit.short_id"
+ class="text-nowrap text-truncate"
+ @click="$emit('input', commit.message)"
+ >
+ <span class="monospace mr-2">{{ commit.short_id }}</span> {{ commit.title }}
+ </gl-dropdown-item>
+ </gl-dropdown>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
new file mode 100644
index 00000000000..a1d3a09cca4
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/commits_header.vue
@@ -0,0 +1,91 @@
+<script>
+import { GlButton } from '@gitlab/ui';
+import _ from 'underscore';
+import { __, n__, sprintf, s__ } from '~/locale';
+import Icon from '~/vue_shared/components/icon.vue';
+
+export default {
+ components: {
+ Icon,
+ GlButton,
+ },
+ props: {
+ isSquashEnabled: {
+ type: Boolean,
+ required: true,
+ },
+ commitsCount: {
+ type: Number,
+ required: false,
+ default: 0,
+ },
+ targetBranch: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ expanded: false,
+ };
+ },
+ computed: {
+ collapseIcon() {
+ return this.expanded ? 'chevron-down' : 'chevron-right';
+ },
+ commitsCountMessage() {
+ return n__(__('%d commit'), __('%d commits'), this.isSquashEnabled ? 1 : this.commitsCount);
+ },
+ modifyLinkMessage() {
+ return this.isSquashEnabled ? __('Modify commit messages') : __('Modify merge commit');
+ },
+ ariaLabel() {
+ return this.expanded ? __('Collapse') : __('Expand');
+ },
+ message() {
+ return sprintf(
+ s__(
+ 'mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}.',
+ ),
+ {
+ commitCount: `<strong class="commits-count-message">${this.commitsCountMessage}</strong>`,
+ mergeCommitCount: `<strong>${s__('mrWidgetCommitsAdded|1 merge commit')}</strong>`,
+ targetBranch: `<span class="label-branch">${_.escape(this.targetBranch)}</span>`,
+ },
+ false,
+ );
+ },
+ },
+ methods: {
+ toggle() {
+ this.expanded = !this.expanded;
+ },
+ },
+};
+</script>
+
+<template>
+ <div>
+ <div
+ class="js-mr-widget-commits-count mr-widget-extension clickable d-flex align-items-center px-3 py-2"
+ @click="toggle()"
+ >
+ <gl-button
+ :aria-label="ariaLabel"
+ variant="blank"
+ class="commit-edit-toggle mr-2"
+ @click.stop="toggle()"
+ >
+ <icon :name="collapseIcon" :size="16" />
+ </gl-button>
+ <span v-if="expanded">{{ __('Collapse') }}</span>
+ <span v-else>
+ <span v-html="message"></span>
+ <gl-button variant="link" class="modify-message-button">
+ {{ modifyLinkMessage }}
+ </gl-button>
+ </span>
+ </div>
+ <div v-show="expanded"><slot></slot></div>
+ </div>
+</template>
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 b8f29649eb5..ce4207864ea 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
@@ -2,17 +2,24 @@
import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg';
import simplePoll from '~/lib/utils/simple_poll';
+import { __ } from '~/locale';
import MergeRequest from '../../../merge_request';
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon.vue';
import eventHub from '../../event_hub';
import SquashBeforeMerge from './squash_before_merge.vue';
+import CommitsHeader from './commits_header.vue';
+import CommitEdit from './commit_edit.vue';
+import CommitMessageDropdown from './commit_message_dropdown.vue';
export default {
name: 'ReadyToMerge',
components: {
statusIcon,
SquashBeforeMerge,
+ CommitsHeader,
+ CommitEdit,
+ CommitMessageDropdown,
},
props: {
mr: { type: Object, required: true },
@@ -22,27 +29,20 @@ export default {
return {
removeSourceBranch: this.mr.shouldRemoveSourceBranch,
mergeWhenBuildSucceeds: false,
- useCommitMessageWithDescription: false,
setToMergeWhenPipelineSucceeds: false,
- showCommitMessageEditor: false,
isMakingRequest: false,
isMergingImmediately: false,
commitMessage: this.mr.commitMessage,
squashBeforeMerge: this.mr.squash,
successSvg,
warningSvg,
+ squashCommitMessage: this.mr.squashCommitMessage,
};
},
computed: {
shouldShowMergeWhenPipelineSucceedsText() {
return this.mr.isPipelineActive;
},
- commitMessageLinkTitle() {
- const withDesc = 'Include description in commit message';
- const withoutDesc = "Don't include description in commit message";
-
- return this.useCommitMessageWithDescription ? withoutDesc : withDesc;
- },
status() {
const { pipeline, isPipelineActive, isPipelineFailed, hasCI, ciStatus } = this.mr;
@@ -84,9 +84,9 @@ export default {
},
mergeButtonText() {
if (this.isMergingImmediately) {
- return 'Merge in progress';
+ return __('Merge in progress');
} else if (this.shouldShowMergeWhenPipelineSucceedsText) {
- return 'Merge when pipeline succeeds';
+ return __('Merge when pipeline succeeds');
}
return 'Merge';
@@ -98,7 +98,7 @@ export default {
const { commitMessage } = this;
return Boolean(
!commitMessage.length ||
- !this.shouldShowMergeControls() ||
+ !this.shouldShowMergeControls ||
this.isMakingRequest ||
this.mr.preventMerge,
);
@@ -110,18 +110,14 @@ export default {
const { commitsCount, enableSquashBeforeMerge } = this.mr;
return enableSquashBeforeMerge && commitsCount > 1;
},
- },
- methods: {
shouldShowMergeControls() {
return this.mr.isMergeAllowed || this.shouldShowMergeWhenPipelineSucceedsText;
},
- updateCommitMessage() {
- const cmwd = this.mr.commitMessageWithDescription;
- this.useCommitMessageWithDescription = !this.useCommitMessageWithDescription;
- this.commitMessage = this.useCommitMessageWithDescription ? cmwd : this.mr.commitMessage;
- },
- toggleCommitMessageEditor() {
- this.showCommitMessageEditor = !this.showCommitMessageEditor;
+ },
+ methods: {
+ updateMergeCommitMessage(includeDescription) {
+ const { commitMessageWithDescription, commitMessage } = this.mr;
+ this.commitMessage = includeDescription ? commitMessageWithDescription : commitMessage;
},
handleMergeButtonClick(mergeWhenBuildSucceeds, mergeImmediately) {
// TODO: Remove no-param-reassign
@@ -139,6 +135,7 @@ export default {
merge_when_pipeline_succeeds: this.setToMergeWhenPipelineSucceeds,
should_remove_source_branch: this.removeSourceBranch === true,
squash: this.squashBeforeMerge,
+ squash_commit_message: this.squashCommitMessage,
};
this.isMakingRequest = true;
@@ -158,7 +155,7 @@ export default {
})
.catch(() => {
this.isMakingRequest = false;
- new Flash('Something went wrong. Please try again.'); // eslint-disable-line
+ new Flash(__('Something went wrong. Please try again.')); // eslint-disable-line
});
},
initiateMergePolling() {
@@ -194,7 +191,7 @@ export default {
}
})
.catch(() => {
- new Flash('Something went wrong while merging this merge request. Please try again.'); // eslint-disable-line
+ new Flash(__('Something went wrong while merging this merge request. Please try again.')); // eslint-disable-line
});
},
initiateRemoveSourceBranchPolling() {
@@ -223,7 +220,7 @@ export default {
}
})
.catch(() => {
- new Flash('Something went wrong while deleting the source branch. Please try again.'); // eslint-disable-line
+ new Flash(__('Something went wrong while deleting the source branch. Please try again.')); // eslint-disable-line
});
},
},
@@ -231,127 +228,136 @@ export default {
</script>
<template>
- <div class="mr-widget-body media">
- <status-icon :status="iconClass" />
- <div class="media-body">
- <div class="mr-widget-body-controls media space-children">
- <span class="btn-group">
- <button
- :disabled="isMergeButtonDisabled"
- :class="mergeButtonClass"
- type="button"
- class="qa-merge-button"
- @click="handleMergeButtonClick()"
- >
- <i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
- {{ mergeButtonText }}
- </button>
- <button
- v-if="shouldShowMergeOptionsDropdown"
- :disabled="isMergeButtonDisabled"
- type="button"
- class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
- data-toggle="dropdown"
- aria-label="Select merge moment"
- >
- <i class="fa fa-chevron-down qa-merge-moment-dropdown" aria-hidden="true"></i>
- </button>
- <ul
- v-if="shouldShowMergeOptionsDropdown"
- class="dropdown-menu dropdown-menu-right"
- role="menu"
- >
- <li>
- <a
- class="merge_when_pipeline_succeeds qa-merge-when-pipeline-succeeds-option"
- href="#"
- @click.prevent="handleMergeButtonClick(true)"
- >
- <span class="media">
- <span class="merge-opt-icon" aria-hidden="true" v-html="successSvg"></span>
- <span class="media-body merge-opt-title">Merge when pipeline succeeds</span>
- </span>
- </a>
- </li>
- <li>
- <a
- class="accept-merge-request qa-merge-immediately-option"
- href="#"
- @click.prevent="handleMergeButtonClick(false, true)"
- >
- <span class="media">
- <span class="merge-opt-icon" aria-hidden="true" v-html="warningSvg"></span>
- <span class="media-body merge-opt-title">Merge immediately</span>
- </span>
- </a>
- </li>
- </ul>
- </span>
- <div class="media-body-wrap space-children">
- <template v-if="shouldShowMergeControls()">
- <label v-if="mr.canRemoveSourceBranch">
- <input
- id="remove-source-branch-input"
- v-model="removeSourceBranch"
- :disabled="isRemoveSourceBranchButtonDisabled"
- class="js-remove-source-branch-checkbox"
- type="checkbox"
- />
- Delete source branch
- </label>
-
- <!-- Placeholder for EE extension of this component -->
- <squash-before-merge
- v-if="shouldShowSquashBeforeMerge"
- v-model="squashBeforeMerge"
- :help-path="mr.squashBeforeMergeHelpPath"
- :is-disabled="isMergeButtonDisabled"
- />
-
- <span v-if="mr.ffOnlyEnabled" class="js-fast-forward-message">
- Fast-forward merge without a merge commit
- </span>
+ <div>
+ <div class="mr-widget-body media">
+ <status-icon :status="iconClass" />
+ <div class="media-body">
+ <div class="mr-widget-body-controls media space-children">
+ <span class="btn-group">
<button
- v-else
:disabled="isMergeButtonDisabled"
- class="js-modify-commit-message-button btn btn-default btn-sm"
+ :class="mergeButtonClass"
type="button"
- @click="toggleCommitMessageEditor"
+ class="qa-merge-button"
+ @click="handleMergeButtonClick()"
>
- Modify commit message
+ <i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
+ {{ mergeButtonText }}
</button>
- </template>
- <template v-else>
- <span class="bold js-resolve-mr-widget-items-message">
- You can only merge once the items above are resolved
- </span>
- </template>
- </div>
- </div>
- <div v-if="showCommitMessageEditor" class="prepend-top-default commit-message-editor">
- <div class="form-group clearfix">
- <label class="col-form-label" for="commit-message"> Commit message </label>
- <div class="col-sm-10">
- <div class="commit-message-container">
- <div class="max-width-marker"></div>
- <textarea
- id="commit-message"
- v-model="commitMessage"
- class="form-control js-commit-message"
- required="required"
- rows="14"
- name="Commit message"
- ></textarea>
- </div>
- <p class="hint">
- Try to keep the first line under 52 characters and the others under 72
- </p>
- <div class="hint">
- <a href="#" @click.prevent="updateCommitMessage"> {{ commitMessageLinkTitle }} </a>
- </div>
+ <button
+ v-if="shouldShowMergeOptionsDropdown"
+ :disabled="isMergeButtonDisabled"
+ type="button"
+ class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
+ data-toggle="dropdown"
+ aria-label="Select merge moment"
+ >
+ <i class="fa fa-chevron-down qa-merge-moment-dropdown" aria-hidden="true"></i>
+ </button>
+ <ul
+ v-if="shouldShowMergeOptionsDropdown"
+ class="dropdown-menu dropdown-menu-right"
+ role="menu"
+ >
+ <li>
+ <a
+ class="merge_when_pipeline_succeeds qa-merge-when-pipeline-succeeds-option"
+ href="#"
+ @click.prevent="handleMergeButtonClick(true)"
+ >
+ <span class="media">
+ <span class="merge-opt-icon" aria-hidden="true" v-html="successSvg"></span>
+ <span class="media-body merge-opt-title">{{
+ __('Merge when pipeline succeeds')
+ }}</span>
+ </span>
+ </a>
+ </li>
+ <li>
+ <a
+ class="accept-merge-request qa-merge-immediately-option"
+ href="#"
+ @click.prevent="handleMergeButtonClick(false, true)"
+ >
+ <span class="media">
+ <span class="merge-opt-icon" aria-hidden="true" v-html="warningSvg"></span>
+ <span class="media-body merge-opt-title">{{ __('Merge immediately') }}</span>
+ </span>
+ </a>
+ </li>
+ </ul>
+ </span>
+ <div class="media-body-wrap space-children">
+ <template v-if="shouldShowMergeControls">
+ <label v-if="mr.canRemoveSourceBranch">
+ <input
+ id="remove-source-branch-input"
+ v-model="removeSourceBranch"
+ :disabled="isRemoveSourceBranchButtonDisabled"
+ class="js-remove-source-branch-checkbox"
+ type="checkbox"
+ />
+ {{ __('Delete source branch') }}
+ </label>
+
+ <!-- Placeholder for EE extension of this component -->
+ <squash-before-merge
+ v-if="shouldShowSquashBeforeMerge"
+ v-model="squashBeforeMerge"
+ :help-path="mr.squashBeforeMergeHelpPath"
+ :is-disabled="isMergeButtonDisabled"
+ />
+ </template>
+ <template v-else>
+ <span class="bold js-resolve-mr-widget-items-message">
+ {{ __('You can only merge once the items above are resolved') }}
+ </span>
+ </template>
</div>
</div>
</div>
</div>
+ <template v-if="shouldShowMergeControls">
+ <div v-if="mr.ffOnlyEnabled" class="mr-fast-forward-message">
+ {{ __('Fast-forward merge without a merge commit') }}
+ </div>
+ <template v-else>
+ <commits-header
+ :is-squash-enabled="squashBeforeMerge"
+ :commits-count="mr.commitsCount"
+ :target-branch="mr.targetBranch"
+ >
+ <ul class="border-top content-list commits-list flex-list">
+ <commit-edit
+ v-if="squashBeforeMerge"
+ v-model="squashCommitMessage"
+ :label="__('Squash commit message')"
+ input-id="squash-message-edit"
+ squash
+ >
+ <commit-message-dropdown
+ slot="header"
+ v-model="squashCommitMessage"
+ :commits="mr.commits"
+ />
+ </commit-edit>
+ <commit-edit
+ v-model="commitMessage"
+ :label="__('Merge commit message')"
+ input-id="merge-message-edit"
+ >
+ <label slot="checkbox">
+ <input
+ id="include-description"
+ type="checkbox"
+ @change="updateMergeCommitMessage($event.target.checked)"
+ />
+ {{ __('Include merge request description') }}
+ </label>
+ </commit-edit>
+ </ul>
+ </commits-header>
+ </template>
+ </template>
</div>
</template>
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 57c4dfbe3b7..abbbe19c5ef 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
@@ -315,7 +315,7 @@ export default {
:endpoint="mr.testResultsPath"
/>
- <div class="mr-widget-section">
+ <div class="mr-widget-section p-0">
<component :is="componentName" :mr="mr" :service="service" />
<section v-if="shouldRenderCollaborationStatus" class="mr-info-list mr-links">
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index 36cac230d9d..58363f632a9 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -42,6 +42,8 @@ export default class MergeRequestStore {
this.mergePipeline = data.merge_pipeline || {};
this.deployments = this.deployments || data.deployments || [];
this.postMergeDeployments = this.postMergeDeployments || [];
+ this.commits = data.commits_without_merge_commits || [];
+ this.squashCommitMessage = data.default_squash_commit_message;
this.initRebase(data);
if (data.issues_links) {
diff --git a/app/assets/javascripts/vue_shared/components/pikaday.vue b/app/assets/javascripts/vue_shared/components/pikaday.vue
index 8bdb5bf22c2..13eb46437dd 100644
--- a/app/assets/javascripts/vue_shared/components/pikaday.vue
+++ b/app/assets/javascripts/vue_shared/components/pikaday.vue
@@ -40,6 +40,7 @@ export default {
toString: date => pikadayToString(date),
onSelect: this.selected.bind(this),
onClose: this.toggled.bind(this),
+ firstDay: gon.first_day_of_week,
});
this.$el.append(this.calendar.el);
diff --git a/app/assets/stylesheets/framework/common.scss b/app/assets/stylesheets/framework/common.scss
index cb449b642e7..c5c3b66438c 100644
--- a/app/assets/stylesheets/framework/common.scss
+++ b/app/assets/stylesheets/framework/common.scss
@@ -391,6 +391,11 @@ img.emoji {
.flex-no-shrink { flex-shrink: 0; }
.ws-initial { white-space: initial; }
.overflow-auto { overflow: auto; }
+.d-flex-center {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+}
/** COMMON SIZING CLASSES **/
.w-0 { width: 0; }
@@ -402,6 +407,10 @@ img.emoji {
.min-height-0 { min-height: 0; }
+.w-3 { width: #{3 * $grid-size}; }
+
+.h-3 { width: #{3 * $grid-size}; }
+
/** COMMON SPACING CLASSES **/
.gl-pl-0 { padding-left: 0; }
.gl-pl-1 { padding-left: #{0.5 * $grid-size}; }
diff --git a/app/assets/stylesheets/framework/files.scss b/app/assets/stylesheets/framework/files.scss
index 037a5adfb7e..6108eaa1ad0 100644
--- a/app/assets/stylesheets/framework/files.scss
+++ b/app/assets/stylesheets/framework/files.scss
@@ -4,6 +4,7 @@
*/
.file-holder {
border: 1px solid $border-color;
+ border-top: 0;
border-radius: $border-radius-default;
&.file-holder-no-border {
@@ -51,6 +52,7 @@
position: absolute;
top: 5px;
right: 15px;
+ margin-left: auto;
.btn {
padding: 0 10px;
@@ -324,10 +326,12 @@ span.idiff {
&,
.file-holder & {
display: flex;
+ flex-wrap: wrap;
align-items: center;
justify-content: space-between;
background-color: $gray-light;
border-bottom: 1px solid $border-color;
+ border-top: 1px solid $border-color;
padding: 5px $gl-padding;
margin: 0;
border-radius: $border-radius-default $border-radius-default 0 0;
@@ -365,16 +369,12 @@ span.idiff {
margin: 0 10px 0 0;
}
- .file-actions {
- white-space: nowrap;
-
- .btn {
- padding: 0 10px;
- font-size: 13px;
- line-height: 28px;
- display: inline-block;
- float: none;
- }
+ .file-actions .btn {
+ padding: 0 10px;
+ font-size: 13px;
+ line-height: 28px;
+ display: inline-block;
+ float: none;
}
@include media-breakpoint-down(xs) {
diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss
index 9eae9a831fa..96dab609a13 100644
--- a/app/assets/stylesheets/framework/variables.scss
+++ b/app/assets/stylesheets/framework/variables.scss
@@ -243,6 +243,7 @@ $gl-padding-8: 8px;
$gl-padding: 16px;
$gl-padding-24: 24px;
$gl-padding-32: 32px;
+$gl-padding-50: 50px;
$gl-col-padding: 15px;
$gl-input-padding: 10px;
$gl-vert-padding: 6px;
@@ -490,6 +491,7 @@ $builds-trace-bg: #111;
*/
$commit-max-width-marker-color: rgba(0, 0, 0, 0);
$commit-message-text-area-bg: rgba(0, 0, 0, 0);
+$commit-stat-summary-height: 36px;
/*
* Common
@@ -664,8 +666,14 @@ $priority-label-empty-state-width: 114px;
Issues Analytics
*/
$issues-analytics-popover-boarder-color: rgba(0, 0, 0, 0.15);
+
/*
Merge Requests
*/
$mr-tabs-height: 51px;
$mr-version-controls-height: 56px;
+
+/*
+Compare Branches
+*/
+$compare-branches-sticky-header-height: 68px;
diff --git a/app/assets/stylesheets/pages/clusters.scss b/app/assets/stylesheets/pages/clusters.scss
index ad12cd101b6..809ba6d4953 100644
--- a/app/assets/stylesheets/pages/clusters.scss
+++ b/app/assets/stylesheets/pages/clusters.scss
@@ -58,6 +58,20 @@
}
}
+.cluster-application-banner {
+ height: 45px;
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+}
+
+.cluster-application-banner-close {
+ align-self: flex-start;
+ font-weight: 500;
+ font-size: 20px;
+ margin: $gl-padding-8 14px 0 0;
+}
+
.cluster-application-description {
flex: 1;
}
diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss
index 02aac58a475..e3b98b26a11 100644
--- a/app/assets/stylesheets/pages/diff.scss
+++ b/app/assets/stylesheets/pages/diff.scss
@@ -7,22 +7,13 @@
cursor: pointer;
@media (min-width: map-get($grid-breakpoints, md)) {
+ $mr-file-header-top: $mr-version-controls-height + $header-height + $mr-tabs-height;
+
position: -webkit-sticky;
position: sticky;
- top: $mr-version-controls-height + $header-height + $mr-tabs-height;
- margin-left: -1px;
- border-left: 1px solid $border-color;
+ top: $mr-file-header-top;
z-index: 102;
- &.is-commit {
- top: $header-height + 36px;
-
- .with-performance-bar & {
- top: $header-height + 36px + $performance-bar-height;
-
- }
- }
-
&::before {
content: '';
position: absolute;
@@ -35,7 +26,23 @@
}
.with-performance-bar & {
- top: $header-height + $performance-bar-height + $mr-version-controls-height + $mr-tabs-height;
+ top: $mr-file-header-top + $performance-bar-height;
+ }
+
+ &.is-commit {
+ top: $header-height + $commit-stat-summary-height;
+
+ .with-performance-bar & {
+ top: $header-height + $commit-stat-summary-height + $performance-bar-height;
+ }
+ }
+
+ &.is-compare {
+ top: $header-height + $compare-branches-sticky-header-height;
+
+ .with-performance-bar & {
+ top: $performance-bar-height + $header-height + $compare-branches-sticky-header-height;
+ }
}
}
@@ -501,6 +508,25 @@
}
}
+.diff-stats {
+ align-items: center;
+ padding: 0 .25rem;
+
+ .diff-stats-group {
+ padding: 0 .25rem;
+ }
+
+ svg.diff-stats-icon {
+ vertical-align: text-bottom;
+ }
+
+ &.is-compare-versions-header {
+ .diff-stats-group {
+ padding: 0 .5rem;
+ }
+ }
+}
+
.file-content .diff-file {
margin: 0;
border: 0;
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 38a7e199c6a..135730d71e9 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -38,9 +38,7 @@
}
.mr-widget-section {
- .media {
- align-items: center;
- }
+ border-radius: $border-radius-default $border-radius-default 0 0;
.code-text {
flex: 1;
@@ -56,6 +54,11 @@
.mr-widget-extension {
border-top: 1px solid $border-color;
background-color: $gray-light;
+
+ &.clickable:hover {
+ background-color: $gl-gray-200;
+ cursor: pointer;
+ }
}
.mr-widget-workflow {
@@ -78,6 +81,7 @@
border-top: 0;
}
+.mr-widget-body,
.mr-widget-section,
.mr-widget-content,
.mr-widget-footer {
@@ -87,11 +91,38 @@
.mr-state-widget {
color: $gl-text-color;
+ .commit-message-edit {
+ border-radius: $border-radius-default;
+ }
+
.mr-widget-section,
.mr-widget-footer {
border-top: solid 1px $border-color;
}
+ .mr-fast-forward-message {
+ padding-left: $gl-padding-50;
+ padding-bottom: $gl-padding;
+ }
+
+ .commits-list {
+ > li {
+ padding: $gl-padding;
+
+ @include media-breakpoint-up(md) {
+ padding-left: $gl-padding-50;
+ }
+ }
+ }
+
+ .mr-commit-dropdown {
+ .dropdown-menu {
+ @include media-breakpoint-up(md) {
+ width: 150%;
+ }
+ }
+ }
+
.mr-widget-footer {
padding: 0;
}
@@ -405,7 +436,7 @@
}
.mr-widget-help {
- padding: 10px 16px 10px 48px;
+ padding: 10px 16px 10px $gl-padding-50;
font-style: italic;
}
@@ -423,10 +454,6 @@
}
}
-.mr-widget-body-controls {
- flex-wrap: wrap;
-}
-
.mr_source_commit,
.mr_target_commit {
margin-bottom: 0;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 2342c284a5e..66866aedfba 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -507,12 +507,6 @@
}
.template-option {
- .logo {
- .btn-template-icon {
- width: 40px !important;
- }
- }
-
padding: 16px 0;
&:not(:first-child) {
@@ -551,9 +545,8 @@
}
.selected-icon {
- svg {
+ img {
display: none;
- top: 7px;
height: 20px;
width: 20px;
}
@@ -946,6 +939,11 @@ pre.light-well {
.flex-wrapper {
min-width: 0;
margin-top: -$gl-padding-8; // negative margin required for flex-wrap
+ flex: 1 1 100%;
+
+ .project-title {
+ line-height: 20px;
+ }
}
p,
@@ -984,14 +982,16 @@ pre.light-well {
}
.controls {
- margin-top: $gl-padding-8;
+ @include media-breakpoint-down(xs) {
+ margin-top: $gl-padding-8;
+ }
- @include media-breakpoint-down(md) {
+ @include media-breakpoint-up(sm) {
margin-top: 0;
}
- @include media-breakpoint-down(xs) {
- margin-top: $gl-padding-8;
+ @include media-breakpoint-up(lg) {
+ flex: 1 1 40%;
}
.icon-wrapper {
@@ -1041,7 +1041,7 @@ pre.light-well {
min-height: 40px;
min-width: 40px;
- .identicon.s64 {
+ .identicon.s48 {
font-size: 16px;
}
}
diff --git a/app/controllers/clusters/clusters_controller.rb b/app/controllers/clusters/clusters_controller.rb
index b9717b97640..3bd91b71d92 100644
--- a/app/controllers/clusters/clusters_controller.rb
+++ b/app/controllers/clusters/clusters_controller.rb
@@ -127,6 +127,7 @@ class Clusters::ClustersController < Clusters::BaseController
params.require(:cluster).permit(
:enabled,
:environment_scope,
+ :base_domain,
platform_kubernetes_attributes: [
:namespace
]
@@ -136,6 +137,7 @@ class Clusters::ClustersController < Clusters::BaseController
:enabled,
:name,
:environment_scope,
+ :base_domain,
platform_kubernetes_attributes: [
:api_url,
:token,
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index 97120273d6b..cc2bb99f55b 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -116,8 +116,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
session[:service_tickets][provider] = ticket
end
+ def build_auth_user(auth_user_class)
+ auth_user_class.new(oauth)
+ end
+
def sign_in_user_flow(auth_user_class)
- auth_user = auth_user_class.new(oauth)
+ auth_user = build_auth_user(auth_user_class)
user = auth_user.find_and_update!
if auth_user.valid_sign_in?
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index a27e3cceaeb..94002095739 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -37,6 +37,6 @@ class Profiles::PreferencesController < Profiles::ApplicationController
end
def preferences_param_names
- [:color_scheme_id, :layout, :dashboard, :project_view, :theme_id]
+ [:color_scheme_id, :layout, :dashboard, :project_view, :theme_id, :first_day_of_week]
end
end
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 79685e8b675..e9cd475a199 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -11,11 +11,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :verify_api_request!, only: :terminal_websocket_authorize
before_action :expire_etag_cache, only: [:index]
- before_action do
- push_frontend_feature_flag(:area_chart, project)
- end
-
- # Returns all environments or all folders based on the :nested param
def index
@environments = project.environments
.with_state(params[:scope] || :available)
diff --git a/app/controllers/projects/error_tracking_controller.rb b/app/controllers/projects/error_tracking_controller.rb
index 9e403e1d25b..88d0755f41f 100644
--- a/app/controllers/projects/error_tracking_controller.rb
+++ b/app/controllers/projects/error_tracking_controller.rb
@@ -15,6 +15,14 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
end
end
+ def list_projects
+ respond_to do |format|
+ format.json do
+ render_project_list_json
+ end
+ end
+ end
+
private
def render_index_json
@@ -32,6 +40,32 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
}
end
+ def render_project_list_json
+ service = ErrorTracking::ListProjectsService.new(
+ project,
+ current_user,
+ list_projects_params
+ )
+ result = service.execute
+
+ if result[:status] == :success
+ render json: {
+ projects: serialize_projects(result[:projects])
+ }
+ else
+ return render(
+ status: result[:http_status] || :bad_request,
+ json: {
+ message: result[:message]
+ }
+ )
+ end
+ end
+
+ def list_projects_params
+ params.require(:error_tracking_setting).permit([:api_host, :token])
+ end
+
def set_polling_interval
Gitlab::PollingInterval.set_header(response, interval: POLLING_INTERVAL)
end
@@ -41,4 +75,10 @@ class Projects::ErrorTrackingController < Projects::ApplicationController
.new(project: project, user: current_user)
.represent(errors)
end
+
+ def serialize_projects(projects)
+ ErrorTracking::ProjectSerializer
+ .new(project: project, user: current_user)
+ .represent(projects)
+ end
end
diff --git a/app/controllers/projects/merge_requests/application_controller.rb b/app/controllers/projects/merge_requests/application_controller.rb
index 54ff7ded8e5..6045ee4e171 100644
--- a/app/controllers/projects/merge_requests/application_controller.rb
+++ b/app/controllers/projects/merge_requests/application_controller.rb
@@ -34,7 +34,8 @@ class Projects::MergeRequests::ApplicationController < Projects::ApplicationCont
:task_num,
:title,
:discussion_locked,
- label_ids: []
+ label_ids: [],
+ update_task: [:index, :checked, :line_number, :line_source]
]
end
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 3e08c0ccd8f..23af2e0521c 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -305,7 +305,7 @@ class IssuableFinder
def use_subquery_for_search?
strong_memoize(:use_subquery_for_search) do
attempt_group_search_optimizations? &&
- Feature.enabled?(:use_subquery_for_group_issues_search, default_enabled: false)
+ Feature.enabled?(:use_subquery_for_group_issues_search, default_enabled: true)
end
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index c8e4e2e3df9..e635f608237 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -150,6 +150,7 @@ module ApplicationSettingsHelper
:email_author_in_body,
:enabled_git_access_protocol,
:enforce_terms,
+ :first_day_of_week,
:gitaly_timeout_default,
:gitaly_timeout_medium,
:gitaly_timeout_fast,
@@ -231,7 +232,8 @@ module ApplicationSettingsHelper
:web_ide_clientside_preview_enabled,
:diff_max_patch_bytes,
:commit_email_hostname,
- :protected_ci_variables
+ :protected_ci_variables,
+ :local_markdown_version
]
end
diff --git a/app/helpers/auto_devops_helper.rb b/app/helpers/auto_devops_helper.rb
index 516c8a353ea..67e7e475920 100644
--- a/app/helpers/auto_devops_helper.rb
+++ b/app/helpers/auto_devops_helper.rb
@@ -9,41 +9,4 @@ module AutoDevopsHelper
!project.repository.gitlab_ci_yml &&
!project.ci_service
end
-
- def auto_devops_warning_message(project)
- if missing_auto_devops_service?(project)
- params = {
- kubernetes: link_to('Kubernetes cluster', project_clusters_path(project))
- }
-
- if missing_auto_devops_domain?(project)
- _('Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly.') % params
- else
- _('Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly.') % params
- end
- elsif missing_auto_devops_domain?(project)
- _('Auto Review Apps and Auto Deploy need a domain name to work correctly.')
- end
- end
-
- # rubocop: disable CodeReuse/ActiveRecord
- def cluster_ingress_ip(project)
- project
- .cluster_ingresses
- .where("external_ip is not null")
- .limit(1)
- .pluck(:external_ip)
- .first
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- private
-
- def missing_auto_devops_domain?(project)
- !(project.auto_devops || project.build_auto_devops)&.has_domain?
- end
-
- def missing_auto_devops_service?(project)
- !project.deployment_platform&.active?
- end
end
diff --git a/app/helpers/environments_helper.rb b/app/helpers/environments_helper.rb
index b3935ae350d..365b94f5a3e 100644
--- a/app/helpers/environments_helper.rb
+++ b/app/helpers/environments_helper.rb
@@ -11,7 +11,6 @@ module EnvironmentsHelper
{
"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
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index f4f46b0fe96..bc1742e8167 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -43,6 +43,17 @@ module PreferencesHelper
]
end
+ def first_day_of_week_choices
+ [
+ [_('Sunday'), 0],
+ [_('Monday'), 1]
+ ]
+ end
+
+ def first_day_of_week_choices_with_default
+ first_day_of_week_choices.unshift([_('System default (%{default})') % { default: default_first_day_of_week }, nil])
+ end
+
def user_application_theme
@user_application_theme ||= Gitlab::Themes.for_user(current_user).css_class
end
@@ -66,4 +77,8 @@ module PreferencesHelper
def excluded_dashboard_choices
['operations']
end
+
+ def default_first_day_of_week
+ first_day_of_week_choices.rassoc(Gitlab::CurrentSettings.first_day_of_week).first
+ end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 88746375c67..daadf9427ba 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -193,6 +193,10 @@ class ApplicationSetting < ActiveRecord::Base
allow_nil: true,
numericality: { only_integer: true, greater_than_or_equal_to: 1.day.seconds }
+ validates :local_markdown_version,
+ allow_nil: true,
+ numericality: { only_integer: true, greater_than_or_equal_to: 0, less_than: 65536 }
+
SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end
@@ -246,6 +250,7 @@ class ApplicationSetting < ActiveRecord::Base
dsa_key_restriction: 0,
ecdsa_key_restriction: 0,
ed25519_key_restriction: 0,
+ first_day_of_week: 0,
gitaly_timeout_default: 55,
gitaly_timeout_fast: 10,
gitaly_timeout_medium: 30,
@@ -303,7 +308,8 @@ class ApplicationSetting < ActiveRecord::Base
usage_stats_set_by_user_id: nil,
diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
commit_email_hostname: default_commit_email_hostname,
- protected_ci_variables: false
+ protected_ci_variables: false,
+ local_markdown_version: 0
}
end
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
index 26bf73f4dd8..52c440ffb2f 100644
--- a/app/models/clusters/applications/prometheus.rb
+++ b/app/models/clusters/applications/prometheus.rb
@@ -53,11 +53,11 @@ module Clusters
end
def upgrade_command(values)
- ::Gitlab::Kubernetes::Helm::UpgradeCommand.new(
- name,
+ ::Gitlab::Kubernetes::Helm::InstallCommand.new(
+ name: name,
version: VERSION,
- chart: chart,
rbac: cluster.platform_kubernetes_rbac?,
+ chart: chart,
files: files_with_replaced_values(values)
)
end
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index a2c48973fa5..7025fc2cc02 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -18,6 +18,7 @@ module Clusters
Applications::Knative.application_name => Applications::Knative
}.freeze
DEFAULT_ENVIRONMENT = '*'.freeze
+ KUBE_INGRESS_BASE_DOMAIN = 'KUBE_INGRESS_BASE_DOMAIN'.freeze
belongs_to :user
@@ -49,7 +50,7 @@ module Clusters
validates :name, cluster_name: true
validates :cluster_type, presence: true
- validates :domain, allow_nil: true, hostname: { allow_numeric_hostname: true, require_valid_tld: true }
+ validates :domain, allow_blank: true, hostname: { allow_numeric_hostname: true, require_valid_tld: true }
validate :restrict_modification, on: :update
validate :no_groups, unless: :group_type?
@@ -65,6 +66,9 @@ module Clusters
delegate :available?, to: :application_ingress, prefix: true, allow_nil: true
delegate :available?, to: :application_prometheus, prefix: true, allow_nil: true
delegate :available?, to: :application_knative, prefix: true, allow_nil: true
+ delegate :external_ip, to: :application_ingress, prefix: true, allow_nil: true
+
+ alias_attribute :base_domain, :domain
enum cluster_type: {
instance_type: 1,
@@ -193,8 +197,42 @@ module Clusters
project_type?
end
+ def kube_ingress_domain
+ @kube_ingress_domain ||= domain.presence || instance_domain || legacy_auto_devops_domain
+ end
+
+ def predefined_variables
+ Gitlab::Ci::Variables::Collection.new.tap do |variables|
+ break variables unless kube_ingress_domain
+
+ variables.append(key: KUBE_INGRESS_BASE_DOMAIN, value: kube_ingress_domain)
+ end
+ end
+
private
+ def instance_domain
+ @instance_domain ||= Gitlab::CurrentSettings.auto_devops_domain
+ end
+
+ # To keep backward compatibility with AUTO_DEVOPS_DOMAIN
+ # environment variable, we need to ensure KUBE_INGRESS_BASE_DOMAIN
+ # is set if AUTO_DEVOPS_DOMAIN is set on any of the following options:
+ # ProjectAutoDevops#Domain, project variables or group variables,
+ # as the AUTO_DEVOPS_DOMAIN is needed for CI_ENVIRONMENT_URL
+ #
+ # This method should is scheduled to be removed on
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/56959
+ def legacy_auto_devops_domain
+ if project_type?
+ project&.auto_devops&.domain.presence ||
+ project.variables.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence ||
+ project.group&.variables&.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence
+ elsif group_type?
+ group.variables.find_by(key: 'AUTO_DEVOPS_DOMAIN')&.value.presence
+ end
+ end
+
def restrict_modification
if provider&.on_creation?
errors.add(:base, "cannot modify during creation")
diff --git a/app/models/clusters/concerns/application_status.rb b/app/models/clusters/concerns/application_status.rb
index a556dd5ad8b..6fe7b4a6bd7 100644
--- a/app/models/clusters/concerns/application_status.rb
+++ b/app/models/clusters/concerns/application_status.rb
@@ -20,7 +20,7 @@ module Clusters
state :update_errored, value: 6
event :make_scheduled do
- transition [:installable, :errored] => :scheduled
+ transition [:installable, :errored, :installed, :updated, :update_errored] => :scheduled
end
event :make_installing do
@@ -29,16 +29,19 @@ module Clusters
event :make_installed do
transition [:installing] => :installed
+ transition [:updating] => :updated
end
event :make_errored do
- transition any => :errored
+ transition any - [:updating] => :errored
+ transition [:updating] => :update_errored
end
event :make_updating do
- transition [:installed, :updated, :update_errored] => :updating
+ transition [:installed, :updated, :update_errored, :scheduled] => :updating
end
+ # Deprecated
event :make_updated do
transition [:updating] => :updated
end
@@ -74,6 +77,10 @@ module Clusters
end
end
+ def updateable?
+ installed? || updated? || update_errored?
+ end
+
def available?
installed? || updated?
end
diff --git a/app/models/clusters/concerns/application_version.rb b/app/models/clusters/concerns/application_version.rb
index e355de23df6..db94e8e08c9 100644
--- a/app/models/clusters/concerns/application_version.rb
+++ b/app/models/clusters/concerns/application_version.rb
@@ -12,6 +12,10 @@ module Clusters
end
end
end
+
+ def update_available?
+ version != self.class.const_get(:VERSION)
+ end
end
end
end
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index 8f3424db295..c8969351ed9 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -98,6 +98,8 @@ module Clusters
.append(key: 'KUBE_NAMESPACE', value: actual_namespace)
.append(key: 'KUBECONFIG', value: kubeconfig, public: false, file: true)
end
+
+ variables.concat(cluster.predefined_variables)
end
end
diff --git a/app/models/concerns/cache_markdown_field.rb b/app/models/concerns/cache_markdown_field.rb
index 5fa6f79bdaa..1a8570b80c3 100644
--- a/app/models/concerns/cache_markdown_field.rb
+++ b/app/models/concerns/cache_markdown_field.rb
@@ -115,7 +115,28 @@ module CacheMarkdownField
end
def latest_cached_markdown_version
- CacheMarkdownField::CACHE_COMMONMARK_VERSION
+ @latest_cached_markdown_version ||= (CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16) | local_version
+ end
+
+ def local_version
+ # because local_markdown_version is stored in application_settings which
+ # uses cached_markdown_version too, we check explicitly to avoid
+ # endless loop
+ return local_markdown_version if has_attribute?(:local_markdown_version)
+
+ settings = Gitlab::CurrentSettings.current_application_settings
+
+ # Following migrations are not properly isolated and
+ # use real models (by calling .ghost method), in these migrations
+ # local_markdown_version attribute doesn't exist yet, so we
+ # use a default value:
+ # db/migrate/20170825104051_migrate_issues_to_ghost_user.rb
+ # db/migrate/20171114150259_merge_requests_author_id_foreign_key.rb
+ if settings.respond_to?(:local_markdown_version)
+ settings.local_markdown_version
+ else
+ 0
+ end
end
included do
diff --git a/app/models/pool_repository.rb b/app/models/pool_repository.rb
index 34220c1b450..4635fc72dc7 100644
--- a/app/models/pool_repository.rb
+++ b/app/models/pool_repository.rb
@@ -96,7 +96,9 @@ class PoolRepository < ActiveRecord::Base
@object_pool ||= Gitlab::Git::ObjectPool.new(
shard.name,
disk_path + '.git',
- source_project.repository.raw)
+ source_project.repository.raw,
+ source_project.full_path
+ )
end
def inspect
diff --git a/app/models/project.rb b/app/models/project.rb
index d4e2ed883bc..8f746f6e094 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1288,7 +1288,7 @@ class Project < ActiveRecord::Base
# Forked import is handled asynchronously
return if forked? && !force
- if gitlab_shell.create_repository(repository_storage, disk_path)
+ if gitlab_shell.create_project_repository(self)
repository.after_create
true
else
diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb
index 2253ad7b543..e353a6443c4 100644
--- a/app/models/project_auto_devops.rb
+++ b/app/models/project_auto_devops.rb
@@ -24,6 +24,12 @@ class ProjectAutoDevops < ActiveRecord::Base
domain.present? || instance_domain.present?
end
+ # From 11.8, AUTO_DEVOPS_DOMAIN has been replaced by KUBE_INGRESS_BASE_DOMAIN.
+ # See Clusters::Cluster#predefined_variables and https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580
+ # for more info.
+ #
+ # Suppport AUTO_DEVOPS_DOMAIN is scheduled to be removed on
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/52363
def predefined_variables
Gitlab::Ci::Variables::Collection.new.tap do |variables|
if has_domain?
diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 559e4f99294..c43bd45a62f 100644
--- a/app/models/project_wiki.rb
+++ b/app/models/project_wiki.rb
@@ -60,7 +60,7 @@ class ProjectWiki
def wiki
@wiki ||= begin
gl_repository = Gitlab::GlRepository.gl_repository(project, true)
- raw_repository = Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', gl_repository)
+ raw_repository = Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', gl_repository, full_path)
create_repo!(raw_repository) unless raw_repository.exists?
@@ -175,7 +175,7 @@ class ProjectWiki
private
def create_repo!(raw_repository)
- gitlab_shell.create_repository(project.repository_storage, disk_path)
+ gitlab_shell.create_wiki_repository(project)
raise CouldNotCreateWikiError unless raw_repository.exists?
diff --git a/app/models/repository.rb b/app/models/repository.rb
index bfd2608bed4..7c50b4488e5 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -1104,6 +1104,9 @@ class Repository
end
def initialize_raw_repository
- Gitlab::Git::Repository.new(project.repository_storage, disk_path + '.git', Gitlab::GlRepository.gl_repository(project, is_wiki))
+ Gitlab::Git::Repository.new(project.repository_storage,
+ disk_path + '.git',
+ Gitlab::GlRepository.gl_repository(project, is_wiki),
+ project.full_path)
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 9c091ac366c..24101eda0b1 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -228,6 +228,9 @@ class User < ApplicationRecord
delegate :path, to: :namespace, allow_nil: true, prefix: true
delegate :notes_filter_for, to: :user_preference
delegate :set_notes_filter, to: :user_preference
+ delegate :first_day_of_week, :first_day_of_week=, to: :user_preference
+
+ accepts_nested_attributes_for :user_preference, update_only: true
state_machine :state, initial: :active do
event :block do
diff --git a/app/serializers/cluster_application_entity.rb b/app/serializers/cluster_application_entity.rb
index 62b23a889c8..02df1480828 100644
--- a/app/serializers/cluster_application_entity.rb
+++ b/app/serializers/cluster_application_entity.rb
@@ -8,4 +8,5 @@ class ClusterApplicationEntity < Grape::Entity
expose :external_ip, if: -> (e, _) { e.respond_to?(:external_ip) }
expose :hostname, if: -> (e, _) { e.respond_to?(:hostname) }
expose :email, if: -> (e, _) { e.respond_to?(:email) }
+ expose :update_available?, as: :update_available, if: -> (e, _) { e.respond_to?(:update_available?) }
end
diff --git a/app/serializers/deployment_entity.rb b/app/serializers/deployment_entity.rb
index aa1d9e6292c..34ae06278c8 100644
--- a/app/serializers/deployment_entity.rb
+++ b/app/serializers/deployment_entity.rb
@@ -24,6 +24,12 @@ class DeploymentEntity < Grape::Entity
expose :user, using: UserEntity
expose :commit, using: CommitEntity
expose :deployable, using: JobEntity
- expose :manual_actions, using: JobEntity
- expose :scheduled_actions, using: JobEntity
+ expose :manual_actions, using: JobEntity, if: -> (*) { can_create_deployment? }
+ expose :scheduled_actions, using: JobEntity, if: -> (*) { can_create_deployment? }
+
+ private
+
+ def can_create_deployment?
+ can?(request.current_user, :create_deployment, request.project)
+ end
end
diff --git a/app/serializers/error_tracking/project_serializer.rb b/app/serializers/error_tracking/project_serializer.rb
index b2406f4d631..68724088fff 100644
--- a/app/serializers/error_tracking/project_serializer.rb
+++ b/app/serializers/error_tracking/project_serializer.rb
@@ -2,6 +2,6 @@
module ErrorTracking
class ProjectSerializer < BaseSerializer
- entity ProjectEntity
+ entity ErrorTracking::ProjectEntity
end
end
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index f764536e762..e95ba09c006 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -160,7 +160,8 @@ module Auth
##
# We still support legacy pipeline triggers which do not have associated
# actor. New permissions model and new triggers are always associated with
- # an actor, so this should be improved in 10.0 version of GitLab.
+ # an actor. So this should be improved once
+ # https://gitlab.com/gitlab-org/gitlab-ce/issues/37452 is resolved.
#
def build_can_push?(requested_project)
# Build can push only to the project from which it originates
diff --git a/app/services/clusters/applications/check_installation_progress_service.rb b/app/services/clusters/applications/check_installation_progress_service.rb
index 21ec26ea233..c592d608b89 100644
--- a/app/services/clusters/applications/check_installation_progress_service.rb
+++ b/app/services/clusters/applications/check_installation_progress_service.rb
@@ -4,7 +4,7 @@ module Clusters
module Applications
class CheckInstallationProgressService < BaseHelmService
def execute
- return unless app.installing?
+ return unless operation_in_progress?
case installation_phase
when Gitlab::Kubernetes::Pod::SUCCEEDED
@@ -16,11 +16,16 @@ module Clusters
end
rescue Kubeclient::HttpError => e
log_error(e)
- app.make_errored!("Kubernetes error: #{e.error_code}") unless app.errored?
+
+ app.make_errored!("Kubernetes error: #{e.error_code}")
end
private
+ def operation_in_progress?
+ app.installing? || app.updating?
+ end
+
def on_success
app.make_installed!
ensure
@@ -28,13 +33,13 @@ module Clusters
end
def on_failed
- app.make_errored!("Installation failed. Check pod logs for #{install_command.pod_name} for more details.")
+ app.make_errored!("Operation failed. Check pod logs for #{pod_name} for more details.")
end
def check_timeout
if timeouted?
begin
- app.make_errored!("Installation timed out. Check pod logs for #{install_command.pod_name} for more details.")
+ app.make_errored!("Operation timed out. Check pod logs for #{pod_name} for more details.")
end
else
ClusterWaitForAppInstallationWorker.perform_in(
@@ -42,20 +47,24 @@ module Clusters
end
end
+ def pod_name
+ install_command.pod_name
+ end
+
def timeouted?
Time.now.utc - app.updated_at.to_time.utc > ClusterWaitForAppInstallationWorker::TIMEOUT
end
def remove_installation_pod
- helm_api.delete_pod!(install_command.pod_name)
+ helm_api.delete_pod!(pod_name)
end
def installation_phase
- helm_api.status(install_command.pod_name)
+ helm_api.status(pod_name)
end
def installation_errors
- helm_api.log(install_command.pod_name)
+ helm_api.log(pod_name)
end
end
end
diff --git a/app/services/clusters/applications/schedule_installation_service.rb b/app/services/clusters/applications/schedule_installation_service.rb
index d75ba70c27e..15c93f1e79b 100644
--- a/app/services/clusters/applications/schedule_installation_service.rb
+++ b/app/services/clusters/applications/schedule_installation_service.rb
@@ -10,6 +10,18 @@ module Clusters
end
def execute
+ application.updateable? ? schedule_upgrade : schedule_install
+ end
+
+ private
+
+ def schedule_upgrade
+ application.make_scheduled!
+
+ ClusterUpgradeAppWorker.perform_async(application.name, application.id)
+ end
+
+ def schedule_install
application.make_scheduled!
ClusterInstallAppWorker.perform_async(application.name, application.id)
diff --git a/app/services/clusters/applications/upgrade_service.rb b/app/services/clusters/applications/upgrade_service.rb
new file mode 100644
index 00000000000..a0ece1d2635
--- /dev/null
+++ b/app/services/clusters/applications/upgrade_service.rb
@@ -0,0 +1,28 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Applications
+ class UpgradeService < BaseHelmService
+ def execute
+ return unless app.scheduled?
+
+ begin
+ app.make_updating!
+
+ # install_command works with upgrades too
+ # as it basically does `helm upgrade --install`
+ helm_api.update(install_command)
+
+ ClusterWaitForAppInstallationWorker.perform_in(
+ ClusterWaitForAppInstallationWorker::INTERVAL, app.name, app.id)
+ rescue Kubeclient::HttpError => e
+ log_error(e)
+ app.make_update_errored!("Kubernetes error: #{e.error_code}")
+ rescue StandardError => e
+ log_error(e)
+ app.make_update_errored!("Can't start upgrade process.")
+ end
+ end
+ end
+ end
+end
diff --git a/app/services/labels/update_service.rb b/app/services/labels/update_service.rb
index e563447c64c..be33947d0eb 100644
--- a/app/services/labels/update_service.rb
+++ b/app/services/labels/update_service.rb
@@ -8,6 +8,7 @@ module Labels
# returns the updated label
def execute(label)
+ params[:name] = params.delete(:new_name) if params.key?(:new_name)
params[:color] = convert_color_name_to_hex if params[:color].present?
label.update(params)
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 86a04587f79..8112c2a4299 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -21,7 +21,7 @@ module MergeRequests
end
handle_wip_event(merge_request)
- update(merge_request)
+ update_task_event(merge_request) || update(merge_request)
end
# rubocop:disable Metrics/AbcSize
@@ -83,6 +83,11 @@ module MergeRequests
end
# rubocop:enable Metrics/AbcSize
+ def handle_task_changes(merge_request)
+ todo_service.mark_pending_todos_as_done(merge_request, current_user)
+ todo_service.update_merge_request(merge_request, current_user)
+ end
+
def merge_from_quick_action(merge_request)
last_diff_sha = params.delete(:merge)
return unless merge_request.mergeable_with_quick_action?(current_user, last_diff_sha: last_diff_sha)
diff --git a/app/services/projects/import_service.rb b/app/services/projects/import_service.rb
index 5861b803996..7214e9efaf6 100644
--- a/app/services/projects/import_service.rb
+++ b/app/services/projects/import_service.rb
@@ -73,7 +73,7 @@ module Projects
project.ensure_repository
project.repository.fetch_as_mirror(project.import_url, refmap: refmap)
else
- gitlab_shell.import_repository(project.repository_storage, project.disk_path, project.import_url)
+ gitlab_shell.import_project_repository(project)
end
rescue Gitlab::Shell::Error => e
# Expire cache to prevent scenarios such as:
diff --git a/app/services/task_list_toggle_service.rb b/app/services/task_list_toggle_service.rb
index b5c4cd3235d..cfe187d9b12 100644
--- a/app/services/task_list_toggle_service.rb
+++ b/app/services/task_list_toggle_service.rb
@@ -32,7 +32,8 @@ class TaskListToggleService
source_line_index = line_number - 1
markdown_task = source_lines[source_line_index]
- return unless markdown_task == line_source
+ # The source in the DB could be using either \n or \r\n line endings
+ return unless markdown_task == line_source || markdown_task == line_source + "\r"
return unless source_checkbox = Taskable::ITEM_PATTERN.match(markdown_task)
currently_checked = TaskList::Item.new(source_checkbox[1]).complete?
diff --git a/app/views/admin/application_settings/_localization.html.haml b/app/views/admin/application_settings/_localization.html.haml
new file mode 100644
index 00000000000..95d016a94a5
--- /dev/null
+++ b/app/views/admin/application_settings/_localization.html.haml
@@ -0,0 +1,11 @@
+= form_for @application_setting, url: admin_application_settings_path(anchor: 'js-localization-settings'), html: { class: 'fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ = f.label :first_day_of_week, _('Default first day of the week'), class: 'label-bold'
+ = f.select :first_day_of_week, first_day_of_week_choices, {}, class: 'form-control'
+ .form-text.text-muted
+ = _('Default first day of the week in calendars and date pickers.')
+
+ = f.submit _('Save changes'), class: "btn btn-success"
diff --git a/app/views/admin/application_settings/preferences.html.haml b/app/views/admin/application_settings/preferences.html.haml
index 00000b86ab7..c468d69d7b8 100644
--- a/app/views/admin/application_settings/preferences.html.haml
+++ b/app/views/admin/application_settings/preferences.html.haml
@@ -56,3 +56,14 @@
= _('Configure Gitaly timeouts.')
.settings-content
= render 'gitaly'
+
+%section.settings.as-localization.no-animate#js-localization-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Localization')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Various localization settings.')
+ .settings-content
+ = render 'localization'
diff --git a/app/views/award_emoji/_awards_block.html.haml b/app/views/award_emoji/_awards_block.html.haml
index a758a63dfb3..8d9c083d223 100644
--- a/app/views/award_emoji/_awards_block.html.haml
+++ b/app/views/award_emoji/_awards_block.html.haml
@@ -3,7 +3,7 @@
- awards_sort(grouped_emojis).each do |emoji, awards|
%button.btn.award-control.js-emoji-btn.has-tooltip{ type: "button",
class: [(award_state_class(awardable, awards, current_user))],
- data: { placement: "bottom", title: award_user_list(awards, current_user) } }
+ data: { title: award_user_list(awards, current_user) } }
= emoji_icon(emoji)
%span.award-control-text.js-counter
= awards.count
@@ -12,7 +12,7 @@
.award-menu-holder.js-award-holder
%button.btn.award-control.has-tooltip.js-add-award{ type: 'button',
'aria-label': _('Add reaction'),
- data: { title: _('Add reaction'), placement: "bottom" } }
+ data: { title: _('Add reaction') } }
%span{ class: "award-control-icon award-control-icon-neutral" }= custom_icon('emoji_slightly_smiling_face')
%span{ class: "award-control-icon award-control-icon-positive" }= custom_icon('emoji_smiley')
%span{ class: "award-control-icon award-control-icon-super-positive" }= custom_icon('emoji_smile')
diff --git a/app/views/clusters/clusters/_form.html.haml b/app/views/clusters/clusters/_form.html.haml
index 4c47e11927e..7acd9ce0562 100644
--- a/app/views/clusters/clusters/_form.html.haml
+++ b/app/views/clusters/clusters/_form.html.haml
@@ -20,12 +20,27 @@
.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_url = help_page_path('user/project/clusters/index', anchor: 'base-domain')
- 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 }
+ .form-group
+ %h5= s_('ClusterIntegration|Base domain')
+ = field.text_field :base_domain, class: 'col-md-6 form-control js-select-on-focus'
+ .form-text.text-muted
+ - auto_devops_url = help_page_path('topics/autodevops/index')
+ - auto_devops_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: auto_devops_url }
+ = s_('ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain.').html_safe % { auto_devops_start: auto_devops_start, auto_devops_end: '</a>'.html_safe }
+ - if @cluster.application_ingress_external_ip.present?
+ = s_('ClusterIntegration|Alternatively')
+ %code #{@cluster.application_ingress_external_ip}.nip.io
+ = s_('ClusterIntegration| can be used instead of a custom domain.')
+ - custom_domain_url = help_page_path('user/project/clusters/index', anchor: 'pointing-your-dns-at-the-cluster-ip')
+ - custom_domain_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: custom_domain_url }
+ = s_('ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}.').html_safe % { custom_domain_start: custom_domain_start, custom_domain_end: '</a>'.html_safe }
+
- if can?(current_user, :update_cluster, @cluster)
.form-group
= field.submit _('Save changes'), class: 'btn btn-success'
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index c1616810185..1a9aca1f6bf 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -60,5 +60,21 @@
= f.select :project_view, project_view_choices, {}, class: 'form-control'
.form-text.text-muted
Choose what content you want to see on a project’s overview page.
+
+ .col-sm-12
+ %hr
+
+ .col-lg-4.profile-settings-sidebar
+ %h4.prepend-top-0
+ = _('Localization')
+ %p
+ = _('Customize language and region related settings.')
+ = succeed '.' do
+ = link_to _('Learn more'), help_page_path('user/profile/preferences', anchor: 'localization'), target: '_blank'
+ .col-lg-8
+ .form-group
+ = f.label :first_day_of_week, class: 'label-bold' do
+ = _('First day of the week')
+ = f.select :first_day_of_week, first_day_of_week_choices_with_default, {}, class: 'form-control'
.form-group
- = f.submit 'Save changes', class: 'btn btn-success'
+ = f.submit _('Save changes'), class: 'btn btn-success'
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 06f0cd9675e..fe9a8ac4182 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -10,7 +10,7 @@
.container-fluid{ class: [limited_container_width, container_class] }
= render "commit_box"
= render "ci_menu"
- = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, is_commit: true
+ = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, diff_page_context: "is-commit"
.limited-width-notes
= render "shared/notes/notes_with_form", :autocomplete => true
diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml
index b6bebbabed0..5774b48a054 100644
--- a/app/views/projects/compare/show.html.haml
+++ b/app/views/projects/compare/show.html.haml
@@ -8,7 +8,7 @@
- if @commits.present?
= render "projects/commits/commit_list"
- = render "projects/diffs/diffs", diffs: @diffs, environment: @environment
+ = render "projects/diffs/diffs", diffs: @diffs, environment: @environment, diff_page_context: "is-compare"
- else
.card.bg-light
.center
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index cc2d0d3b2d8..2dba3fcd664 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -2,7 +2,7 @@
- show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true)
- can_create_note = !@diff_notes_disabled && can?(current_user, :create_note, diffs.project)
- diff_files = diffs.diff_files
-- is_commit = local_assigns.fetch(:is_commit, false)
+- diff_page_context = local_assigns.fetch(:diff_page_context, nil)
.content-block.oneline-block.files-changed.diff-files-changed.js-diff-files-changed
.files-changed-inner
@@ -25,4 +25,4 @@
= render 'projects/diffs/warning', diff_files: diffs
.files{ data: { can_create_note: can_create_note } }
- = render partial: 'projects/diffs/file', collection: diff_files, as: :diff_file, locals: { project: diffs.project, environment: environment, is_commit: is_commit }
+ = render partial: 'projects/diffs/file', collection: diff_files, as: :diff_file, locals: { project: diffs.project, environment: environment, diff_page_context: diff_page_context }
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index 5565ae1d98b..855b719dc45 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -1,11 +1,11 @@
- environment = local_assigns.fetch(:environment, nil)
-- is_commit = local_assigns.fetch(:is_commit, false)
+- diff_page_context = local_assigns.fetch(:diff_page_context, nil)
- file_hash = hexdigest(diff_file.file_path)
- image_diff = diff_file.rich_viewer && diff_file.rich_viewer.partial_name == 'image'
- image_replaced = diff_file.old_content_sha && diff_file.old_content_sha != diff_file.content_sha
.diff-file.file-holder{ id: file_hash, data: diff_file_html_data(project, diff_file.file_path, diff_file.content_sha) }
- .js-file-title.file-title-flex-parent{ class: is_commit ? "is-commit" : "" }
+ .js-file-title.file-title-flex-parent{ class: diff_page_context }
.file-header-content
= render "projects/diffs/file_header", diff_file: diff_file, url: "##{file_hash}"
diff --git a/app/views/projects/environments/index.html.haml b/app/views/projects/environments/index.html.haml
index d66de7ab698..99cbbc11acd 100644
--- a/app/views/projects/environments/index.html.haml
+++ b/app/views/projects/environments/index.html.haml
@@ -2,7 +2,6 @@
- page_title _("Environments")
#environments-list-view{ data: { environments_data: environments_list_data,
- "can-create-deployment" => can?(current_user, :create_deployment, @project).to_s,
"can-read-environment" => can?(current_user, :read_environment, @project).to_s,
"can-create-environment" => can?(current_user, :create_environment, @project).to_s,
"new-environment-path" => new_project_environment_path(@project),
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 8275996b522..ff7c36c2d5b 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -16,6 +16,9 @@
= _('A project is where you house your files (repository), plan your work (issues), and publish your documentation (wiki), %{among_other_things_link}.').html_safe % { among_other_things_link: among_other_things_link }
%p
= _('All features are enabled for blank projects, from templates, or when importing, but you can disable them afterward in the project settings.')
+ %p
+ - pages_getting_started_guide = link_to _('Pages getting started guide'), help_page_path("user/project/pages/getting_started_part_two", anchor: "fork-a-project-to-get-started-from"), target: '_blank'
+ = _('Information about additional Pages templates and how to install them can be found in our %{pages_getting_started_guide}.').html_safe % { pages_getting_started_guide: pages_getting_started_guide }
.md
= brand_new_project_guidelines
%p
diff --git a/app/views/projects/project_templates/_built_in_templates.html.haml b/app/views/projects/project_templates/_built_in_templates.html.haml
index 2a0ce4bd16b..6159f1c3542 100644
--- a/app/views/projects/project_templates/_built_in_templates.html.haml
+++ b/app/views/projects/project_templates/_built_in_templates.html.haml
@@ -1,7 +1,7 @@
- Gitlab::ProjectTemplate.all.each do |template|
.template-option.d-flex.align-items-center
- .logo.append-right-10
- = custom_icon(template.logo, size: 40)
+ .logo.append-right-10.px-1
+ = image_tag template.logo, size: 32, class: "btn-template-icon icon-#{template.name}"
.description
%strong
= template.title
diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
index 5ec5a06396e..8c4d1c32ebe 100644
--- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
+++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml
@@ -4,10 +4,6 @@
= form_errors(@project)
%fieldset.builds-feature.js-auto-devops-settings
.form-group
- - message = auto_devops_warning_message(@project)
- - if message
- %p.auto-devops-warning-message.settings-message.text-center
- = message.html_safe
= f.fields_for :auto_devops_attributes, @auto_devops do |form|
.card.auto-devops-card
.card-body
@@ -21,19 +17,12 @@
= s_('CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found.')
= link_to _('More information'), help_page_path('topics/autodevops/index.md'), target: '_blank'
.card-footer.js-extra-settings{ class: @project.auto_devops_enabled? || 'hidden' }
- = form.label :domain do
- %strong= _('Domain')
- = form.text_field :domain, class: 'form-control', placeholder: 'domain.com'
- .form-text.text-muted
- = s_('CICD|You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages.')
- - if cluster_ingress_ip = cluster_ingress_ip(@project)
- = s_('%{nip_domain} can be used as an alternative to a custom domain.').html_safe % { nip_domain: "<code>#{cluster_ingress_ip}.nip.io</code>".html_safe }
- = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'auto-devops-base-domain'), target: '_blank'
-
+ %p.settings-message.text-center
+ - kubernetes_cluster_link = help_page_path('user/project/clusters/index')
+ - kubernetes_cluster_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: kubernetes_cluster_link }
+ = s_('CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly.').html_safe % { kubernetes_cluster_start: kubernetes_cluster_start, kubernetes_cluster_end: '</a>'.html_safe }
%label.prepend-top-10
%strong= s_('CICD|Deployment strategy')
- %p.settings-message.text-center
- = s_('CICD|Deployment strategy needs a domain name to work correctly.')
.form-check
= form.radio_button :deploy_strategy, 'continuous', class: 'form-check-input'
= form.label :deploy_strategy_continuous, class: 'form-check-label' do
diff --git a/app/views/shared/icons/_express.svg b/app/views/shared/icons/_express.svg
deleted file mode 100644
index a51e81e5568..00000000000
--- a/app/views/shared/icons/_express.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="27" height="32" viewBox="0 0 27 32" class="btn-template-icon icon-node-express"><g fill="none" fill-rule="evenodd"><path d="M-3 0h32v32H-3z"/><path fill="#353535" d="M1.192 16.267c.04 2.065.288 3.982.745 5.75.456 1.767 1.16 3.307 2.115 4.618.953 1.31 2.185 2.343 3.694 3.098 1.51.755 3.357 1.132 5.54 1.132 3.22 0 5.89-.844 8.016-2.532 2.125-1.69 3.446-4.22 3.962-7.597h1.192c-.437 3.575-1.847 6.345-4.23 8.312-2.384 1.966-5.324 2.95-8.82 2.95-2.383.04-4.42-.338-6.107-1.133-1.69-.794-3.07-1.917-4.142-3.367-1.073-1.45-1.867-3.158-2.383-5.124C.258 20.408 0 18.294 0 16.028c0-2.542.377-4.806 1.132-6.792C1.887 7.25 2.88 5.57 4.112 4.2 5.34 2.83 6.77 1.79 8.4 1.074 10.03.358 11.698 0 13.406 0c2.383 0 4.44.457 6.167 1.37 1.728.914 3.138 2.126 4.23 3.635 1.093 1.51 1.887 3.238 2.384 5.184.496 1.945.705 3.97.625 6.077H1.193zm24.43-1.192c0-1.867-.26-3.645-.775-5.333-.516-1.688-1.28-3.168-2.294-4.44-1.013-1.27-2.274-2.273-3.784-3.008-1.51-.735-3.258-1.102-5.244-1.102-1.67 0-3.228.317-4.678.953-1.45.636-2.72 1.56-3.813 2.77-1.092 1.212-1.976 2.672-2.652 4.38-.675 1.708-1.072 3.635-1.19 5.78h24.43z"/></g></svg>
diff --git a/app/views/shared/icons/_rails.svg b/app/views/shared/icons/_rails.svg
deleted file mode 100644
index 852bd183cc7..00000000000
--- a/app/views/shared/icons/_rails.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="32" height="20" viewBox="0 0 32 20" class="btn-template-icon icon-rails"><g fill="none" fill-rule="evenodd"><path d="M0-6h32v32H0z"/><path fill="#c00" fill-rule="nonzero" d="M.985 19.636s.422-4.163 3.375-9.087c2.954-4.924 7.99-8.65 12.083-9.017 8.144-.816 15.46 6.485 15.46 6.485s-.24.168-.494.38C23.42 2.49 18.54 5.274 17.005 6.02c-7.033 3.925-4.91 13.616-4.91 13.616H.987zM24.137 2.32c-.45-.182-.9-.35-1.364-.505l.056-.93c.885.254 1.237.423 1.363.493l-.056.943zM22.8 5.304c.45.028.915.084 1.393.183l-.056.872c-.464-.1-.928-.155-1.392-.17l.056-.885zM17.597.913c-.407 0-.815.015-1.223.058l-.268-.83c.465-.056.915-.084 1.35-.084l.282.858h-.14zm.676 5.178c.35-.154.76-.31 1.237-.45l.31.93c-.41.125-.817.294-1.225.49l-.323-.97zm-6.386-3.7c-.366.184-.718.395-1.083.62l-.647-.985c.38-.225.745-.42 1.097-.604l.633.97zm2.883 6.33c.252-.323.548-.646.87-.942l.634.957c-.31.323-.59.647-.83 1L14.77 8.72zm-2.04 4.53c.112-.506.24-1.027.422-1.547l1.012.802c-.14.548-.24 1.097-.295 1.645l-1.14-.9zM6.57 6.57c-.34.35-.662.73-.958 1.11L4.53 6.752c.323-.352.674-.704 1.04-1.055l1 .872zm-4.25 6.286c-.224.52-.52 1.21-.702 1.688L0 13.954c.14-.38.436-1.084.703-1.69l1.618.592zm10.2 3.967l1.518.548c.084.663.21 1.28.337 1.83l-1.688-.605c-.07-.422-.14-1.027-.168-1.772z"/></g></svg>
diff --git a/app/views/shared/icons/_spring.svg b/app/views/shared/icons/_spring.svg
deleted file mode 100644
index ccf18749029..00000000000
--- a/app/views/shared/icons/_spring.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32" class="btn-template-icon icon-java-spring"><g fill="none" fill-rule="evenodd"><path d="M0 0h32v32H0z"/><path fill="#70AD51" d="M5.466 27.993c.586.473 1.446.385 1.918-.202.475-.585.386-1.445-.2-1.92-.585-.474-1.444-.383-1.92.202-.45.555-.392 1.356.115 1.844l-.266-.234C1.972 24.762 0 20.597 0 15.978 0 7.168 7.168 0 15.98 0c4.48 0 8.53 1.857 11.435 4.836.66-.898 1.232-1.902 1.7-3.015 2.036 6.118 3.233 11.26 2.795 15.31-.592 8.274-7.508 14.83-15.93 14.83-3.912 0-7.496-1.416-10.276-3.757l-.238-.21zm23.58-4.982c4.01-5.336 1.775-13.965-.085-19.48-1.657 3.453-5.738 6.094-9.262 6.93-3.303.788-6.226.142-9.283 1.318-6.97 2.68-6.86 10.992-3.02 12.86.002 0 .23.124.227.12 0-.002 5.644-1.122 8.764-2.274 4.56-1.684 9.566-5.835 11.213-10.657-.877 5.015-5.182 9.84-9.507 12.056-2.302 1.182-4.092 1.445-7.88 2.756-.464.158-.828.314-.828.314.96-.16 1.917-.212 1.917-.212 5.393-.255 13.807 1.516 17.745-3.73z"/></g></svg>
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index e1564d57426..df17ae95e2a 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -12,21 +12,20 @@
- css_class += " no-description" if project.description.blank? && !show_last_commit_as_description
- cache_key = project_list_cache_key(project)
- updated_tooltip = time_ago_with_tooltip(project.last_activity_date)
-- css_details_class = compact_mode ? "d-flex flex-column flex-sm-row flex-md-row align-items-sm-center" : "align-items-center flex-md-fill flex-lg-column d-sm-flex d-lg-block"
-- css_controls_class = compact_mode ? "" : "align-items-md-end align-items-lg-center flex-lg-row"
+- css_controls_class = compact_mode ? "" : "flex-lg-row justify-content-lg-between"
%li.project-row.d-flex{ class: css_class }
= cache(cache_key) do
- if avatar
- .avatar-container.s64.flex-grow-0.flex-shrink-0
+ .avatar-container.s48.flex-grow-0.flex-shrink-0
= link_to project_path(project), class: dom_class(project) do
- if project.creator && use_creator_avatar
- = image_tag avatar_icon_for_user(project.creator, 64), class: "avatar s65", alt:''
+ = image_tag avatar_icon_for_user(project.creator, 48), class: "avatar s65", alt:''
- else
- = project_icon(project, alt: '', class: 'avatar project-avatar s64', width: 64, height: 64)
- .project-details.flex-sm-fill{ class: css_details_class }
- .flex-wrapper.flex-fill
- .d-flex.align-items-center.flex-wrap
+ = project_icon(project, alt: '', class: 'avatar project-avatar s48', width: 48, height: 48)
+ .project-details.d-sm-flex.flex-sm-fill.align-items-center
+ .flex-wrapper
+ .d-flex.align-items-center.flex-wrap.project-title
%h2.d-flex.prepend-top-8
= link_to project_path(project), class: 'text-plain' do
%span.project-full-name.append-right-8><
@@ -52,13 +51,13 @@
%span.user-access-role.d-block= Gitlab::Access.human_access(access)
- if show_last_commit_as_description
- .description.d-none.d-sm-block.prepend-top-8.append-right-default
+ .description.d-none.d-sm-block.append-right-default
= link_to_markdown(project.commit.title, project_commit_path(project, project.commit), class: "commit-row-message")
- elsif project.description.present?
- .description.d-none.d-sm-block.prepend-top-8.append-right-default
+ .description.d-none.d-sm-block.append-right-default
= markdown_field(project, :description)
- .controls.d-flex.flex-row.flex-sm-column.flex-md-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0{ class: css_controls_class }
+ .controls.d-flex.flex-sm-column.align-items-center.align-items-sm-end.flex-wrap.flex-shrink-0{ class: css_controls_class }
.icon-container.d-flex.align-items-center
- if project.archived
%span.d-flex.icon-wrapper.badge.badge-warning archived
@@ -74,13 +73,13 @@
= number_with_delimiter(project.forks_count)
- if show_merge_request_count?(disabled: !merge_requests, compact_mode: compact_mode)
= link_to project_merge_requests_path(project),
- class: "d-none d-lg-flex align-items-center icon-wrapper merge-requests has-tooltip",
+ class: "d-none d-xl-flex align-items-center icon-wrapper merge-requests has-tooltip",
title: _('Merge Requests'), data: { container: 'body', placement: 'top' } do
= sprite_icon('git-merge', size: 14, css_class: 'append-right-4')
= number_with_delimiter(project.open_merge_requests_count)
- if show_issue_count?(disabled: !issues, compact_mode: compact_mode)
= link_to project_issues_path(project),
- class: "d-none d-lg-flex align-items-center icon-wrapper issues has-tooltip",
+ class: "d-none d-xl-flex align-items-center icon-wrapper issues has-tooltip",
title: _('Issues'), data: { container: 'body', placement: 'top' } do
= sprite_icon('issues', size: 14, css_class: 'append-right-4')
= number_with_delimiter(project.open_issues_count)
@@ -89,19 +88,3 @@
= render_project_pipeline_status(project.pipeline_status, tooltip_placement: 'top')
.updated-note
%span Updated #{updated_tooltip}
-
- .d-none.d-lg-flex.align-item-stretch
- - unless compact_mode
- - if current_user
- %button.star-button.btn.btn-default.d-flex.align-items-center.star-btn.toggle-star{ type: "button", data: { endpoint: toggle_star_project_path(project, :json) } }
- - if current_user.starred?(project)
- = sprite_icon('star', { css_class: 'icon' })
- %span.starred= s_('ProjectOverview|Unstar')
- - else
- = sprite_icon('star-o', { css_class: 'icon' })
- %span= s_('ProjectOverview|Star')
-
- - else
- = link_to new_user_session_path, class: 'btn btn-default has-tooltip count-badge-button d-flex align-items-center star-btn', title: s_('ProjectOverview|You must sign in to star a project') do
- = sprite_icon('star-o', { css_class: 'icon' })
- %span= s_('ProjectOverview|Star')
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 85c123c2704..410411b1294 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -23,6 +23,7 @@
- cronjob:prune_web_hook_logs
- gcp_cluster:cluster_install_app
+- gcp_cluster:cluster_upgrade_app
- gcp_cluster:cluster_provision
- gcp_cluster:cluster_wait_for_app_installation
- gcp_cluster:wait_for_cluster_creation
diff --git a/app/workers/cluster_upgrade_app_worker.rb b/app/workers/cluster_upgrade_app_worker.rb
new file mode 100644
index 00000000000..d1a538859b4
--- /dev/null
+++ b/app/workers/cluster_upgrade_app_worker.rb
@@ -0,0 +1,13 @@
+# frozen_string_literal: true
+
+class ClusterUpgradeAppWorker
+ include ApplicationWorker
+ include ClusterQueue
+ include ClusterApplications
+
+ def perform(app_name, app_id)
+ find_application(app_name, app_id) do |app|
+ Clusters::Applications::UpgradeService.new(app).execute
+ end
+ end
+end
diff --git a/app/workers/repository_fork_worker.rb b/app/workers/repository_fork_worker.rb
index 7eae07d3f6b..a9b88a133be 100644
--- a/app/workers/repository_fork_worker.rb
+++ b/app/workers/repository_fork_worker.rb
@@ -15,19 +15,19 @@ class RepositoryForkWorker
return target_project.import_state.mark_as_failed(_('Source project cannot be found.'))
end
- fork_repository(target_project, source_project.repository_storage, source_project.disk_path)
+ fork_repository(target_project, source_project)
end
private
- def fork_repository(target_project, source_repository_storage_name, source_disk_path)
+ def fork_repository(target_project, source_project)
return unless start_fork(target_project)
Gitlab::Metrics.add_event(:fork_repository)
- result = gitlab_shell.fork_repository(source_repository_storage_name, source_disk_path,
- target_project.repository_storage, target_project.disk_path)
- raise "Unable to fork project #{target_project.id} for repository #{source_disk_path} -> #{target_project.disk_path}" unless result
+ result = gitlab_shell.fork_repository(source_project, target_project)
+
+ raise "Unable to fork project #{target_project.id} for repository #{source_project.disk_path} -> #{target_project.disk_path}" unless result
target_project.after_import
end
diff --git a/changelogs/unreleased/2105-add-setting-for-first-day-of-the-week.yml b/changelogs/unreleased/2105-add-setting-for-first-day-of-the-week.yml
new file mode 100644
index 00000000000..f4a52b1aacd
--- /dev/null
+++ b/changelogs/unreleased/2105-add-setting-for-first-day-of-the-week.yml
@@ -0,0 +1,5 @@
+---
+title: Add setting for first day of the week
+merge_request: 22755
+author: Fabian Schneider @fabsrc
+type: added
diff --git a/changelogs/unreleased/44332-add-openid-profile-scopes.yml b/changelogs/unreleased/44332-add-openid-profile-scopes.yml
new file mode 100644
index 00000000000..b554fab5139
--- /dev/null
+++ b/changelogs/unreleased/44332-add-openid-profile-scopes.yml
@@ -0,0 +1,5 @@
+---
+title: GitLab now supports the profile and email scopes from OpenID Connect
+merge_request: 24335
+author: Goten Xiao
+type: added
diff --git a/changelogs/unreleased/52347-lines-changed-statistics-is-not-easily-visible-in-mr-changes-view.yml b/changelogs/unreleased/52347-lines-changed-statistics-is-not-easily-visible-in-mr-changes-view.yml
new file mode 100644
index 00000000000..cf1c4378f18
--- /dev/null
+++ b/changelogs/unreleased/52347-lines-changed-statistics-is-not-easily-visible-in-mr-changes-view.yml
@@ -0,0 +1,5 @@
+---
+title: Show MR statistics in diff comparisons
+merge_request: !24569
+author:
+type: changed
diff --git a/changelogs/unreleased/52363-ui-changes-to-cluster-and-ado-pages.yml b/changelogs/unreleased/52363-ui-changes-to-cluster-and-ado-pages.yml
new file mode 100644
index 00000000000..eb4851971fb
--- /dev/null
+++ b/changelogs/unreleased/52363-ui-changes-to-cluster-and-ado-pages.yml
@@ -0,0 +1,5 @@
+---
+title: Moves domain setting from Auto DevOps to Cluster's page
+merge_request: 24580
+author:
+type: added
diff --git a/changelogs/unreleased/56014-api-merge-request-squash-commit-messages.yml b/changelogs/unreleased/56014-api-merge-request-squash-commit-messages.yml
new file mode 100644
index 00000000000..e324baa94a3
--- /dev/null
+++ b/changelogs/unreleased/56014-api-merge-request-squash-commit-messages.yml
@@ -0,0 +1,5 @@
+---
+title: API allows setting the squash commit message when squashing a merge request
+merge_request: 24784
+author:
+type: added
diff --git a/changelogs/unreleased/56014-better-squash-commit-messages.yml b/changelogs/unreleased/56014-better-squash-commit-messages.yml
deleted file mode 100644
index b08d584ac0a..00000000000
--- a/changelogs/unreleased/56014-better-squash-commit-messages.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Default squash commit message is now selected from the longest commit when
- squashing merge requests
-merge_request: 24518
-author:
-type: changed
diff --git a/changelogs/unreleased/56543-project-lists-further-iteration-improvements.yml b/changelogs/unreleased/56543-project-lists-further-iteration-improvements.yml
new file mode 100644
index 00000000000..388ff1d062a
--- /dev/null
+++ b/changelogs/unreleased/56543-project-lists-further-iteration-improvements.yml
@@ -0,0 +1,5 @@
+---
+title: Project list UI improvements
+merge_request: 24855
+author:
+type: other
diff --git a/changelogs/unreleased/56788-unicorn-metric-labels.yml b/changelogs/unreleased/56788-unicorn-metric-labels.yml
new file mode 100644
index 00000000000..824c981780c
--- /dev/null
+++ b/changelogs/unreleased/56788-unicorn-metric-labels.yml
@@ -0,0 +1,5 @@
+---
+title: Clean up unicorn sampler metric labels
+merge_request: 24626
+author: bjk-gitlab
+type: fixed
diff --git a/changelogs/unreleased/56938-diff-file-headers-on-compare-not-quite-right.yml b/changelogs/unreleased/56938-diff-file-headers-on-compare-not-quite-right.yml
new file mode 100644
index 00000000000..f619a009a63
--- /dev/null
+++ b/changelogs/unreleased/56938-diff-file-headers-on-compare-not-quite-right.yml
@@ -0,0 +1,5 @@
+---
+title: Correct spacing for comparison page
+merge_request: !24783
+author:
+type: fixed
diff --git a/changelogs/unreleased/adriel-remove-feature-flag.yml b/changelogs/unreleased/adriel-remove-feature-flag.yml
new file mode 100644
index 00000000000..d442e120d60
--- /dev/null
+++ b/changelogs/unreleased/adriel-remove-feature-flag.yml
@@ -0,0 +1,5 @@
+---
+title: Update metrics dashboard graph design
+merge_request: 24653
+author:
+type: changed
diff --git a/changelogs/unreleased/api-group-labels.yml b/changelogs/unreleased/api-group-labels.yml
new file mode 100644
index 00000000000..0df6f15a9b6
--- /dev/null
+++ b/changelogs/unreleased/api-group-labels.yml
@@ -0,0 +1,5 @@
+---
+title: 'API: Add support for group labels'
+merge_request: 21368
+author: Robert Schilling
+type: added
diff --git a/changelogs/unreleased/fix-repo-settings-file-upload-error.yml b/changelogs/unreleased/fix-repo-settings-file-upload-error.yml
new file mode 100644
index 00000000000..b219fdfaa1e
--- /dev/null
+++ b/changelogs/unreleased/fix-repo-settings-file-upload-error.yml
@@ -0,0 +1,5 @@
+---
+title: Fix bug causing repository mirror settings UI to break
+merge_request: 23712
+author:
+type: fixed
diff --git a/changelogs/unreleased/gitaly-update-1.18.0.yml b/changelogs/unreleased/gitaly-update-1.18.0.yml
new file mode 100644
index 00000000000..392527f5e5d
--- /dev/null
+++ b/changelogs/unreleased/gitaly-update-1.18.0.yml
@@ -0,0 +1,5 @@
+---
+title: Upgrade gitaly to 1.18.0
+merge_request: 24981
+author:
+type: other
diff --git a/changelogs/unreleased/jlenny-AddPagesTemplates.yml b/changelogs/unreleased/jlenny-AddPagesTemplates.yml
new file mode 100644
index 00000000000..0985e4e18ed
--- /dev/null
+++ b/changelogs/unreleased/jlenny-AddPagesTemplates.yml
@@ -0,0 +1,5 @@
+---
+title: Add templates for most popular Pages templates
+merge_request: 24906
+author:
+type: added
diff --git a/changelogs/unreleased/jlenny-NewAndroidTemplate.yml b/changelogs/unreleased/jlenny-NewAndroidTemplate.yml
new file mode 100644
index 00000000000..ae8c58da859
--- /dev/null
+++ b/changelogs/unreleased/jlenny-NewAndroidTemplate.yml
@@ -0,0 +1,5 @@
+---
+title: Add template for Android with Fastlane
+merge_request: 24722
+author:
+type: changed
diff --git a/changelogs/unreleased/local-markdown-version-bkp3.yml b/changelogs/unreleased/local-markdown-version-bkp3.yml
new file mode 100644
index 00000000000..ce5bff6ae6b
--- /dev/null
+++ b/changelogs/unreleased/local-markdown-version-bkp3.yml
@@ -0,0 +1,5 @@
+---
+title: Allow admins to invalidate markdown texts by setting local markdown version.
+merge_request:
+author:
+type: added
diff --git a/changelogs/unreleased/move-permission-check-manual-actions-on-deployments.yml b/changelogs/unreleased/move-permission-check-manual-actions-on-deployments.yml
new file mode 100644
index 00000000000..9e979b48ad1
--- /dev/null
+++ b/changelogs/unreleased/move-permission-check-manual-actions-on-deployments.yml
@@ -0,0 +1,5 @@
+---
+title: Move permission check of manual actions of deployments
+merge_request: 24660
+author:
+type: other
diff --git a/changelogs/unreleased/support-chunking-in-client.yml b/changelogs/unreleased/support-chunking-in-client.yml
new file mode 100644
index 00000000000..e50648ea4b2
--- /dev/null
+++ b/changelogs/unreleased/support-chunking-in-client.yml
@@ -0,0 +1,5 @@
+---
+title: Fix code search when text is larger than max gRPC message size
+merge_request: 24111
+author:
+type: changed
diff --git a/changelogs/unreleased/tooltips-to-top.yml b/changelogs/unreleased/tooltips-to-top.yml
new file mode 100644
index 00000000000..51bf127089e
--- /dev/null
+++ b/changelogs/unreleased/tooltips-to-top.yml
@@ -0,0 +1,5 @@
+---
+title: Change spawning of tooltips to be top by default
+merge_request: 21223
+author:
+type: changed
diff --git a/changelogs/unreleased/use_upgrade_install_for_helm_apps.yml b/changelogs/unreleased/use_upgrade_install_for_helm_apps.yml
new file mode 100644
index 00000000000..b41c3cfa1ab
--- /dev/null
+++ b/changelogs/unreleased/use_upgrade_install_for_helm_apps.yml
@@ -0,0 +1,5 @@
+---
+title: Added ability to upgrade cluster applications
+merge_request: 24789
+author:
+type: added
diff --git a/config/initializers/doorkeeper_openid_connect.rb b/config/initializers/doorkeeper_openid_connect.rb
index e97c0fcbd6b..fd5a62c39c6 100644
--- a/config/initializers/doorkeeper_openid_connect.rb
+++ b/config/initializers/doorkeeper_openid_connect.rb
@@ -31,8 +31,27 @@ Doorkeeper::OpenidConnect.configure do
o.claim(:name) { |user| user.name }
o.claim(:nickname) { |user| user.username }
- o.claim(:email) { |user| user.public_email }
- o.claim(:email_verified) { |user| true if user.public_email? }
+
+ # Check whether the application has access to the email scope, and grant
+ # access to the user's primary email address if so, otherwise their
+ # public email address (if present)
+ # This allows existing solutions built for GitLab's old behavior to keep
+ # working without modification.
+ o.claim(:email) do |user, scopes|
+ scopes.exists?(:email) ? user.email : user.public_email
+ end
+ o.claim(:email_verified) do |user, scopes|
+ if scopes.exists?(:email)
+ user.primary_email_verified?
+ elsif user.public_email?
+ user.verified_email?(user.public_email)
+ else
+ # If there is no public email set, tell doorkicker-openid-connect to
+ # exclude the email_verified claim by returning nil.
+ nil
+ end
+ end
+
o.claim(:website) { |user| user.full_website_url if user.website_url? }
o.claim(:profile) { |user| Gitlab::Routing.url_helpers.user_url user }
o.claim(:picture) { |user| user.avatar_url(only_path: false) }
diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml
index 9f451046462..a2dff92908e 100644
--- a/config/locales/doorkeeper.en.yml
+++ b/config/locales/doorkeeper.en.yml
@@ -64,6 +64,8 @@ en:
read_registry: Grants permission to read container registry images
openid: Authenticate using OpenID Connect
sudo: Perform API actions as any user in the system
+ profile: Allows read-only access to the user's personal information using OpenID Connect
+ email: Allows read-only access to the user's primary email address using OpenID Connect
scope_desc:
api:
Grants complete read/write access to the API, including all groups and projects.
@@ -77,6 +79,10 @@ en:
Grants permission to authenticate with GitLab using OpenID Connect. Also gives read-only access to the user's profile and group memberships.
sudo:
Grants permission to perform API actions as any user in the system, when authenticated as an admin user.
+ profile:
+ Grants read-only access to the user's profile data using OpenID Connect.
+ email:
+ Grants read-only access to the user's primary email address using OpenID Connect.
flash:
applications:
create:
diff --git a/config/routes/project.rb b/config/routes/project.rb
index d730479cf2b..b4ebc7df4fe 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -444,7 +444,11 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
end
end
- resources :error_tracking, only: [:index], controller: :error_tracking
+ resources :error_tracking, only: [:index], controller: :error_tracking do
+ collection do
+ post :list_projects
+ end
+ end
# Since both wiki and repository routing contains wildcard characters
# its preferable to keep it below all other project routes
diff --git a/db/migrate/20140502125220_migrate_repo_size.rb b/db/migrate/20140502125220_migrate_repo_size.rb
index a69b02cddc4..bff1f01c654 100644
--- a/db/migrate/20140502125220_migrate_repo_size.rb
+++ b/db/migrate/20140502125220_migrate_repo_size.rb
@@ -11,7 +11,7 @@ class MigrateRepoSize < ActiveRecord::Migration[4.2]
path = File.join(namespace_path, project['project_path'] + '.git')
begin
- repo = Gitlab::Git::Repository.new('default', path, '')
+ repo = Gitlab::Git::Repository.new('default', path, '', '')
if repo.empty?
print '-'
else
diff --git a/db/migrate/20181027114222_add_first_day_of_week_to_user_preferences.rb b/db/migrate/20181027114222_add_first_day_of_week_to_user_preferences.rb
new file mode 100644
index 00000000000..a0e76c2186e
--- /dev/null
+++ b/db/migrate/20181027114222_add_first_day_of_week_to_user_preferences.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+class AddFirstDayOfWeekToUserPreferences < ActiveRecord::Migration
+ DOWNTIME = false
+
+ def change
+ add_column :user_preferences, :first_day_of_week, :integer
+ end
+end
diff --git a/db/migrate/20181028120717_add_first_day_of_week_to_application_settings.rb b/db/migrate/20181028120717_add_first_day_of_week_to_application_settings.rb
new file mode 100644
index 00000000000..53cfaa289f6
--- /dev/null
+++ b/db/migrate/20181028120717_add_first_day_of_week_to_application_settings.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class AddFirstDayOfWeekToApplicationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+ disable_ddl_transaction!
+
+ DOWNTIME = false
+
+ def up
+ add_column_with_default(:application_settings, :first_day_of_week, :integer, default: 0)
+ end
+
+ def down
+ remove_column(:application_settings, :first_day_of_week)
+ end
+end
diff --git a/db/migrate/20190130091630_add_local_cached_markdown_version.rb b/db/migrate/20190130091630_add_local_cached_markdown_version.rb
new file mode 100644
index 00000000000..00570e6458c
--- /dev/null
+++ b/db/migrate/20190130091630_add_local_cached_markdown_version.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddLocalCachedMarkdownVersion < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :local_markdown_version, :integer, default: 0, null: false
+ end
+end
diff --git a/db/post_migrate/20190204115450_migrate_auto_dev_ops_domain_to_cluster_domain.rb b/db/post_migrate/20190204115450_migrate_auto_dev_ops_domain_to_cluster_domain.rb
new file mode 100644
index 00000000000..392e64eeade
--- /dev/null
+++ b/db/post_migrate/20190204115450_migrate_auto_dev_ops_domain_to_cluster_domain.rb
@@ -0,0 +1,49 @@
+# frozen_string_literal: true
+
+class MigrateAutoDevOpsDomainToClusterDomain < ActiveRecord::Migration[5.0]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def up
+ execute(update_clusters_domain_query)
+ end
+
+ def down
+ # no-op
+ end
+
+ private
+
+ def update_clusters_domain_query
+ if Gitlab::Database.mysql?
+ mysql_query
+ else
+ postgresql_query
+ end
+ end
+
+ def mysql_query
+ <<~HEREDOC
+ UPDATE clusters, project_auto_devops, cluster_projects
+ SET
+ clusters.domain = project_auto_devops.domain
+ WHERE
+ cluster_projects.cluster_id = clusters.id
+ AND project_auto_devops.project_id = cluster_projects.project_id
+ AND project_auto_devops.domain != ''
+ HEREDOC
+ end
+
+ def postgresql_query
+ <<~HEREDOC
+ UPDATE clusters
+ SET domain = project_auto_devops.domain
+ FROM cluster_projects, project_auto_devops
+ WHERE
+ cluster_projects.cluster_id = clusters.id
+ AND project_auto_devops.project_id = cluster_projects.project_id
+ AND project_auto_devops.domain != ''
+ HEREDOC
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 20c8dab4c3e..023eee5f33e 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: 20190131122559) do
+ActiveRecord::Schema.define(version: 20190204115450) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -168,6 +168,8 @@ ActiveRecord::Schema.define(version: 20190131122559) do
t.string "commit_email_hostname"
t.boolean "protected_ci_variables", default: false, null: false
t.string "runners_registration_token_encrypted"
+ t.integer "local_markdown_version", default: 0, null: false
+ t.integer "first_day_of_week", default: 0, null: false
t.index ["usage_stats_set_by_user_id"], name: "index_application_settings_on_usage_stats_set_by_user_id", using: :btree
end
@@ -2153,6 +2155,7 @@ ActiveRecord::Schema.define(version: 20190131122559) do
t.integer "merge_request_notes_filter", limit: 2, default: 0, null: false
t.datetime_with_timezone "created_at", null: false
t.datetime_with_timezone "updated_at", null: false
+ t.integer "first_day_of_week"
t.string "issues_sort"
t.string "merge_requests_sort"
t.index ["user_id"], name: "index_user_preferences_on_user_id", unique: true, using: :btree
diff --git a/doc/README.md b/doc/README.md
index 1a0359f9e2a..f87ff925e94 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -336,7 +336,7 @@ Viewing [Container Scanning reports](https://docs.gitlab.com/ee/user/project/mer
There are two ways to use GitLab:
- [GitLab self-managed](#gitlab-self-managed): Install, administer, and maintain your own GitLab instance.
-- [GitLab.com](#gitlab-com): GitLab's SaaS offering. You don't need to install anything to use GitLab.com,
+- [GitLab.com](#gitlabcom): GitLab's SaaS offering. You don't need to install anything to use GitLab.com,
you only need to [sign up](https://gitlab.com/users/sign_in) and start using GitLab straight away.
The following sections outline tiers and features within GitLab self-managed and GitLab.com.
diff --git a/doc/administration/gitaly/index.md b/doc/administration/gitaly/index.md
index abef7a6cd33..0795d3dad40 100644
--- a/doc/administration/gitaly/index.md
+++ b/doc/administration/gitaly/index.md
@@ -52,10 +52,10 @@ is used.
### Network architecture
- gitlab-rails shards repositories into "repository storages"
-- gitlab-rails/config/gitlab.yml contains a map from storage names to
+- `gitlab-rails/config/gitlab.yml` contains a map from storage names to
(Gitaly address, Gitaly token) pairs
- the `storage name` -\> `(Gitaly address, Gitaly token)` map in
- gitlab.yml is the single source of truth for the Gitaly network
+ `gitlab.yml` is the single source of truth for the Gitaly network
topology
- a (Gitaly address, Gitaly token) corresponds to a Gitaly server
- a Gitaly server hosts one or more storages
@@ -65,7 +65,7 @@ is used.
gitlab-shell, and Gitaly itself
- special case: a Gitaly server must be able to make RPC calls **to
itself** via its own (Gitaly address, Gitaly token) pair as
- specified in gitlab-rails/config/gitlab.yml
+ specified in `gitlab-rails/config/gitlab.yml`
- Gitaly servers must not be exposed to the public internet
Gitaly network traffic is unencrypted so you should use a firewall to
@@ -125,7 +125,7 @@ Omnibus installations:
```ruby
# /etc/gitlab/gitlab.rb
-# Avoid running unnecessary services on the gitaly server
+# Avoid running unnecessary services on the Gitaly server
postgresql['enable'] = false
redis['enable'] = false
nginx['enable'] = false
@@ -153,7 +153,7 @@ gitaly['storage'] = [
{ 'name' => 'storage1', 'path' => '/mnt/gitlab/storage1/repositories' },
]
-# To use tls for gitaly you need to add
+# To use TLS for Gitaly you need to add
gitaly['tls_listen_addr'] = "0.0.0.0:9999"
gitaly['certificate_path'] = "path/to/cert.pem"
gitaly['key_path'] = "path/to/key.pem"
@@ -239,11 +239,11 @@ repository from your GitLab server over HTTP.
> [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
+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.
The admin needs to bring their own certificate as we do not provide that automatically.
-The certificate to be used needs to be installed on all gitaly nodes and on all client nodes that communicate with it following procedures described in [GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates)
+The certificate to be used needs to be installed on all Gitaly nodes and on all client nodes that communicate with it following procedures described in [GitLab custom certificate configuration](https://docs.gitlab.com/omnibus/settings/ssl.html#install-custom-public-certificates)
### Example TLS configuration
@@ -261,7 +261,7 @@ git_data_dirs({
gitlab_rails['gitaly_token'] = 'abc123secret'
```
-#### On gitaly server nodes:
+#### On Gitaly server nodes:
```ruby
gitaly['tls_listen_addr'] = "0.0.0.0:9999"
@@ -289,7 +289,7 @@ gitlab:
token: 'abc123secret'
```
-#### On gitaly server nodes:
+#### On Gitaly server nodes:
```toml
# /home/git/gitaly/config.toml
diff --git a/doc/administration/index.md b/doc/administration/index.md
index 184754cd467..12fec2753bf 100644
--- a/doc/administration/index.md
+++ b/doc/administration/index.md
@@ -65,6 +65,7 @@ Learn how to install, configure, update, and maintain your GitLab instance.
- [Backup and restore](../raketasks/backup_restore.md): Backup and restore your GitLab instance.
- [Operations](operations/index.md): Keeping GitLab up and running (clean up Redis sessions, moving repositories, Sidekiq MemoryKiller, Unicorn).
- [Restart GitLab](restart_gitlab.md): Learn how to restart GitLab and its components.
+- [Invalidate markdown cache](invalidate_markdown_cache.md): Invalidate any cached markdown.
#### Updating GitLab
diff --git a/doc/administration/invalidate_markdown_cache.md b/doc/administration/invalidate_markdown_cache.md
new file mode 100644
index 00000000000..ad64cb077c1
--- /dev/null
+++ b/doc/administration/invalidate_markdown_cache.md
@@ -0,0 +1,16 @@
+# Invalidate Markdown Cache
+
+For performance reasons, GitLab caches the HTML version of markdown text
+(e.g. issue and merge request descriptions, comments). It's possible
+that these cached versions become outdated, for example
+when the `external_url` configuration option is changed - causing links
+in the cached text to refer to the old URL.
+
+To avoid this problem, the administrator can invalidate the existing cache by
+increasing the `local_markdown_version` setting in application settings. This can
+be done by [changing the application settings through
+the API](../api/settings.md#change-application-settings):
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/application/settings?local_markdown_version=<increased_number>
+```
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index c9a2778b3a4..6ea0ac0d495 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -48,6 +48,8 @@ The following metrics are available:
| upload_file_does_not_exist | Counter | 10.7 in EE, 11.5 in CE | Number of times an upload record could not find its file |
| failed_login_captcha_total | Gauge | 11.0 | Counter of failed CAPTCHA attempts during login |
| successful_login_captcha_total | Gauge | 11.0 | Counter of successful CAPTCHA attempts during login |
+| unicorn_active_connections | Gauge | 11.0 | The number of active Unicorn connections (workers) |
+| unicorn_queued_connections | Gauge | 11.0 | The number of queued Unicorn connections |
### Ruby metrics
diff --git a/doc/administration/operations/filesystem_benchmarking.md b/doc/administration/operations/filesystem_benchmarking.md
index 0397452e650..c0c242733a2 100644
--- a/doc/administration/operations/filesystem_benchmarking.md
+++ b/doc/administration/operations/filesystem_benchmarking.md
@@ -7,13 +7,72 @@ systems.
Normally when talking about filesystem performance the biggest concern is
with Network Filesystems (NFS). However, even some local disks can have slow
-IO. The information on this page can be used for either scenario.
+I/O. The information on this page can be used for either scenario.
-## Write Performance
+## Executing benchmarks
-The following one-line command is a quick benchmark for filesystem write
+### Benchmarking with `fio`
+
+We recommend using
+[fio](https://fio.readthedocs.io/en/latest/fio_doc.html) to test I/O
+performance. This test should be run both on the NFS server and on the
+application nodes that talk to the NFS server.
+
+To install:
+
+- On Ubuntu: `apt install fio`.
+- On `yum`-managed environments: `yum install fio`.
+
+Then run the following:
+
+```sh
+fio --randrepeat=1 --ioengine=libaio --direct=1 --gtod_reduce=1 --name=test --filename=/path/to/git-data/testfile --bs=4k --iodepth=64 --size=4G --readwrite=randrw --rwmixread=75
+```
+
+This will create a 4GB file in `/path/to/git-data/testfile`. It performs
+4KB reads and writes using a 75%/25% split within the file, with 64
+operations running at a time. Be sure to delete the file after the test
+completes.
+
+The output will vary depending on what version of `fio` installed. The following
+is an example output from `fio` v2.2.10 on a networked solid-state drive (SSD):
+
+```
+test: (g=0): rw=randrw, bs=4K-4K/4K-4K/4K-4K, ioengine=libaio, iodepth=64
+ fio-2.2.10
+ Starting 1 process
+ test: Laying out IO file(s) (1 file(s) / 1024MB)
+ Jobs: 1 (f=1): [m(1)] [100.0% done] [131.4MB/44868KB/0KB /s] [33.7K/11.3K/0 iops] [eta 00m:00s]
+ test: (groupid=0, jobs=1): err= 0: pid=10287: Sat Feb 2 17:40:10 2019
+ read : io=784996KB, bw=133662KB/s, iops=33415, runt= 5873msec
+ write: io=263580KB, bw=44880KB/s, iops=11219, runt= 5873msec
+ cpu : usr=6.56%, sys=23.11%, ctx=266267, majf=0, minf=8
+ IO depths : 1=0.1%, 2=0.1%, 4=0.1%, 8=0.1%, 16=0.1%, 32=0.1%, >=64=100.0%
+ submit : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.0%, >=64=0.0%
+ complete : 0=0.0%, 4=100.0%, 8=0.0%, 16=0.0%, 32=0.0%, 64=0.1%, >=64=0.0%
+ issued : total=r=196249/w=65895/d=0, short=r=0/w=0/d=0, drop=r=0/w=0/d=0
+ latency : target=0, window=0, percentile=100.00%, depth=64
+
+ Run status group 0 (all jobs):
+ READ: io=784996KB, aggrb=133661KB/s, minb=133661KB/s, maxb=133661KB/s, mint=5873msec, maxt=5873msec
+ WRITE: io=263580KB, aggrb=44879KB/s, minb=44879KB/s, maxb=44879KB/s, mint=5873msec, maxt=5873msec
+```
+
+Notice the `iops` values in this output. In this example, the SSD
+performed 33,415 read operations per second and 11,219 write operations
+per second. A spinning disk might yield 2,000 and 700 read and write
+operations per second.
+
+### Simple benchmarking
+
+NOTE: **Note:** This test is naive but may be useful if `fio` is not
+available on the system. It's possible to receive good results on this
+test but still have poor performance due to read speed and various other
+factors.
+
+The following one-line commands provide a quick benchmark for filesystem write and read
performance. This will write 1,000 small files to the directory in which it is
-executed.
+executed, and then read the same 1,000 files.
1. Change into the root of the appropriate
[repository storage path](../repository_storage_paths.md).
@@ -27,13 +86,18 @@ executed.
```sh
time for i in {0..1000}; do echo 'test' > "test${i}.txt"; done
```
+1. To benchmark read performance, run the command:
+
+ ```sh
+ time for i in {0..1000}; do cat "test${i}.txt" > /dev/null; done
+ ```
1. Remove the test files:
```sh
cd ../; rm -rf test
```
-The output of the `time for ...` command will look similar to the following. The
+The output of the `time for ...` commands will look similar to the following. The
important metric is the `real` time.
```sh
@@ -42,12 +106,13 @@ $ time for i in {0..1000}; do echo 'test' > "test${i}.txt"; done
real 0m0.116s
user 0m0.025s
sys 0m0.091s
+
+$ time for i in {0..1000}; do cat "test${i}.txt" > /dev/null; done
+
+real 0m3.118s
+user 0m1.267s
+sys 0m1.663s
```
From experience with multiple customers, this task should take under 10
-seconds to indicate good filesystem performance.
-
-NOTE: **Note:**
-This test is naive and only evaluates write performance. It's possible to
-receive good results on this test but still have poor performance due to read
-speed and various other factors. \ No newline at end of file
+seconds to indicate good filesystem performance.
diff --git a/doc/api/README.md b/doc/api/README.md
index 692f63a400c..a060e0481bf 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -29,6 +29,7 @@ The following API resources are available:
- [Group access requests](access_requests.md)
- [Group badges](group_badges.md)
- [Group issue boards](group_boards.md)
+ - [Group labels](group_labels.md)
- [Group-level variables](group_level_variables.md)
- [Group members](members.md)
- [Group milestones](group_milestones.md)
diff --git a/doc/api/group_labels.md b/doc/api/group_labels.md
new file mode 100644
index 00000000000..c36d34b4af1
--- /dev/null
+++ b/doc/api/group_labels.md
@@ -0,0 +1,201 @@
+# Group Label API
+
+>**Note:** This feature was [introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21368) in GitLab 11.8.
+
+This API supports managing of [group labels](../user/project/labels.md#project-labels-and-group-labels). It allows to list, create, update, and delete group labels. Furthermore, users can subscribe and unsubscribe to and from group labels.
+
+## List group labels
+
+Get all labels for a given group.
+
+```
+GET /groups/:id/labels
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+
+```bash
+curl --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels
+```
+
+Example response:
+
+```json
+[
+ {
+ "id": 7,
+ "name": "bug",
+ "color": "#FF0000",
+ "description": null,
+ "open_issues_count": 0,
+ "closed_issues_count": 0,
+ "open_merge_requests_count": 0,
+ "subscribed": false
+ },
+ {
+ "id": 4,
+ "name": "feature",
+ "color": "#228B22",
+ "description": null,
+ "open_issues_count": 0,
+ "closed_issues_count": 0,
+ "open_merge_requests_count": 0,
+ "subscribed": false
+ }
+]
+```
+
+## Create a new group label
+
+Create a new group label for a given group.
+
+```
+POST /groups/:id/labels
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | ------- | -------- | ---------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `name` | string | yes | The name of the label |
+| `color` | string | yes | The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the [CSS color names](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords) |
+| `description` | string | no | The description of the label |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" --data '{"name": "Feature Proposal", "color": "#FFA500", "description": "Describes new ideas" }' https://gitlab.example.com/api/v4/groups/5/labels
+```
+
+Example response:
+
+```json
+{
+ "id": 9,
+ "name": "Feature Proposal",
+ "color": "#FFA500",
+ "description": "Describes new ideas",
+ "open_issues_count": 0,
+ "closed_issues_count": 0,
+ "open_merge_requests_count": 0,
+ "subscribed": false
+}
+```
+
+## Update a group label
+
+Updates an existing group label. At least one parameter is required, to update the group label.
+
+```
+PUT /groups/:id/labels
+```
+
+| Attribute | Type | Required | Description |
+| ------------- | ------- | -------- | ---------------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `name` | string | yes | The name of the label |
+| `new_name` | string | no | The new name of the label |
+| `color` | string | no | The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the [CSS color names](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value#Color_keywords) |
+| `description` | string | no | The description of the label |
+
+```bash
+curl --request PUT --header "PRIVATE-TOKEN: <your_access_token>" --header "Content-Type: application/json" --data '{"name": "Feature Proposal", "new_name": "Feature Idea" }' https://gitlab.example.com/api/v4/groups/5/labels
+```
+
+Example response:
+
+```json
+{
+ "id": 9,
+ "name": "Feature Idea",
+ "color": "#FFA500",
+ "description": "Describes new ideas",
+ "open_issues_count": 0,
+ "closed_issues_count": 0,
+ "open_merge_requests_count": 0,
+ "subscribed": false
+}
+```
+
+## Delete a group label
+
+Deletes a group label with a given name.
+
+```
+DELETE /groups/:id/labels
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ------- | -------- | --------------------- |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `name` | string | yes | The name of the label |
+
+```bash
+curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels?name=bug
+```
+
+## Subscribe to a group label
+
+Subscribes the authenticated user to a group label to receive notifications. If
+the user is already subscribed to the label, the status code `304` is returned.
+
+```
+POST /groups/:id/labels/:label_id/subscribe
+```
+
+| Attribute | Type | Required | Description |
+| ---------- | ----------------- | -------- | ------------------------------------ |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `label_id` | integer or string | yes | The ID or title of a group's label |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels/9/subscribe
+```
+
+Example response:
+
+```json
+{
+ "id": 9,
+ "name": "Feature Idea",
+ "color": "#FFA500",
+ "description": "Describes new ideas",
+ "open_issues_count": 0,
+ "closed_issues_count": 0,
+ "open_merge_requests_count": 0,
+ "subscribed": true
+}
+```
+
+## Unsubscribe from a group label
+
+Unsubscribes the authenticated user from a group label to not receive
+notifications from it. If the user is not subscribed to the label, the status
+code `304` is returned.
+
+```
+POST /groups/:id/labels/:label_id/unsubscribe
+```
+
+| Attribute | Type | Required | Description |
+| ---------- | ----------------- | -------- | ------------------------------------ |
+| `id` | integer/string | yes | The ID or [URL-encoded path of the group](README.md#namespaced-path-encoding) owned by the authenticated user |
+| `label_id` | integer or string | yes | The ID or title of a group's label |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" https://gitlab.example.com/api/v4/groups/5/labels/9/unsubscribe
+```
+
+Example response:
+
+```json
+{
+ "id": 9,
+ "name": "Feature Idea",
+ "color": "#FFA500",
+ "description": "Describes new ideas",
+ "open_issues_count": 0,
+ "closed_issues_count": 0,
+ "open_merge_requests_count": 0,
+ "subscribed": false
+}
+```
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 802ff1d1df9..d58cd45538d 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -994,6 +994,8 @@ Parameters:
- `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user
- `merge_request_iid` (required) - Internal ID of MR
- `merge_commit_message` (optional) - Custom merge commit message
+- `squash_commit_message` (optional) - Custom squash commit message
+- `squash` (optional) - if `true` the commits will be squashed into a single commit on merge
- `should_remove_source_branch` (optional) - if `true` removes the source branch
- `merge_when_pipeline_succeeds` (optional) - if `true` the MR is merged when the pipeline succeeds
- `sha` (optional) - if present, then this SHA must match the HEAD of the source branch, otherwise the merge will fail
diff --git a/doc/api/settings.md b/doc/api/settings.md
index c329e3cdf24..2e0a2a09133 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -57,11 +57,13 @@ Example response:
"dsa_key_restriction": 0,
"ecdsa_key_restriction": 0,
"ed25519_key_restriction": 0,
+ "first_day_of_week": 0,
"enforce_terms": true,
"terms": "Hello world!",
"performance_bar_allowed_group_id": 42,
"instance_statistics_visibility_private": false,
- "user_show_add_ssh_key_message": true
+ "user_show_add_ssh_key_message": true,
+ "local_markdown_version": 0
}
```
@@ -113,11 +115,13 @@ Example response:
"dsa_key_restriction": 0,
"ecdsa_key_restriction": 0,
"ed25519_key_restriction": 0,
+ "first_day_of_week": 0,
"enforce_terms": true,
"terms": "Hello world!",
"performance_bar_allowed_group_id": 42,
"instance_statistics_visibility_private": false,
- "user_show_add_ssh_key_message": true
+ "user_show_add_ssh_key_message": true,
+ "local_markdown_version": 0
}
```
@@ -157,6 +161,7 @@ are listed in the descriptions of the relevant settings.
| `email_author_in_body` | boolean | no | Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead. |
| `enabled_git_access_protocol` | string | no | Enabled protocols for Git access. Allowed values are: `ssh`, `http`, and `nil` to allow both protocols. |
| `enforce_terms` | boolean | no | (**If enabled, requires:** `terms`) Enforce application ToS to all users. |
+| `first_day_of_week` | integer | no | Start day of the week for calendar views and date pickers. Valid values are `0` (default) for Sunday and `1` for Monday. |
| `gitaly_timeout_default` | integer | no | Default Gitaly timeout, in seconds. This timeout is not enforced for git fetch/push operations or Sidekiq jobs. Set to `0` to disable timeouts. |
| `gitaly_timeout_fast` | integer | no | Gitaly fast operation timeout, in seconds. Some Gitaly operations are expected to be fast. If they exceed this threshold, there may be a problem with a storage shard and 'failing fast' can help maintain the stability of the GitLab instance. Set to `0` to disable timeouts. |
| `gitaly_timeout_medium` | integer | no | Medium Gitaly timeout, in seconds. This should be a value between the Fast and the Default timeout. Set to `0` to disable timeouts. |
@@ -235,3 +240,4 @@ are listed in the descriptions of the relevant settings.
| `user_oauth_applications` | boolean | no | Allow users to register any application to use GitLab as an OAuth provider. |
| `user_show_add_ssh_key_message` | boolean | no | When set to `false` disable the "You won't be able to pull or push project code via SSH" warning shown to users with no uploaded SSH key. |
| `version_check_enabled` | boolean | no | Let GitLab inform you when an update is available. |
+| `local_markdown_version` | integer | no | Increase this value when any cached markdown should be invalidated. |
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 97e133a2e2f..32c73c4f398 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -98,7 +98,7 @@ future GitLab releases.**
| **CI_PIPELINE_SOURCE** | 10.0 | all | Indicates how the pipeline was triggered. Possible options are: `push`, `web`, `trigger`, `schedule`, `api`, and `pipeline`. For pipelines created before GitLab 9.5, this will show as `unknown` |
| **CI_PIPELINE_TRIGGERED** | all | all | The flag to indicate that job was [triggered] |
| **CI_PIPELINE_URL** | 11.1 | 0.5 | Pipeline details URL |
-| **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the job is run |
+| **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the job is run. If the GitLab Runner `builds_dir` parameter is set, this variable is set relative to the value of `builds_dir`. For more information, see [Advanced configuration](https://docs.gitlab.com/runner/configuration/advanced-configuration.html#the-runners-section) for GitLab Runner. |
| **CI_PROJECT_ID** | all | all | The unique id of the current project that GitLab CI uses internally |
| **CI_PROJECT_NAME** | 8.10 | 0.5 | The project name that is currently being built (actually it is project folder name) |
| **CI_PROJECT_NAMESPACE** | 8.10 | 0.5 | The project namespace (username or groupname) that is currently being built |
diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md
index 24feb1378a1..c5344139ab4 100644
--- a/doc/development/contributing/issue_workflow.md
+++ b/doc/development/contributing/issue_workflow.md
@@ -20,7 +20,7 @@ All labels, their meaning and priority are defined on the
If you come across an issue that has none of these, and you're allowed to set
labels, you can _always_ add the team and type, and often also the subject.
-[milestones-page]: https://gitlab.com/gitlab-org/gitlab-ce/milestones
+[milestones-page]: https://gitlab.com/groups/gitlab-org/-/milestones
## Type labels
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
index 9bef0635e3f..19b6181c9a2 100644
--- a/doc/development/contributing/merge_request_workflow.md
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -86,6 +86,9 @@ request is as follows:
guidelines](../merge_request_performance_guidelines.md).
1. For tests that use Capybara or PhantomJS, see this [article on how
to write reliable asynchronous tests](https://robots.thoughtbot.com/write-reliable-asynchronous-integration-tests-with-capybara).
+1. If your merge request introduces changes that require additional steps when
+ installing GitLab from source, add them to `doc/install/installation.md` in
+ the same merge request.
Please keep the change in a single MR **as small as possible**. If you want to
contribute a large feature think very hard what the minimum viable change is.
diff --git a/doc/development/i18n/externalization.md b/doc/development/i18n/externalization.md
index 00db58a45a2..223585ebb55 100644
--- a/doc/development/i18n/externalization.md
+++ b/doc/development/i18n/externalization.md
@@ -231,11 +231,11 @@ This makes use of [`Intl.DateTimeFormat`].
- In Ruby/HAML, we have two ways of adding format to dates and times:
1. **Through the `l` helper**, i.e. `l(active_session.created_at, format: :short)`. We have some predefined formats for
-[dates](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.7.0/config/locales/en.yml#L54) and [times](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.7.0/config/locales/en.yml#L261).
+ [dates](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.7.0/config/locales/en.yml#L54) and [times](https://gitlab.com/gitlab-org/gitlab-ce/blob/v11.7.0/config/locales/en.yml#L261).
If you need to add a new format, because other parts of the code could benefit from it,
you'll need to add it to [en.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/locales/en.yml) file.
- 2. **Through `strftime`**, i.e. `milestone.start_date.strftime('%b %-d')`. We use `strftime` in case none of the formats
- defined on [en.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/locales/en.yml) matches the date/time
+ 1. **Through `strftime`**, i.e. `milestone.start_date.strftime('%b %-d')`. We use `strftime` in case none of the formats
+ defined on [en.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/locales/en.yml) matches the date/time
specifications we need, and if there is no need to add it as a new format because is very particular (i.e. it's only used in a single view).
## Best practices
diff --git a/doc/development/i18n/index.md b/doc/development/i18n/index.md
index c44690a4c5d..5e4923341af 100644
--- a/doc/development/i18n/index.md
+++ b/doc/development/i18n/index.md
@@ -51,4 +51,4 @@ able to proofread and instructions on becoming a proofreader yourself.
Translations are typically included in the next major or minor release.
-See [Merging translations from Crowdin](merging_translations.md)
+See [Merging translations from Crowdin](merging_translations.md).
diff --git a/doc/development/i18n/merging_translations.md b/doc/development/i18n/merging_translations.md
index d172aa6da21..2fa7558d30b 100644
--- a/doc/development/i18n/merging_translations.md
+++ b/doc/development/i18n/merging_translations.md
@@ -4,7 +4,7 @@ Crowdin automatically syncs the `gitlab.pot` file presenting newly
added translations to the community of translators.
At the same time, it creates a merge request to merge all newly added
-& approved translations. Find the [merge reqeust created by
+& approved translations. Find the [merge request created by
`gitlab-crowdin-bot`](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests?scope=all&utf8=%E2%9C%93&state=opened&author_username=gitlab-crowdin-bot)
to see new and merged merge requests. They are created in EE and need
to be ported to CE manually.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 45a76bd1d01..a8064ae046e 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -345,11 +345,15 @@ cd /home/git
```sh
# Clone GitLab repository
-sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 11-7-stable gitlab
+sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b X-Y-stable gitlab
```
+Make sure to replace `X-Y-stable` with the stable branch that matches the
+version you want to install. For example, if you want to install 11.8 you would
+use the branch name `11-8-stable`.
+
CAUTION: **Caution:**
-You can change `11-7-stable` to `master` if you want the *bleeding edge* version, but never install `master` on a production server!
+You can change `X-Y-stable` to `master` if you want the *bleeding edge* version, but never install `master` on a production server!
### Configure It
@@ -691,6 +695,11 @@ 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.
+NOTE: **Note:**
+Verify that the installed version is greater than 1.12.1 by running `nginx -v`. If it's lower, you may receive the error below:
+`nginx: [emerg] unknown "start$temp=[filtered]$rest" variable
+nginx: configuration file /etc/nginx/nginx.conf test failed`
+
### Restart
```sh
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index 80de39c207a..1b53be15b44 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -114,7 +114,8 @@ To create a new SSH key pair:
and want to tell which is which. It is optional.
1. Next, you will be prompted to input a file path to save your SSH key pair to.
- If you don't already have an SSH key pair, use the suggested path by pressing
+ If you don't already have an SSH key pair and aren't generating a [deploy key](#deploy-keys),
+ use the suggested path by pressing
<kbd>Enter</kbd>. Using the suggested path will normally allow your SSH client
to automatically use the SSH key pair with no additional configuration.
@@ -128,7 +129,7 @@ To create a new SSH key pair:
<kbd>Enter</kbd> twice.
If, in any case, you want to add or change the password of your SSH key pair,
- you can use the `-p`flag:
+ you can use the `-p` flag:
```
ssh-keygen -p -o -f <keyname>
@@ -258,7 +259,8 @@ Integration (CI) server. By using deploy keys, you don't have to set up a
dummy user account.
If you are a project maintainer or owner, you can add a deploy key in the
-project settings under the section 'Repository'. Specify a title for the new
+project's **Settings > Repository** page by expanding the
+**Deploy Keys** section. Specify a title for the new
deploy key and paste a public SSH key. After this, the machine that uses
the corresponding private SSH key has read-only or read-write (if enabled)
access to the project.
@@ -300,8 +302,8 @@ of broader usage for something like "Anywhere you need to give read access to
your repository".
Once a GitLab administrator adds the Global Deployment key, project maintainers
-and owners can add it in project's **Settings > Repository** section by expanding the
-**Deploy Key** section and clicking **Enable** next to the appropriate key listed
+and owners can add it in project's **Settings > Repository** page by expanding the
+**Deploy Keys** section and clicking **Enable** next to the appropriate key listed
under **Public deploy keys available to any project**.
NOTE: **Note:**
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 325de50cab0..463bdd59282 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -126,14 +126,22 @@ Auto Deploy, and Auto Monitoring will be silently skipped.
## Auto DevOps base domain
+NOTE: **Note**
+`AUTO_DEVOPS_DOMAIN` environment variable is deprecated and
+[is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959) in GitLab 12.0.
+
The Auto DevOps base domain is required if you want to make use of [Auto
Review Apps](#auto-review-apps) and [Auto Deploy](#auto-deploy). It can be defined
-in three places:
+in any of the following places:
-- either under the project's CI/CD settings while [enabling Auto DevOps](#enabling-auto-devops)
+- either under the cluster's settings, whether for [projects](../../user/project/clusters/index.md#base-domain) or [groups](../../user/group/clusters/index.md#base-domain)
- or in instance-wide settings in the **admin area > Settings** under the "Continuous Integration and Delivery" section
-- or at the project as a variable: `AUTO_DEVOPS_DOMAIN` (required if you want to use [multiple clusters](#using-multiple-kubernetes-clusters))
-- or at the group level as a variable: `AUTO_DEVOPS_DOMAIN`
+- or at the project level as a variable: `KUBE_INGRESS_BASE_DOMAIN`
+- or at the group level as a variable: `KUBE_INGRESS_BASE_DOMAIN`.
+
+NOTE: **Note**
+The Auto DevOps base domain variable (`KUBE_INGRESS_BASE_DOMAIN`) follows the same order of precedence
+as other environment [variables](../../ci/variables/README.md#priority-of-variables).
A wildcard DNS A record matching the base domain(s) is required, for example,
given a base domain of `example.com`, you'd need a DNS entry like:
@@ -170,13 +178,13 @@ In the [Auto DevOps template](https://gitlab.com/gitlab-org/gitlab-ce/blob/maste
Those environments are tied to jobs that use [Auto Deploy](#auto-deploy), so
except for the environment scope, they would also need to have a different
domain they would be deployed to. This is why you need to define a separate
-`AUTO_DEVOPS_DOMAIN` variable for all the above
+`KUBE_INGRESS_BASE_DOMAIN` variable for all the above
[based on the environment](../../ci/variables/README.md#limiting-environment-scopes-of-variables).
The following table is an example of how the three different clusters would
be configured.
-| Cluster name | Cluster environment scope | `AUTO_DEVOPS_DOMAIN` variable value | Variable environment scope | Notes |
+| Cluster name | Cluster environment scope | `KUBE_INGRESS_BASE_DOMAIN` variable value | Variable environment scope | Notes |
| ------------ | -------------- | ----------------------------- | ------------- | ------ |
| review | `review/*` | `review.example.com` | `review/*` | The review cluster which will run all [Review Apps](../../ci/review_apps/index.md). `*` is a wildcard, which means it will be used by every environment name starting with `review/`. |
| staging | `staging` | `staging.example.com` | `staging` | (Optional) The staging cluster which will run the deployments of the staging environments. You need to [enable it first](#deploy-policy-for-staging-and-production-environments). |
@@ -190,14 +198,11 @@ To add a different cluster for each environment:
![Auto DevOps multiple clusters](img/autodevops_multiple_clusters.png)
1. After the clusters are created, navigate to each one and install Helm Tiller
- and Ingress.
+ and Ingress. Wait for the Ingress IP address to be assigned.
1. Make sure you have [configured your DNS](#auto-devops-base-domain) with the
specified Auto DevOps domains.
-1. Navigate to your project's **Settings > CI/CD > Environment variables** and add
- the `AUTO_DEVOPS_DOMAIN` variables with their respective environment
- scope.
-
- ![Auto DevOps domain variables](img/autodevops_domain_variables.png)
+1. Navigate to each cluster's page, through **Operations > Kubernetes**,
+ and add the domain based on its Ingress IP address.
Now that all is configured, you can test your setup by creating a merge request
and verifying that your app is deployed as a review app in the Kubernetes
@@ -205,10 +210,9 @@ cluster with the `review/*` environment scope. Similarly, you can check the
other environments.
NOTE: **Note:**
-Auto DevOps is not supported for a group with multiple clusters, as it
-is not possible to set `AUTO_DEVOPS_DOMAIN` per environment on the group
-level. This will be resolved in the future with the [following issue](
-https://gitlab.com/gitlab-org/gitlab-ce/issues/52363).
+From GitLab 11.8, `KUBE_INGRESS_BASE_DOMAIN` replaces `AUTO_DEVOPS_DOMAIN`.
+`AUTO_DEVOPS_DOMAIN` [is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959)
+in GitLab 12.0.
## Enabling/Disabling Auto DevOps
@@ -681,7 +685,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| **Variable** | **Description** |
| ------------ | --------------- |
-| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-domain); by default set automatically by the [Auto DevOps setting](#enabling-auto-devops). |
+| `AUTO_DEVOPS_DOMAIN` | The [Auto DevOps domain](#auto-devops-domain). By default, set automatically by the [Auto DevOps setting](#enabling-auto-devops). This variable is deprecated and [is scheduled to be removed](https://gitlab.com/gitlab-org/gitlab-ce/issues/56959) in GitLab 12.0. Use `KUBE_INGRESS_BASE_DOMAIN` instead. |
| `AUTO_DEVOPS_CHART` | The Helm Chart used to deploy your apps; defaults to the one [provided by GitLab](https://gitlab.com/charts/auto-deploy-app). |
| `AUTO_DEVOPS_CHART_REPOSITORY` | The Helm Chart repository used to search for charts; defaults to `https://charts.gitlab.io`. |
| `REPLICAS` | The number of replicas to deploy; defaults to 1. |
@@ -711,6 +715,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac
| `DAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `dast` job. If the variable is present, the job will not be created. |
| `PERFORMANCE_DISABLED` | From GitLab 11.0, this variable can be used to disable the `performance` job. If the variable is present, the job will not be created. |
| `K8S_SECRET_*` | From GitLab 11.7, any variable prefixed with [`K8S_SECRET_`](#application-secret-variables) will be made available by Auto DevOps as environment variables to the deployed application. |
+| `KUBE_INGRESS_BASE_DOMAIN` | From GitLab 11.8, this variable can be used to set a domain per cluster. See [cluster domains](../../user/project/clusters/index.md#base-domain) for more information. |
TIP: **Tip:**
Set up the replica variables using a
diff --git a/doc/user/group/clusters/index.md b/doc/user/group/clusters/index.md
index 9f9b2da23e1..9fc50741407 100644
--- a/doc/user/group/clusters/index.md
+++ b/doc/user/group/clusters/index.md
@@ -59,11 +59,16 @@ Add another cluster similar to the first one and make sure to
[set an environment scope](#environment-scopes) that will
differentiate the new cluster from the rest.
-NOTE: **Note:**
-Auto DevOps is not supported for a group with multiple clusters, as it
-is not possible to set `AUTO_DEVOPS_DOMAIN` per environment on the group
-level. This will be resolved in the future with the [following issue](
-https://gitlab.com/gitlab-org/gitlab-ce/issues/52363).
+## Base domain
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580) in GitLab 11.8.
+
+Domains at the cluster level permit support for multiple domains
+per [multiple Kubernetes clusters](#multiple-kubernetes-clusters-premium). When specifying a domain,
+this will be automatically set as an environment variable (`KUBE_INGRESS_BASE_DOMAIN`) during
+the [Auto DevOps](../../../topics/autodevops/index.md) stages.
+
+The domain should have a wildcard DNS configured to the Ingress IP address.
## Environment scopes **[PREMIUM]**
diff --git a/doc/user/index.md b/doc/user/index.md
index fc68404d0c2..36aba5e01c6 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -12,28 +12,22 @@ includes, except [GitLab administrator](../README.md#administrator-documentation
settings, unless you have admin privileges to install, configure,
and upgrade your GitLab instance.
-For GitLab.com, admin privileges are restricted to the GitLab team.
+Admin privileges for [GitLab.com](https://gitlab.com/) are restricted to the GitLab team.
-If you run your own GitLab instance and are looking for the administration settings,
-please refer to the [administration](../README.md#administrator-documentation)
-documentation.
+For more information on configuring GitLab self-managed instances, see [Administrator documentation](../README.md#administrator-documentation).
## Overview
-GitLab is a fully integrated software development platform that enables you
-and your team to work cohesively, faster, transparently, and effectively,
-since the discussion of a new idea until taking that idea to production all
-the way through, from within the same platform.
+GitLab is a fully integrated software development platform that enables your team to be transparent, fast, effective, and cohesive from discussion on a new idea to production, all on the same platform.
-Please check this page for an overview on [GitLab's features](https://about.gitlab.com/features/).
+For more information, see [All GitLab Features](https://about.gitlab.com/features/).
### Concepts
-For an overview on concepts involved when developing code on GitLab,
-read the articles on:
+To get familiar with the concepts needed to develop code on GitLab, read the following articles:
-- [Mastering Code Review With GitLab](https://about.gitlab.com/2017/03/17/demo-mastering-code-review-with-gitlab/).
-- [GitLab Workflow, an Overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/#gitlab-workflow-use-case-scenario).
+- [Demo: Mastering Code Review With GitLab](https://about.gitlab.com/2017/03/17/demo-mastering-code-review-with-gitlab/).
+- [GitLab Workflow: An Overview](https://about.gitlab.com/2016/10/25/gitlab-workflow-an-overview/#gitlab-workflow-use-case-scenario).
- [Tutorial: It's all connected in GitLab](https://about.gitlab.com/2016/03/08/gitlab-tutorial-its-all-connected/): an overview on code collaboration with GitLab.
- [Trends in Version Control Land: Microservices](https://about.gitlab.com/2016/08/16/trends-in-version-control-land-microservices/).
- [Trends in Version Control Land: Innersourcing](https://about.gitlab.com/2016/07/07/trends-version-control-innersourcing/).
@@ -42,16 +36,16 @@ read the articles on:
GitLab is a Git-based platform that integrates a great number of essential tools for software development and deployment, and project management:
-- Code hosting in repositories with version control
-- Track proposals for new implementations, bug reports, and feedback with a
+- Hosting code in repositories with version control
+- Tracking proposals for new implementations, bug reports, and feedback with a
fully featured [Issue Tracker](project/issues/index.md#issue-tracker)
-- Organize and prioritize with [Issue Boards](project/issues/index.md#issue-boards)
-- Code review in [Merge Requests](project/merge_requests/index.md) with live-preview changes per
+- Organizing and prioritizing with [Issue Boards](project/issues/index.md#issue-boards)
+- Reviewing code in [Merge Requests](project/merge_requests/index.md) with live-preview changes per
branch with [Review Apps](../ci/review_apps/index.md)
-- Build, test and deploy with built-in [Continuous Integration](../ci/README.md)
-- Deploy your personal and professional static websites with [GitLab Pages](project/pages/index.md)
-- Integrate with Docker with [GitLab Container Registry](project/container_registry.md)
-- Track the development lifecycle with [GitLab Cycle Analytics](project/cycle_analytics.md)
+- Building, testing and deploying with built-in [Continuous Integration](../ci/README.md)
+- Deploying personal and professional static websites with [GitLab Pages](project/pages/index.md)
+- Integrating with Docker by using [GitLab Container Registry](project/container_registry.md)
+- Tracking the development lifecycle by usingn [GitLab Cycle Analytics](project/cycle_analytics.md)
With GitLab Enterprise Edition, you can also:
@@ -68,15 +62,15 @@ and [Multiple Issue Boards](https://docs.gitlab.com/ee/user/project/issue_board.
- [Export issues as CSV](https://docs.gitlab.com/ee/user/project/issues/csv_export.html)
- View your entire CI/CD pipeline involving more than one project with [Multiple-Project Pipeline Graphs](https://docs.gitlab.com/ee/ci/multi_project_pipeline_graphs.html)
- [Lock files](https://docs.gitlab.com/ee/user/project/file_lock.html) to prevent conflicts
-- View of the current health and status of each CI environment running on Kubernetes with [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html)
-- Leverage your continuous delivery method with [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html)
+- View the current health and status of each CI environment running on Kubernetes with [Deploy Boards](https://docs.gitlab.com/ee/user/project/deploy_boards.html)
+- Leverage continuous delivery method with [Canary Deployments](https://docs.gitlab.com/ee/user/project/canary_deployments.html)
You can also [integrate](project/integrations/project_services.md) GitLab with numerous third-party applications, such as Mattermost, Microsoft Teams, HipChat, Trello, Slack, Bamboo CI, JIRA, and a lot more.
## Projects
-In GitLab, you can create [projects](project/index.md) for numerous reasons, such as, host
-your code, use it as an issue tracker, collaborate on code, and continuously
+In GitLab, you can create [projects](project/index.md) to host
+your code, track issues, collaborate on code, and continuously
build, test, and deploy your app with built-in GitLab CI/CD. Or, you can do
it all at once, from one single project.
diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md
index eb2d731343e..363d3db8db1 100644
--- a/doc/user/profile/preferences.md
+++ b/doc/user/profile/preferences.md
@@ -87,3 +87,11 @@ You can choose between 3 options:
- Files and Readme (default)
- Readme
- Activity
+
+## Localization
+
+### First day of the week
+
+The first day of the week can be customised for calendar views and date pickers.
+
+You can choose **Sunday** or **Monday** as the first day of the week. If you select **System Default**, the system-wide default setting will be used.
diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md
index bb815695cb1..85a4af24dc5 100644
--- a/doc/user/project/clusters/index.md
+++ b/doc/user/project/clusters/index.md
@@ -172,6 +172,17 @@ functionalities needed to successfully build and deploy a containerized
application. Bear in mind that the same credentials are used for all the
applications running on the cluster.
+## Base domain
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/24580) in GitLab 11.8.
+
+Domains at the cluster level permit support for multiple domains
+per [multiple Kubernetes clusters](#multiple-kubernetes-clusters-premium). When specifying a domain,
+this will be automatically set as an environment variable (`KUBE_INGRESS_BASE_DOMAIN`) during
+the [Auto DevOps](../../../topics/autodevops/index.md) stages.
+
+The domain should have a wildcard DNS configured to the Ingress IP address.
+
## Access controls
When creating a cluster in GitLab, you will be asked if you would like to create an
@@ -254,6 +265,12 @@ install it manually.
## Installing applications
+NOTE: **Note:**
+Before starting the installation of applications, make sure that time is synchronized
+between your GitLab server and your Kubernetes cluster. Otherwise, installation could fail
+and you may get errors like `Error: remote error: tls: bad certificate`
+in the `stdout` of pods created by GitLab in your Kubernetes cluster.
+
GitLab provides a one-click install for various applications which can
be added directly to your configured cluster. Those applications are
needed for [Review Apps](../../../ci/review_apps/index.md) and
@@ -449,6 +466,7 @@ GitLab CI/CD build environment.
| `KUBE_CA_PEM_FILE` | Path to a file containing PEM data. Only present if a custom CA bundle was specified. |
| `KUBE_CA_PEM` | (**deprecated**) Raw PEM data. Only if a custom CA bundle was specified. |
| `KUBECONFIG` | Path to a file containing `kubeconfig` for this deployment. CA bundle would be embedded if specified. This config also embeds the same token defined in `KUBE_TOKEN` so you likely will only need this variable. This variable name is also automatically picked up by `kubectl` so you won't actually need to reference it explicitly if using `kubectl`. |
+| `KUBE_INGRESS_BASE_DOMAIN` | From GitLab 11.8, this variable can be used to set a domain per cluster. See [cluster domains](#base-domain) for more information. | 
NOTE: **NOTE:**
Prior to GitLab 11.5, `KUBE_TOKEN` was the Kubernetes token of the main
diff --git a/doc/user/project/merge_requests/img/squash_mr_message.png b/doc/user/project/merge_requests/img/squash_mr_message.png
new file mode 100644
index 00000000000..8734cab29aa
--- /dev/null
+++ b/doc/user/project/merge_requests/img/squash_mr_message.png
Binary files differ
diff --git a/doc/user/project/merge_requests/squash_and_merge.md b/doc/user/project/merge_requests/squash_and_merge.md
index 34cba867e2c..4ff8ec3a7e6 100644
--- a/doc/user/project/merge_requests/squash_and_merge.md
+++ b/doc/user/project/merge_requests/squash_and_merge.md
@@ -23,11 +23,14 @@ The squashed commit's commit message will be either:
- Taken from the first multi-line commit message in the merge.
- The merge request's title if no multi-line commit message is found.
-Note that the squashed commit is still followed by a merge commit,
-as the merge method for this example repository uses a merge commit.
-Squashing also works with the fast-forward merge strategy, see
-[squashing and fast-forward merge](#squash-and-fast-forward-merge) for more
-details.
+It can be customized before merging a merge request.
+
+![A squash commit message editor](img/squash_mr_message.png)
+
+NOTE: **Note:**
+The squashed commit in this example is followed by a merge commit, as the merge method for this example repository uses a merge commit.
+
+Squashing also works with the fast-forward merge strategy, see [squashing and fast-forward merge](#squash-and-fast-forward-merge) for more details.
## Use cases
@@ -60,7 +63,7 @@ This can then be overridden at the time of accepting the merge request:
The squashed commit has the following metadata:
-- Message: the message of the squash commit.
+- Message: the message of the squash commit, or a customized message.
- Author: the author of the merge request.
- Committer: the user who initiated the squash.
diff --git a/doc/user/project/pages/getting_started_part_three.md b/doc/user/project/pages/getting_started_part_three.md
index 68dd3330d7a..b2da1c85c62 100644
--- a/doc/user/project/pages/getting_started_part_three.md
+++ b/doc/user/project/pages/getting_started_part_three.md
@@ -79,11 +79,14 @@ running on your instance).
![DNS A record pointing to GitLab.com Pages server](img/dns_add_new_a_record_example_updated_2018.png)
-NOTE: **Note:**
-Note that if you use your root domain for your GitLab Pages website **only**, and if
-your domain registrar supports this feature, you can add a DNS apex `CNAME`
-record instead of an `A` record. The main advantage of doing so is that when GitLab Pages
-IP on GitLab.com changes for whatever reason, you don't need to update your `A` record.
+CAUTION: **Caution:**
+Note that if you use your root domain for your GitLab Pages website
+**only**, and if your domain registrar supports this feature, you can
+add a DNS apex `CNAME` record instead of an `A` record. The main
+advantage of doing so is that when GitLab Pages IP on GitLab.com
+changes for whatever reason, you don't need to update your `A` record.
+There may be a few exceptions, but **this method is not recommended**
+as it most likely won't work if you set an `MX` record for your root domain.
#### DNS CNAME record
@@ -114,14 +117,16 @@ co-exist, so you need to place the TXT record in a special subdomain of its own.
#### TL;DR
-If the domain has multiple uses (e.g., you host email on it as well):
+For root domains (`domain.com`), set a DNS `A` record and verify your
+domain's ownership with a TXT record:
| From | DNS Record | To |
| ---- | ---------- | -- |
| domain.com | A | 35.185.44.232 |
| domain.com | TXT | gitlab-pages-verification-code=00112233445566778899aabbccddeeff |
-If the domain is dedicated to GitLab Pages use and no other services run on it:
+For subdomains (`subdomain.domain.com`), set a DNS `CNAME` record and
+verify your domain's ownership with a TXT record:
| From | DNS Record | To |
| ---- | ---------- | -- |
diff --git a/doc/user/project/pages/getting_started_part_two.md b/doc/user/project/pages/getting_started_part_two.md
index 8dbbdd051f1..c9081a6d72b 100644
--- a/doc/user/project/pages/getting_started_part_two.md
+++ b/doc/user/project/pages/getting_started_part_two.md
@@ -71,7 +71,7 @@ created for the steps below.
To turn a **project website** forked from the Pages group into a **user/group** website, you'll need to:
- Rename it to `namespace.gitlab.io`: navigate to project's **Settings** > expand **Advanced settings** > and scroll down to **Rename repository**
-- Adjust your SSG's [base URL](#urls-and-baseurls) to from `"project-name"` to `""`. This setting will be at a different place for each SSG, as each of them have their own structure and file tree. Most likely, it will be in the SSG's config file.
+- Adjust your SSG's [base URL](#urls-and-baseurls) from `"project-name"` to `""`. This setting will be at a different place for each SSG, as each of them have their own structure and file tree. Most likely, it will be in the SSG's config file.
> **Notes:**
>
diff --git a/doc/user/project/pipelines/job_artifacts.md b/doc/user/project/pipelines/job_artifacts.md
index a8b47558c99..0d0575b1ab4 100644
--- a/doc/user/project/pipelines/job_artifacts.md
+++ b/doc/user/project/pipelines/job_artifacts.md
@@ -38,8 +38,9 @@ turn are defined with the `paths` keyword. All paths to files and directories
are relative to the repository that was cloned during the build. These uploaded
artifacts will be kept in GitLab for 1 week as defined by the `expire_in`
definition. You have the option to keep the artifacts from expiring via the
-[web interface](#browsing-job-artifacts). If you don't define an expiry date,
-the artifacts will be kept forever.
+[web interface](#browsing-job-artifacts). If the expiry time is not defined,
+it defaults to the [instance wide
+setting](../../admin_area/settings/continuous_integration.md#default-artifacts-expiration-core-only).
For more examples on artifacts, follow the [artifacts reference in
`.gitlab-ci.yml`](../../../ci/yaml/README.md#artifacts).
diff --git a/doc/workflow/repository_mirroring.md b/doc/workflow/repository_mirroring.md
index 1213474b7d8..8a2f4e1b40e 100644
--- a/doc/workflow/repository_mirroring.md
+++ b/doc/workflow/repository_mirroring.md
@@ -88,6 +88,14 @@ The mirrored repository will be listed. For example, `https://*****:*****@github
The repository will push soon. To force a push, click the appropriate button.
+## Setting up a push mirror to another GitLab instance with 2FA activated
+
+1. On the destination GitLab instance, create a [personal access token](../user/profile/personal_access_tokens.md) with `API` scope.
+1. On the source GitLab instance:
+ 1. Fill in the **Git repository URL** field using this format: `https://oauth2@<destination host>/<your_gitlab_group_or_name>/<your_gitlab_project>.git`.
+ 1. Fill in **Password** field with the GitLab personal access token created on the destination GitLab instance.
+ 1. Click the **Mirror repository** button.
+
## Pulling from a remote repository **[STARTER]**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/51) in GitLab Enterprise Edition 8.2.
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 9cbfc0e35ff..4dd1b459554 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -109,6 +109,7 @@ module API
mount ::API::Features
mount ::API::Files
mount ::API::GroupBoards
+ mount ::API::GroupLabels
mount ::API::GroupMilestones
mount ::API::Groups
mount ::API::GroupVariables
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index a1f0efa3c68..beb8ce349b4 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -1019,12 +1019,17 @@ module API
label.open_merge_requests_count(options[:current_user])
end
- expose :priority do |label, options|
- label.priority(options[:project])
+ expose :subscribed do |label, options|
+ label.subscribed?(options[:current_user], options[:parent])
end
+ end
- expose :subscribed do |label, options|
- label.subscribed?(options[:current_user], options[:project])
+ class GroupLabel < Label
+ end
+
+ class ProjectLabel < Label
+ expose :priority do |label, options|
+ label.priority(options[:parent])
end
end
diff --git a/lib/api/group_labels.rb b/lib/api/group_labels.rb
new file mode 100644
index 00000000000..0dbc5f45a68
--- /dev/null
+++ b/lib/api/group_labels.rb
@@ -0,0 +1,63 @@
+# frozen_string_literal: true
+
+module API
+ class GroupLabels < Grape::API
+ include PaginationParams
+ helpers ::API::Helpers::LabelHelpers
+
+ before { authenticate! }
+
+ params do
+ requires :id, type: String, desc: 'The ID of a group'
+ end
+ resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
+ desc 'Get all labels of the group' do
+ detail 'This feature was added in GitLab 11.8'
+ success Entities::GroupLabel
+ end
+ params do
+ use :pagination
+ end
+ get ':id/labels' do
+ get_labels(user_group, Entities::GroupLabel)
+ end
+
+ desc 'Create a new label' do
+ detail 'This feature was added in GitLab 11.8'
+ success Entities::GroupLabel
+ end
+ params do
+ use :label_create_params
+ end
+ post ':id/labels' do
+ create_label(user_group, Entities::GroupLabel)
+ end
+
+ desc 'Update an existing label. At least one optional parameter is required.' do
+ detail 'This feature was added in GitLab 11.8'
+ success Entities::GroupLabel
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the label to be updated'
+ optional :new_name, type: String, desc: 'The new name of the label'
+ optional :color, type: String, desc: "The new color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names"
+ optional :description, type: String, desc: 'The new description of label'
+ at_least_one_of :new_name, :color, :description
+ end
+ put ':id/labels' do
+ update_label(user_group, Entities::GroupLabel)
+ end
+
+ desc 'Delete an existing label' do
+ detail 'This feature was added in GitLab 11.8'
+ success Entities::GroupLabel
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the label to be deleted'
+ end
+ delete ':id/labels' do
+ delete_label(user_group)
+ end
+ end
+ end
+end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index e3d0b981065..2eb7b04711a 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -84,8 +84,8 @@ module API
page || not_found!('Wiki Page')
end
- def available_labels_for(label_parent)
- search_params = { include_ancestor_groups: true }
+ def available_labels_for(label_parent, include_ancestor_groups: true)
+ search_params = { include_ancestor_groups: include_ancestor_groups }
if label_parent.is_a?(Project)
search_params[:project_id] = label_parent.id
@@ -170,13 +170,6 @@ module API
end
end
- def find_project_label(id)
- labels = available_labels_for(user_project)
- label = labels.find_by_id(id) || labels.find_by_title(id)
-
- label || not_found!('Label')
- end
-
# rubocop: disable CodeReuse/ActiveRecord
def find_project_issue(iid)
IssuesFinder.new(current_user, project_id: user_project.id).find_by!(iid: iid)
diff --git a/lib/api/helpers/label_helpers.rb b/lib/api/helpers/label_helpers.rb
new file mode 100644
index 00000000000..c11e7d614ab
--- /dev/null
+++ b/lib/api/helpers/label_helpers.rb
@@ -0,0 +1,82 @@
+# frozen_string_literal: true
+
+module API
+ module Helpers
+ module LabelHelpers
+ extend Grape::API::Helpers
+
+ params :label_create_params do
+ requires :name, type: String, desc: 'The name of the label to be created'
+ requires :color, type: String, desc: "The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names"
+ optional :description, type: String, desc: 'The description of label to be created'
+ end
+
+ def find_label(parent, id, include_ancestor_groups: true)
+ labels = available_labels_for(parent, include_ancestor_groups: include_ancestor_groups)
+ label = labels.find_by_id(id) || labels.find_by_title(id)
+
+ label || not_found!('Label')
+ end
+
+ def get_labels(parent, entity)
+ present paginate(available_labels_for(parent)), with: entity, current_user: current_user, parent: parent
+ end
+
+ def create_label(parent, entity)
+ authorize! :admin_label, parent
+
+ label = available_labels_for(parent).find_by_title(params[:name])
+ conflict!('Label already exists') if label
+
+ priority = params.delete(:priority)
+ label_params = declared_params(include_missing: false)
+
+ label =
+ if parent.is_a?(Project)
+ ::Labels::CreateService.new(label_params).execute(project: parent)
+ else
+ ::Labels::CreateService.new(label_params).execute(group: parent)
+ end
+
+ if label.persisted?
+ if parent.is_a?(Project)
+ label.prioritize!(parent, priority) if priority
+ end
+
+ present label, with: entity, current_user: current_user, parent: parent
+ else
+ render_validation_error!(label)
+ end
+ end
+
+ def update_label(parent, entity)
+ authorize! :admin_label, parent
+
+ label = find_label(parent, params[:name], include_ancestor_groups: false)
+ update_priority = params.key?(:priority)
+ priority = params.delete(:priority)
+
+ label = ::Labels::UpdateService.new(declared_params(include_missing: false)).execute(label)
+ render_validation_error!(label) unless label.valid?
+
+ if parent.is_a?(Project) && update_priority
+ if priority.nil?
+ label.unprioritize!(parent)
+ else
+ label.prioritize!(parent, priority)
+ end
+ end
+
+ present label, with: entity, current_user: current_user, parent: parent
+ end
+
+ def delete_label(parent)
+ authorize! :admin_label, parent
+
+ label = find_label(parent, params[:name], include_ancestor_groups: false)
+
+ destroy_conditionally!(label)
+ end
+ end
+ end
+end
diff --git a/lib/api/labels.rb b/lib/api/labels.rb
index d5eb2b94669..d729d3ee625 100644
--- a/lib/api/labels.rb
+++ b/lib/api/labels.rb
@@ -3,6 +3,7 @@
module API
class Labels < Grape::API
include PaginationParams
+ helpers ::API::Helpers::LabelHelpers
before { authenticate! }
@@ -11,62 +12,28 @@ module API
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Get all labels of the project' do
- success Entities::Label
+ success Entities::ProjectLabel
end
params do
use :pagination
end
get ':id/labels' do
- present paginate(available_labels_for(user_project)), with: Entities::Label, current_user: current_user, project: user_project
+ get_labels(user_project, Entities::ProjectLabel)
end
desc 'Create a new label' do
- success Entities::Label
+ success Entities::ProjectLabel
end
params do
- requires :name, type: String, desc: 'The name of the label to be created'
- requires :color, type: String, desc: "The color of the label given in 6-digit hex notation with leading '#' sign (e.g. #FFAABB) or one of the allowed CSS color names"
- optional :description, type: String, desc: 'The description of label to be created'
+ use :label_create_params
optional :priority, type: Integer, desc: 'The priority of the label', allow_blank: true
end
- # rubocop: disable CodeReuse/ActiveRecord
post ':id/labels' do
- authorize! :admin_label, user_project
-
- label = available_labels_for(user_project).find_by(title: params[:name])
- conflict!('Label already exists') if label
-
- priority = params.delete(:priority)
- label = ::Labels::CreateService.new(declared_params(include_missing: false)).execute(project: user_project)
-
- if label.valid?
- label.prioritize!(user_project, priority) if priority
- present label, with: Entities::Label, current_user: current_user, project: user_project
- else
- render_validation_error!(label)
- end
- end
- # rubocop: enable CodeReuse/ActiveRecord
-
- desc 'Delete an existing label' do
- success Entities::Label
- end
- params do
- requires :name, type: String, desc: 'The name of the label to be deleted'
- end
- # rubocop: disable CodeReuse/ActiveRecord
- delete ':id/labels' do
- authorize! :admin_label, user_project
-
- label = user_project.labels.find_by(title: params[:name])
- not_found!('Label') unless label
-
- destroy_conditionally!(label)
+ create_label(user_project, Entities::ProjectLabel)
end
- # rubocop: enable CodeReuse/ActiveRecord
desc 'Update an existing label. At least one optional parameter is required.' do
- success Entities::Label
+ success Entities::ProjectLabel
end
params do
requires :name, type: String, desc: 'The name of the label to be updated'
@@ -76,33 +43,19 @@ module API
optional :priority, type: Integer, desc: 'The priority of the label', allow_blank: true
at_least_one_of :new_name, :color, :description, :priority
end
- # rubocop: disable CodeReuse/ActiveRecord
put ':id/labels' do
- authorize! :admin_label, user_project
-
- label = user_project.labels.find_by(title: params[:name])
- not_found!('Label not found') unless label
-
- update_priority = params.key?(:priority)
- priority = params.delete(:priority)
- label_params = declared_params(include_missing: false)
- # Rename new name to the actual label attribute name
- label_params[:name] = label_params.delete(:new_name) if label_params.key?(:new_name)
-
- label = ::Labels::UpdateService.new(label_params).execute(label)
- render_validation_error!(label) unless label.valid?
-
- if update_priority
- if priority.nil?
- label.unprioritize!(user_project)
- else
- label.prioritize!(user_project, priority)
- end
- end
+ update_label(user_project, Entities::ProjectLabel)
+ end
- present label, with: Entities::Label, current_user: current_user, project: user_project
+ desc 'Delete an existing label' do
+ success Entities::ProjectLabel
+ end
+ params do
+ requires :name, type: String, desc: 'The name of the label to be deleted'
+ end
+ delete ':id/labels' do
+ delete_label(user_project)
end
- # rubocop: enable CodeReuse/ActiveRecord
end
end
end
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 4179aaa93a0..df46b4446ff 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -343,6 +343,7 @@ module API
end
params do
optional :merge_commit_message, type: String, desc: 'Custom merge commit message'
+ optional :squash_commit_message, type: String, desc: 'Custom squash commit message'
optional :should_remove_source_branch, type: Boolean,
desc: 'When true, the source branch will be deleted if possible'
optional :merge_when_pipeline_succeeds, type: Boolean,
@@ -370,6 +371,7 @@ module API
merge_params = {
commit_message: params[:merge_commit_message],
+ squash_commit_message: params[:squash_commit_message],
should_remove_source_branch: params[:should_remove_source_branch]
}
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 95371961398..b16faffe335 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -121,6 +121,7 @@ module API
optional :terminal_max_session_time, type: Integer, desc: 'Maximum time for web terminal websocket connection (in seconds). Set to 0 for unlimited time.'
optional :usage_ping_enabled, type: Boolean, desc: 'Every week GitLab will report license usage back to GitLab, Inc.'
optional :instance_statistics_visibility_private, type: Boolean, desc: 'When set to `true` Instance statistics will only be available to admins'
+ optional :local_markdown_version, type: Integer, desc: "Local markdown version, increase this value when any cached markdown should be invalidated"
ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
optional :"#{type}_key_restriction",
diff --git a/lib/api/subscriptions.rb b/lib/api/subscriptions.rb
index 74ad3c35a61..dfb54446ddf 100644
--- a/lib/api/subscriptions.rb
+++ b/lib/api/subscriptions.rb
@@ -2,51 +2,88 @@
module API
class Subscriptions < Grape::API
+ helpers ::API::Helpers::LabelHelpers
+
before { authenticate! }
- subscribable_types = {
- 'merge_requests' => proc { |id| find_merge_request_with_access(id, :update_merge_request) },
- 'issues' => proc { |id| find_project_issue(id) },
- 'labels' => proc { |id| find_project_label(id) }
- }
+ subscribables = [
+ {
+ type: 'merge_requests',
+ entity: Entities::MergeRequest,
+ source: Project,
+ finder: ->(id) { find_merge_request_with_access(id, :update_merge_request) }
+ },
+ {
+ type: 'issues',
+ entity: Entities::Issue,
+ source: Project,
+ finder: ->(id) { find_project_issue(id) }
+ },
+ {
+ type: 'labels',
+ entity: Entities::ProjectLabel,
+ source: Project,
+ finder: ->(id) { find_label(user_project, id) }
+ },
+ {
+ type: 'labels',
+ entity: Entities::GroupLabel,
+ source: Group,
+ finder: ->(id) { find_label(user_group, id) }
+ }
+ ]
- params do
- requires :id, type: String, desc: 'The ID of a project'
- requires :subscribable_id, type: String, desc: 'The ID of a resource'
- end
- resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
- subscribable_types.each do |type, finder|
- type_singularized = type.singularize
- entity_class = Entities.const_get(type_singularized.camelcase)
+ subscribables.each do |subscribable|
+ source_type = subscribable[:source].name.underscore
+ params do
+ requires :id, type: String, desc: "The #{source_type} ID"
+ requires :subscribable_id, type: String, desc: 'The ID of a resource'
+ end
+ resource source_type.pluralize, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Subscribe to a resource' do
- success entity_class
+ success subscribable[:entity]
end
- post ":id/#{type}/:subscribable_id/subscribe" do
- resource = instance_exec(params[:subscribable_id], &finder)
+ post ":id/#{subscribable[:type]}/:subscribable_id/subscribe" do
+ parent = parent_resource(source_type)
+ resource = instance_exec(params[:subscribable_id], &subscribable[:finder])
- if resource.subscribed?(current_user, user_project)
+ if resource.subscribed?(current_user, parent)
not_modified!
else
- resource.subscribe(current_user, user_project)
- present resource, with: entity_class, current_user: current_user, project: user_project
+ resource.subscribe(current_user, parent)
+ present resource, with: subscribable[:entity], current_user: current_user, project: parent, parent: parent
end
end
desc 'Unsubscribe from a resource' do
- success entity_class
+ success subscribable[:entity]
end
- post ":id/#{type}/:subscribable_id/unsubscribe" do
- resource = instance_exec(params[:subscribable_id], &finder)
+ post ":id/#{subscribable[:type]}/:subscribable_id/unsubscribe" do
+ parent = parent_resource(source_type)
+ resource = instance_exec(params[:subscribable_id], &subscribable[:finder])
- if !resource.subscribed?(current_user, user_project)
+ if !resource.subscribed?(current_user, parent)
not_modified!
else
- resource.unsubscribe(current_user, user_project)
- present resource, with: entity_class, current_user: current_user, project: user_project
+ resource.unsubscribe(current_user, parent)
+ present resource, with: subscribable[:entity], current_user: current_user, project: parent, parent: parent
end
end
end
end
+
+ private
+
+ helpers do
+ def parent_resource(source_type)
+ case source_type
+ when 'project'
+ user_project
+ else
+ nil
+ end
+ end
+ end
end
end
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index 184c7418e75..22ed1d8e7b4 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -93,7 +93,7 @@ module Backup
progress.puts "Error: #{e}".color(:red)
end
else
- restore_repo_success = gitlab_shell.create_repository(project.repository_storage, project.disk_path)
+ restore_repo_success = gitlab_shell.create_project_repository(project)
end
if restore_repo_success
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index 7aa02009aa0..b2ef04d23d7 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -12,6 +12,9 @@ module Gitlab
# Scopes used for OpenID Connect
OPENID_SCOPES = [:openid].freeze
+ # OpenID Connect profile scopes
+ PROFILE_SCOPES = [:profile, :email].freeze
+
# Default scopes for OAuth applications that don't define their own
DEFAULT_SCOPES = [:api].freeze
@@ -284,7 +287,7 @@ module Gitlab
# Other available scopes
def optional_scopes
- available_scopes + OPENID_SCOPES - DEFAULT_SCOPES
+ available_scopes + OPENID_SCOPES + PROFILE_SCOPES - DEFAULT_SCOPES
end
def registry_scopes
diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb
index eaead41a720..75a3f17f549 100644
--- a/lib/gitlab/bitbucket_import/importer.rb
+++ b/lib/gitlab/bitbucket_import/importer.rb
@@ -65,9 +65,9 @@ module Gitlab
def import_wiki
return if project.wiki.repository_exists?
- disk_path = project.wiki.disk_path
- import_url = project.import_url.sub(/\.git\z/, ".git/wiki")
- gitlab_shell.import_repository(project.repository_storage, disk_path, import_url)
+ wiki = WikiFormatter.new(project)
+
+ gitlab_shell.import_wiki_repository(project, wiki)
rescue StandardError => e
errors << { type: :wiki, errors: e.message }
end
diff --git a/lib/gitlab/bitbucket_import/wiki_formatter.rb b/lib/gitlab/bitbucket_import/wiki_formatter.rb
new file mode 100644
index 00000000000..b8ff43b777b
--- /dev/null
+++ b/lib/gitlab/bitbucket_import/wiki_formatter.rb
@@ -0,0 +1,25 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module BitbucketImport
+ class WikiFormatter
+ attr_reader :project
+
+ def initialize(project)
+ @project = project
+ end
+
+ def disk_path
+ project.wiki.disk_path
+ end
+
+ def full_path
+ project.wiki.full_path
+ end
+
+ def import_url
+ project.import_url.sub(/\.git\z/, ".git/wiki")
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml
new file mode 100644
index 00000000000..9c534b2b8e7
--- /dev/null
+++ b/lib/gitlab/ci/templates/Android-Fastlane.gitlab-ci.yml
@@ -0,0 +1,121 @@
+# Read more about how to use this script on this blog post https://about.gitlab.com/2019/01/28/android-publishing-with-gitlab-and-fastlane/
+# You will also need to configure your build.gradle, Dockerfile, and fastlane configuration to make this work.
+# If you are looking for a simpler template that does not publish, see the Android template.
+
+stages:
+ - environment
+ - build
+ - test
+ - internal
+ - alpha
+ - beta
+ - production
+
+
+.updateContainerJob:
+ image: docker:stable
+ stage: environment
+ services:
+ - docker:dind
+ script:
+ - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
+ - docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG || true
+ - docker build --cache-from $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG -t $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG .
+ - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
+
+updateContainer:
+ extends: .updateContainerJob
+ only:
+ changes:
+ - Dockerfile
+
+ensureContainer:
+ extends: .updateContainerJob
+ allow_failure: true
+ before_script:
+ - "mkdir -p ~/.docker && echo '{\"experimental\": \"enabled\"}' > ~/.docker/config.json"
+ - docker login -u gitlab-ci-token -p $CI_JOB_TOKEN $CI_REGISTRY
+ # Skip update container `script` if the container already exists
+ # via https://gitlab.com/gitlab-org/gitlab-ce/issues/26866#note_97609397 -> https://stackoverflow.com/a/52077071/796832
+ - docker manifest inspect $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG > /dev/null && exit || true
+
+
+.build_job:
+ image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
+ stage: build
+ before_script:
+ # We store this binary file in a variable as hex with this command: `xxd -p android-app.jks`
+ # Then we convert the hex back to a binary file
+ - echo "$signing_jks_file_hex" | xxd -r -p - > android-signing-keystore.jks
+ - "export VERSION_CODE=$CI_PIPELINE_IID && echo $VERSION_CODE"
+ - "export VERSION_SHA=`echo ${CI_COMMIT_SHA:0:8}` && echo $VERSION_SHA"
+ after_script:
+ - rm -f android-signing-keystore.jks || true
+ artifacts:
+ paths:
+ - app/build/outputs
+
+buildDebug:
+ extends: .build_job
+ script:
+ - bundle exec fastlane buildDebug
+
+buildRelease:
+ extends: .build_job
+ script:
+ - bundle exec fastlane buildRelease
+ environment:
+ name: production
+
+testDebug:
+ image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
+ stage: test
+ dependencies:
+ - buildDebug
+ script:
+ - bundle exec fastlane test
+
+publishInternal:
+ image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
+ stage: internal
+ dependencies:
+ - buildRelease
+ when: manual
+ before_script:
+ - echo $google_play_service_account_api_key_json > ~/google_play_api_key.json
+ after_script:
+ - rm ~/google_play_api_key.json
+ script:
+ - bundle exec fastlane internal
+
+.promote_job:
+ image: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
+ when: manual
+ dependencies: []
+ before_script:
+ - echo $google_play_service_account_api_key_json > ~/google_play_api_key.json
+ after_script:
+ - rm ~/google_play_api_key.json
+
+promoteAlpha:
+ extends: .promote_job
+ stage: alpha
+ script:
+ - bundle exec fastlane promote_internal_to_alpha
+
+promoteBeta:
+ extends: .promote_job
+ stage: beta
+ script:
+ - bundle exec fastlane promote_alpha_to_beta
+
+promoteProduction:
+ extends: .promote_job
+ stage: production
+ # We only allow production promotion on `master` because
+ # it has its own production scoped secret variables
+ only:
+ - master
+ script:
+ - bundle exec fastlane promote_beta_to_production
+ \ No newline at end of file
diff --git a/lib/gitlab/ci/templates/Android.gitlab-ci.yml b/lib/gitlab/ci/templates/Android.gitlab-ci.yml
index 6e138639b71..c169e3f7686 100644
--- a/lib/gitlab/ci/templates/Android.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Android.gitlab-ci.yml
@@ -1,4 +1,6 @@
# Read more about this script on this blog post https://about.gitlab.com/2018/10/24/setting-up-gitlab-ci-for-android-projects/, by Jason Lenny
+# If you are interested in using Android with FastLane for publishing take a look at the Android-Fastlane template.
+
image: openjdk:8-jdk
variables:
diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
index 75a5bf142d2..e369d26f22f 100644
--- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml
@@ -21,8 +21,8 @@
#
# In order to deploy, you must have a Kubernetes cluster configured either
# via a project integration, or via group/project variables.
-# AUTO_DEVOPS_DOMAIN must also be set as a variable at the group or project
-# level, or manually added below.
+# KUBE_INGRESS_BASE_DOMAIN must also be set on the cluster settings,
+# as a variable at the group or project level, or manually added below.
#
# Continuous deployment to production is enabled by default.
# If you want to deploy to staging first, set STAGING_ENABLED environment variable.
@@ -41,8 +41,8 @@
image: alpine:latest
variables:
- # AUTO_DEVOPS_DOMAIN is the application deployment domain and should be set as a variable at the group or project level.
- # AUTO_DEVOPS_DOMAIN: domain.example.com
+ # KUBE_INGRESS_BASE_DOMAIN is the application deployment domain and should be set as a variable at the group or project level.
+ # KUBE_INGRESS_BASE_DOMAIN: domain.example.com
POSTGRES_USER: user
POSTGRES_PASSWORD: testing-password
@@ -251,7 +251,7 @@ review:
- persist_environment_url
environment:
name: review/$CI_COMMIT_REF_NAME
- url: http://$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_SLUG.$AUTO_DEVOPS_DOMAIN
+ url: http://$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_SLUG.$KUBE_INGRESS_BASE_DOMAIN
on_stop: stop_review
artifacts:
paths: [environment_url.txt]
@@ -306,7 +306,7 @@ staging:
- deploy
environment:
name: staging
- url: http://$CI_PROJECT_PATH_SLUG-staging.$AUTO_DEVOPS_DOMAIN
+ url: http://$CI_PROJECT_PATH_SLUG-staging.$KUBE_INGRESS_BASE_DOMAIN
only:
refs:
- master
@@ -330,7 +330,7 @@ canary:
- deploy canary
environment:
name: production
- url: http://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN
+ url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
when: manual
only:
refs:
@@ -354,7 +354,7 @@ canary:
- persist_environment_url
environment:
name: production
- url: http://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN
+ url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
artifacts:
paths: [environment_url.txt]
@@ -403,7 +403,7 @@ production_manual:
- persist_environment_url
environment:
name: production
- url: http://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN
+ url: http://$CI_PROJECT_PATH_SLUG.$KUBE_INGRESS_BASE_DOMAIN
artifacts:
paths: [environment_url.txt]
@@ -689,7 +689,7 @@ rollout 100%:
--set application.database_url="$DATABASE_URL" \
--set application.secretName="$APPLICATION_SECRET_NAME" \
--set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \
- --set service.commonName="le.$AUTO_DEVOPS_DOMAIN" \
+ --set service.commonName="le.$KUBE_INGRESS_BASE_DOMAIN" \
--set service.url="$CI_ENVIRONMENT_URL" \
--set service.additionalHosts="$additional_hosts" \
--set replicaCount="$replicas" \
@@ -725,7 +725,7 @@ rollout 100%:
--set application.database_url="$DATABASE_URL" \
--set application.secretName="$APPLICATION_SECRET_NAME" \
--set application.secretChecksum="$APPLICATION_SECRET_CHECKSUM" \
- --set service.commonName="le.$AUTO_DEVOPS_DOMAIN" \
+ --set service.commonName="le.$KUBE_INGRESS_BASE_DOMAIN" \
--set service.url="$CI_ENVIRONMENT_URL" \
--set service.additionalHosts="$additional_hosts" \
--set replicaCount="$replicas" \
@@ -823,11 +823,24 @@ rollout 100%:
kubectl describe namespace "$KUBE_NAMESPACE" || kubectl create namespace "$KUBE_NAMESPACE"
}
+
+ # Function to ensure backwards compatibility with AUTO_DEVOPS_DOMAIN
+ function ensure_kube_ingress_base_domain() {
+ if [ -z ${KUBE_INGRESS_BASE_DOMAIN+x} ]; then
+ export KUBE_INGRESS_BASE_DOMAIN=$AUTO_DEVOPS_DOMAIN
+ fi
+ }
+
function check_kube_domain() {
- if [ -z ${AUTO_DEVOPS_DOMAIN+x} ]; then
- echo "In order to deploy or use Review Apps, AUTO_DEVOPS_DOMAIN variable must be set"
- echo "You can do it in Auto DevOps project settings or defining a variable at group or project level"
+ ensure_kube_ingress_base_domain
+
+ if [ -z ${KUBE_INGRESS_BASE_DOMAIN+x} ]; then
+ echo "In order to deploy or use Review Apps,"
+ echo "AUTO_DEVOPS_DOMAIN or KUBE_INGRESS_BASE_DOMAIN variables must be set"
+ echo "From 11.8, you can set KUBE_INGRESS_BASE_DOMAIN in cluster settings"
+ echo "or by defining a variable at group or project level."
echo "You can also manually add it in .gitlab-ci.yml"
+ echo "AUTO_DEVOPS_DOMAIN support will be dropped on 12.0"
false
else
true
diff --git a/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml b/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml
index fc3d4ecdbba..25a32ba0f74 100644
--- a/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml
+++ b/lib/gitlab/ci/templates/dotNET.gitlab-ci.yml
@@ -57,6 +57,7 @@ test_job:
script:
- '& "$env:NUNIT_PATH" ".\$env:TEST_FOLDER\Tests.dll"' # running NUnit tests
artifacts:
+ when: always # save test results even when the task fails
expire_in: 1 week # save gitlab server space, we copy the files we need to deploy folder later on
paths:
- '.\TestResult.xml' # saving NUnit results to copy to deploy folder
diff --git a/lib/gitlab/git/object_pool.rb b/lib/gitlab/git/object_pool.rb
index 1c6242b444a..e93ca3e11f8 100644
--- a/lib/gitlab/git/object_pool.rb
+++ b/lib/gitlab/git/object_pool.rb
@@ -10,12 +10,13 @@ module Gitlab
delegate :exists?, :size, to: :repository
delegate :unlink_repository, :delete, to: :object_pool_service
- attr_reader :storage, :relative_path, :source_repository
+ attr_reader :storage, :relative_path, :source_repository, :gl_project_path
- def initialize(storage, relative_path, source_repository)
+ def initialize(storage, relative_path, source_repository, gl_project_path)
@storage = storage
@relative_path = relative_path
@source_repository = source_repository
+ @gl_project_path = gl_project_path
end
def create
@@ -31,12 +32,12 @@ module Gitlab
end
def to_gitaly_repository
- Gitlab::GitalyClient::Util.repository(storage, relative_path, GL_REPOSITORY)
+ Gitlab::GitalyClient::Util.repository(storage, relative_path, GL_REPOSITORY, gl_project_path)
end
# Allows for reusing other RPCs by 'tricking' Gitaly to think its a repository
def repository
- @repository ||= Gitlab::Git::Repository.new(storage, relative_path, GL_REPOSITORY)
+ @repository ||= Gitlab::Git::Repository.new(storage, relative_path, GL_REPOSITORY, gl_project_path)
end
private
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index 786c90f9272..54bbd531398 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -67,7 +67,7 @@ module Gitlab
# Relative path of repo
attr_reader :relative_path
- attr_reader :storage, :gl_repository, :relative_path
+ attr_reader :storage, :gl_repository, :relative_path, :gl_project_path
# This remote name has to be stable for all types of repositories that
# can join an object pool. If it's structure ever changes, a migration
@@ -78,10 +78,11 @@ module Gitlab
# This initializer method is only used on the client side (gitlab-ce).
# Gitaly-ruby uses a different initializer.
- def initialize(storage, relative_path, gl_repository)
+ def initialize(storage, relative_path, gl_repository, gl_project_path)
@storage = storage
@relative_path = relative_path
@gl_repository = gl_repository
+ @gl_project_path = gl_project_path
@name = @relative_path.split("/").last
end
@@ -872,7 +873,7 @@ module Gitlab
end
def gitaly_repository
- Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository)
+ Gitlab::GitalyClient::Util.repository(@storage, @relative_path, @gl_repository, @gl_project_path)
end
def gitaly_ref_client
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index 8a1abfbf874..a7e20d9429e 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -324,13 +324,40 @@ module Gitlab
GitalyClient.call(@storage, :repository_service, :search_files_by_name, request, timeout: GitalyClient.fast_timeout).flat_map(&:files)
end
- def search_files_by_content(ref, query)
- request = Gitaly::SearchFilesByContentRequest.new(repository: @gitaly_repo, ref: ref, query: query)
- GitalyClient.call(@storage, :repository_service, :search_files_by_content, request).flat_map(&:matches)
+ def search_files_by_content(ref, query, chunked_response: true)
+ request = Gitaly::SearchFilesByContentRequest.new(repository: @gitaly_repo, ref: ref, query: query, chunked_response: chunked_response)
+ response = GitalyClient.call(@storage, :repository_service, :search_files_by_content, request)
+
+ search_results_from_response(response)
end
private
+ def search_results_from_response(gitaly_response)
+ matches = []
+ current_match = +""
+
+ gitaly_response.each do |message|
+ next if message.nil?
+
+ # Old client will ignore :chunked_response flag
+ # and return messages with `matches` key.
+ # This code path will be removed post 12.0 release
+ if message.matches.any?
+ matches += message.matches
+ else
+ current_match << message.match_data
+
+ if message.end_of_match
+ matches << current_match
+ current_match = +""
+ end
+ end
+ end
+
+ matches
+ end
+
def gitaly_fetch_stream_to_file(save_path, rpc_name, request_class, timeout)
request = request_class.new(repository: @gitaly_repo)
response = GitalyClient.call(
diff --git a/lib/gitlab/gitaly_client/util.rb b/lib/gitlab/gitaly_client/util.rb
index dce5d6a8ad0..899921f76e4 100644
--- a/lib/gitlab/gitaly_client/util.rb
+++ b/lib/gitlab/gitaly_client/util.rb
@@ -4,7 +4,7 @@ module Gitlab
module GitalyClient
module Util
class << self
- def repository(repository_storage, relative_path, gl_repository)
+ def repository(repository_storage, relative_path, gl_repository, gl_project_path)
git_env = Gitlab::Git::HookEnv.all(gl_repository)
git_object_directory = git_env['GIT_OBJECT_DIRECTORY_RELATIVE'].presence
git_alternate_object_directories = Array.wrap(git_env['GIT_ALTERNATE_OBJECT_DIRECTORIES_RELATIVE'])
@@ -14,14 +14,16 @@ module Gitlab
relative_path: relative_path,
gl_repository: gl_repository.to_s,
git_object_directory: git_object_directory.to_s,
- git_alternate_object_directories: git_alternate_object_directories
+ git_alternate_object_directories: git_alternate_object_directories,
+ gl_project_path: gl_project_path
)
end
def git_repository(gitaly_repository)
Gitlab::Git::Repository.new(gitaly_repository.storage_name,
gitaly_repository.relative_path,
- gitaly_repository.gl_repository)
+ gitaly_repository.gl_repository,
+ gitaly_repository.gl_project_path)
end
end
end
diff --git a/lib/gitlab/github_import/importer/repository_importer.rb b/lib/gitlab/github_import/importer/repository_importer.rb
index bc3ea9e9226..e2dfb00dcc5 100644
--- a/lib/gitlab/github_import/importer/repository_importer.rb
+++ b/lib/gitlab/github_import/importer/repository_importer.rb
@@ -6,11 +6,12 @@ module Gitlab
class RepositoryImporter
include Gitlab::ShellAdapter
- attr_reader :project, :client
+ attr_reader :project, :client, :wiki_formatter
def initialize(project, client)
@project = project
@client = client
+ @wiki_formatter = ::Gitlab::LegacyGithubImport::WikiFormatter.new(project)
end
# Returns true if we should import the wiki for the project.
@@ -57,9 +58,7 @@ module Gitlab
end
def import_wiki_repository
- wiki_path = "#{project.disk_path}.wiki"
-
- gitlab_shell.import_repository(project.repository_storage, wiki_path, wiki_url)
+ gitlab_shell.import_wiki_repository(project, wiki_formatter)
true
rescue Gitlab::Shell::Error => e
@@ -72,7 +71,7 @@ module Gitlab
end
def wiki_url
- project.import_url.sub(/\.git\z/, '.wiki.git')
+ wiki_formatter.import_url
end
def update_clone_time
diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb
index 9b1794eec91..3235d3ccc4e 100644
--- a/lib/gitlab/gon_helper.rb
+++ b/lib/gitlab/gon_helper.rb
@@ -24,6 +24,7 @@ module Gitlab
gon.emoji_sprites_css_path = ActionController::Base.helpers.stylesheet_path('emoji_sprites')
gon.test_env = Rails.env.test?
gon.suggested_label_colors = LabelsHelper.suggested_colors
+ gon.first_day_of_week = current_user&.first_day_of_week || Gitlab::CurrentSettings.first_day_of_week
if current_user
gon.current_user_id = current_user.id
diff --git a/lib/gitlab/kubernetes/helm/api.rb b/lib/gitlab/kubernetes/helm/api.rb
index b9903e37f40..7dfd9ed4f35 100644
--- a/lib/gitlab/kubernetes/helm/api.rb
+++ b/lib/gitlab/kubernetes/helm/api.rb
@@ -20,14 +20,7 @@ module Gitlab
kubeclient.create_pod(command.pod_resource)
end
- def update(command)
- namespace.ensure_exists!
-
- update_config_map(command)
-
- delete_pod!(command.pod_name)
- kubeclient.create_pod(command.pod_resource)
- end
+ alias_method :update, :install
##
# Returns Pod phase
@@ -62,6 +55,8 @@ module Gitlab
def create_config_map(command)
command.config_map_resource.tap do |config_map_resource|
+ break unless config_map_resource
+
if config_map_exists?(config_map_resource)
kubeclient.update_config_map(config_map_resource)
else
diff --git a/lib/gitlab/kubernetes/helm/install_command.rb b/lib/gitlab/kubernetes/helm/install_command.rb
index a1ab5e048ac..f931248b747 100644
--- a/lib/gitlab/kubernetes/helm/install_command.rb
+++ b/lib/gitlab/kubernetes/helm/install_command.rb
@@ -42,8 +42,17 @@ module Gitlab
'helm repo update' if repository
end
+ # Uses `helm upgrade --install` which means we can use this for both
+ # installation and uprade of applications
def install_command
- command = ['helm', 'install', chart] + install_command_flags
+ command = ['helm', 'upgrade', name, chart] +
+ install_flag +
+ reset_values_flag +
+ optional_tls_flags +
+ optional_version_flag +
+ rbac_create_flag +
+ namespace_flag +
+ value_flag
command.shelljoin
end
@@ -56,17 +65,20 @@ module Gitlab
postinstall.join("\n") if postinstall
end
- def install_command_flags
- name_flag = ['--name', name]
- namespace_flag = ['--namespace', Gitlab::Kubernetes::Helm::NAMESPACE]
- value_flag = ['-f', "/data/helm/#{name}/config/values.yaml"]
+ def install_flag
+ ['--install']
+ end
- name_flag +
- optional_tls_flags +
- optional_version_flag +
- rbac_create_flag +
- namespace_flag +
- value_flag
+ def reset_values_flag
+ ['--reset-values']
+ end
+
+ def value_flag
+ ['-f', "/data/helm/#{name}/config/values.yaml"]
+ end
+
+ def namespace_flag
+ ['--namespace', Gitlab::Kubernetes::Helm::NAMESPACE]
end
def rbac_create_flag
diff --git a/lib/gitlab/kubernetes/helm/upgrade_command.rb b/lib/gitlab/kubernetes/helm/upgrade_command.rb
deleted file mode 100644
index 9daffc138b5..00000000000
--- a/lib/gitlab/kubernetes/helm/upgrade_command.rb
+++ /dev/null
@@ -1,65 +0,0 @@
-# frozen_string_literal: true
-
-module Gitlab
- module Kubernetes
- module Helm
- class UpgradeCommand
- include BaseCommand
- include ClientCommand
-
- attr_reader :name, :chart, :version, :repository, :files
-
- def initialize(name, chart:, files:, rbac:, version: nil, repository: nil)
- @name = name
- @chart = chart
- @rbac = rbac
- @version = version
- @files = files
- @repository = repository
- end
-
- def generate_script
- super + [
- init_command,
- wait_for_tiller_command,
- repository_command,
- script_command
- ].compact.join("\n")
- end
-
- def rbac?
- @rbac
- end
-
- def pod_name
- "upgrade-#{name}"
- end
-
- private
-
- def script_command
- upgrade_flags = "#{optional_version_flag}#{optional_tls_flags}" \
- " --reset-values" \
- " --install" \
- " --namespace #{::Gitlab::Kubernetes::Helm::NAMESPACE}" \
- " -f /data/helm/#{name}/config/values.yaml"
-
- "helm upgrade #{name} #{chart}#{upgrade_flags}"
- end
-
- def optional_version_flag
- " --version #{version}" if version
- end
-
- def optional_tls_flags
- return unless files.key?(:'ca.pem')
-
- " --tls" \
- " --tls-ca-cert #{files_dir}/ca.pem" \
- " --tls-cert #{files_dir}/cert.pem" \
- " --tls-key #{files_dir}/key.pem"
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/legacy_github_import/importer.rb b/lib/gitlab/legacy_github_import/importer.rb
index c526d31a591..f3323c98af2 100644
--- a/lib/gitlab/legacy_github_import/importer.rb
+++ b/lib/gitlab/legacy_github_import/importer.rb
@@ -267,7 +267,7 @@ module Gitlab
def import_wiki
unless project.wiki.repository_exists?
wiki = WikiFormatter.new(project)
- gitlab_shell.import_repository(project.repository_storage, wiki.disk_path, wiki.import_url)
+ gitlab_shell.import_wiki_repository(project, wiki)
end
rescue Gitlab::Shell::Error => e
# GitHub error message when the wiki repo has not been created,
diff --git a/lib/gitlab/legacy_github_import/wiki_formatter.rb b/lib/gitlab/legacy_github_import/wiki_formatter.rb
index ea52be5ee0f..cf1e21ad1e1 100644
--- a/lib/gitlab/legacy_github_import/wiki_formatter.rb
+++ b/lib/gitlab/legacy_github_import/wiki_formatter.rb
@@ -13,6 +13,10 @@ module Gitlab
project.wiki.disk_path
end
+ def full_path
+ project.wiki.full_path
+ end
+
def import_url
project.import_url.sub(/\.git\z/, ".wiki.git")
end
diff --git a/lib/gitlab/metrics/samplers/unicorn_sampler.rb b/lib/gitlab/metrics/samplers/unicorn_sampler.rb
index 4c4ec026823..4c5b849cc51 100644
--- a/lib/gitlab/metrics/samplers/unicorn_sampler.rb
+++ b/lib/gitlab/metrics/samplers/unicorn_sampler.rb
@@ -23,13 +23,13 @@ module Gitlab
def sample
Raindrops::Linux.tcp_listener_stats(tcp_listeners).each do |addr, stats|
- unicorn_active_connections.set({ type: 'tcp', address: addr }, stats.active)
- unicorn_queued_connections.set({ type: 'tcp', address: addr }, stats.queued)
+ unicorn_active_connections.set({ socket_type: 'tcp', socket_address: addr }, stats.active)
+ unicorn_queued_connections.set({ socket_type: 'tcp', socket_address: addr }, stats.queued)
end
Raindrops::Linux.unix_listener_stats(unix_listeners).each do |addr, stats|
- unicorn_active_connections.set({ type: 'unix', address: addr }, stats.active)
- unicorn_queued_connections.set({ type: 'unix', address: addr }, stats.queued)
+ unicorn_active_connections.set({ socket_type: 'unix', socket_address: addr }, stats.active)
+ unicorn_queued_connections.set({ socket_type: 'unix', socket_address: addr }, stats.queued)
end
end
diff --git a/lib/gitlab/project_template.rb b/lib/gitlab/project_template.rb
index 3bfd6ee892c..ef656e5b2ce 100644
--- a/lib/gitlab/project_template.rb
+++ b/lib/gitlab/project_template.rb
@@ -2,14 +2,12 @@
module Gitlab
class ProjectTemplate
- attr_reader :title, :name, :description, :preview
+ attr_reader :title, :name, :description, :preview, :logo
- def initialize(name, title, description, preview)
- @name, @title, @description, @preview = name, title, description, preview
+ def initialize(name, title, description, preview, logo = 'illustrations/gitlab_logo.svg')
+ @name, @title, @description, @preview, @logo = name, title, description, preview, logo
end
- alias_method :logo, :name
-
def file
archive_path.open
end
@@ -27,9 +25,14 @@ module Gitlab
end
TEMPLATES_TABLE = [
- ProjectTemplate.new('rails', 'Ruby on Rails', 'Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/rails'),
- ProjectTemplate.new('spring', 'Spring', 'Includes an MVC structure, mvnw and pom.xml to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/spring'),
- ProjectTemplate.new('express', 'NodeJS Express', 'Includes an MVC structure to help you get started.', 'https://gitlab.com/gitlab-org/project-templates/express')
+ ProjectTemplate.new('rails', 'Ruby on Rails', _('Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/rails', 'illustrations/logos/rails.svg'),
+ ProjectTemplate.new('spring', 'Spring', _('Includes an MVC structure, mvnw and pom.xml to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/spring', 'illustrations/logos/spring.svg'),
+ ProjectTemplate.new('express', 'NodeJS Express', _('Includes an MVC structure to help you get started.'), 'https://gitlab.com/gitlab-org/project-templates/express', 'illustrations/logos/express.svg'),
+ ProjectTemplate.new('hugo', 'Pages/Hugo', _('Everything you need to create a GitLab Pages site using Hugo.'), 'https://gitlab.com/pages/hugo'),
+ ProjectTemplate.new('jekyll', 'Pages/Jekyll', _('Everything you need to create a GitLab Pages site using Jekyll.'), 'https://gitlab.com/pages/jekyll'),
+ ProjectTemplate.new('plainhtml', 'Pages/Plain HTML', _('Everything you need to create a GitLab Pages site using plain HTML.'), 'https://gitlab.com/pages/plain-html'),
+ ProjectTemplate.new('gitbook', 'Pages/GitBook', _('Everything you need to create a GitLab Pages site using GitBook.'), 'https://gitlab.com/pages/gitbook'),
+ ProjectTemplate.new('hexo', 'Pages/Hexo', _('Everything you need to create a GitLab Pages site using Hexo.'), 'https://gitlab.com/pages/hexo')
].freeze
class << self
diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb
index bdf21cf3134..1153e69d3de 100644
--- a/lib/gitlab/shell.rb
+++ b/lib/gitlab/shell.rb
@@ -64,27 +64,48 @@ module Gitlab
end
end
+ # Convenience methods for initializing a new repository with a Project model.
+ def create_project_repository(project)
+ create_repository(project.repository_storage, project.disk_path, project.full_path)
+ end
+
+ def create_wiki_repository(project)
+ create_repository(project.repository_storage, project.wiki.disk_path, project.wiki.full_path)
+ end
+
# Init new repository
#
# storage - the shard key
- # name - project disk path
+ # disk_path - project disk path
+ # gl_project_path - project name
#
# Ex.
- # create_repository("default", "gitlab/gitlab-ci")
+ # create_repository("default", "path/to/gitlab-ci", "gitlab/gitlab-ci")
#
- def create_repository(storage, name)
- relative_path = name.dup
+ def create_repository(storage, disk_path, gl_project_path)
+ relative_path = disk_path.dup
relative_path << '.git' unless relative_path.end_with?('.git')
- repository = Gitlab::Git::Repository.new(storage, relative_path, '')
+ # During creation of a repository, gl_repository may not be known
+ # because that depends on a yet-to-be assigned project ID in the
+ # database (e.g. project-1234), so for now it is blank.
+ repository = Gitlab::Git::Repository.new(storage, relative_path, '', gl_project_path)
wrapped_gitaly_errors { repository.gitaly_repository_client.create_repository }
true
rescue => err # Once the Rugged codes gets removes this can be improved
- Rails.logger.error("Failed to add repository #{storage}/#{name}: #{err}")
+ Rails.logger.error("Failed to add repository #{storage}/#{disk_path}: #{err}")
false
end
+ def import_wiki_repository(project, wiki_formatter)
+ import_repository(project.repository_storage, wiki_formatter.disk_path, wiki_formatter.import_url, project.wiki.full_path)
+ end
+
+ def import_project_repository(project)
+ import_repository(project.repository_storage, project.disk_path, project.import_url, project.full_path)
+ end
+
# Import repository
#
# storage - project's storage name
@@ -94,13 +115,13 @@ module Gitlab
# Ex.
# import_repository("nfs-file06", "gitlab/gitlab-ci", "https://gitlab.com/gitlab-org/gitlab-test.git")
#
- def import_repository(storage, name, url)
+ def import_repository(storage, name, url, gl_project_path)
if url.start_with?('.', '/')
raise Error.new("don't use disk paths with import_repository: #{url.inspect}")
end
relative_path = "#{name}.git"
- cmd = GitalyGitlabProjects.new(storage, relative_path)
+ cmd = GitalyGitlabProjects.new(storage, relative_path, gl_project_path)
success = cmd.import_project(url, git_timeout)
raise Error, cmd.output unless success
@@ -125,18 +146,13 @@ module Gitlab
end
# Fork repository to new path
- # forked_from_storage - forked-from project's storage name
- # forked_from_disk_path - project disk relative path
- # forked_to_storage - forked-to project's storage name
- # forked_to_disk_path - forked project disk relative path
- #
- # Ex.
- # fork_repository("nfs-file06", "gitlab/gitlab-ci", "nfs-file07", "new-namespace/gitlab-ci")
- def fork_repository(forked_from_storage, forked_from_disk_path, forked_to_storage, forked_to_disk_path)
- forked_from_relative_path = "#{forked_from_disk_path}.git"
- fork_args = [forked_to_storage, "#{forked_to_disk_path}.git"]
+ # source_project - forked-from Project
+ # target_project - forked-to Project
+ def fork_repository(source_project, target_project)
+ forked_from_relative_path = "#{source_project.disk_path}.git"
+ fork_args = [target_project.repository_storage, "#{target_project.disk_path}.git", target_project.full_path]
- GitalyGitlabProjects.new(forked_from_storage, forked_from_relative_path).fork_repository(*fork_args)
+ GitalyGitlabProjects.new(source_project.repository_storage, forked_from_relative_path, source_project.full_path).fork_repository(*fork_args)
end
# Removes a repository from file system, using rm_diretory which is an alias
@@ -397,16 +413,17 @@ module Gitlab
end
class GitalyGitlabProjects
- attr_reader :shard_name, :repository_relative_path, :output
+ attr_reader :shard_name, :repository_relative_path, :output, :gl_project_path
- def initialize(shard_name, repository_relative_path)
+ def initialize(shard_name, repository_relative_path, gl_project_path)
@shard_name = shard_name
@repository_relative_path = repository_relative_path
@output = ''
+ @gl_project_path = gl_project_path
end
def import_project(source, _timeout)
- raw_repository = Gitlab::Git::Repository.new(shard_name, repository_relative_path, nil)
+ raw_repository = Gitlab::Git::Repository.new(shard_name, repository_relative_path, nil, gl_project_path)
Gitlab::GitalyClient::RepositoryService.new(raw_repository).import_repository(source)
true
@@ -415,9 +432,9 @@ module Gitlab
false
end
- def fork_repository(new_shard_name, new_repository_relative_path)
- target_repository = Gitlab::Git::Repository.new(new_shard_name, new_repository_relative_path, nil)
- raw_repository = Gitlab::Git::Repository.new(shard_name, repository_relative_path, nil)
+ def fork_repository(new_shard_name, new_repository_relative_path, new_project_name)
+ target_repository = Gitlab::Git::Repository.new(new_shard_name, new_repository_relative_path, nil, new_project_name)
+ raw_repository = Gitlab::Git::Repository.new(shard_name, repository_relative_path, nil, gl_project_path)
Gitlab::GitalyClient::RepositoryService.new(target_repository).fork_repository(raw_repository)
rescue GRPC::BadStatus => e
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9ec590f90d8..a3f78968a55 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -22,16 +22,6 @@ msgstr ""
msgid " or "
msgstr ""
-msgid "%d addition"
-msgid_plural "%d additions"
-msgstr[0] ""
-msgstr[1] ""
-
-msgid "%d changed file"
-msgid_plural "%d changed files"
-msgstr[0] ""
-msgstr[1] ""
-
msgid "%d commit"
msgid_plural "%d commits"
msgstr[0] ""
@@ -42,10 +32,8 @@ msgid_plural "%d commits behind"
msgstr[0] ""
msgstr[1] ""
-msgid "%d deleted"
-msgid_plural "%d deletions"
-msgstr[0] ""
-msgstr[1] ""
+msgid "%d commits"
+msgstr ""
msgid "%d exporter"
msgid_plural "%d exporters"
@@ -138,9 +126,6 @@ msgstr ""
msgid "%{lock_path} is locked by GitLab User %{lock_user_id}"
msgstr ""
-msgid "%{nip_domain} can be used as an alternative to a custom domain."
-msgstr ""
-
msgid "%{number_commits_behind} commits behind %{default_branch}, %{number_commits_ahead} commits ahead"
msgstr ""
@@ -900,15 +885,6 @@ msgstr ""
msgid "Auto DevOps, runners and job artifacts"
msgstr ""
-msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
-msgstr ""
-
-msgid "Auto Review Apps and Auto Deploy need a domain name and a %{kubernetes} to work correctly."
-msgstr ""
-
-msgid "Auto Review Apps and Auto Deploy need a domain name to work correctly."
-msgstr ""
-
msgid "Auto-cancel redundant, pending pipelines"
msgstr ""
@@ -1281,9 +1257,6 @@ msgstr ""
msgid "CICD|Deployment strategy"
msgstr ""
-msgid "CICD|Deployment strategy needs a domain name to work correctly."
-msgstr ""
-
msgid "CICD|Jobs"
msgstr ""
@@ -1293,7 +1266,7 @@ msgstr ""
msgid "CICD|The Auto DevOps pipeline will run if no alternative CI configuration file is found."
msgstr ""
-msgid "CICD|You need to specify a domain if you want to use Auto Review Apps and Auto Deploy stages."
+msgid "CICD|You must add a %{kubernetes_cluster_start}Kubernetes cluster integration%{kubernetes_cluster_end} to this project with a domain in order for your deployment strategy to work correctly."
msgstr ""
msgid "CICD|instance enabled"
@@ -1566,6 +1539,12 @@ msgstr ""
msgid "Closed (moved)"
msgstr ""
+msgid "ClusterIntegration| %{custom_domain_start}More information%{custom_domain_end}."
+msgstr ""
+
+msgid "ClusterIntegration| can be used instead of a custom domain."
+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 ""
@@ -1575,6 +1554,9 @@ msgstr ""
msgid "ClusterIntegration|%{boldNotice} This will add some extra resources like a load balancer, which may incur additional costs depending on the hosting provider your Kubernetes cluster is installed on. If you are using Google Kubernetes Engine, you can %{pricingLink}."
msgstr ""
+msgid "ClusterIntegration|%{title} upgraded successfully."
+msgstr ""
+
msgid "ClusterIntegration|API URL"
msgstr ""
@@ -1596,6 +1578,9 @@ msgstr ""
msgid "ClusterIntegration|After installing Ingress, you will need to point your wildcard DNS at the generated external IP address in order to view your app after it is deployed. %{ingressHelpLink}"
msgstr ""
+msgid "ClusterIntegration|Alternatively"
+msgstr ""
+
msgid "ClusterIntegration|An error occured while trying to fetch project zones: %{error}"
msgstr ""
@@ -1617,6 +1602,9 @@ msgstr ""
msgid "ClusterIntegration|Are you sure you want to remove this Kubernetes cluster's integration? This will not delete your actual Kubernetes cluster."
msgstr ""
+msgid "ClusterIntegration|Base domain"
+msgstr ""
+
msgid "ClusterIntegration|CA Certificate"
msgstr ""
@@ -1893,6 +1881,9 @@ msgstr ""
msgid "ClusterIntegration|Request to begin installing failed"
msgstr ""
+msgid "ClusterIntegration|Retry upgrade"
+msgstr ""
+
msgid "ClusterIntegration|Save changes"
msgstr ""
@@ -1935,12 +1926,18 @@ msgstr ""
msgid "ClusterIntegration|Something went wrong on our end."
msgstr ""
+msgid "ClusterIntegration|Something went wrong when upgrading %{title}. Please check the logs and try again."
+msgstr ""
+
msgid "ClusterIntegration|Something went wrong while creating your Kubernetes cluster on Google Kubernetes Engine"
msgstr ""
msgid "ClusterIntegration|Something went wrong while installing %{title}"
msgstr ""
+msgid "ClusterIntegration|Specifying a domain will allow you to use Auto Review Apps and Auto Deploy stages for %{auto_devops_start}Auto DevOps%{auto_devops_end}. The domain should have a wildcard DNS configured matching the domain."
+msgstr ""
+
msgid "ClusterIntegration|The IP address is in the process of being assigned. Please check your Kubernetes cluster or Quotas on Google Kubernetes Engine if it takes a long time."
msgstr ""
@@ -1956,6 +1953,18 @@ msgstr ""
msgid "ClusterIntegration|Token"
msgstr ""
+msgid "ClusterIntegration|Upgrade"
+msgstr ""
+
+msgid "ClusterIntegration|Upgrade failed"
+msgstr ""
+
+msgid "ClusterIntegration|Upgraded"
+msgstr ""
+
+msgid "ClusterIntegration|Upgrading"
+msgstr ""
+
msgid "ClusterIntegration|Validating project billing status"
msgstr ""
@@ -2444,6 +2453,9 @@ msgstr ""
msgid "Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import."
msgstr ""
+msgid "Customize language and region related settings."
+msgstr ""
+
msgid "Customize your pipeline configuration, view your pipeline status and coverage report."
msgstr ""
@@ -2507,6 +2519,12 @@ msgstr ""
msgid "Default Branch"
msgstr ""
+msgid "Default first day of the week"
+msgstr ""
+
+msgid "Default first day of the week in calendars and date pickers."
+msgstr ""
+
msgid "Default: Directly import the Google Code email address or username"
msgstr ""
@@ -2546,6 +2564,9 @@ msgstr ""
msgid "Delete list"
msgstr ""
+msgid "Delete source branch"
+msgstr ""
+
msgid "Delete this attachment"
msgstr ""
@@ -3172,6 +3193,21 @@ msgstr ""
msgid "Everyone can contribute"
msgstr ""
+msgid "Everything you need to create a GitLab Pages site using GitBook."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Hexo."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Hugo."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using Jekyll."
+msgstr ""
+
+msgid "Everything you need to create a GitLab Pages site using plain HTML."
+msgstr ""
+
msgid "Except policy:"
msgstr ""
@@ -3271,6 +3307,9 @@ msgstr ""
msgid "Failure"
msgstr ""
+msgid "Fast-forward merge without a merge commit"
+msgstr ""
+
msgid "Faster as it re-uses the project workspace (falling back to clone if it doesn't exist)"
msgstr ""
@@ -3283,6 +3322,11 @@ msgstr ""
msgid "Fields on this page are now uneditable, you can configure"
msgstr ""
+msgid "File"
+msgid_plural "Files"
+msgstr[0] ""
+msgstr[1] ""
+
msgid "File added"
msgstr ""
@@ -3355,6 +3399,9 @@ msgstr ""
msgid "Finished"
msgstr ""
+msgid "First day of the week"
+msgstr ""
+
msgid "FirstPushedBy|First"
msgstr ""
@@ -3915,15 +3962,30 @@ msgstr ""
msgid "Include a Terms of Service agreement and Privacy Policy that all users must accept."
msgstr ""
+msgid "Include merge request description"
+msgstr ""
+
msgid "Include the username in the URL if required: <code>https://username@gitlab.company.com/group/project.git</code>."
msgstr ""
+msgid "Includes an MVC structure to help you get started."
+msgstr ""
+
+msgid "Includes an MVC structure, Gemfile, Rakefile, along with many others, to help you get started."
+msgstr ""
+
+msgid "Includes an MVC structure, mvnw and pom.xml to help you get started."
+msgstr ""
+
msgid "Incompatible Project"
msgstr ""
msgid "Indicates whether this runner can pick jobs without tags"
msgstr ""
+msgid "Information about additional Pages templates and how to install them can be found in our %{pages_getting_started_guide}."
+msgstr ""
+
msgid "Inline"
msgstr ""
@@ -4297,6 +4359,9 @@ msgstr ""
msgid "Loading…"
msgstr ""
+msgid "Localization"
+msgstr ""
+
msgid "Lock"
msgstr ""
@@ -4447,9 +4512,18 @@ msgstr ""
msgid "Merge Requests"
msgstr ""
+msgid "Merge commit message"
+msgstr ""
+
msgid "Merge events"
msgstr ""
+msgid "Merge immediately"
+msgstr ""
+
+msgid "Merge in progress"
+msgstr ""
+
msgid "Merge request"
msgstr ""
@@ -4459,6 +4533,9 @@ msgstr ""
msgid "Merge requests are a place to propose changes you've made to a project and discuss those changes with others"
msgstr ""
+msgid "Merge when pipeline succeeds"
+msgstr ""
+
msgid "MergeRequests|Add a reply"
msgstr ""
@@ -4615,6 +4692,15 @@ msgstr ""
msgid "Modal|Close"
msgstr ""
+msgid "Modify commit messages"
+msgstr ""
+
+msgid "Modify merge commit"
+msgstr ""
+
+msgid "Monday"
+msgstr ""
+
msgid "Monitor your errors by integrating with Sentry"
msgstr ""
@@ -5087,6 +5173,9 @@ msgstr ""
msgid "Pages Domains"
msgstr ""
+msgid "Pages getting started guide"
+msgstr ""
+
msgid "Pagination|Last »"
msgstr ""
@@ -6605,7 +6694,10 @@ msgstr ""
msgid "Snippets"
msgstr ""
-msgid "Someone edited this issue at the same time you did. The description has been updated and you will need to make your changes again."
+msgid "Someone edited this %{issueType} at the same time you did. The description has been updated and you will need to make your changes again."
+msgstr ""
+
+msgid "Someone edited this merge request at the same time you did. Please refresh the page to see changes."
msgstr ""
msgid "Something went wrong on our end"
@@ -6632,6 +6724,9 @@ msgstr ""
msgid "Something went wrong while closing the %{issuable}. Please try again later"
msgstr ""
+msgid "Something went wrong while deleting the source branch. Please try again."
+msgstr ""
+
msgid "Something went wrong while fetching comments. Please try again."
msgstr ""
@@ -6644,6 +6739,9 @@ msgstr ""
msgid "Something went wrong while fetching the registry list."
msgstr ""
+msgid "Something went wrong while merging this merge request. Please try again."
+msgstr ""
+
msgid "Something went wrong while reopening the %{issuable}. Please try again later"
msgstr ""
@@ -6788,6 +6886,9 @@ msgstr ""
msgid "Specify the following URL during the Runner setup:"
msgstr ""
+msgid "Squash commit message"
+msgstr ""
+
msgid "Squash commits"
msgstr ""
@@ -6926,6 +7027,9 @@ msgstr ""
msgid "Suggested change"
msgstr ""
+msgid "Sunday"
+msgstr ""
+
msgid "Support for custom certificates is disabled. Ask your system's administrator to enable it."
msgstr ""
@@ -6938,6 +7042,9 @@ msgstr ""
msgid "System Info"
msgstr ""
+msgid "System default (%{default})"
+msgstr ""
+
msgid "System metrics (Custom)"
msgstr ""
@@ -7977,6 +8084,9 @@ msgstr ""
msgid "Various email settings."
msgstr ""
+msgid "Various localization settings."
+msgstr ""
+
msgid "Various settings that affect GitLab performance."
msgstr ""
@@ -8304,6 +8414,9 @@ msgstr ""
msgid "You can only edit files when you are on a branch"
msgstr ""
+msgid "You can only merge once the items above are resolved"
+msgstr ""
+
msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}"
msgstr ""
@@ -8598,6 +8711,12 @@ msgstr[1] ""
msgid "missing"
msgstr ""
+msgid "mrWidgetCommitsAdded|%{commitCount} and %{mergeCommitCount} will be added to %{targetBranch}."
+msgstr ""
+
+msgid "mrWidgetCommitsAdded|1 merge commit"
+msgstr ""
+
msgid "mrWidget| Please restore it or use a different %{missingBranchName} branch"
msgstr ""
diff --git a/package.json b/package.json
index 97d8fd3b17f..7110baef8b3 100644
--- a/package.json
+++ b/package.json
@@ -28,7 +28,7 @@
"@babel/plugin-syntax-import-meta": "^7.2.0",
"@babel/preset-env": "^7.3.1",
"@gitlab/csslab": "^1.8.0",
- "@gitlab/svgs": "^1.48.0",
+ "@gitlab/svgs": "^1.51.0",
"@gitlab/ui": "^2.0.2",
"apollo-boost": "^0.1.20",
"apollo-client": "^2.4.5",
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index d5377f1d1c1..e476cbe29a2 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -40,17 +40,9 @@ module QA
element :login_page
end
- def initialize
- # The login page is usually the entry point for all the scenarios so
- # we need to wait for the instance to start. That said, in some cases
- # we are already logged-in so we check both cases here.
- # Check if we're already logged in first. If we don't then we have to
- # wait 10 seconds for the check for the login page to fail every time
- # we use this class when we're already logged in (E.g., whenever we
- # create a personal access token to use for API access).
- wait(max: 500) do
- Page::Main::Menu.act { has_personal_area?(wait: 0) } ||
- has_element?(:login_page)
+ def page_loaded?
+ wait(max: 60) do
+ has_element?(:login_page)
end
end
diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb
index 12c2409a5a7..2de39b8ebf5 100644
--- a/qa/qa/page/project/settings/ci_cd.rb
+++ b/qa/qa/page/project/settings/ci_cd.rb
@@ -13,9 +13,7 @@ module QA # rubocop:disable Naming/FileName
view 'app/views/projects/settings/ci_cd/_autodevops_form.html.haml' do
element :enable_auto_devops_field, 'check_box :enabled' # rubocop:disable QA/ElementWithPattern
- element :domain_field, 'text_field :domain' # rubocop:disable QA/ElementWithPattern
element :enable_auto_devops_button, "%strong= s_('CICD|Default to Auto DevOps pipeline')" # rubocop:disable QA/ElementWithPattern
- element :domain_input, "%strong= _('Domain')" # rubocop:disable QA/ElementWithPattern
element :save_changes_button, "submit _('Save changes')" # rubocop:disable QA/ElementWithPattern
end
@@ -31,10 +29,9 @@ module QA # rubocop:disable Naming/FileName
end
end
- def enable_auto_devops_with_domain(domain)
+ def enable_auto_devops
expand_section(:autodevops_settings) do
check 'Default to Auto DevOps pipeline'
- fill_in 'Domain', with: domain
click_on 'Save changes'
end
end
diff --git a/qa/qa/resource/kubernetes_cluster.rb b/qa/qa/resource/kubernetes_cluster.rb
index d67e5f6da20..986b31da528 100644
--- a/qa/qa/resource/kubernetes_cluster.rb
+++ b/qa/qa/resource/kubernetes_cluster.rb
@@ -6,12 +6,16 @@ module QA
module Resource
class KubernetesCluster < Base
attr_writer :project, :cluster,
- :install_helm_tiller, :install_ingress, :install_prometheus, :install_runner
+ :install_helm_tiller, :install_ingress, :install_prometheus, :install_runner, :domain
attribute :ingress_ip do
Page::Project::Operations::Kubernetes::Show.perform(&:ingress_ip)
end
+ attribute :domain do
+ "#{ingress_ip}.nip.io"
+ end
+
def fabricate!
@project.visit!
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
index 6632c2977ef..2fb8402edd8 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/create_project_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Manage', :smoke do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/72
+ context 'Manage', :smoke, :quarantine do
describe 'Project creation' do
it 'user creates a new project' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
index d4cedc9362d..e172206eb88 100644
--- a/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
+++ b/qa/qa/specs/features/browser_ui/1_manage/project/view_project_activity_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Manage' do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/21
+ context 'Manage', :quarantine do
describe 'Project activity' do
it 'user creates an event in the activity page upon Git push' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
index 10cc0480794..545da0a8b85 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/squash_merge_request_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Create' do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/31
+ context 'Create', :quarantine do
describe 'Merge request squashing' do
it 'user squashes commits while merging' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb
index 0837b720df1..e444bc7ef1b 100644
--- a/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb
+++ b/qa/qa/specs/features/browser_ui/4_verify/ci_variable/add_ci_variable_spec.rb
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Verify' do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/30
+ context 'Verify', :quarantine do
describe 'CI variable support' do
it 'user adds a CI variable' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
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 6f39a755392..aa01e5a618e 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
@@ -1,7 +1,8 @@
# frozen_string_literal: true
module QA
- context 'Release' do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/staging/issues/26
+ context 'Release', :quarantine do
describe 'Deploy key creation' do
it 'user adds a deploy key' do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
index 11a9653db81..97d36095a69 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
@@ -3,7 +3,8 @@
require 'digest/sha1'
module QA
- context 'Release', :docker do
+ # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/70
+ context 'Release', :docker, :quarantine do
describe 'Git clone using a deploy key' do
def login
Runtime::Browser.visit(:gitlab, Page::Main::Login)
diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
index b0ff83db86b..5c8ec465143 100644
--- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
+++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb
@@ -52,13 +52,13 @@ module QA
end
kubernetes_cluster.populate(:ingress_ip)
-
@project.visit!
Page::Project::Menu.act { click_ci_cd_settings }
Page::Project::Settings::CICD.perform do |p|
- p.enable_auto_devops_with_domain(
- "#{kubernetes_cluster.ingress_ip}.nip.io")
+ p.enable_auto_devops
end
+
+ kubernetes_cluster.populate(:domain)
end
after(:all) do
diff --git a/qa/spec/spec_helper.rb b/qa/spec/spec_helper.rb
index 3537ba7c235..0f3cf5f4408 100644
--- a/qa/spec/spec_helper.rb
+++ b/qa/spec/spec_helper.rb
@@ -3,6 +3,20 @@ require_relative '../qa'
Dir[::File.join(__dir__, 'support', '**', '*.rb')].each { |f| require f }
RSpec.configure do |config|
+ ServerNotRespondingError = Class.new(RuntimeError)
+
+ # The login page could take some time to load the first time it is visited.
+ # We visit the login page and wait for it to properly load only once at the beginning of the suite.
+ config.before(:suite) do
+ if QA::Runtime::Scenario.respond_to?(:gitlab_address)
+ QA::Runtime::Browser.visit(:gitlab, QA::Page::Main::Login)
+
+ unless QA::Page::Main::Login.perform(&:page_loaded?)
+ raise ServerNotRespondingError, "Login page did not load at #{QA::Page::Main::Login.perform(&:current_url)}"
+ end
+ end
+ end
+
config.before do |example|
QA::Runtime::Logger.debug("Starting test: #{example.full_description}") if QA::Runtime::Env.debug?
diff --git a/spec/controllers/groups/clusters_controller_spec.rb b/spec/controllers/groups/clusters_controller_spec.rb
index 0f28499194e..360030102e0 100644
--- a/spec/controllers/groups/clusters_controller_spec.rb
+++ b/spec/controllers/groups/clusters_controller_spec.rb
@@ -429,12 +429,14 @@ describe Groups::ClustersController do
end
let(:cluster) { create(:cluster, :provided_by_user, cluster_type: :group_type, groups: [group]) }
+ let(:domain) { 'test-domain.com' }
let(:params) do
{
cluster: {
enabled: false,
- name: 'my-new-cluster-name'
+ name: 'my-new-cluster-name',
+ base_domain: domain
}
}
end
@@ -447,6 +449,20 @@ describe Groups::ClustersController do
expect(flash[:notice]).to eq('Kubernetes cluster was successfully updated.')
expect(cluster.enabled).to be_falsey
expect(cluster.name).to eq('my-new-cluster-name')
+ expect(cluster.domain).to eq('test-domain.com')
+ end
+
+ context 'when domain is invalid' do
+ let(:domain) { 'not-a-valid-domain' }
+
+ it 'should not update cluster attributes' do
+ go
+
+ cluster.reload
+ expect(response).to render_template(:show)
+ expect(cluster.name).not_to eq('my-new-cluster-name')
+ expect(cluster.domain).not_to eq('test-domain.com')
+ end
end
context 'when format is json' do
@@ -456,7 +472,8 @@ describe Groups::ClustersController do
{
cluster: {
enabled: false,
- name: 'my-new-cluster-name'
+ name: 'my-new-cluster-name',
+ domain: domain
}
}
end
diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb
index 012f016b091..760c0fab130 100644
--- a/spec/controllers/profiles/preferences_controller_spec.rb
+++ b/spec/controllers/profiles/preferences_controller_spec.rb
@@ -42,7 +42,8 @@ describe Profiles::PreferencesController do
prefs = {
color_scheme_id: '1',
dashboard: 'stars',
- theme_id: '2'
+ theme_id: '2',
+ first_day_of_week: '1'
}.with_indifferent_access
expect(user).to receive(:assign_attributes).with(ActionController::Parameters.new(prefs).permit!)
diff --git a/spec/controllers/projects/error_tracking_controller_spec.rb b/spec/controllers/projects/error_tracking_controller_spec.rb
index 6464398cea1..844c61f1ace 100644
--- a/spec/controllers/projects/error_tracking_controller_spec.rb
+++ b/spec/controllers/projects/error_tracking_controller_spec.rb
@@ -107,8 +107,11 @@ describe Projects::ErrorTrackingController do
let(:http_status) { :no_content }
before do
- expect(list_issues_service).to receive(:execute)
- .and_return(status: :error, message: error_message, http_status: http_status)
+ expect(list_issues_service).to receive(:execute).and_return(
+ status: :error,
+ message: error_message,
+ http_status: http_status
+ )
end
it 'returns http_status with message' do
@@ -122,6 +125,113 @@ describe Projects::ErrorTrackingController do
end
end
+ describe 'POST #list_projects' do
+ context 'with insufficient permissions' do
+ before do
+ project.add_guest(user)
+ end
+
+ it 'returns 404' do
+ post :list_projects, params: list_projects_params
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+ end
+
+ context 'with an anonymous user' do
+ before do
+ sign_out(user)
+ end
+
+ it 'redirects to sign-in page' do
+ post :list_projects, params: list_projects_params
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+
+ context 'with authorized user' do
+ let(:list_projects_service) { spy(:list_projects_service) }
+ let(:sentry_project) { build(:error_tracking_project) }
+
+ let(:permitted_params) do
+ ActionController::Parameters.new(
+ list_projects_params[:error_tracking_setting]
+ ).permit!
+ end
+
+ before do
+ allow(ErrorTracking::ListProjectsService)
+ .to receive(:new).with(project, user, permitted_params)
+ .and_return(list_projects_service)
+ end
+
+ context 'service result is successful' do
+ before do
+ expect(list_projects_service).to receive(:execute)
+ .and_return(status: :success, projects: [sentry_project])
+ end
+
+ it 'returns a list of projects' do
+ post :list_projects, params: list_projects_params
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('error_tracking/list_projects')
+ expect(json_response['projects']).to eq([sentry_project].as_json)
+ end
+ end
+
+ context 'service result is erroneous' do
+ let(:error_message) { 'error message' }
+
+ context 'without http_status' do
+ before do
+ expect(list_projects_service).to receive(:execute)
+ .and_return(status: :error, message: error_message)
+ end
+
+ it 'returns 400 with message' do
+ get :list_projects, params: list_projects_params
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to eq(error_message)
+ end
+ end
+
+ context 'with explicit http_status' do
+ let(:http_status) { :no_content }
+
+ before do
+ expect(list_projects_service).to receive(:execute).and_return(
+ status: :error,
+ message: error_message,
+ http_status: http_status
+ )
+ end
+
+ it 'returns http_status with message' do
+ get :list_projects, params: list_projects_params
+
+ expect(response).to have_gitlab_http_status(http_status)
+ expect(json_response['message']).to eq(error_message)
+ end
+ end
+ end
+ end
+
+ private
+
+ def list_projects_params(opts = {})
+ project_params(
+ format: :json,
+ error_tracking_setting: {
+ api_host: 'gitlab.com',
+ token: 'token'
+ }
+ )
+ end
+ end
+
private
def project_params(opts = {})
diff --git a/spec/features/clusters/cluster_detail_page_spec.rb b/spec/features/clusters/cluster_detail_page_spec.rb
new file mode 100644
index 00000000000..0a9c4bcaf12
--- /dev/null
+++ b/spec/features/clusters/cluster_detail_page_spec.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Clusterable > Show page' do
+ let(:current_user) { create(:user) }
+
+ before do
+ sign_in(current_user)
+ end
+
+ shared_examples 'editing domain' do
+ before do
+ clusterable.add_maintainer(current_user)
+ end
+
+ it 'allow the user to set domain' do
+ visit cluster_path
+
+ within '#cluster-integration' do
+ fill_in('cluster_base_domain', with: 'test.com')
+ click_on 'Save changes'
+ end
+
+ expect(page.status_code).to eq(200)
+ expect(page).to have_content('Kubernetes cluster was successfully updated.')
+ end
+
+ context 'when there is a cluster with ingress and external ip' do
+ before do
+ cluster.create_application_ingress!(external_ip: '192.168.1.100')
+
+ visit cluster_path
+ end
+
+ it 'shows help text with the domain as an alternative to custom domain' do
+ within '#cluster-integration' do
+ expect(page).to have_content('Alternatively 192.168.1.100.nip.io can be used instead of a custom domain')
+ end
+ end
+ end
+
+ context 'when there is no ingress' do
+ it 'alternative to custom domain is not shown' do
+ visit cluster_path
+
+ within '#cluster-integration' do
+ expect(page).not_to have_content('can be used instead of a custom domain.')
+ end
+ end
+ end
+ end
+
+ context 'when clusterable is a project' do
+ it_behaves_like 'editing domain' do
+ let(:clusterable) { create(:project) }
+ let(:cluster) { create(:cluster, :provided_by_gcp, :project, projects: [clusterable]) }
+ let(:cluster_path) { project_cluster_path(clusterable, cluster) }
+ end
+ end
+
+ context 'when clusterable is a group' do
+ it_behaves_like 'editing domain' do
+ let(:clusterable) { create(:group) }
+ let(:cluster) { create(:cluster, :provided_by_gcp, :group, groups: [clusterable]) }
+ let(:cluster_path) { group_cluster_path(clusterable, cluster) }
+ end
+ end
+end
diff --git a/spec/features/merge_request/user_accepts_merge_request_spec.rb b/spec/features/merge_request/user_accepts_merge_request_spec.rb
index 00ac7c72a11..5fa23dbb998 100644
--- a/spec/features/merge_request/user_accepts_merge_request_spec.rb
+++ b/spec/features/merge_request/user_accepts_merge_request_spec.rb
@@ -80,8 +80,8 @@ describe 'User accepts a merge request', :js do
end
it 'accepts a merge request' do
- click_button('Modify commit message')
- fill_in('Commit message', with: 'wow such merge')
+ find('.js-mr-widget-commits-count').click
+ fill_in('merge-message-edit', with: 'wow such merge')
click_button('Merge')
diff --git a/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
index 8d2d4279d3c..c6b11fce388 100644
--- a/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
+++ b/spec/features/merge_request/user_customizes_merge_commit_message_spec.rb
@@ -13,7 +13,7 @@ describe 'Merge request < User customizes merge commit message', :js do
description: "Description\n\nclosing #{issue_1.to_reference}, #{issue_2.to_reference}"
)
end
- let(:textbox) { page.find(:css, '.js-commit-message', visible: false) }
+ let(:textbox) { page.find(:css, '#merge-message-edit', visible: false) }
let(:default_message) do
[
"Merge branch 'feature' into 'master'",
@@ -38,16 +38,16 @@ describe 'Merge request < User customizes merge commit message', :js do
end
it 'toggles commit message between message with description and without description' do
- expect(page).not_to have_selector('.js-commit-message')
- click_button "Modify commit message"
+ expect(page).not_to have_selector('#merge-message-edit')
+ first('.js-mr-widget-commits-count').click
expect(textbox).to be_visible
expect(textbox.value).to eq(default_message)
- click_link "Include description in commit message"
+ check('Include merge request description')
expect(textbox.value).to eq(message_with_description)
- click_link "Don't include description in commit message"
+ uncheck('Include merge request description')
expect(textbox.value).to eq(default_message)
end
diff --git a/spec/features/merge_request/user_resolves_conflicts_spec.rb b/spec/features/merge_request/user_resolves_conflicts_spec.rb
index 50c723776a3..16c058ab6bd 100644
--- a/spec/features/merge_request/user_resolves_conflicts_spec.rb
+++ b/spec/features/merge_request/user_resolves_conflicts_spec.rb
@@ -37,6 +37,8 @@ describe 'Merge request > User resolves conflicts', :js do
click_on 'Changes'
wait_for_requests
+ find('.js-toggle-tree-list').click
+
within find('.diff-file', text: 'files/ruby/popen.rb') do
expect(page).to have_selector('.line_content.new', text: "vars = { 'PWD' => path }")
expect(page).to have_selector('.line_content.new', text: "options = { chdir: path }")
diff --git a/spec/features/merge_request/user_sees_versions_spec.rb b/spec/features/merge_request/user_sees_versions_spec.rb
index 63d8decc2d2..aa91ade46ca 100644
--- a/spec/features/merge_request/user_sees_versions_spec.rb
+++ b/spec/features/merge_request/user_sees_versions_spec.rb
@@ -42,7 +42,7 @@ describe 'Merge request > User sees versions', :js do
expect(page).to have_content 'latest version'
end
- expect(page).to have_content '8 changed files'
+ expect(page).to have_content '8 Files'
end
it_behaves_like 'allows commenting',
@@ -76,7 +76,7 @@ describe 'Merge request > User sees versions', :js do
end
it 'shows comments that were last relevant at that version' do
- expect(page).to have_content '5 changed files'
+ expect(page).to have_content '5 Files'
position = Gitlab::Diff::Position.new(
old_path: ".gitmodules",
@@ -120,8 +120,15 @@ describe 'Merge request > User sees versions', :js do
diff_id: merge_request_diff3.id,
start_sha: '6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9'
)
- expect(page).to have_content '4 changed files'
- expect(page).to have_content '15 additions 6 deletions'
+ expect(page).to have_content '4 Files'
+
+ additions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group svg.ic-file-addition')
+ .ancestor('.diff-stats-group').text
+ deletions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group svg.ic-file-deletion')
+ .ancestor('.diff-stats-group').text
+
+ expect(additions_content).to eq '15'
+ expect(deletions_content).to eq '6'
position = Gitlab::Diff::Position.new(
old_path: ".gitmodules",
@@ -141,8 +148,14 @@ describe 'Merge request > User sees versions', :js do
end
it 'show diff between new and old version' do
- expect(page).to have_content '4 changed files'
- expect(page).to have_content '15 additions 6 deletions'
+ additions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group svg.ic-file-addition')
+ .ancestor('.diff-stats-group').text
+ deletions_content = page.find('.diff-stats.is-compare-versions-header .diff-stats-group svg.ic-file-deletion')
+ .ancestor('.diff-stats-group').text
+
+ expect(page).to have_content '4 Files'
+ expect(additions_content).to eq '15'
+ expect(deletions_content).to eq '6'
end
it 'returns to latest version when "Show latest version" button is clicked' do
@@ -150,7 +163,7 @@ describe 'Merge request > User sees versions', :js do
page.within '.mr-version-dropdown' do
expect(page).to have_content 'latest version'
end
- expect(page).to have_content '8 changed files'
+ expect(page).to have_content '8 Files'
end
it_behaves_like 'allows commenting',
@@ -176,7 +189,7 @@ describe 'Merge request > User sees versions', :js do
find('.btn-default').click
click_link 'version 1'
end
- expect(page).to have_content '0 changed files'
+ expect(page).to have_content '0 Files'
end
end
@@ -202,7 +215,7 @@ describe 'Merge request > User sees versions', :js do
expect(page).to have_content 'version 1'
end
- expect(page).to have_content '0 changed files'
+ expect(page).to have_content '0 Files'
end
end
diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb
index 6f8ec0015ad..4c85abe9971 100644
--- a/spec/features/projects/settings/pipelines_settings_spec.rb
+++ b/spec/features/projects/settings/pipelines_settings_spec.rb
@@ -98,14 +98,12 @@ describe "Projects > Settings > Pipelines settings" do
expect(page).not_to have_content('instance enabled')
expect(find_field('project_auto_devops_attributes_enabled')).not_to be_checked
check 'Default to Auto DevOps pipeline'
- fill_in('project_auto_devops_attributes_domain', with: 'test.com')
click_on 'Save changes'
end
expect(page.status_code).to eq(200)
expect(project.auto_devops).to be_present
expect(project.auto_devops).to be_enabled
- expect(project.auto_devops.domain).to eq('test.com')
page.within '#autodevops-settings' do
expect(find_field('project_auto_devops_attributes_enabled')).to be_checked
@@ -113,29 +111,6 @@ describe "Projects > Settings > Pipelines settings" do
end
end
end
-
- context 'when there is a cluster with ingress and external_ip' do
- before do
- cluster = create(:cluster, projects: [project])
- cluster.create_application_ingress!(external_ip: '192.168.1.100')
- end
-
- it 'shows the help text with the nip.io domain as an alternative to custom domain' do
- visit project_settings_ci_cd_path(project)
- expect(page).to have_content('192.168.1.100.nip.io can be used as an alternative to a custom domain')
- end
- end
-
- context 'when there is no ingress' do
- before do
- create(:cluster, projects: [project])
- end
-
- it 'alternative to custom domain is not shown' do
- visit project_settings_ci_cd_path(project)
- expect(page).not_to have_content('can be used as an alternative to a custom domain')
- end
- end
end
describe 'runners registration token' do
diff --git a/spec/features/snippets/show_spec.rb b/spec/features/snippets/show_spec.rb
index eb974c7c7fd..74fdfcf492e 100644
--- a/spec/features/snippets/show_spec.rb
+++ b/spec/features/snippets/show_spec.rb
@@ -79,14 +79,6 @@ describe 'Snippet', :js do
expect(page).not_to have_xpath("//ol//li//ul")
end
end
-
- context 'with cached CommonMark html' do
- let(:snippet) { create(:personal_snippet, :public, file_name: file_name, content: content, cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION) }
-
- it 'renders correctly' do
- expect(page).not_to have_xpath("//ol//li//ul")
- end
- end
end
context 'switching to the simple viewer' do
diff --git a/spec/fixtures/api/schemas/cluster_status.json b/spec/fixtures/api/schemas/cluster_status.json
index 138a6c5ed6b..5ebc09a96dc 100644
--- a/spec/fixtures/api/schemas/cluster_status.json
+++ b/spec/fixtures/api/schemas/cluster_status.json
@@ -34,7 +34,8 @@
"status_reason": { "type": ["string", "null"] },
"external_ip": { "type": ["string", "null"] },
"hostname": { "type": ["string", "null"] },
- "email": { "type": ["string", "null"] }
+ "email": { "type": ["string", "null"] },
+ "update_available": { "type": ["boolean", "null"] }
},
"required" : [ "name", "status" ]
}
diff --git a/spec/fixtures/api/schemas/public_api/v4/group_labels.json b/spec/fixtures/api/schemas/public_api/v4/group_labels.json
new file mode 100644
index 00000000000..f6c327abfdd
--- /dev/null
+++ b/spec/fixtures/api/schemas/public_api/v4/group_labels.json
@@ -0,0 +1,18 @@
+{
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties" : {
+ "id" : { "type": "integer" },
+ "name" : { "type": "string "},
+ "color" : { "type": "string "},
+ "description" : { "type": "string "},
+ "open_issues_count" : { "type": "integer "},
+ "closed_issues_count" : { "type": "integer "},
+ "open_merge_requests_count" : { "type": "integer "},
+ "subscribed" : { "type": "boolean" },
+ "priority" : { "type": "null" }
+ },
+ "additionalProperties": false
+ }
+}
diff --git a/spec/helpers/auto_devops_helper_spec.rb b/spec/helpers/auto_devops_helper_spec.rb
index 75c30dbfe48..223e562238d 100644
--- a/spec/helpers/auto_devops_helper_spec.rb
+++ b/spec/helpers/auto_devops_helper_spec.rb
@@ -90,39 +90,4 @@ describe AutoDevopsHelper do
it { is_expected.to eq(false) }
end
end
-
- describe '.auto_devops_warning_message' do
- subject { helper.auto_devops_warning_message(project) }
-
- context 'when the service is missing' do
- before do
- allow(helper).to receive(:missing_auto_devops_service?).and_return(true)
- end
-
- context 'when the domain is missing' do
- before do
- allow(helper).to receive(:missing_auto_devops_domain?).and_return(true)
- end
-
- it { is_expected.to match(/Auto Review Apps and Auto Deploy need a domain name and a .* to work correctly./) }
- end
-
- context 'when the domain is not missing' do
- before do
- allow(helper).to receive(:missing_auto_devops_domain?).and_return(false)
- end
-
- it { is_expected.to match(/Auto Review Apps and Auto Deploy need a .* to work correctly./) }
- end
- end
-
- context 'when the domain is missing' do
- before do
- allow(helper).to receive(:missing_auto_devops_service?).and_return(false)
- allow(helper).to receive(:missing_auto_devops_domain?).and_return(true)
- end
-
- it { is_expected.to eq('Auto Review Apps and Auto Deploy need a domain name to work correctly.') }
- end
- end
end
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index c112c8ed633..4c395248644 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -35,6 +35,30 @@ describe PreferencesHelper do
end
end
+ describe '#first_day_of_week_choices' do
+ it 'returns Sunday and Monday as choices' do
+ expect(helper.first_day_of_week_choices).to eq [
+ ['Sunday', 0],
+ ['Monday', 1]
+ ]
+ end
+ end
+
+ describe '#first_day_of_week_choices_with_default' do
+ it 'returns choices including system default' do
+ expect(helper.first_day_of_week_choices_with_default).to eq [
+ ['System default (Sunday)', nil], ['Sunday', 0], ['Monday', 1]
+ ]
+ end
+
+ it 'returns choices including system default set to Monday' do
+ stub_application_setting(first_day_of_week: 1)
+ expect(helper.first_day_of_week_choices_with_default).to eq [
+ ['System default (Monday)', nil], ['Sunday', 0], ['Monday', 1]
+ ]
+ end
+ end
+
describe '#user_application_theme' do
context 'with a user' do
it "returns user's theme's css_class" do
diff --git a/spec/javascripts/clusters/components/application_row_spec.js b/spec/javascripts/clusters/components/application_row_spec.js
index d1f4a1cebb4..8cb9713964e 100644
--- a/spec/javascripts/clusters/components/application_row_spec.js
+++ b/spec/javascripts/clusters/components/application_row_spec.js
@@ -208,6 +208,144 @@ describe('Application Row', () => {
});
});
+ describe('Upgrade button', () => {
+ it('has indeterminate state on page load', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: null,
+ });
+ const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
+
+ expect(upgradeBtn).toBe(null);
+ });
+
+ it('has enabled "Upgrade" when "upgradeAvailable" is true', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ upgradeAvailable: true,
+ });
+ const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
+
+ expect(upgradeBtn).not.toBe(null);
+ expect(upgradeBtn.innerHTML).toContain('Upgrade');
+ });
+
+ it('has enabled "Retry upgrade" when APPLICATION_STATUS.UPDATE_ERRORED', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.UPDATE_ERRORED,
+ });
+ const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
+
+ expect(upgradeBtn).not.toBe(null);
+ expect(vm.upgradeFailed).toBe(true);
+ expect(upgradeBtn.innerHTML).toContain('Retry upgrade');
+ });
+
+ it('has disabled "Retry upgrade" when APPLICATION_STATUS.UPDATING', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.UPDATING,
+ });
+ const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
+
+ expect(upgradeBtn).not.toBe(null);
+ expect(vm.isUpgrading).toBe(true);
+ expect(upgradeBtn.innerHTML).toContain('Upgrading');
+ });
+
+ it('clicking upgrade button emits event', () => {
+ spyOn(eventHub, '$emit');
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.UPDATE_ERRORED,
+ });
+ const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
+
+ upgradeBtn.click();
+
+ expect(eventHub.$emit).toHaveBeenCalledWith('upgradeApplication', {
+ id: DEFAULT_APPLICATION_STATE.id,
+ params: {},
+ });
+ });
+
+ it('clicking disabled upgrade button emits nothing', () => {
+ spyOn(eventHub, '$emit');
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.UPDATING,
+ });
+ const upgradeBtn = vm.$el.querySelector('.js-cluster-application-upgrade-button');
+
+ upgradeBtn.click();
+
+ expect(eventHub.$emit).not.toHaveBeenCalled();
+ });
+
+ it('displays an error message if application upgrade failed', () => {
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ title: 'GitLab Runner',
+ status: APPLICATION_STATUS.UPDATE_ERRORED,
+ });
+ const failureMessage = vm.$el.querySelector(
+ '.js-cluster-application-upgrade-failure-message',
+ );
+
+ expect(failureMessage).not.toBe(null);
+ expect(failureMessage.innerHTML).toContain(
+ 'Something went wrong when upgrading GitLab Runner. Please check the logs and try again.',
+ );
+ });
+ });
+
+ describe('Version', () => {
+ it('displays a version number if application has been upgraded', () => {
+ const version = '0.1.45';
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.UPDATED,
+ version,
+ });
+ const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details');
+ const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version');
+
+ expect(upgradeDetails.innerHTML).toContain('Upgraded');
+ expect(versionEl).not.toBe(null);
+ expect(versionEl.innerHTML).toContain(version);
+ });
+
+ it('contains a link to the chart repo if application has been upgraded', () => {
+ const version = '0.1.45';
+ const chartRepo = 'https://gitlab.com/charts/gitlab-runner';
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.UPDATED,
+ chartRepo,
+ version,
+ });
+ const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version');
+
+ expect(versionEl.href).toEqual(chartRepo);
+ expect(versionEl.target).toEqual('_blank');
+ });
+
+ it('does not display a version number if application upgrade failed', () => {
+ const version = '0.1.45';
+ vm = mountComponent(ApplicationRow, {
+ ...DEFAULT_APPLICATION_STATE,
+ status: APPLICATION_STATUS.UPDATE_ERRORED,
+ version,
+ });
+ const upgradeDetails = vm.$el.querySelector('.js-cluster-application-upgrade-details');
+ const versionEl = vm.$el.querySelector('.js-cluster-application-upgrade-version');
+
+ expect(upgradeDetails.innerHTML).toContain('failed');
+ expect(versionEl).toBe(null);
+ });
+ });
+
describe('Error block', () => {
it('does not show error block when there is no error', () => {
vm = mountComponent(ApplicationRow, {
diff --git a/spec/javascripts/clusters/stores/clusters_store_spec.js b/spec/javascripts/clusters/stores/clusters_store_spec.js
index dfce2656e4c..37a4d6614f6 100644
--- a/spec/javascripts/clusters/stores/clusters_store_spec.js
+++ b/spec/javascripts/clusters/stores/clusters_store_spec.js
@@ -85,6 +85,9 @@ describe('Clusters Store', () => {
statusReason: mockResponseData.applications[2].status_reason,
requestStatus: null,
requestReason: null,
+ version: mockResponseData.applications[2].version,
+ upgradeAvailable: mockResponseData.applications[2].update_available,
+ chartRepo: 'https://gitlab.com/charts/gitlab-runner',
},
prometheus: {
title: 'Prometheus',
diff --git a/spec/javascripts/diffs/components/compare_versions_spec.js b/spec/javascripts/diffs/components/compare_versions_spec.js
index 2f0385454d7..e886f962d2f 100644
--- a/spec/javascripts/diffs/components/compare_versions_spec.js
+++ b/spec/javascripts/diffs/components/compare_versions_spec.js
@@ -10,6 +10,10 @@ describe('CompareVersions', () => {
const targetBranch = { branchName: 'tmp-wine-dev', versionIndex: -1 };
beforeEach(() => {
+ store.state.diffs.addedLines = 10;
+ store.state.diffs.removedLines = 20;
+ store.state.diffs.diffFiles.push('test');
+
vm = createComponentWithStore(Vue.extend(CompareVersionsComponent), store, {
mergeRequestDiffs: diffsMockData,
mergeRequestDiff: diffsMockData[0],
diff --git a/spec/javascripts/diffs/components/diff_file_header_spec.js b/spec/javascripts/diffs/components/diff_file_header_spec.js
index b77907ff26f..787a81fd88f 100644
--- a/spec/javascripts/diffs/components/diff_file_header_spec.js
+++ b/spec/javascripts/diffs/components/diff_file_header_spec.js
@@ -24,6 +24,10 @@ describe('diff_file_header', () => {
beforeEach(() => {
const diffFile = diffDiscussionMock.diff_file;
+
+ diffFile.added_lines = 2;
+ diffFile.removed_lines = 1;
+
props = {
diffFile: { ...diffFile },
canCurrentUserFork: false,
diff --git a/spec/javascripts/diffs/components/diff_stats_spec.js b/spec/javascripts/diffs/components/diff_stats_spec.js
new file mode 100644
index 00000000000..984b3026209
--- /dev/null
+++ b/spec/javascripts/diffs/components/diff_stats_spec.js
@@ -0,0 +1,33 @@
+import { shallowMount } from '@vue/test-utils';
+import DiffStats from '~/diffs/components/diff_stats.vue';
+
+describe('diff_stats', () => {
+ it('does not render a group if diffFileLengths is not passed in', () => {
+ const wrapper = shallowMount(DiffStats, {
+ propsData: {
+ addedLines: 1,
+ removedLines: 2,
+ },
+ });
+ const groups = wrapper.findAll('.diff-stats-group');
+
+ expect(groups.length).toBe(2);
+ });
+
+ it('shows amount of files changed, lines added and lines removed when passed all props', () => {
+ const wrapper = shallowMount(DiffStats, {
+ propsData: {
+ addedLines: 100,
+ removedLines: 200,
+ diffFilesLength: 300,
+ },
+ });
+ const additions = wrapper.find('icon-stub[name="file-addition"]').element.parentNode;
+ const deletions = wrapper.find('icon-stub[name="file-deletion"]').element.parentNode;
+ const filesChanged = wrapper.find('icon-stub[name="doc-code"]').element.parentNode;
+
+ expect(additions.textContent).toContain('100');
+ expect(deletions.textContent).toContain('200');
+ expect(filesChanged.textContent).toContain('300');
+ });
+});
diff --git a/spec/javascripts/diffs/components/tree_list_spec.js b/spec/javascripts/diffs/components/tree_list_spec.js
index c5ef48a81e9..9e556698f34 100644
--- a/spec/javascripts/diffs/components/tree_list_spec.js
+++ b/spec/javascripts/diffs/components/tree_list_spec.js
@@ -35,12 +35,6 @@ describe('Diffs tree list component', () => {
vm.$destroy();
});
- it('renders diff stats', () => {
- expect(vm.$el.textContent).toContain('1 changed file');
- expect(vm.$el.textContent).toContain('10 additions');
- expect(vm.$el.textContent).toContain('20 deletions');
- });
-
it('renders empty text', () => {
expect(vm.$el.textContent).toContain('No files found');
});
diff --git a/spec/javascripts/environments/environment_item_spec.js b/spec/javascripts/environments/environment_item_spec.js
index 7618c2f50ce..a89e50045da 100644
--- a/spec/javascripts/environments/environment_item_spec.js
+++ b/spec/javascripts/environments/environment_item_spec.js
@@ -25,7 +25,6 @@ describe('Environment item', () => {
component = new EnvironmentItem({
propsData: {
model: mockItem,
- canCreateDeployment: false,
canReadEnvironment: true,
service: {},
},
@@ -117,7 +116,6 @@ describe('Environment item', () => {
component = new EnvironmentItem({
propsData: {
model: environment,
- canCreateDeployment: true,
canReadEnvironment: true,
service: {},
},
diff --git a/spec/javascripts/environments/environment_table_spec.js b/spec/javascripts/environments/environment_table_spec.js
index 0e5e50a59a5..52895f35f3a 100644
--- a/spec/javascripts/environments/environment_table_spec.js
+++ b/spec/javascripts/environments/environment_table_spec.js
@@ -26,7 +26,6 @@ describe('Environment table', () => {
vm = mountComponent(Component, {
environments: [mockItem],
- canCreateDeployment: false,
canReadEnvironment: true,
});
diff --git a/spec/javascripts/environments/environments_app_spec.js b/spec/javascripts/environments/environments_app_spec.js
index e2d81eb454a..9220f7a264f 100644
--- a/spec/javascripts/environments/environments_app_spec.js
+++ b/spec/javascripts/environments/environments_app_spec.js
@@ -9,7 +9,6 @@ describe('Environment', () => {
const mockData = {
endpoint: 'environments.json',
canCreateEnvironment: true,
- canCreateDeployment: true,
canReadEnvironment: true,
cssContainerClass: 'container',
newEnvironmentPath: 'environments/new',
diff --git a/spec/javascripts/environments/folder/environments_folder_view_spec.js b/spec/javascripts/environments/folder/environments_folder_view_spec.js
index 7f0a9475d5f..d9ee7e74e28 100644
--- a/spec/javascripts/environments/folder/environments_folder_view_spec.js
+++ b/spec/javascripts/environments/folder/environments_folder_view_spec.js
@@ -13,7 +13,6 @@ describe('Environments Folder View', () => {
const mockData = {
endpoint: 'environments.json',
folderName: 'review',
- canCreateDeployment: true,
canReadEnvironment: true,
cssContainerClass: 'container',
};
diff --git a/spec/javascripts/helpers/vue_test_utils_helper.js b/spec/javascripts/helpers/vue_test_utils_helper.js
new file mode 100644
index 00000000000..19e27388eeb
--- /dev/null
+++ b/spec/javascripts/helpers/vue_test_utils_helper.js
@@ -0,0 +1,19 @@
+/* eslint-disable import/prefer-default-export */
+
+const vNodeContainsText = (vnode, text) =>
+ (vnode.text && vnode.text.includes(text)) ||
+ (vnode.children && vnode.children.filter(child => vNodeContainsText(child, text)).length);
+
+/**
+ * Determines whether a `shallowMount` Wrapper contains text
+ * within one of it's slots. This will also work on Wrappers
+ * acquired with `find()`, but only if it's parent Wrapper
+ * was shallowMounted.
+ * NOTE: Prefer checking the rendered output of a component
+ * wherever possible using something like `text()` instead.
+ * @param {Wrapper} shallowWrapper - Vue test utils wrapper (shallowMounted)
+ * @param {String} slotName
+ * @param {String} text
+ */
+export const shallowWrapperContainsSlotText = (shallowWrapper, slotName, text) =>
+ !!shallowWrapper.vm.$slots[slotName].filter(vnode => vNodeContainsText(vnode, text)).length;
diff --git a/spec/javascripts/helpers/vue_test_utils_helper_spec.js b/spec/javascripts/helpers/vue_test_utils_helper_spec.js
new file mode 100644
index 00000000000..41714066da5
--- /dev/null
+++ b/spec/javascripts/helpers/vue_test_utils_helper_spec.js
@@ -0,0 +1,48 @@
+import { shallowMount } from '@vue/test-utils';
+import { shallowWrapperContainsSlotText } from './vue_test_utils_helper';
+
+describe('Vue test utils helpers', () => {
+ describe('shallowWrapperContainsSlotText', () => {
+ const mockText = 'text';
+ const mockSlot = `<div>${mockText}</div>`;
+ let mockComponent;
+
+ beforeEach(() => {
+ mockComponent = shallowMount(
+ {
+ render(h) {
+ h(`<div>mockedComponent</div>`);
+ },
+ },
+ {
+ slots: {
+ default: mockText,
+ namedSlot: mockSlot,
+ },
+ },
+ );
+ });
+
+ it('finds text within shallowWrapper default slot', () => {
+ expect(shallowWrapperContainsSlotText(mockComponent, 'default', mockText)).toBe(true);
+ });
+
+ it('finds text within shallowWrapper named slot', () => {
+ expect(shallowWrapperContainsSlotText(mockComponent, 'namedSlot', mockText)).toBe(true);
+ });
+
+ it('returns false when text is not present', () => {
+ const searchText = 'absent';
+
+ expect(shallowWrapperContainsSlotText(mockComponent, 'default', searchText)).toBe(false);
+ expect(shallowWrapperContainsSlotText(mockComponent, 'namedSlot', searchText)).toBe(false);
+ });
+
+ it('searches with case-sensitivity', () => {
+ const searchText = mockText.toUpperCase();
+
+ expect(shallowWrapperContainsSlotText(mockComponent, 'default', searchText)).toBe(false);
+ expect(shallowWrapperContainsSlotText(mockComponent, 'namedSlot', searchText)).toBe(false);
+ });
+ });
+});
diff --git a/spec/javascripts/issue_show/components/description_spec.js b/spec/javascripts/issue_show/components/description_spec.js
index 72716b97f5f..2eeed6770be 100644
--- a/spec/javascripts/issue_show/components/description_spec.js
+++ b/spec/javascripts/issue_show/components/description_spec.js
@@ -21,7 +21,8 @@ describe('Description component', () => {
if (!document.querySelector('.issuable-meta')) {
const metaData = document.createElement('div');
metaData.classList.add('issuable-meta');
- metaData.innerHTML = '<span id="task_status"></span><span id="task_status_short"></span>';
+ metaData.innerHTML =
+ '<div class="flash-container"></div><span id="task_status"></span><span id="task_status_short"></span>';
document.body.appendChild(metaData);
}
@@ -33,6 +34,10 @@ describe('Description component', () => {
vm.$destroy();
});
+ afterAll(() => {
+ $('.issuable-meta .flash-container').remove();
+ });
+
it('animates description changes', done => {
vm.descriptionHtml = 'changed';
@@ -192,12 +197,11 @@ describe('Description component', () => {
it('should create flash notification and emit an event to parent', () => {
const msg =
'Someone edited this issue at the same time you did. The description has been updated and you will need to make your changes again.';
- spyOn(window, 'Flash');
spyOn(vm, '$emit');
vm.taskListUpdateError();
- expect(window.Flash).toHaveBeenCalledWith(msg);
+ expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(msg);
expect(vm.$emit).toHaveBeenCalledWith('taskListUpdateFailed');
});
});
diff --git a/spec/javascripts/lib/utils/file_upload_spec.js b/spec/javascripts/lib/utils/file_upload_spec.js
index 92c9cc70aaf..8f7092f63de 100644
--- a/spec/javascripts/lib/utils/file_upload_spec.js
+++ b/spec/javascripts/lib/utils/file_upload_spec.js
@@ -9,28 +9,56 @@ describe('File upload', () => {
<span class="js-filename"></span>
</form>
`);
+ });
+
+ describe('when there is a matching button and input', () => {
+ beforeEach(() => {
+ fileUpload('.js-button', '.js-input');
+ });
+
+ it('clicks file input after clicking button', () => {
+ const btn = document.querySelector('.js-button');
+ const input = document.querySelector('.js-input');
+
+ spyOn(input, 'click');
+
+ btn.click();
+
+ expect(input.click).toHaveBeenCalled();
+ });
+
+ it('updates file name text', () => {
+ const input = document.querySelector('.js-input');
- fileUpload('.js-button', '.js-input');
+ input.value = 'path/to/file/index.js';
+
+ input.dispatchEvent(new CustomEvent('change'));
+
+ expect(document.querySelector('.js-filename').textContent).toEqual('index.js');
+ });
});
- it('clicks file input after clicking button', () => {
- const btn = document.querySelector('.js-button');
+ it('fails gracefully when there is no matching button', () => {
const input = document.querySelector('.js-input');
+ const btn = document.querySelector('.js-button');
+ fileUpload('.js-not-button', '.js-input');
spyOn(input, 'click');
btn.click();
- expect(input.click).toHaveBeenCalled();
+ expect(input.click).not.toHaveBeenCalled();
});
- it('updates file name text', () => {
+ it('fails gracefully when there is no matching input', () => {
const input = document.querySelector('.js-input');
+ const btn = document.querySelector('.js-button');
+ fileUpload('.js-button', '.js-not-input');
- input.value = 'path/to/file/index.js';
+ spyOn(input, 'click');
- input.dispatchEvent(new CustomEvent('change'));
+ btn.click();
- expect(document.querySelector('.js-filename').textContent).toEqual('index.js');
+ expect(input.click).not.toHaveBeenCalled();
});
});
diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js
index 32623d1781a..ab809930804 100644
--- a/spec/javascripts/merge_request_spec.js
+++ b/spec/javascripts/merge_request_spec.js
@@ -40,30 +40,51 @@ describe('MergeRequest', function() {
expect($('.js-task-list-field').val()).toBe('- [x] Task List Item');
});
- it('submits an ajax request on tasklist:changed', done => {
+ describe('tasklist', () => {
const lineNumber = 8;
const lineSource = '- [ ] item 8';
const index = 3;
const checked = true;
- $('.js-task-list-field').trigger({
- type: 'tasklist:changed',
- detail: { lineNumber, lineSource, index, checked },
+ it('submits an ajax request on tasklist:changed', done => {
+ $('.js-task-list-field').trigger({
+ type: 'tasklist:changed',
+ detail: { lineNumber, lineSource, index, checked },
+ });
+
+ setTimeout(() => {
+ expect(axios.patch).toHaveBeenCalledWith(
+ `${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`,
+ {
+ merge_request: {
+ description: '- [ ] Task List Item',
+ lock_version: undefined,
+ update_task: { line_number: lineNumber, line_source: lineSource, index, checked },
+ },
+ },
+ );
+
+ done();
+ });
});
- setTimeout(() => {
- expect(axios.patch).toHaveBeenCalledWith(
- `${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`,
- {
- merge_request: {
- description: '- [ ] Task List Item',
- lock_version: undefined,
- update_task: { line_number: lineNumber, line_source: lineSource, index, checked },
- },
- },
- );
+ it('shows an error notification when tasklist update failed', done => {
+ mock
+ .onPatch(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`)
+ .reply(409, {});
+
+ $('.js-task-list-field').trigger({
+ type: 'tasklist:changed',
+ detail: { lineNumber, lineSource, index, checked },
+ });
+
+ setTimeout(() => {
+ expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
+ 'Someone edited this merge request at the same time you did. Please refresh the page to see changes.',
+ );
- done();
+ done();
+ });
});
});
});
diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js
new file mode 100644
index 00000000000..0b36fc9f5f7
--- /dev/null
+++ b/spec/javascripts/monitoring/charts/area_spec.js
@@ -0,0 +1,220 @@
+import { shallowMount } from '@vue/test-utils';
+import { GlAreaChart } from '@gitlab/ui/dist/charts';
+import { shallowWrapperContainsSlotText } from 'spec/helpers/vue_test_utils_helper';
+import Area from '~/monitoring/components/charts/area.vue';
+import MonitoringStore from '~/monitoring/stores/monitoring_store';
+import MonitoringMock, { deploymentData } from '../mock_data';
+
+describe('Area component', () => {
+ const mockWidgets = 'mockWidgets';
+ let mockGraphData;
+ let areaChart;
+ let spriteSpy;
+
+ beforeEach(() => {
+ const store = new MonitoringStore();
+ store.storeMetrics(MonitoringMock.data);
+ store.storeDeploymentData(deploymentData);
+
+ [mockGraphData] = store.groups[0].metrics;
+
+ areaChart = shallowMount(Area, {
+ propsData: {
+ graphData: mockGraphData,
+ containerWidth: 0,
+ deploymentData: store.deploymentData,
+ },
+ slots: {
+ default: mockWidgets,
+ },
+ });
+
+ spriteSpy = spyOnDependency(Area, 'getSvgIconPathContent').and.callFake(
+ () => new Promise(resolve => resolve()),
+ );
+ });
+
+ afterEach(() => {
+ areaChart.destroy();
+ });
+
+ it('renders chart title', () => {
+ expect(areaChart.find({ ref: 'graphTitle' }).text()).toBe(mockGraphData.title);
+ });
+
+ it('contains graph widgets from slot', () => {
+ expect(areaChart.find({ ref: 'graphWidgets' }).text()).toBe(mockWidgets);
+ });
+
+ describe('wrapped components', () => {
+ describe('GitLab UI area chart', () => {
+ let glAreaChart;
+
+ beforeEach(() => {
+ glAreaChart = areaChart.find(GlAreaChart);
+ });
+
+ it('is a Vue instance', () => {
+ expect(glAreaChart.isVueInstance()).toBe(true);
+ });
+
+ it('receives data properties needed for proper chart render', () => {
+ const props = glAreaChart.props();
+
+ expect(props.data).toBe(areaChart.vm.chartData);
+ expect(props.option).toBe(areaChart.vm.chartOptions);
+ expect(props.formatTooltipText).toBe(areaChart.vm.formatTooltipText);
+ expect(props.thresholds).toBe(areaChart.props('alertData'));
+ });
+
+ it('recieves a tooltip title', () => {
+ const mockTitle = 'mockTitle';
+ areaChart.vm.tooltip.title = mockTitle;
+
+ expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipTitle', mockTitle)).toBe(true);
+ });
+
+ it('recieves tooltip content', () => {
+ const mockContent = 'mockContent';
+ areaChart.vm.tooltip.content = mockContent;
+
+ expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipContent', mockContent)).toBe(
+ true,
+ );
+ });
+
+ describe('when tooltip is showing deployment data', () => {
+ beforeEach(() => {
+ areaChart.vm.tooltip.isDeployment = true;
+ });
+
+ it('uses deployment title', () => {
+ expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipTitle', 'Deployed')).toBe(
+ true,
+ );
+ });
+
+ it('renders commit sha in tooltip content', () => {
+ const mockSha = 'mockSha';
+ areaChart.vm.tooltip.sha = mockSha;
+
+ expect(shallowWrapperContainsSlotText(glAreaChart, 'tooltipContent', mockSha)).toBe(true);
+ });
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('formatTooltipText', () => {
+ const mockDate = deploymentData[0].created_at;
+ const generateSeriesData = type => ({
+ seriesData: [
+ {
+ componentSubType: type,
+ value: [mockDate, 5.55555],
+ },
+ ],
+ value: mockDate,
+ });
+
+ describe('series is of line type', () => {
+ beforeEach(() => {
+ areaChart.vm.formatTooltipText(generateSeriesData('line'));
+ });
+
+ it('formats tooltip title', () => {
+ expect(areaChart.vm.tooltip.title).toBe('31 May 2017, 9:23PM');
+ });
+
+ it('formats tooltip content', () => {
+ expect(areaChart.vm.tooltip.content).toBe('CPU (Cores) 5.556');
+ });
+ });
+
+ describe('series is of scatter type', () => {
+ beforeEach(() => {
+ areaChart.vm.formatTooltipText(generateSeriesData('scatter'));
+ });
+
+ it('formats tooltip title', () => {
+ expect(areaChart.vm.tooltip.title).toBe('31 May 2017, 9:23PM');
+ });
+
+ it('formats tooltip sha', () => {
+ expect(areaChart.vm.tooltip.sha).toBe('f5bcd1d9');
+ });
+ });
+ });
+
+ describe('getScatterSymbol', () => {
+ beforeEach(() => {
+ areaChart.vm.getScatterSymbol();
+ });
+
+ it('gets rocket svg path content for use as deployment data symbol', () => {
+ expect(spriteSpy).toHaveBeenCalledWith('rocket');
+ });
+ });
+
+ describe('onResize', () => {
+ const mockWidth = 233;
+ const mockHeight = 144;
+
+ beforeEach(() => {
+ spyOn(Element.prototype, 'getBoundingClientRect').and.callFake(() => ({
+ width: mockWidth,
+ height: mockHeight,
+ }));
+ areaChart.vm.onResize();
+ });
+
+ it('sets area chart width', () => {
+ expect(areaChart.vm.width).toBe(mockWidth);
+ });
+
+ it('sets area chart height', () => {
+ expect(areaChart.vm.height).toBe(mockHeight);
+ });
+ });
+ });
+
+ describe('computed', () => {
+ describe('chartData', () => {
+ it('utilizes all data points', () => {
+ expect(Object.keys(areaChart.vm.chartData)).toEqual(['Cores']);
+ expect(areaChart.vm.chartData.Cores.length).toBe(297);
+ });
+
+ it('creates valid data', () => {
+ const data = areaChart.vm.chartData.Cores;
+
+ expect(
+ data.filter(([time, value]) => new Date(time).getTime() > 0 && typeof value === 'number')
+ .length,
+ ).toBe(data.length);
+ });
+ });
+
+ describe('scatterSeries', () => {
+ it('utilizes deployment data', () => {
+ expect(areaChart.vm.scatterSeries.data).toEqual([
+ ['2017-05-31T21:23:37.881Z', 0],
+ ['2017-05-30T20:08:04.629Z', 0],
+ ['2017-05-30T17:42:38.409Z', 0],
+ ]);
+ });
+ });
+
+ describe('xAxisLabel', () => {
+ it('constructs a label for the chart x-axis', () => {
+ expect(areaChart.vm.xAxisLabel).toBe('Core Usage');
+ });
+ });
+
+ describe('yAxisLabel', () => {
+ it('constructs a label for the chart y-axis', () => {
+ expect(areaChart.vm.yAxisLabel).toBe('CPU (Cores)');
+ });
+ });
+ });
+});
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js
index 97b9671c809..b1778029a77 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/dashboard_spec.js
@@ -25,15 +25,22 @@ export default propsData;
describe('Dashboard', () => {
let DashboardComponent;
+ let mock;
beforeEach(() => {
setFixtures(`
<div class="prometheus-graphs"></div>
<div class="layout-page"></div>
`);
+
+ mock = new MockAdapter(axios);
DashboardComponent = Vue.extend(Dashboard);
});
+ afterEach(() => {
+ mock.restore();
+ });
+
describe('no metrics are available yet', () => {
it('shows a getting started empty state when no metrics are present', () => {
const component = new DashboardComponent({
@@ -47,16 +54,10 @@ describe('Dashboard', () => {
});
describe('requests information to the server', () => {
- let mock;
beforeEach(() => {
- mock = new MockAdapter(axios);
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
});
- afterEach(() => {
- mock.restore();
- });
-
it('shows up a loading state', done => {
const component = new DashboardComponent({
el: document.querySelector('.prometheus-graphs'),
@@ -152,15 +153,12 @@ describe('Dashboard', () => {
});
describe('when the window resizes', () => {
- let mock;
beforeEach(() => {
- mock = new MockAdapter(axios);
mock.onGet(mockApiEndpoint).reply(200, metricsGroupsAPIResponse);
jasmine.clock().install();
});
afterEach(() => {
- mock.restore();
jasmine.clock().uninstall();
});
diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js
index b4e2cd75d47..ffc7148fde2 100644
--- a/spec/javascripts/monitoring/mock_data.js
+++ b/spec/javascripts/monitoring/mock_data.js
@@ -326,6 +326,7 @@ export const metricsGroupsAPIResponse = {
{
id: 6,
title: 'CPU usage',
+ y_label: 'CPU',
weight: 1,
queries: [
{
diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js
index 7ae45c40c28..348743081eb 100644
--- a/spec/javascripts/notes/mock_data.js
+++ b/spec/javascripts/notes/mock_data.js
@@ -165,7 +165,6 @@ export const note = {
report_abuse_path:
'/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_546&user_id=1',
path: '/gitlab-org/gitlab-ce/notes/546',
- cached_markdown_version: 11,
};
export const discussionMock = {
diff --git a/spec/javascripts/vue_mr_widget/components/states/commit_edit_spec.js b/spec/javascripts/vue_mr_widget/components/states/commit_edit_spec.js
new file mode 100644
index 00000000000..994d6255324
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/states/commit_edit_spec.js
@@ -0,0 +1,85 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue';
+
+const localVue = createLocalVue();
+const testCommitMessage = 'Test commit message';
+const testLabel = 'Test label';
+const testInputId = 'test-input-id';
+
+describe('Commits edit component', () => {
+ let wrapper;
+
+ const createComponent = (slots = {}) => {
+ wrapper = shallowMount(localVue.extend(CommitEdit), {
+ localVue,
+ sync: false,
+ propsData: {
+ value: testCommitMessage,
+ label: testLabel,
+ inputId: testInputId,
+ },
+ slots: {
+ ...slots,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findTextarea = () => wrapper.find('.form-control');
+
+ it('has a correct label', () => {
+ const labelElement = wrapper.find('.col-form-label');
+
+ expect(labelElement.text()).toBe(testLabel);
+ });
+
+ describe('textarea', () => {
+ it('has a correct ID', () => {
+ expect(findTextarea().attributes('id')).toBe(testInputId);
+ });
+
+ it('has a correct value', () => {
+ expect(findTextarea().element.value).toBe(testCommitMessage);
+ });
+
+ it('emits an input event and receives changed value', () => {
+ const changedCommitMessage = 'Changed commit message';
+
+ findTextarea().element.value = changedCommitMessage;
+ findTextarea().trigger('input');
+
+ expect(wrapper.emitted().input[0]).toEqual([changedCommitMessage]);
+ expect(findTextarea().element.value).toBe(changedCommitMessage);
+ });
+ });
+
+ describe('when slots are present', () => {
+ beforeEach(() => {
+ createComponent({
+ header: `<div class="test-header">${testCommitMessage}</div>`,
+ checkbox: `<label slot="checkbox" class="test-checkbox">${testLabel}</label >`,
+ });
+ });
+
+ it('renders header slot correctly', () => {
+ const headerSlotElement = wrapper.find('.test-header');
+
+ expect(headerSlotElement.exists()).toBe(true);
+ expect(headerSlotElement.text()).toBe(testCommitMessage);
+ });
+
+ it('renders checkbox slot correctly', () => {
+ const checkboxSlotElement = wrapper.find('.test-checkbox');
+
+ expect(checkboxSlotElement.exists()).toBe(true);
+ expect(checkboxSlotElement.text()).toBe(testLabel);
+ });
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js
new file mode 100644
index 00000000000..daf1cc2d98b
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_commit_message_dropdown_spec.js
@@ -0,0 +1,61 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import { GlDropdownItem } from '@gitlab/ui';
+import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue';
+
+const localVue = createLocalVue();
+const commits = [
+ {
+ title: 'Commit 1',
+ short_id: '78d5b7',
+ message: 'Update test.txt',
+ },
+ {
+ title: 'Commit 2',
+ short_id: '34cbe28b',
+ message: 'Fixed test',
+ },
+ {
+ title: 'Commit 3',
+ short_id: 'fa42932a',
+ message: 'Added changelog',
+ },
+];
+
+describe('Commits message dropdown component', () => {
+ let wrapper;
+
+ const createComponent = () => {
+ wrapper = shallowMount(localVue.extend(CommitMessageDropdown), {
+ localVue,
+ sync: false,
+ propsData: {
+ commits,
+ },
+ });
+ };
+
+ beforeEach(() => {
+ createComponent();
+ });
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findDropdownElements = () => wrapper.findAll(GlDropdownItem);
+ const findFirstDropdownElement = () => findDropdownElements().at(0);
+
+ it('should have 3 elements in dropdown list', () => {
+ expect(findDropdownElements().length).toBe(3);
+ });
+
+ it('should have correct message for the first dropdown list element', () => {
+ expect(findFirstDropdownElement().text()).toBe('78d5b7 Commit 1');
+ });
+
+ it('should emit a commit title on selecting commit', () => {
+ findFirstDropdownElement().vm.$emit('click');
+
+ expect(wrapper.emitted().input[0]).toEqual(['Update test.txt']);
+ });
+});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_commits_header_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_commits_header_spec.js
new file mode 100644
index 00000000000..5cf6408cf34
--- /dev/null
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_commits_header_spec.js
@@ -0,0 +1,110 @@
+import { createLocalVue, shallowMount } from '@vue/test-utils';
+import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue';
+import Icon from '~/vue_shared/components/icon.vue';
+
+const localVue = createLocalVue();
+
+describe('Commits header component', () => {
+ let wrapper;
+
+ const createComponent = props => {
+ wrapper = shallowMount(localVue.extend(CommitsHeader), {
+ localVue,
+ sync: false,
+ propsData: {
+ isSquashEnabled: false,
+ targetBranch: 'master',
+ commitsCount: 5,
+ ...props,
+ },
+ });
+ };
+
+ afterEach(() => {
+ wrapper.destroy();
+ });
+
+ const findHeaderWrapper = () => wrapper.find('.js-mr-widget-commits-count');
+ const findCommitToggle = () => wrapper.find('.commit-edit-toggle');
+ const findIcon = () => wrapper.find(Icon);
+ const findCommitsCountMessage = () => wrapper.find('.commits-count-message');
+ const findTargetBranchMessage = () => wrapper.find('.label-branch');
+ const findModifyButton = () => wrapper.find('.modify-message-button');
+
+ describe('when collapsed', () => {
+ it('toggle has aria-label equal to Expand', () => {
+ createComponent();
+
+ expect(findCommitToggle().attributes('aria-label')).toBe('Expand');
+ });
+
+ it('has a chevron-right icon', () => {
+ createComponent();
+ wrapper.setData({ expanded: false });
+
+ expect(findIcon().props('name')).toBe('chevron-right');
+ });
+
+ describe('when squash is disabled', () => {
+ beforeEach(() => {
+ createComponent();
+ });
+
+ it('has commits count message showing correct amount of commits', () => {
+ expect(findCommitsCountMessage().text()).toBe('5 commits');
+ });
+
+ it('has button with modify merge commit message', () => {
+ expect(findModifyButton().text()).toBe('Modify merge commit');
+ });
+ });
+
+ describe('when squash is enabled', () => {
+ beforeEach(() => {
+ createComponent({ isSquashEnabled: true });
+ });
+
+ it('has commits count message showing one commit when squash is enabled', () => {
+ expect(findCommitsCountMessage().text()).toBe('1 commit');
+ });
+
+ it('has button with modify commit messages text', () => {
+ expect(findModifyButton().text()).toBe('Modify commit messages');
+ });
+ });
+
+ it('has correct target branch displayed', () => {
+ createComponent();
+
+ expect(findTargetBranchMessage().text()).toBe('master');
+ });
+ });
+
+ describe('when expanded', () => {
+ beforeEach(() => {
+ createComponent();
+ wrapper.setData({ expanded: true });
+ });
+
+ it('toggle has aria-label equal to collapse', done => {
+ wrapper.vm.$nextTick(() => {
+ expect(findCommitToggle().attributes('aria-label')).toBe('Collapse');
+ done();
+ });
+ });
+
+ it('has a chevron-down icon', done => {
+ wrapper.vm.$nextTick(() => {
+ expect(findIcon().props('name')).toBe('chevron-down');
+ done();
+ });
+ });
+
+ it('has a collapse text', done => {
+ wrapper.vm.$nextTick(() => {
+ expect(findHeaderWrapper().text()).toBe('Collapse');
+ done();
+ });
+ });
+ });
+});
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 e387367d1a2..631da202d1d 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,10 +1,14 @@
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 CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue';
+import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue';
+import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.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 squashCommitMessage = 'This is the squash commit message';
const commitMessageWithDescription = 'This is the commit message description';
const createTestMr = customConfig => {
const mr = {
@@ -19,9 +23,11 @@ const createTestMr = customConfig => {
sha: '12345678',
squash: false,
commitMessage,
+ squashCommitMessage,
commitMessageWithDescription,
shouldRemoveSourceBranch: true,
canRemoveSourceBranch: false,
+ targetBranch: 'master',
};
Object.assign(mr, customConfig.mr);
@@ -98,21 +104,6 @@ describe('ReadyToMerge', () => {
});
});
- describe('commitMessageLinkTitle', () => {
- const withDesc = 'Include description in commit message';
- const withoutDesc = "Don't include description in commit message";
-
- it('should return message with description', () => {
- expect(vm.commitMessageLinkTitle).toEqual(withDesc);
- });
-
- it('should return message without description', () => {
- vm.useCommitMessageWithDescription = true;
-
- expect(vm.commitMessageLinkTitle).toEqual(withoutDesc);
- });
- });
-
describe('status', () => {
it('defaults to success', () => {
vm.mr.pipeline = true;
@@ -279,55 +270,43 @@ describe('ReadyToMerge', () => {
vm.mr.isMergeAllowed = false;
vm.mr.isPipelineActive = false;
- expect(vm.shouldShowMergeControls()).toBeFalsy();
+ expect(vm.shouldShowMergeControls).toBeFalsy();
});
it('should return true when the build succeeded or build not required to succeed', () => {
vm.mr.isMergeAllowed = true;
vm.mr.isPipelineActive = false;
- expect(vm.shouldShowMergeControls()).toBeTruthy();
+ expect(vm.shouldShowMergeControls).toBeTruthy();
});
it('should return true when showing the MWPS button and a pipeline is running that needs to be successful', () => {
vm.mr.isMergeAllowed = false;
vm.mr.isPipelineActive = true;
- expect(vm.shouldShowMergeControls()).toBeTruthy();
+ expect(vm.shouldShowMergeControls).toBeTruthy();
});
it('should return true when showing the MWPS button but not required for the pipeline to succeed', () => {
vm.mr.isMergeAllowed = true;
vm.mr.isPipelineActive = true;
- expect(vm.shouldShowMergeControls()).toBeTruthy();
+ expect(vm.shouldShowMergeControls).toBeTruthy();
});
});
- describe('updateCommitMessage', () => {
+ describe('updateMergeCommitMessage', () => {
it('should revert flag and change commitMessage', () => {
- expect(vm.useCommitMessageWithDescription).toBeFalsy();
expect(vm.commitMessage).toEqual(commitMessage);
- vm.updateCommitMessage();
+ vm.updateMergeCommitMessage(true);
- expect(vm.useCommitMessageWithDescription).toBeTruthy();
expect(vm.commitMessage).toEqual(commitMessageWithDescription);
- vm.updateCommitMessage();
+ vm.updateMergeCommitMessage(false);
- expect(vm.useCommitMessageWithDescription).toBeFalsy();
expect(vm.commitMessage).toEqual(commitMessage);
});
});
- describe('toggleCommitMessageEditor', () => {
- it('should toggle showCommitMessageEditor flag', () => {
- expect(vm.showCommitMessageEditor).toBeFalsy();
- vm.toggleCommitMessageEditor();
-
- expect(vm.showCommitMessageEditor).toBeTruthy();
- });
- });
-
describe('handleMergeButtonClick', () => {
const returnPromise = status =>
new Promise(resolve => {
@@ -623,7 +602,7 @@ describe('ReadyToMerge', () => {
});
});
- describe('Squash checkbox component', () => {
+ describe('render children components', () => {
let wrapper;
const localVue = createLocalVue();
@@ -642,25 +621,101 @@ describe('ReadyToMerge', () => {
});
const findCheckboxElement = () => wrapper.find(SquashBeforeMerge);
+ const findCommitsHeaderElement = () => wrapper.find(CommitsHeader);
+ const findCommitEditElements = () => wrapper.findAll(CommitEdit);
+ const findCommitDropdownElement = () => wrapper.find(CommitMessageDropdown);
+
+ describe('squash checkbox', () => {
+ it('should be rendered when squash before merge is enabled and there is more than 1 commit', () => {
+ createLocalComponent({
+ mr: { commitsCount: 2, enableSquashBeforeMerge: true },
+ });
- 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();
});
- 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();
+ });
});
- it('should not be rendered when squash before merge is disabled', () => {
- createLocalComponent({ mr: { commitsCount: 2, enableSquashBeforeMerge: false } });
+ describe('commits count collapsible header', () => {
+ it('should be rendered if fast-forward is disabled', () => {
+ createLocalComponent();
- expect(findCheckboxElement().exists()).toBeFalsy();
+ expect(findCommitsHeaderElement().exists()).toBeTruthy();
+ });
+
+ it('should not be rendered if fast-forward is enabled', () => {
+ createLocalComponent({ mr: { ffOnlyEnabled: true } });
+
+ expect(findCommitsHeaderElement().exists()).toBeFalsy();
+ });
});
- it('should not be rendered when there is only 1 commit', () => {
- createLocalComponent({ mr: { commitsCount: 1, enableSquashBeforeMerge: true } });
+ describe('commits edit components', () => {
+ it('should have one edit component when squash is disabled', () => {
+ createLocalComponent();
+
+ expect(findCommitEditElements().length).toBe(1);
+ });
- expect(findCheckboxElement().exists()).toBeFalsy();
+ const findFirstCommitEditLabel = () =>
+ findCommitEditElements()
+ .at(0)
+ .props('label');
+
+ it('should have two edit components when squash is enabled', () => {
+ createLocalComponent({
+ mr: {
+ commitsCount: 2,
+ squash: true,
+ enableSquashBeforeMerge: true,
+ },
+ });
+
+ expect(findCommitEditElements().length).toBe(2);
+ });
+
+ it('should have correct edit merge commit label', () => {
+ createLocalComponent();
+
+ expect(findFirstCommitEditLabel()).toBe('Merge commit message');
+ });
+
+ it('should have correct edit squash commit label', () => {
+ createLocalComponent({
+ mr: {
+ commitsCount: 2,
+ squash: true,
+ enableSquashBeforeMerge: true,
+ },
+ });
+
+ expect(findFirstCommitEditLabel()).toBe('Squash commit message');
+ });
+ });
+
+ describe('commits dropdown', () => {
+ it('should not be rendered if squash is disabled', () => {
+ createLocalComponent();
+
+ expect(findCommitDropdownElement().exists()).toBeFalsy();
+ });
+
+ it('should be rendered if squash is enabled', () => {
+ createLocalComponent({ mr: { squash: true } });
+
+ expect(findCommitDropdownElement().exists()).toBeTruthy();
+ });
});
});
@@ -696,10 +751,6 @@ describe('ReadyToMerge', () => {
expect(vm.$el.querySelector('.js-remove-source-branch-checkbox')).toBeNull();
});
- it('does not show modify commit message button', () => {
- expect(vm.$el.querySelector('.js-modify-commit-message-button')).toBeNull();
- });
-
it('shows message to resolve all items before being allowed to merge', () => {
expect(vm.$el.querySelector('.js-resolve-mr-widget-items-message')).toBeDefined();
});
@@ -712,7 +763,7 @@ describe('ReadyToMerge', () => {
mr: { ffOnlyEnabled: false },
});
- expect(customVm.$el.querySelector('.js-fast-forward-message')).toBeNull();
+ expect(customVm.$el.querySelector('.mr-fast-forward-message')).toBeNull();
expect(customVm.$el.querySelector('.js-modify-commit-message-button')).toBeDefined();
});
@@ -721,7 +772,7 @@ describe('ReadyToMerge', () => {
mr: { ffOnlyEnabled: true },
});
- expect(customVm.$el.querySelector('.js-fast-forward-message')).toBeDefined();
+ expect(customVm.$el.querySelector('.mr-fast-forward-message')).toBeDefined();
expect(customVm.$el.querySelector('.js-modify-commit-message-button')).toBeNull();
});
});
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index 75b197fb2ba..6ef07f81705 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -215,12 +215,14 @@ export default {
project_archived: false,
default_merge_commit_message_with_description:
"Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22",
+ default_squash_commit_message: 'Test squash commit message',
diverged_commits_count: 0,
only_allow_merge_if_pipeline_succeeds: false,
commit_change_content_path: '/root/acets-app/merge_requests/22/commit_change_content',
merge_commit_path:
'http://localhost:3000/root/acets-app/commit/53027d060246c8f47e4a9310fb332aa52f221775',
troubleshooting_docs_path: 'help',
+ squash: true,
};
export const mockStore = {
diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb
index 209a547c3b3..3b52f6666d0 100644
--- a/spec/lib/banzai/object_renderer_spec.rb
+++ b/spec/lib/banzai/object_renderer_spec.rb
@@ -11,7 +11,7 @@ describe Banzai::ObjectRenderer do
)
end
- let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION) }
+ let(:object) { Note.new(note: 'hello', note_html: '<p dir="auto">hello</p>', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16) }
describe '#render' do
context 'with cache' do
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 236808c0b69..a4a6338961e 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -19,7 +19,7 @@ describe Gitlab::Auth do
it 'optional_scopes contains all non-default scopes' do
stub_container_registry_config(enabled: true)
- expect(subject.optional_scopes).to eq %i[read_user sudo read_repository read_registry openid]
+ expect(subject.optional_scopes).to eq %i[read_user sudo read_repository read_registry openid profile email]
end
context 'registry_scopes' do
diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
index afd8f5da39f..a07c5371134 100644
--- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb
+++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb
@@ -61,7 +61,7 @@ describe ::Gitlab::BareRepositoryImport::Repository do
let(:wiki_path) { File.join(root_path, "#{hashed_path}.wiki.git") }
before do
- gitlab_shell.create_repository(repository_storage, hashed_path)
+ gitlab_shell.create_repository(repository_storage, hashed_path, 'group/project')
Gitlab::GitalyClient::StorageSettings.allow_disk_access do
repository = Rugged::Repository.new(repo_path)
repository.config['gitlab.fullpath'] = 'to/repo'
diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
index 0def685f177..c432cc223b9 100644
--- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb
@@ -218,7 +218,7 @@ describe Gitlab::BitbucketImport::Importer do
describe 'wiki import' do
it 'is skipped when the wiki exists' do
expect(project.wiki).to receive(:repository_exists?) { true }
- expect(importer.gitlab_shell).not_to receive(:import_repository)
+ expect(importer.gitlab_shell).not_to receive(:import_wiki_repository)
importer.execute
@@ -227,11 +227,7 @@ describe Gitlab::BitbucketImport::Importer do
it 'imports to the project disk_path' do
expect(project.wiki).to receive(:repository_exists?) { false }
- expect(importer.gitlab_shell).to receive(:import_repository).with(
- project.repository_storage,
- project.wiki.disk_path,
- project.import_url + '/wiki'
- )
+ expect(importer.gitlab_shell).to receive(:import_wiki_repository)
importer.execute
diff --git a/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb b/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb
new file mode 100644
index 00000000000..795fd069ab2
--- /dev/null
+++ b/spec/lib/gitlab/bitbucket_import/wiki_formatter_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe Gitlab::BitbucketImport::WikiFormatter do
+ let(:project) do
+ create(:project,
+ namespace: create(:namespace, path: 'gitlabhq'),
+ import_url: 'https://xxx@bitbucket.org/gitlabhq/sample.gitlabhq.git')
+ end
+
+ subject(:wiki) { described_class.new(project) }
+
+ describe '#disk_path' do
+ it 'appends .wiki to disk path' do
+ expect(wiki.disk_path).to eq project.wiki.disk_path
+ end
+ end
+
+ describe '#full_path' do
+ it 'appends .wiki to project path' do
+ expect(wiki.full_path).to eq project.wiki.full_path
+ end
+ end
+
+ describe '#import_url' do
+ it 'returns URL of the wiki repository' do
+ expect(wiki.import_url).to eq 'https://xxx@bitbucket.org/gitlabhq/sample.gitlabhq.git/wiki'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/git/blame_spec.rb b/spec/lib/gitlab/git/blame_spec.rb
index e704d1c673c..0010c0304eb 100644
--- a/spec/lib/gitlab/git/blame_spec.rb
+++ b/spec/lib/gitlab/git/blame_spec.rb
@@ -2,7 +2,7 @@
require "spec_helper"
describe Gitlab::Git::Blame, :seed_helper do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:blame) do
Gitlab::Git::Blame.new(repository, SeedRepo::Commit::ID, "CONTRIBUTING.md")
end
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index 1bcec04d28f..a1b5cea88c0 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -3,7 +3,7 @@
require "spec_helper"
describe Gitlab::Git::Blob, :seed_helper do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:rugged) do
Rugged::Repository.new(File.join(TestEnv.repos_path, TEST_REPO_PATH))
end
diff --git a/spec/lib/gitlab/git/branch_spec.rb b/spec/lib/gitlab/git/branch_spec.rb
index 0df282d0ae3..0764e525ede 100644
--- a/spec/lib/gitlab/git/branch_spec.rb
+++ b/spec/lib/gitlab/git/branch_spec.rb
@@ -1,7 +1,7 @@
require "spec_helper"
describe Gitlab::Git::Branch, :seed_helper do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:rugged) do
Rugged::Repository.new(File.join(TestEnv.repos_path, repository.relative_path))
end
@@ -64,7 +64,7 @@ describe Gitlab::Git::Branch, :seed_helper do
context 'with active, stale and future branches' do
let(:repository) do
- Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '')
+ Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '', 'group/project')
end
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb
index db68062e433..2611ebed25b 100644
--- a/spec/lib/gitlab/git/commit_spec.rb
+++ b/spec/lib/gitlab/git/commit_spec.rb
@@ -3,7 +3,7 @@ require "spec_helper"
describe Gitlab::Git::Commit, :seed_helper do
include GitHelpers
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:rugged_repo) do
Rugged::Repository.new(File.join(TestEnv.repos_path, TEST_REPO_PATH))
end
@@ -146,7 +146,7 @@ describe Gitlab::Git::Commit, :seed_helper do
end
context 'with broken repo' do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_BROKEN_REPO_PATH, '') }
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_BROKEN_REPO_PATH, '', 'group/project') }
it 'returns nil' do
expect(described_class.find(repository, SeedRepo::Commit::ID)).to be_nil
diff --git a/spec/lib/gitlab/git/compare_spec.rb b/spec/lib/gitlab/git/compare_spec.rb
index 771c71a16a9..65dfb93d0db 100644
--- a/spec/lib/gitlab/git/compare_spec.rb
+++ b/spec/lib/gitlab/git/compare_spec.rb
@@ -1,7 +1,7 @@
require "spec_helper"
describe Gitlab::Git::Compare, :seed_helper do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:compare) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::Commit::ID, straight: false) }
let(:compare_straight) { Gitlab::Git::Compare.new(repository, SeedRepo::BigCommit::ID, SeedRepo::Commit::ID, straight: true) }
diff --git a/spec/lib/gitlab/git/diff_spec.rb b/spec/lib/gitlab/git/diff_spec.rb
index 8a4415506c4..1d22329b670 100644
--- a/spec/lib/gitlab/git/diff_spec.rb
+++ b/spec/lib/gitlab/git/diff_spec.rb
@@ -1,7 +1,7 @@
require "spec_helper"
describe Gitlab::Git::Diff, :seed_helper do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:gitaly_diff) do
Gitlab::GitalyClient::Diff.new(
from_path: '.gitmodules',
diff --git a/spec/lib/gitlab/git/remote_repository_spec.rb b/spec/lib/gitlab/git/remote_repository_spec.rb
index 53ed7c5a13a..e166628d4ca 100644
--- a/spec/lib/gitlab/git/remote_repository_spec.rb
+++ b/spec/lib/gitlab/git/remote_repository_spec.rb
@@ -1,15 +1,15 @@
require 'spec_helper'
describe Gitlab::Git::RemoteRepository, :seed_helper do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
subject { described_class.new(repository) }
describe '#empty?' do
using RSpec::Parameterized::TableSyntax
where(:repository, :result) do
- Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') | false
- Gitlab::Git::Repository.new('default', 'does-not-exist.git', '') | true
+ Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') | false
+ Gitlab::Git::Repository.new('default', 'does-not-exist.git', '', 'group/project') | true
end
with_them do
@@ -44,11 +44,11 @@ describe Gitlab::Git::RemoteRepository, :seed_helper do
using RSpec::Parameterized::TableSyntax
where(:other_repository, :result) do
- repository | true
- Gitlab::Git::Repository.new(repository.storage, repository.relative_path, '') | true
- Gitlab::Git::Repository.new('broken', TEST_REPO_PATH, '') | false
- Gitlab::Git::Repository.new(repository.storage, 'wrong/relative-path.git', '') | false
- Gitlab::Git::Repository.new('broken', 'wrong/relative-path.git', '') | false
+ repository | true
+ Gitlab::Git::Repository.new(repository.storage, repository.relative_path, '', 'group/project') | true
+ Gitlab::Git::Repository.new('broken', TEST_REPO_PATH, '', 'group/project') | false
+ Gitlab::Git::Repository.new(repository.storage, 'wrong/relative-path.git', '', 'group/project') | false
+ Gitlab::Git::Repository.new('broken', 'wrong/relative-path.git', '', 'group/project') | false
end
with_them do
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index a19e3e84f83..cf9e0cccc71 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -19,8 +19,10 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
- let(:mutable_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
+ let(:mutable_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '', 'group/project') }
+ let(:mutable_repository_path) { File.join(TestEnv.repos_path, mutable_repository.relative_path) }
+ let(:mutable_repository_rugged) { Rugged::Repository.new(mutable_repository_path) }
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:repository_path) { File.join(TestEnv.repos_path, repository.relative_path) }
let(:repository_rugged) { Rugged::Repository.new(repository_path) }
let(:storage_path) { TestEnv.repos_path }
@@ -434,13 +436,13 @@ describe Gitlab::Git::Repository, :seed_helper do
describe '#fetch_repository_as_mirror' do
let(:new_repository) do
- Gitlab::Git::Repository.new('default', 'my_project.git', '')
+ Gitlab::Git::Repository.new('default', 'my_project.git', '', 'group/project')
end
subject { new_repository.fetch_repository_as_mirror(repository) }
before do
- Gitlab::Shell.new.create_repository('default', 'my_project')
+ Gitlab::Shell.new.create_repository('default', 'my_project', 'group/project')
end
after do
@@ -497,6 +499,48 @@ describe Gitlab::Git::Repository, :seed_helper do
end
end
+ describe '#search_files_by_content' do
+ let(:repository) { mutable_repository }
+ let(:repository_rugged) { mutable_repository_rugged }
+
+ before do
+ repository.create_branch('search-files-by-content-branch', 'master')
+ new_commit_edit_new_file_on_branch(repository_rugged, 'encoding/CHANGELOG', 'search-files-by-content-branch', 'committing something', 'search-files-by-content change')
+ new_commit_edit_new_file_on_branch(repository_rugged, 'anotherfile', 'search-files-by-content-branch', 'committing something', 'search-files-by-content change')
+ end
+
+ after do
+ ensure_seeds
+ end
+
+ shared_examples 'search files by content' do
+ it 'should have 2 items' do
+ expect(search_results.size).to eq(2)
+ end
+
+ it 'should have the correct matching line' do
+ expect(search_results).to contain_exactly("search-files-by-content-branch:encoding/CHANGELOG\u00001\u0000search-files-by-content change\n",
+ "search-files-by-content-branch:anotherfile\u00001\u0000search-files-by-content change\n")
+ end
+ end
+
+ it_should_behave_like 'search files by content' do
+ let(:search_results) do
+ repository.search_files_by_content('search-files-by-content', 'search-files-by-content-branch')
+ end
+ end
+
+ it_should_behave_like 'search files by content' do
+ let(:search_results) do
+ repository.gitaly_repository_client.search_files_by_content(
+ 'search-files-by-content-branch',
+ 'search-files-by-content',
+ chunked_response: false
+ )
+ end
+ end
+ end
+
describe '#find_remote_root_ref' do
it 'gets the remote root ref from GitalyClient' do
expect_any_instance_of(Gitlab::GitalyClient::RemoteService)
@@ -544,7 +588,7 @@ describe Gitlab::Git::Repository, :seed_helper do
# Add new commits so that there's a renamed file in the commit history
@commit_with_old_name_id = new_commit_edit_old_file(repository_rugged).oid
@rename_commit_id = new_commit_move_file(repository_rugged).oid
- @commit_with_new_name_id = new_commit_edit_new_file(repository_rugged).oid
+ @commit_with_new_name_id = new_commit_edit_new_file(repository_rugged, "encoding/CHANGELOG", "Edit encoding/CHANGELOG", "I'm a new changelog with different text").oid
end
after do
@@ -1230,7 +1274,7 @@ describe Gitlab::Git::Repository, :seed_helper do
end
describe '#gitattribute' do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_GITATTRIBUTES_REPO_PATH, '') }
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_GITATTRIBUTES_REPO_PATH, '', 'group/project') }
after do
ensure_seeds
@@ -1249,7 +1293,7 @@ describe Gitlab::Git::Repository, :seed_helper do
end
context 'without gitattributes file' do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
it 'returns nil' do
expect(repository.gitattribute("README.md", 'gitlab-language')).to eq(nil)
@@ -1513,7 +1557,7 @@ describe Gitlab::Git::Repository, :seed_helper do
context 'repository does not exist' do
it 'raises NoRepository and does not call Gitaly WriteConfig' do
- repository = Gitlab::Git::Repository.new('default', 'does/not/exist.git', '')
+ repository = Gitlab::Git::Repository.new('default', 'does/not/exist.git', '', 'group/project')
expect(repository.gitaly_repository_client).not_to receive(:write_config)
@@ -1803,7 +1847,7 @@ describe Gitlab::Git::Repository, :seed_helper do
out: '/dev/null',
err: '/dev/null')
- empty_repo = described_class.new('default', 'empty-repo.git', '')
+ empty_repo = described_class.new('default', 'empty-repo.git', '', 'group/empty-repo')
expect(empty_repo.checksum).to eq '0000000000000000000000000000000000000000'
end
@@ -1818,13 +1862,13 @@ describe Gitlab::Git::Repository, :seed_helper do
File.truncate(File.join(storage_path, 'non-valid.git/HEAD'), 0)
- non_valid = described_class.new('default', 'non-valid.git', '')
+ non_valid = described_class.new('default', 'non-valid.git', '', 'a/non-valid')
expect { non_valid.checksum }.to raise_error(Gitlab::Git::Repository::InvalidRepository)
end
it 'raises Gitlab::Git::Repository::NoRepository error when there is no repo' do
- broken_repo = described_class.new('default', 'a/path.git', '')
+ broken_repo = described_class.new('default', 'a/path.git', '', 'a/path')
expect { broken_repo.checksum }.to raise_error(Gitlab::Git::Repository::NoRepository)
end
@@ -1964,7 +2008,7 @@ describe Gitlab::Git::Repository, :seed_helper do
end
# Build the options hash that's passed to Rugged::Commit#create
- def commit_options(repo, index, message)
+ def commit_options(repo, index, target, ref, message)
options = {}
options[:tree] = index.write_tree(repo)
options[:author] = {
@@ -1978,8 +2022,8 @@ describe Gitlab::Git::Repository, :seed_helper do
time: Time.gm(2014, "mar", 3, 20, 15, 1)
}
options[:message] ||= message
- options[:parents] = repo.empty? ? [] : [repo.head.target].compact
- options[:update_ref] = "HEAD"
+ options[:parents] = repo.empty? ? [] : [target].compact
+ options[:update_ref] = ref
options
end
@@ -1995,6 +2039,8 @@ describe Gitlab::Git::Repository, :seed_helper do
options = commit_options(
repo,
index,
+ repo.head.target,
+ "HEAD",
"Edit CHANGELOG in its original location"
)
@@ -2003,19 +2049,24 @@ describe Gitlab::Git::Repository, :seed_helper do
end
# Writes a new commit to the repo and returns a Rugged::Commit. Replaces the
- # contents of encoding/CHANGELOG with new text.
- def new_commit_edit_new_file(repo)
- oid = repo.write("I'm a new changelog with different text", :blob)
+ # contents of the specified file_path with new text.
+ def new_commit_edit_new_file(repo, file_path, commit_message, text, branch = repo.head)
+ oid = repo.write(text, :blob)
index = repo.index
- index.read_tree(repo.head.target.tree)
- index.add(path: "encoding/CHANGELOG", oid: oid, mode: 0100644)
-
- options = commit_options(repo, index, "Edit encoding/CHANGELOG")
-
+ index.read_tree(branch.target.tree)
+ index.add(path: file_path, oid: oid, mode: 0100644)
+ options = commit_options(repo, index, branch.target, branch.canonical_name, commit_message)
sha = Rugged::Commit.create(repo, options)
repo.lookup(sha)
end
+ # Writes a new commit to the repo and returns a Rugged::Commit. Replaces the
+ # contents of encoding/CHANGELOG with new text.
+ def new_commit_edit_new_file_on_branch(repo, file_path, branch_name, commit_message, text)
+ branch = repo.branches[branch_name]
+ new_commit_edit_new_file(repo, file_path, commit_message, text, branch)
+ end
+
# Writes a new commit to the repo and returns a Rugged::Commit. Moves the
# CHANGELOG file to the encoding/ directory.
def new_commit_move_file(repo)
@@ -2027,7 +2078,7 @@ describe Gitlab::Git::Repository, :seed_helper do
index.add(path: "encoding/CHANGELOG", oid: oid, mode: 0100644)
index.remove("CHANGELOG")
- options = commit_options(repo, index, "Move CHANGELOG to encoding/")
+ options = commit_options(repo, index, repo.head.target, "HEAD", "Move CHANGELOG to encoding/")
sha = Rugged::Commit.create(repo, options)
repo.lookup(sha)
diff --git a/spec/lib/gitlab/git/tag_spec.rb b/spec/lib/gitlab/git/tag_spec.rb
index b51e3879f49..4c0291f64f0 100644
--- a/spec/lib/gitlab/git/tag_spec.rb
+++ b/spec/lib/gitlab/git/tag_spec.rb
@@ -1,7 +1,7 @@
require "spec_helper"
describe Gitlab::Git::Tag, :seed_helper do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
describe '#tags' do
describe 'first tag' do
diff --git a/spec/lib/gitlab/git/tree_spec.rb b/spec/lib/gitlab/git/tree_spec.rb
index bec875fb03d..4a4d69490a3 100644
--- a/spec/lib/gitlab/git/tree_spec.rb
+++ b/spec/lib/gitlab/git/tree_spec.rb
@@ -1,7 +1,7 @@
require "spec_helper"
describe Gitlab::Git::Tree, :seed_helper do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
context :repo do
let(:tree) { Gitlab::Git::Tree.where(repository, SeedRepo::Commit::ID) }
diff --git a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
index aff47599ad6..d5508dbff5d 100644
--- a/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/remote_service_spec.rb
@@ -33,7 +33,7 @@ describe Gitlab::GitalyClient::RemoteService do
end
describe '#fetch_internal_remote' do
- let(:remote_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '') }
+ let(:remote_repository) { Gitlab::Git::Repository.new('default', TEST_MUTABLE_REPO_PATH, '', 'group/project') }
it 'sends an fetch_internal_remote message and returns the result value' do
expect_any_instance_of(Gitaly::RemoteService::Stub)
diff --git a/spec/lib/gitlab/gitaly_client/util_spec.rb b/spec/lib/gitlab/gitaly_client/util_spec.rb
index 550db6db6d9..78a5e195ad1 100644
--- a/spec/lib/gitlab/gitaly_client/util_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/util_spec.rb
@@ -7,6 +7,7 @@ describe Gitlab::GitalyClient::Util do
let(:gl_repository) { 'project-1' }
let(:git_object_directory) { '.git/objects' }
let(:git_alternate_object_directory) { ['/dir/one', '/dir/two'] }
+ let(:gl_project_path) { 'namespace/myproject' }
let(:git_env) do
{
'GIT_OBJECT_DIRECTORY_RELATIVE' => git_object_directory,
@@ -15,7 +16,7 @@ describe Gitlab::GitalyClient::Util do
end
subject do
- described_class.repository(repository_storage, relative_path, gl_repository)
+ described_class.repository(repository_storage, relative_path, gl_repository, gl_project_path)
end
it 'creates a Gitaly::Repository with the given data' do
@@ -27,6 +28,7 @@ describe Gitlab::GitalyClient::Util do
expect(subject.gl_repository).to eq(gl_repository)
expect(subject.git_object_directory).to eq(git_object_directory)
expect(subject.git_alternate_object_directories).to eq(git_alternate_object_directory)
+ expect(subject.gl_project_path).to eq(gl_project_path)
end
end
end
diff --git a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
index 77f5b2ffa37..47233ea6ee2 100644
--- a/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
+++ b/spec/lib/gitlab/github_import/importer/repository_importer_spec.rb
@@ -5,6 +5,14 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do
let(:import_state) { double(:import_state) }
let(:client) { double(:client) }
+ let(:wiki) do
+ double(
+ :wiki,
+ disk_path: 'foo.wiki',
+ full_path: 'group/foo.wiki'
+ )
+ end
+
let(:project) do
double(
:project,
@@ -15,7 +23,9 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do
repository: repository,
create_wiki: true,
import_state: import_state,
- lfs_enabled?: true
+ full_path: 'group/foo',
+ lfs_enabled?: true,
+ wiki: wiki
)
end
@@ -195,7 +205,7 @@ describe Gitlab::GithubImport::Importer::RepositoryImporter do
it 'imports the wiki repository' do
expect(importer.gitlab_shell)
.to receive(:import_repository)
- .with('foo', 'foo.wiki', 'foo.wiki.git')
+ .with('foo', 'foo.wiki', 'foo.wiki.git', 'group/foo.wiki')
expect(importer.import_wiki_repository).to eq(true)
end
diff --git a/spec/lib/gitlab/kubernetes/helm/api_spec.rb b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
index c7f92cbb143..8433d40b2ea 100644
--- a/spec/lib/gitlab/kubernetes/helm/api_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/api_spec.rb
@@ -171,51 +171,6 @@ describe Gitlab::Kubernetes::Helm::Api do
end
end
- describe '#update' do
- let(:rbac) { false }
-
- let(:command) do
- Gitlab::Kubernetes::Helm::UpgradeCommand.new(
- application_name,
- chart: 'chart-name',
- files: files,
- rbac: rbac
- )
- end
-
- before do
- allow(namespace).to receive(:ensure_exists!).once
-
- allow(client).to receive(:update_config_map).and_return(nil)
- allow(client).to receive(:create_pod).and_return(nil)
- allow(client).to receive(:delete_pod).and_return(nil)
- end
-
- it 'ensures the namespace exists before creating the pod' do
- expect(namespace).to receive(:ensure_exists!).once.ordered
- expect(client).to receive(:create_pod).once.ordered
-
- subject.update(command)
- end
-
- it 'removes an existing pod before updating' do
- expect(client).to receive(:delete_pod).with('upgrade-app-name', 'gitlab-managed-apps').once.ordered
- expect(client).to receive(:create_pod).once.ordered
-
- subject.update(command)
- end
-
- it 'updates the config map on kubeclient when one exists' do
- resource = Gitlab::Kubernetes::ConfigMap.new(
- application_name, files
- ).generate
-
- expect(client).to receive(:update_config_map).with(resource).once
-
- subject.update(command)
- end
- end
-
describe '#status' do
let(:phase) { Gitlab::Kubernetes::Pod::RUNNING }
let(:pod) { Kubeclient::Resource.new(status: { phase: phase }) } # partial representation
diff --git a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
index 82ed4d47857..db76d5d207e 100644
--- a/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
+++ b/spec/lib/gitlab/kubernetes/helm/install_command_spec.rb
@@ -21,6 +21,15 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
)
end
+ let(:tls_flags) do
+ <<~EOS.squish
+ --tls
+ --tls-ca-cert /data/helm/app-name/config/ca.pem
+ --tls-cert /data/helm/app-name/config/cert.pem
+ --tls-key /data/helm/app-name/config/key.pem
+ EOS
+ end
+
subject { install_command }
it_behaves_like 'helm commands' do
@@ -36,12 +45,10 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:helm_install_comand) do
<<~EOS.squish
- helm install chart-name
- --name app-name
- --tls
- --tls-ca-cert /data/helm/app-name/config/ca.pem
- --tls-cert /data/helm/app-name/config/cert.pem
- --tls-key /data/helm/app-name/config/key.pem
+ helm upgrade app-name chart-name
+ --install
+ --reset-values
+ #{tls_flags}
--version 1.2.3
--set rbac.create\\=false,rbac.enabled\\=false
--namespace gitlab-managed-apps
@@ -66,12 +73,10 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:helm_install_command) do
<<~EOS.squish
- helm install chart-name
- --name app-name
- --tls
- --tls-ca-cert /data/helm/app-name/config/ca.pem
- --tls-cert /data/helm/app-name/config/cert.pem
- --tls-key /data/helm/app-name/config/key.pem
+ helm upgrade app-name chart-name
+ --install
+ --reset-values
+ #{tls_flags}
--version 1.2.3
--set rbac.create\\=true,rbac.enabled\\=true
--namespace gitlab-managed-apps
@@ -95,12 +100,10 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:helm_install_command) do
<<~EOS.squish
- helm install chart-name
- --name app-name
- --tls
- --tls-ca-cert /data/helm/app-name/config/ca.pem
- --tls-cert /data/helm/app-name/config/cert.pem
- --tls-key /data/helm/app-name/config/key.pem
+ helm upgrade app-name chart-name
+ --install
+ --reset-values
+ #{tls_flags}
--version 1.2.3
--set rbac.create\\=false,rbac.enabled\\=false
--namespace gitlab-managed-apps
@@ -120,15 +123,22 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
helm repo add app-name https://repository.example.com
helm repo update
+ /bin/date
+ /bin/true
#{helm_install_command}
EOS
end
let(:helm_install_command) do
- <<~EOS.strip
- /bin/date
- /bin/true
- helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --set rbac.create\\=false,rbac.enabled\\=false --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml
+ <<~EOS.squish
+ helm upgrade app-name chart-name
+ --install
+ --reset-values
+ #{tls_flags}
+ --version 1.2.3
+ --set rbac.create\\=false,rbac.enabled\\=false
+ --namespace gitlab-managed-apps
+ -f /data/helm/app-name/config/values.yaml
EOS
end
end
@@ -145,14 +155,21 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
helm repo add app-name https://repository.example.com
helm repo update
#{helm_install_command}
+ /bin/date
+ /bin/false
EOS
end
let(:helm_install_command) do
- <<~EOS.strip
- helm install chart-name --name app-name --tls --tls-ca-cert /data/helm/app-name/config/ca.pem --tls-cert /data/helm/app-name/config/cert.pem --tls-key /data/helm/app-name/config/key.pem --version 1.2.3 --set rbac.create\\=false,rbac.enabled\\=false --namespace gitlab-managed-apps -f /data/helm/app-name/config/values.yaml
- /bin/date
- /bin/false
+ <<~EOS.squish
+ helm upgrade app-name chart-name
+ --install
+ --reset-values
+ #{tls_flags}
+ --version 1.2.3
+ --set rbac.create\\=false,rbac.enabled\\=false
+ --namespace gitlab-managed-apps
+ -f /data/helm/app-name/config/values.yaml
EOS
end
end
@@ -174,8 +191,9 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:helm_install_command) do
<<~EOS.squish
- helm install chart-name
- --name app-name
+ helm upgrade app-name chart-name
+ --install
+ --reset-values
--version 1.2.3
--set rbac.create\\=false,rbac.enabled\\=false
--namespace gitlab-managed-apps
@@ -201,12 +219,10 @@ describe Gitlab::Kubernetes::Helm::InstallCommand do
let(:helm_install_command) do
<<~EOS.squish
- helm install chart-name
- --name app-name
- --tls
- --tls-ca-cert /data/helm/app-name/config/ca.pem
- --tls-cert /data/helm/app-name/config/cert.pem
- --tls-key /data/helm/app-name/config/key.pem
+ helm upgrade app-name chart-name
+ --install
+ --reset-values
+ #{tls_flags}
--set rbac.create\\=false,rbac.enabled\\=false
--namespace gitlab-managed-apps
-f /data/helm/app-name/config/values.yaml
diff --git a/spec/lib/gitlab/kubernetes/helm/upgrade_command_spec.rb b/spec/lib/gitlab/kubernetes/helm/upgrade_command_spec.rb
deleted file mode 100644
index 9b201dae417..00000000000
--- a/spec/lib/gitlab/kubernetes/helm/upgrade_command_spec.rb
+++ /dev/null
@@ -1,140 +0,0 @@
-# frozen_string_literal: true
-
-require 'rails_helper'
-
-describe Gitlab::Kubernetes::Helm::UpgradeCommand do
- let(:application) { build(:clusters_applications_prometheus) }
- let(:files) { { 'ca.pem': 'some file content' } }
- let(:namespace) { ::Gitlab::Kubernetes::Helm::NAMESPACE }
- let(:rbac) { false }
- let(:upgrade_command) do
- described_class.new(
- application.name,
- chart: application.chart,
- files: files,
- rbac: rbac
- )
- end
-
- subject { upgrade_command }
-
- it_behaves_like 'helm commands' do
- let(:commands) do
- <<~EOS
- helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
- helm upgrade #{application.name} #{application.chart} --tls --tls-ca-cert /data/helm/#{application.name}/config/ca.pem --tls-cert /data/helm/#{application.name}/config/cert.pem --tls-key /data/helm/#{application.name}/config/key.pem --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml
- EOS
- end
- end
-
- context 'rbac is true' do
- let(:rbac) { true }
-
- it_behaves_like 'helm commands' do
- let(:commands) do
- <<~EOS
- helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
- helm upgrade #{application.name} #{application.chart} --tls --tls-ca-cert /data/helm/#{application.name}/config/ca.pem --tls-cert /data/helm/#{application.name}/config/cert.pem --tls-key /data/helm/#{application.name}/config/key.pem --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml
- EOS
- end
- end
- end
-
- context 'with an application with a repository' do
- let(:ci_runner) { create(:ci_runner) }
- let(:application) { build(:clusters_applications_runner, runner: ci_runner) }
- let(:upgrade_command) do
- described_class.new(
- application.name,
- chart: application.chart,
- files: files,
- rbac: rbac,
- repository: application.repository
- )
- end
-
- it_behaves_like 'helm commands' do
- let(:commands) do
- <<~EOS
- helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
- helm repo add #{application.name} #{application.repository}
- helm upgrade #{application.name} #{application.chart} --tls --tls-ca-cert /data/helm/#{application.name}/config/ca.pem --tls-cert /data/helm/#{application.name}/config/cert.pem --tls-key /data/helm/#{application.name}/config/key.pem --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml
- EOS
- end
- end
- end
-
- context 'when there is no ca.pem file' do
- let(:files) { { 'file.txt': 'some content' } }
-
- it_behaves_like 'helm commands' do
- let(:commands) do
- <<~EOS
- helm init --upgrade
- for i in $(seq 1 30); do helm version && break; sleep 1s; echo "Retrying ($i)..."; done
- helm upgrade #{application.name} #{application.chart} --reset-values --install --namespace #{namespace} -f /data/helm/#{application.name}/config/values.yaml
- EOS
- end
- end
- end
-
- describe '#pod_resource' do
- subject { upgrade_command.pod_resource }
-
- context 'rbac is enabled' do
- let(:rbac) { true }
-
- it 'generates a pod that uses the tiller serviceAccountName' do
- expect(subject.spec.serviceAccountName).to eq('tiller')
- end
- end
-
- context 'rbac is not enabled' do
- let(:rbac) { false }
-
- it 'generates a pod that uses the default serviceAccountName' do
- expect(subject.spec.serviceAcccountName).to be_nil
- end
- end
- end
-
- describe '#config_map_resource' do
- let(:metadata) do
- {
- name: "values-content-configuration-#{application.name}",
- namespace: namespace,
- labels: { name: "values-content-configuration-#{application.name}" }
- }
- end
- let(:resource) { ::Kubeclient::Resource.new(metadata: metadata, data: files) }
-
- it 'returns a KubeClient resource with config map content for the application' do
- expect(subject.config_map_resource).to eq(resource)
- end
- end
-
- describe '#rbac?' do
- subject { upgrade_command.rbac? }
-
- context 'rbac is enabled' do
- let(:rbac) { true }
-
- it { is_expected.to be_truthy }
- end
-
- context 'rbac is not enabled' do
- let(:rbac) { false }
-
- it { is_expected.to be_falsey }
- end
- end
-
- describe '#pod_name' do
- it 'returns the pod name' do
- expect(subject.pod_name).to eq("upgrade-#{application.name}")
- end
- end
-end
diff --git a/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb b/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb
index 7723533aee2..7519707293c 100644
--- a/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb
+++ b/spec/lib/gitlab/legacy_github_import/wiki_formatter_spec.rb
@@ -10,11 +10,17 @@ describe Gitlab::LegacyGithubImport::WikiFormatter do
subject(:wiki) { described_class.new(project) }
describe '#disk_path' do
- it 'appends .wiki to project path' do
+ it 'appends .wiki to disk path' do
expect(wiki.disk_path).to eq project.wiki.disk_path
end
end
+ describe '#full_path' do
+ it 'appends .wiki to project path' do
+ expect(wiki.full_path).to eq project.wiki.full_path
+ end
+ end
+
describe '#import_url' do
it 'returns URL of the wiki repository' do
expect(wiki.import_url).to eq 'https://xxx@github.com/gitlabhq/sample.gitlabhq.wiki.git'
diff --git a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
index 771b633a2b9..4b03f3c2532 100644
--- a/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/unicorn_sampler_spec.rb
@@ -37,7 +37,7 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do
end
it 'updates metrics type unix and with addr' do
- labels = { type: 'unix', address: socket_address }
+ labels = { socket_type: 'unix', socket_address: socket_address }
expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active')
expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued')
@@ -69,7 +69,7 @@ describe Gitlab::Metrics::Samplers::UnicornSampler do
end
it 'updates metrics type unix and with addr' do
- labels = { type: 'tcp', address: tcp_socket_address }
+ labels = { socket_type: 'tcp', socket_address: tcp_socket_address }
expect(subject).to receive_message_chain(:unicorn_active_connections, :set).with(labels, 'active')
expect(subject).to receive_message_chain(:unicorn_queued_connections, :set).with(labels, 'queued')
diff --git a/spec/lib/gitlab/project_template_spec.rb b/spec/lib/gitlab/project_template_spec.rb
index 57b0ef8d1ad..1cc2bde50e9 100644
--- a/spec/lib/gitlab/project_template_spec.rb
+++ b/spec/lib/gitlab/project_template_spec.rb
@@ -6,7 +6,12 @@ describe Gitlab::ProjectTemplate do
expected = [
described_class.new('rails', 'Ruby on Rails', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/rails'),
described_class.new('spring', 'Spring', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/spring'),
- described_class.new('express', 'NodeJS Express', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/express')
+ described_class.new('express', 'NodeJS Express', 'Includes an MVC structure, .gitignore, Gemfile, and more great stuff', 'https://gitlab.com/gitlab-org/project-templates/express'),
+ described_class.new('hugo', 'Pages/Hugo', 'Everything you need to get started using a Hugo Pages site.', 'https://gitlab.com/pages/hugo'),
+ described_class.new('jekyll', 'Pages/Jekyll', 'Everything you need to get started using a Jekyll Pages site.', 'https://gitlab.com/pages/jekyll'),
+ described_class.new('plainhtml', 'Pages/Plain HTML', 'Everything you need to get started using a plain HTML Pages site.', 'https://gitlab.com/pages/plain-html'),
+ described_class.new('gitbook', 'Pages/GitBook', 'Everything you need to get started using a GitBook Pages site.', 'https://gitlab.com/pages/gitbook'),
+ described_class.new('hexo', 'Pages/Hexo', 'Everything you need to get started using a plan Hexo Pages site.', 'https://gitlab.com/pages/hexo')
]
expect(described_class.all).to be_an(Array)
diff --git a/spec/lib/gitlab/shell_spec.rb b/spec/lib/gitlab/shell_spec.rb
index 6ce9d515a0f..033e1bf81a1 100644
--- a/spec/lib/gitlab/shell_spec.rb
+++ b/spec/lib/gitlab/shell_spec.rb
@@ -412,7 +412,7 @@ describe Gitlab::Shell do
end
it 'creates a repository' do
- expect(gitlab_shell.create_repository(repository_storage, repo_name)).to be_truthy
+ expect(gitlab_shell.create_repository(repository_storage, repo_name, repo_name)).to be_truthy
expect(File.stat(created_path).mode & 0o777).to eq(0o770)
@@ -427,7 +427,7 @@ describe Gitlab::Shell do
# should cause #create_repository to fail.
FileUtils.touch(created_path)
- expect(gitlab_shell.create_repository(repository_storage, repo_name)).to be_falsy
+ expect(gitlab_shell.create_repository(repository_storage, repo_name, repo_name)).to be_falsy
end
end
@@ -474,13 +474,10 @@ describe Gitlab::Shell do
end
describe '#fork_repository' do
+ let(:target_project) { create(:project) }
+
subject do
- gitlab_shell.fork_repository(
- project.repository_storage,
- project.disk_path,
- 'nfs-file05',
- 'fork/path'
- )
+ gitlab_shell.fork_repository(project, target_project)
end
it 'returns true when the command succeeds' do
@@ -505,7 +502,7 @@ describe Gitlab::Shell do
it 'returns true when the command succeeds' do
expect_any_instance_of(Gitlab::GitalyClient::RepositoryService).to receive(:import_repository).with(import_url)
- result = gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url)
+ result = gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url, project.full_path)
expect(result).to be_truthy
end
@@ -516,7 +513,7 @@ describe Gitlab::Shell do
expect_any_instance_of(Gitlab::Shell::GitalyGitlabProjects).to receive(:output) { 'error'}
expect do
- gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url)
+ gitlab_shell.import_repository(project.repository_storage, project.disk_path, import_url, project.full_path)
end.to raise_error(Gitlab::Shell::Error, "error")
end
end
diff --git a/spec/migrations/clean_up_for_members_spec.rb b/spec/migrations/clean_up_for_members_spec.rb
index 7876536cb3e..1a79f94cf0d 100644
--- a/spec/migrations/clean_up_for_members_spec.rb
+++ b/spec/migrations/clean_up_for_members_spec.rb
@@ -2,6 +2,10 @@ require 'spec_helper'
require Rails.root.join('db', 'migrate', '20171216111734_clean_up_for_members.rb')
describe CleanUpForMembers, :migration do
+ before do
+ stub_feature_flags(enforced_sso: false)
+ end
+
let(:migration) { described_class.new }
let(:groups) { table(:namespaces) }
let!(:group_member) { create_group_member }
diff --git a/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb b/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb
new file mode 100644
index 00000000000..b1ff3cfd355
--- /dev/null
+++ b/spec/migrations/migrate_auto_dev_ops_domain_to_cluster_domain_spec.rb
@@ -0,0 +1,114 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+require Rails.root.join('db', 'post_migrate', '20190204115450_migrate_auto_dev_ops_domain_to_cluster_domain.rb')
+
+describe MigrateAutoDevOpsDomainToClusterDomain, :migration do
+ include MigrationHelpers::ClusterHelpers
+
+ let(:migration) { described_class.new }
+ let(:project_auto_devops_table) { table(:project_auto_devops) }
+ let(:clusters_table) { table(:clusters) }
+ let(:cluster_projects_table) { table(:cluster_projects) }
+
+ # Following lets are needed by MigrationHelpers::ClusterHelpers
+ let(:cluster_kubernetes_namespaces_table) { table(:clusters_kubernetes_namespaces) }
+ let(:projects_table) { table(:projects) }
+ let(:namespaces_table) { table(:namespaces) }
+ let(:provider_gcp_table) { table(:cluster_providers_gcp) }
+ let(:platform_kubernetes_table) { table(:cluster_platforms_kubernetes) }
+
+ before do
+ setup_cluster_projects_with_domain(quantity: 20, domain: domain)
+ end
+
+ context 'with ProjectAutoDevOps with no domain' do
+ let(:domain) { nil }
+
+ it 'should not update cluster project' do
+ migrate!
+
+ expect(clusters_without_domain.count).to eq(clusters_table.count)
+ end
+ end
+
+ context 'with ProjectAutoDevOps with domain' do
+ let(:domain) { 'example-domain.com' }
+
+ it 'should update all cluster projects' do
+ migrate!
+
+ expect(clusters_with_domain.count).to eq(clusters_table.count)
+ end
+ end
+
+ context 'when only some ProjectAutoDevOps have domain set' do
+ let(:domain) { 'example-domain.com' }
+
+ before do
+ setup_cluster_projects_with_domain(quantity: 25, domain: nil)
+ end
+
+ it 'should only update specific cluster projects' do
+ migrate!
+
+ expect(clusters_with_domain.count).to eq(20)
+
+ project_auto_devops_with_domain.each do |project_auto_devops|
+ cluster_project = find_cluster_project(project_auto_devops.project_id)
+ cluster = find_cluster(cluster_project.cluster_id)
+
+ expect(cluster.domain).to be_present
+ end
+
+ expect(clusters_without_domain.count).to eq(25)
+
+ project_auto_devops_without_domain.each do |project_auto_devops|
+ cluster_project = find_cluster_project(project_auto_devops.project_id)
+ cluster = find_cluster(cluster_project.cluster_id)
+
+ expect(cluster.domain).not_to be_present
+ end
+ end
+ end
+
+ def setup_cluster_projects_with_domain(quantity:, domain:)
+ create_cluster_project_list(quantity)
+
+ cluster_projects = cluster_projects_table.last(quantity)
+
+ cluster_projects.each do |cluster_project|
+ specific_domain = "#{cluster_project.id}-#{domain}" if domain
+
+ project_auto_devops_table.create(
+ project_id: cluster_project.project_id,
+ enabled: true,
+ domain: specific_domain
+ )
+ end
+ end
+
+ def find_cluster_project(project_id)
+ cluster_projects_table.where(project_id: project_id).first
+ end
+
+ def find_cluster(cluster_id)
+ clusters_table.where(id: cluster_id).first
+ end
+
+ def project_auto_devops_with_domain
+ project_auto_devops_table.where.not("domain IS NULL OR domain = ''")
+ end
+
+ def project_auto_devops_without_domain
+ project_auto_devops_table.where("domain IS NULL OR domain = ''")
+ end
+
+ def clusters_with_domain
+ clusters_table.where.not("domain IS NULL OR domain = ''")
+ end
+
+ def clusters_without_domain
+ clusters_table.where("domain IS NULL OR domain = ''")
+ end
+end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 96aa9a82b71..789e14e8a20 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -70,6 +70,13 @@ describe ApplicationSetting do
.is_greater_than(0)
end
+ it do
+ is_expected.to validate_numericality_of(:local_markdown_version)
+ .only_integer
+ .is_greater_than_or_equal_to(0)
+ .is_less_than(65536)
+ end
+
context 'key restrictions' do
it 'supports all key types' do
expect(described_class::SUPPORTED_KEY_TYPES).to contain_exactly(:rsa, :dsa, :ecdsa, :ed25519)
diff --git a/spec/models/clusters/applications/cert_manager_spec.rb b/spec/models/clusters/applications/cert_manager_spec.rb
index 79a06c35459..cf5cbf8ec5c 100644
--- a/spec/models/clusters/applications/cert_manager_spec.rb
+++ b/spec/models/clusters/applications/cert_manager_spec.rb
@@ -5,6 +5,7 @@ describe Clusters::Applications::CertManager do
include_examples 'cluster application core specs', :clusters_applications_cert_managers
include_examples 'cluster application status specs', :clusters_applications_cert_managers
+ include_examples 'cluster application version specs', :clusters_applications_cert_managers
include_examples 'cluster application initial status specs'
describe '#install_command' do
diff --git a/spec/models/clusters/applications/ingress_spec.rb b/spec/models/clusters/applications/ingress_spec.rb
index 6d48131d1cc..03ca18c6943 100644
--- a/spec/models/clusters/applications/ingress_spec.rb
+++ b/spec/models/clusters/applications/ingress_spec.rb
@@ -7,6 +7,7 @@ describe Clusters::Applications::Ingress do
include_examples 'cluster application core specs', :clusters_applications_ingress
include_examples 'cluster application status specs', :clusters_applications_ingress
+ include_examples 'cluster application version specs', :clusters_applications_ingress
include_examples 'cluster application helm specs', :clusters_applications_ingress
include_examples 'cluster application initial status specs'
diff --git a/spec/models/clusters/applications/jupyter_spec.rb b/spec/models/clusters/applications/jupyter_spec.rb
index b73a243f6e0..2c22c24c498 100644
--- a/spec/models/clusters/applications/jupyter_spec.rb
+++ b/spec/models/clusters/applications/jupyter_spec.rb
@@ -3,6 +3,7 @@ require 'rails_helper'
describe Clusters::Applications::Jupyter do
include_examples 'cluster application core specs', :clusters_applications_jupyter
include_examples 'cluster application status specs', :clusters_applications_jupyter
+ include_examples 'cluster application version specs', :clusters_applications_jupyter
include_examples 'cluster application helm specs', :clusters_applications_jupyter
it { is_expected.to belong_to(:oauth_application) }
diff --git a/spec/models/clusters/applications/knative_spec.rb b/spec/models/clusters/applications/knative_spec.rb
index 5519615d52d..cd29e0d4f53 100644
--- a/spec/models/clusters/applications/knative_spec.rb
+++ b/spec/models/clusters/applications/knative_spec.rb
@@ -9,6 +9,7 @@ describe Clusters::Applications::Knative do
include_examples 'cluster application core specs', :clusters_applications_knative
include_examples 'cluster application status specs', :clusters_applications_knative
include_examples 'cluster application helm specs', :clusters_applications_knative
+ include_examples 'cluster application version specs', :clusters_applications_knative
include_examples 'cluster application initial status specs'
before do
diff --git a/spec/models/clusters/applications/prometheus_spec.rb b/spec/models/clusters/applications/prometheus_spec.rb
index 073fbded8ac..caf59b0fc31 100644
--- a/spec/models/clusters/applications/prometheus_spec.rb
+++ b/spec/models/clusters/applications/prometheus_spec.rb
@@ -5,6 +5,7 @@ describe Clusters::Applications::Prometheus do
include_examples 'cluster application core specs', :clusters_applications_prometheus
include_examples 'cluster application status specs', :clusters_applications_prometheus
+ include_examples 'cluster application version specs', :clusters_applications_prometheus
include_examples 'cluster application helm specs', :clusters_applications_prometheus
include_examples 'cluster application initial status specs'
@@ -206,8 +207,8 @@ describe Clusters::Applications::Prometheus do
let(:prometheus) { build(:clusters_applications_prometheus) }
let(:values) { prometheus.values }
- it 'returns an instance of Gitlab::Kubernetes::Helm::GetCommand' do
- expect(prometheus.upgrade_command(values)).to be_an_instance_of(::Gitlab::Kubernetes::Helm::UpgradeCommand)
+ it 'returns an instance of Gitlab::Kubernetes::Helm::InstallCommand' do
+ expect(prometheus.upgrade_command(values)).to be_an_instance_of(::Gitlab::Kubernetes::Helm::InstallCommand)
end
it 'should be initialized with 3 arguments' do
diff --git a/spec/models/clusters/applications/runner_spec.rb b/spec/models/clusters/applications/runner_spec.rb
index 96b7b02dbaf..38758ff97bc 100644
--- a/spec/models/clusters/applications/runner_spec.rb
+++ b/spec/models/clusters/applications/runner_spec.rb
@@ -5,6 +5,7 @@ describe Clusters::Applications::Runner do
include_examples 'cluster application core specs', :clusters_applications_runner
include_examples 'cluster application status specs', :clusters_applications_runner
+ include_examples 'cluster application version specs', :clusters_applications_runner
include_examples 'cluster application helm specs', :clusters_applications_runner
include_examples 'cluster application initial status specs'
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
index 0161db740ee..92ce2b0999a 100644
--- a/spec/models/clusters/cluster_spec.rb
+++ b/spec/models/clusters/cluster_spec.rb
@@ -30,6 +30,7 @@ describe Clusters::Cluster do
it { is_expected.to delegate_method(:available?).to(:application_ingress).with_prefix }
it { is_expected.to delegate_method(:available?).to(:application_prometheus).with_prefix }
it { is_expected.to delegate_method(:available?).to(:application_knative).with_prefix }
+ it { is_expected.to delegate_method(:external_ip).to(:application_ingress).with_prefix }
it { is_expected.to respond_to :project }
@@ -514,4 +515,108 @@ describe Clusters::Cluster do
it { is_expected.to be_falsey }
end
end
+
+ describe '#kube_ingress_domain' do
+ let(:cluster) { create(:cluster, :provided_by_gcp) }
+
+ subject { cluster.kube_ingress_domain }
+
+ context 'with domain set in cluster' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, :with_domain) }
+
+ it { is_expected.to eq(cluster.domain) }
+ end
+
+ context 'with no domain on cluster' do
+ context 'with a project cluster' do
+ let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
+ let(:project) { cluster.project }
+
+ context 'with domain set at instance level' do
+ before do
+ stub_application_setting(auto_devops_domain: 'global_domain.com')
+
+ it { is_expected.to eq('global_domain.com') }
+ end
+ end
+
+ context 'with domain set on ProjectAutoDevops' do
+ before do
+ auto_devops = project.build_auto_devops(domain: 'legacy-ado-domain.com')
+ auto_devops.save
+ end
+
+ it { is_expected.to eq('legacy-ado-domain.com') }
+ end
+
+ context 'with domain set as environment variable on project' do
+ before do
+ variable = project.variables.build(key: 'AUTO_DEVOPS_DOMAIN', value: 'project-ado-domain.com')
+ variable.save
+ end
+
+ it { is_expected.to eq('project-ado-domain.com') }
+ end
+
+ context 'with domain set as environment variable on the group project' do
+ let(:group) { create(:group) }
+
+ before do
+ project.update(parent_id: group.id)
+ variable = group.variables.build(key: 'AUTO_DEVOPS_DOMAIN', value: 'group-ado-domain.com')
+ variable.save
+ end
+
+ it { is_expected.to eq('group-ado-domain.com') }
+ end
+ end
+
+ context 'with a group cluster' do
+ let(:cluster) { create(:cluster, :group, :provided_by_gcp) }
+
+ context 'with domain set as environment variable for the group' do
+ let(:group) { cluster.group }
+
+ before do
+ variable = group.variables.build(key: 'AUTO_DEVOPS_DOMAIN', value: 'group-ado-domain.com')
+ variable.save
+ end
+
+ it { is_expected.to eq('group-ado-domain.com') }
+ end
+ end
+ end
+ end
+
+ describe '#predefined_variables' do
+ subject { cluster.predefined_variables }
+
+ context 'with an instance domain' do
+ let(:cluster) { create(:cluster, :provided_by_gcp) }
+
+ before do
+ stub_application_setting(auto_devops_domain: 'global_domain.com')
+ end
+
+ it 'should include KUBE_INGRESS_BASE_DOMAIN' do
+ expect(subject.to_hash).to include(KUBE_INGRESS_BASE_DOMAIN: 'global_domain.com')
+ end
+ end
+
+ context 'with a cluster domain' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, domain: 'example.com') }
+
+ it 'should include KUBE_INGRESS_BASE_DOMAIN' do
+ expect(subject.to_hash).to include(KUBE_INGRESS_BASE_DOMAIN: 'example.com')
+ end
+ end
+
+ context 'with no domain' do
+ let(:cluster) { create(:cluster, :provided_by_gcp, :project) }
+
+ it 'should return an empty array' do
+ expect(subject.to_hash).to be_empty
+ end
+ end
+ end
end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
index 6c8a223092e..c273fa7e164 100644
--- a/spec/models/clusters/platforms/kubernetes_spec.rb
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -297,6 +297,19 @@ describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching
end
end
end
+
+ context 'with a domain' do
+ let!(:cluster) do
+ create(:cluster, :provided_by_gcp, :with_domain,
+ platform_kubernetes: kubernetes)
+ end
+
+ it 'sets KUBE_INGRESS_BASE_DOMAIN' do
+ expect(subject).to include(
+ { key: 'KUBE_INGRESS_BASE_DOMAIN', value: cluster.domain, public: true }
+ )
+ end
+ end
end
describe '#terminals' do
diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb
index 29197ef372e..447279f19a8 100644
--- a/spec/models/concerns/cache_markdown_field_spec.rb
+++ b/spec/models/concerns/cache_markdown_field_spec.rb
@@ -60,6 +60,10 @@ describe CacheMarkdownField do
changes_applied
end
end
+
+ def has_attribute?(attr_name)
+ attribute_names.include?(attr_name)
+ end
end
def thing_subclass(new_attr)
@@ -72,8 +76,8 @@ describe CacheMarkdownField do
let(:updated_markdown) { '`Bar`' }
let(:updated_html) { '<p dir="auto"><code>Bar</code></p>' }
- let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION) }
- let(:cache_version) { CacheMarkdownField::CACHE_COMMONMARK_VERSION }
+ let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) }
+ let(:cache_version) { CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16 }
before do
stub_commonmark_sourcepos_disabled
@@ -94,11 +98,11 @@ describe CacheMarkdownField do
it { expect(thing.foo).to eq(markdown) }
it { expect(thing.foo_html).to eq(html) }
it { expect(thing.foo_html_changed?).not_to be_truthy }
- it { expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) }
+ it { expect(thing.cached_markdown_version).to eq(cache_version) }
end
context 'a changed markdown field' do
- let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) }
+ let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version - 1) }
before do
thing.foo = updated_markdown
@@ -139,7 +143,7 @@ describe CacheMarkdownField do
end
context 'a non-markdown field changed' do
- let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version) }
+ let(:thing) { ThingWithMarkdownFields.new(foo: markdown, foo_html: html, cached_markdown_version: cache_version - 1) }
before do
thing.bar = 'OK'
@@ -160,7 +164,7 @@ describe CacheMarkdownField do
end
it { expect(thing.foo_html).to eq(updated_html) }
- it { expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION) }
+ it { expect(thing.cached_markdown_version).to eq(cache_version) }
end
describe '#cached_html_up_to_date?' do
@@ -174,21 +178,35 @@ describe CacheMarkdownField do
is_expected.to be_falsy
end
- it 'returns false when the version is too early' do
- thing.cached_markdown_version -= 1
+ it 'returns false when the cached version is too old' do
+ thing.cached_markdown_version = cache_version - 1
is_expected.to be_falsy
end
- it 'returns false when the version is too late' do
- thing.cached_markdown_version += 1
+ it 'returns false when the cached version is in future' do
+ thing.cached_markdown_version = cache_version + 1
is_expected.to be_falsy
end
- it 'returns true when the version is just right' do
+ it 'returns false when the local version was bumped' do
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2)
thing.cached_markdown_version = cache_version
+ is_expected.to be_falsy
+ end
+
+ it 'returns true when the local version is default' do
+ thing.cached_markdown_version = cache_version
+
+ is_expected.to be_truthy
+ end
+
+ it 'returns true when the cached version is just right' do
+ allow(Gitlab::CurrentSettings.current_application_settings).to receive(:local_markdown_version).and_return(2)
+ thing.cached_markdown_version = cache_version + 2
+
is_expected.to be_truthy
end
@@ -221,14 +239,9 @@ describe CacheMarkdownField do
describe '#latest_cached_markdown_version' do
subject { thing.latest_cached_markdown_version }
- it 'returns commonmark version' do
- thing.cached_markdown_version = CacheMarkdownField::CACHE_COMMONMARK_VERSION_START + 1
- is_expected.to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION)
- end
-
- it 'returns default version when version is nil' do
+ it 'returns default version' do
thing.cached_markdown_version = nil
- is_expected.to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION)
+ is_expected.to eq(cache_version)
end
end
@@ -255,7 +268,7 @@ describe CacheMarkdownField do
thing.cached_markdown_version = nil
thing.refresh_markdown_cache
- expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION)
+ expect(thing.cached_markdown_version).to eq(cache_version)
end
end
@@ -336,7 +349,7 @@ describe CacheMarkdownField do
expect(thing.foo_html).to eq(updated_html)
expect(thing.baz_html).to eq(updated_html)
- expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION)
+ expect(thing.cached_markdown_version).to eq(cache_version)
end
end
@@ -356,7 +369,7 @@ describe CacheMarkdownField do
expect(thing.foo_html).to eq(updated_html)
expect(thing.baz_html).to eq(updated_html)
- expect(thing.cached_markdown_version).to eq(CacheMarkdownField::CACHE_COMMONMARK_VERSION)
+ expect(thing.cached_markdown_version).to eq(cache_version)
end
end
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index ae137aa7b78..c1767ed0535 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -1765,7 +1765,7 @@ describe Project do
context 'using a regular repository' do
it 'creates the repository' do
expect(shell).to receive(:create_repository)
- .with(project.repository_storage, project.disk_path)
+ .with(project.repository_storage, project.disk_path, project.full_path)
.and_return(true)
expect(project.repository).to receive(:after_create)
@@ -1775,7 +1775,7 @@ describe Project do
it 'adds an error if the repository could not be created' do
expect(shell).to receive(:create_repository)
- .with(project.repository_storage, project.disk_path)
+ .with(project.repository_storage, project.disk_path, project.full_path)
.and_return(false)
expect(project.repository).not_to receive(:after_create)
@@ -1808,7 +1808,7 @@ describe Project do
.and_return(false)
allow(shell).to receive(:create_repository)
- .with(project.repository_storage, project.disk_path)
+ .with(project.repository_storage, project.disk_path, project.full_path)
.and_return(true)
expect(project).to receive(:create_repository).with(force: true)
@@ -1832,7 +1832,7 @@ describe Project do
.and_return(false)
expect(shell).to receive(:create_repository)
- .with(project.repository_storage, project.disk_path)
+ .with(project.repository_storage, project.disk_path, project.full_path)
.and_return(true)
project.ensure_repository
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 48a43801b9f..3ccc706edf2 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -7,7 +7,7 @@ describe ProjectWiki do
let(:repository) { project.repository }
let(:gitlab_shell) { Gitlab::Shell.new }
let(:project_wiki) { described_class.new(project, user) }
- let(:raw_repository) { Gitlab::Git::Repository.new(project.repository_storage, subject.disk_path + '.git', 'foo') }
+ let(:raw_repository) { Gitlab::Git::Repository.new(project.repository_storage, subject.disk_path + '.git', 'foo', 'group/project.wiki') }
let(:commit) { project_wiki.repository.head_commit }
subject { project_wiki }
@@ -75,7 +75,7 @@ describe ProjectWiki do
# Create a fresh project which will not have a wiki
project_wiki = described_class.new(create(:project), user)
gitlab_shell = double(:gitlab_shell)
- allow(gitlab_shell).to receive(:create_repository)
+ allow(gitlab_shell).to receive(:create_wiki_repository)
allow(project_wiki).to receive(:gitlab_shell).and_return(gitlab_shell)
expect { project_wiki.send(:wiki) }.to raise_exception(ProjectWiki::CouldNotCreateWikiError)
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index 4978c43c9b5..f78760bf567 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -2291,6 +2291,7 @@ describe Repository do
expect(subject).to be_a(Gitlab::Git::Repository)
expect(subject.relative_path).to eq(project.disk_path + '.git')
expect(subject.gl_repository).to eq("project-#{project.id}")
+ expect(subject.gl_project_path).to eq(project.full_path)
end
context 'with a wiki repository' do
@@ -2300,6 +2301,7 @@ describe Repository do
expect(subject).to be_a(Gitlab::Git::Repository)
expect(subject.relative_path).to eq(project.disk_path + '.wiki.git')
expect(subject.gl_repository).to eq("wiki-#{project.id}")
+ expect(subject.gl_project_path).to eq(project.full_path)
end
end
end
diff --git a/spec/models/resource_label_event_spec.rb b/spec/models/resource_label_event_spec.rb
index 3797960ac3d..7eeb2fae57d 100644
--- a/spec/models/resource_label_event_spec.rb
+++ b/spec/models/resource_label_event_spec.rb
@@ -81,14 +81,14 @@ RSpec.describe ResourceLabelEvent, type: :model do
expect(subject.outdated_markdown?).to be true
end
- it 'returns true markdown is outdated' do
- subject.attributes = { cached_markdown_version: 0 }
+ it 'returns true if markdown is outdated' do
+ subject.attributes = { cached_markdown_version: ((CacheMarkdownField::CACHE_COMMONMARK_VERSION - 1) << 16) | 0 }
expect(subject.outdated_markdown?).to be true
end
it 'returns false if label and reference are set' do
- subject.attributes = { reference: 'whatever', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION }
+ subject.attributes = { reference: 'whatever', cached_markdown_version: CacheMarkdownField::CACHE_COMMONMARK_VERSION << 16 }
expect(subject.outdated_markdown?).to be false
end
diff --git a/spec/presenters/blob_presenter_spec.rb b/spec/presenters/blob_presenter_spec.rb
index e85e7a41017..bb1db9a3d51 100644
--- a/spec/presenters/blob_presenter_spec.rb
+++ b/spec/presenters/blob_presenter_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
describe BlobPresenter, :seed_helper do
- let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '') }
+ let(:repository) { Gitlab::Git::Repository.new('default', TEST_REPO_PATH, '', 'group/project') }
let(:git_blob) do
Gitlab::Git::Blob.find(
diff --git a/spec/requests/api/group_labels_spec.rb b/spec/requests/api/group_labels_spec.rb
new file mode 100644
index 00000000000..3769f8b78e4
--- /dev/null
+++ b/spec/requests/api/group_labels_spec.rb
@@ -0,0 +1,258 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe API::GroupLabels do
+ let(:user) { create(:user) }
+ let(:group) { create(:group) }
+ let!(:group_member) { create(:group_member, group: group, user: user) }
+ let!(:label1) { create(:group_label, title: 'feature', group: group) }
+ let!(:label2) { create(:group_label, title: 'bug', group: group) }
+
+ describe 'GET :id/labels' do
+ it 'returns all available labels for the group' do
+ get api("/groups/#{group.id}/labels", user)
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(response).to match_response_schema('public_api/v4/group_labels')
+ expect(response).to include_pagination_headers
+ expect(json_response).to be_an Array
+ expect(json_response.size).to eq(2)
+ expect(json_response.map {|r| r['name'] }).to contain_exactly('feature', 'bug')
+ end
+ end
+
+ describe 'POST /groups/:id/labels' do
+ it 'returns created label when all params are given' do
+ post api("/groups/#{group.id}/labels", user),
+ params: {
+ name: 'Foo',
+ color: '#FFAABB',
+ description: 'test'
+ }
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['name']).to eq('Foo')
+ expect(json_response['color']).to eq('#FFAABB')
+ expect(json_response['description']).to eq('test')
+ end
+
+ it 'returns created label when only required params are given' do
+ post api("/groups/#{group.id}/labels", user),
+ params: {
+ name: 'Foo & Bar',
+ color: '#FFAABB'
+ }
+
+ expect(response.status).to eq(201)
+ expect(json_response['name']).to eq('Foo & Bar')
+ expect(json_response['color']).to eq('#FFAABB')
+ expect(json_response['description']).to be_nil
+ end
+
+ it 'returns a 400 bad request if name not given' do
+ post api("/groups/#{group.id}/labels", user), params: { color: '#FFAABB' }
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'returns a 400 bad request if color is not given' do
+ post api("/groups/#{group.id}/labels", user), params: { name: 'Foobar' }
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it 'returns 409 if label already exists' do
+ post api("/groups/#{group.id}/labels", user),
+ params: {
+ name: label1.name,
+ color: '#FFAABB'
+ }
+
+ expect(response).to have_gitlab_http_status(409)
+ expect(json_response['message']).to eq('Label already exists')
+ end
+ end
+
+ describe 'DELETE /groups/:id/labels' do
+ it 'returns 204 for existing label' do
+ delete api("/groups/#{group.id}/labels", user), params: { name: label1.name }
+
+ expect(response).to have_gitlab_http_status(204)
+ end
+
+ it 'returns 404 for non existing label' do
+ delete api("/groups/#{group.id}/labels", user), params: { name: 'label2' }
+
+ expect(response).to have_gitlab_http_status(404)
+ expect(json_response['message']).to eq('404 Label Not Found')
+ end
+
+ it 'returns 400 for wrong parameters' do
+ delete api("/groups/#{group.id}/labels", user)
+
+ expect(response).to have_gitlab_http_status(400)
+ end
+
+ it "does not delete parent's group labels", :nested_groups do
+ subgroup = create(:group, parent: group)
+ subgroup_label = create(:group_label, title: 'feature', group: subgroup)
+
+ delete api("/groups/#{subgroup.id}/labels", user), params: { name: subgroup_label.name }
+
+ expect(response).to have_gitlab_http_status(204)
+ expect(subgroup.labels.size).to eq(0)
+ expect(group.labels).to include(label1)
+ end
+
+ it_behaves_like '412 response' do
+ let(:request) { api("/groups/#{group.id}/labels", user) }
+ let(:params) { { name: label1.name } }
+ end
+ end
+
+ describe 'PUT /groups/:id/labels' do
+ it 'returns 200 if name and colors and description are changed' do
+ put api("/groups/#{group.id}/labels", user),
+ params: {
+ name: label1.name,
+ new_name: 'New Label',
+ color: '#FFFFFF',
+ description: 'test'
+ }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(json_response['name']).to eq('New Label')
+ expect(json_response['color']).to eq('#FFFFFF')
+ expect(json_response['description']).to eq('test')
+ end
+
+ it "does not update parent's group label", :nested_groups do
+ subgroup = create(:group, parent: group)
+ subgroup_label = create(:group_label, title: 'feature', group: subgroup)
+
+ put api("/groups/#{subgroup.id}/labels", user),
+ params: {
+ name: subgroup_label.name,
+ new_name: 'New Label'
+ }
+
+ expect(response).to have_gitlab_http_status(200)
+ expect(subgroup.labels[0].name).to eq('New Label')
+ expect(label1.name).to eq('feature')
+ end
+
+ it 'returns 404 if label does not exist' do
+ put api("/groups/#{group.id}/labels", user),
+ params: {
+ name: 'label2',
+ new_name: 'label3'
+ }
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+
+ it 'returns 400 if no label name given' do
+ put api("/groups/#{group.id}/labels", user), params: { new_name: label1.name }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('name is missing')
+ end
+
+ it 'returns 400 if no new parameters given' do
+ put api("/groups/#{group.id}/labels", user), params: { name: label1.name }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response['error']).to eq('new_name, color, description are missing, '\
+ 'at least one parameter must be provided')
+ end
+ end
+
+ describe 'POST /groups/:id/labels/:label_id/subscribe' do
+ context 'when label_id is a label title' do
+ it 'subscribes to the label' do
+ post api("/groups/#{group.id}/labels/#{label1.title}/subscribe", user)
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['name']).to eq(label1.title)
+ expect(json_response['subscribed']).to be_truthy
+ end
+ end
+
+ context 'when label_id is a label ID' do
+ it 'subscribes to the label' do
+ post api("/groups/#{group.id}/labels/#{label1.id}/subscribe", user)
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['name']).to eq(label1.title)
+ expect(json_response['subscribed']).to be_truthy
+ end
+ end
+
+ context 'when user is already subscribed to label' do
+ before do
+ label1.subscribe(user)
+ end
+
+ it 'returns 304' do
+ post api("/groups/#{group.id}/labels/#{label1.id}/subscribe", user)
+
+ expect(response).to have_gitlab_http_status(304)
+ end
+ end
+
+ context 'when label ID is not found' do
+ it 'returns 404 error' do
+ post api("/groups/#{group.id}/labels/1234/subscribe", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+
+ describe 'POST /groups/:id/labels/:label_id/unsubscribe' do
+ before do
+ label1.subscribe(user)
+ end
+
+ context 'when label_id is a label title' do
+ it 'unsubscribes from the label' do
+ post api("/groups/#{group.id}/labels/#{label1.title}/unsubscribe", user)
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['name']).to eq(label1.title)
+ expect(json_response['subscribed']).to be_falsey
+ end
+ end
+
+ context 'when label_id is a label ID' do
+ it 'unsubscribes from the label' do
+ post api("/groups/#{group.id}/labels/#{label1.id}/unsubscribe", user)
+
+ expect(response).to have_gitlab_http_status(201)
+ expect(json_response['name']).to eq(label1.title)
+ expect(json_response['subscribed']).to be_falsey
+ end
+ end
+
+ context 'when user is already unsubscribed from label' do
+ before do
+ label1.unsubscribe(user)
+ end
+
+ it 'returns 304' do
+ post api("/groups/#{group.id}/labels/#{label1.id}/unsubscribe", user)
+
+ expect(response).to have_gitlab_http_status(304)
+ end
+ end
+
+ context 'when label ID is not found' do
+ it 'returns 404 error' do
+ post api("/groups/#{group.id}/labels/1234/unsubscribe", user)
+
+ expect(response).to have_gitlab_http_status(404)
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 51343287a13..0f5f6e38819 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -951,6 +951,29 @@ describe API::MergeRequests do
expect(response).to have_gitlab_http_status(404)
end
+
+ describe "the squash_commit_message param" do
+ let(:squash_commit) do
+ project.repository.commits_between(json_response['diff_refs']['start_sha'], json_response['merge_commit_sha']).first
+ end
+
+ it "results in a specific squash commit message when set" do
+ squash_commit_message = 'My custom squash commit message'
+
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: {
+ squash: true,
+ squash_commit_message: squash_commit_message
+ }
+
+ expect(squash_commit.message.chomp).to eq(squash_commit_message)
+ end
+
+ it "results in a default squash commit message when not set" do
+ put api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/merge", user), params: { squash: true }
+
+ expect(squash_commit.message).to eq(merge_request.default_squash_commit_message)
+ end
+ end
end
describe "PUT /projects/:id/merge_requests/:merge_request_iid" do
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 45fb1562e84..f33eb5b9e02 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -64,7 +64,8 @@ describe API::Settings, 'Settings' do
performance_bar_allowed_group_path: group.full_path,
instance_statistics_visibility_private: true,
diff_max_patch_bytes: 150_000,
- default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE
+ default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_MERGE,
+ local_markdown_version: 3
}
expect(response).to have_gitlab_http_status(200)
@@ -90,6 +91,7 @@ describe API::Settings, 'Settings' do
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)
+ expect(json_response['local_markdown_version']).to eq(3)
end
end
diff --git a/spec/requests/openid_connect_spec.rb b/spec/requests/openid_connect_spec.rb
index 2b148c1b563..2a455523e2c 100644
--- a/spec/requests/openid_connect_spec.rb
+++ b/spec/requests/openid_connect_spec.rb
@@ -35,7 +35,7 @@ describe 'OpenID Connect requests' do
'name' => 'Alice',
'nickname' => 'alice',
'email' => 'public@example.com',
- 'email_verified' => true,
+ 'email_verified' => false,
'website' => 'https://example.com',
'profile' => 'http://localhost/alice',
'picture' => "http://localhost/uploads/-/system/user/avatar/#{user.id}/dk.png",
@@ -111,6 +111,18 @@ describe 'OpenID Connect requests' do
it 'does not include any unknown claims' do
expect(json_response.keys).to eq %w[sub sub_legacy] + user_info_claims.keys
end
+
+ it 'includes email and email_verified claims' do
+ expect(json_response.keys).to include('email', 'email_verified')
+ end
+
+ it 'has public email in email claim' do
+ expect(json_response['email']).to eq(user.public_email)
+ end
+
+ it 'has false in email_verified claim' do
+ expect(json_response['email_verified']).to eq(false)
+ end
end
context 'ID token payload' do
@@ -175,7 +187,35 @@ describe 'OpenID Connect requests' do
expect(response).to have_gitlab_http_status(200)
expect(json_response['issuer']).to eq('http://localhost')
expect(json_response['jwks_uri']).to eq('http://www.example.com/oauth/discovery/keys')
- expect(json_response['scopes_supported']).to eq(%w[api read_user sudo read_repository openid])
+ expect(json_response['scopes_supported']).to eq(%w[api read_user sudo read_repository openid profile email])
+ end
+ end
+
+ context 'Application with OpenID and email scopes' do
+ let(:application) { create :oauth_application, scopes: 'openid email' }
+
+ it 'token response includes an ID token' do
+ request_access_token!
+
+ expect(json_response).to include 'id_token'
+ end
+
+ context 'UserInfo payload' do
+ before do
+ request_user_info!
+ end
+
+ it 'includes the email and email_verified claims' do
+ expect(json_response.keys).to include('email', 'email_verified')
+ end
+
+ it 'has private email in email claim' do
+ expect(json_response['email']).to eq(user.email)
+ end
+
+ it 'has true in email_verified claim' do
+ expect(json_response['email_verified']).to eq(true)
+ end
end
end
end
diff --git a/spec/serializers/cluster_application_entity_spec.rb b/spec/serializers/cluster_application_entity_spec.rb
index 88d16a5b360..7e151c3744e 100644
--- a/spec/serializers/cluster_application_entity_spec.rb
+++ b/spec/serializers/cluster_application_entity_spec.rb
@@ -21,6 +21,14 @@ describe ClusterApplicationEntity do
expect(subject[:status_reason]).to be_nil
end
+ context 'non-helm application' do
+ let(:application) { build(:clusters_applications_runner, version: '0.0.0') }
+
+ it 'has update_available' do
+ expect(subject[:update_available]).to be_truthy
+ end
+ end
+
context 'when application is errored' do
let(:application) { build(:clusters_applications_helm, :errored) }
diff --git a/spec/serializers/deployment_entity_spec.rb b/spec/serializers/deployment_entity_spec.rb
index cfa5414b40f..894fd7a0a12 100644
--- a/spec/serializers/deployment_entity_spec.rb
+++ b/spec/serializers/deployment_entity_spec.rb
@@ -1,14 +1,22 @@
require 'spec_helper'
describe DeploymentEntity do
- let(:user) { create(:user) }
+ let(:user) { developer }
+ let(:developer) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:project) { create(:project) }
let(:request) { double('request') }
- let(:deployment) { create(:deployment) }
+ let(:deployment) { create(:deployment, deployable: build, project: project) }
+ let(:build) { create(:ci_build, :manual, pipeline: pipeline) }
+ let(:pipeline) { create(:ci_pipeline, project: project, user: user) }
let(:entity) { described_class.new(deployment, request: request) }
subject { entity.as_json }
before do
+ project.add_developer(developer)
+ project.add_reporter(reporter)
allow(request).to receive(:current_user).and_return(user)
+ allow(request).to receive(:project).and_return(project)
end
it 'exposes internal deployment id' do
@@ -23,6 +31,24 @@ describe DeploymentEntity do
expect(subject).to include(:created_at)
end
+ context 'when the pipeline has another manual action' do
+ let(:other_build) { create(:ci_build, :manual, name: 'another deploy', pipeline: pipeline) }
+ let!(:other_deployment) { create(:deployment, deployable: other_build) }
+
+ it 'returns another manual action' do
+ expect(subject[:manual_actions].count).to eq(1)
+ expect(subject[:manual_actions].first[:name]).to eq('another deploy')
+ end
+
+ context 'when user is a reporter' do
+ let(:user) { reporter }
+
+ it 'returns another manual action' do
+ expect(subject[:manual_actions]).not_to be_present
+ end
+ end
+ end
+
describe 'scheduled_actions' do
let(:project) { create(:project, :repository) }
let(:pipeline) { create(:ci_pipeline, project: project, user: user) }
diff --git a/spec/serializers/environment_serializer_spec.rb b/spec/serializers/environment_serializer_spec.rb
index 87493a28d1f..3541bd5f12e 100644
--- a/spec/serializers/environment_serializer_spec.rb
+++ b/spec/serializers/environment_serializer_spec.rb
@@ -10,6 +10,10 @@ describe EnvironmentSerializer do
.represent(resource)
end
+ before do
+ project.add_developer(user)
+ end
+
context 'when there is a single object provided' do
let(:project) { create(:project, :repository) }
let(:deployable) { create(:ci_build) }
diff --git a/spec/services/clusters/applications/check_installation_progress_service_spec.rb b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
index 45b8ce94815..19446ce1cf8 100644
--- a/spec/services/clusters/applications/check_installation_progress_service_spec.rb
+++ b/spec/services/clusters/applications/check_installation_progress_service_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Clusters::Applications::CheckInstallationProgressService do
+describe Clusters::Applications::CheckInstallationProgressService, '#execute' do
RESCHEDULE_PHASES = Gitlab::Kubernetes::Pod::PHASES - [Gitlab::Kubernetes::Pod::SUCCEEDED, Gitlab::Kubernetes::Pod::FAILED].freeze
let(:application) { create(:clusters_applications_helm, :installing) }
@@ -21,24 +21,39 @@ describe Clusters::Applications::CheckInstallationProgressService do
expect(ClusterWaitForAppInstallationWorker).to receive(:perform_in).once
expect(service).not_to receive(:remove_installation_pod)
- service.execute
+ expect do
+ service.execute
+
+ application.reload
+ end.not_to change(application, :status)
- expect(application).to be_installing
expect(application.status_reason).to be_nil
end
end
+ end
+ end
- context 'when timeouted' do
- let(:application) { create(:clusters_applications_helm, :timeouted) }
+ shared_examples 'error logging' do
+ context 'when installation raises a Kubeclient::HttpError' do
+ let(:cluster) { create(:cluster, :provided_by_user, :project) }
- it 'make the application errored' do
- expect(ClusterWaitForAppInstallationWorker).not_to receive(:perform_in)
+ before do
+ application.update!(cluster: cluster)
- service.execute
+ expect(service).to receive(:installation_phase).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil))
+ end
- expect(application).to be_errored
- expect(application.status_reason).to eq("Installation timed out. Check pod logs for install-helm for more details.")
- end
+ it 'shows the response code from the error' do
+ service.execute
+
+ expect(application).to be_errored.or(be_update_errored)
+ expect(application.status_reason).to eq('Kubernetes error: 401')
+ end
+
+ it 'should log error' do
+ expect(service.send(:logger)).to receive(:error)
+
+ service.execute
end
end
end
@@ -48,10 +63,15 @@ describe Clusters::Applications::CheckInstallationProgressService do
allow(service).to receive(:remove_installation_pod).and_return(nil)
end
- describe '#execute' do
+ context 'when application is updating' do
+ let(:application) { create(:clusters_applications_helm, :updating) }
+
+ include_examples 'error logging'
+
+ RESCHEDULE_PHASES.each { |phase| it_behaves_like 'a not yet terminated installation', phase }
+
context 'when installation POD succeeded' do
let(:phase) { Gitlab::Kubernetes::Pod::SUCCEEDED }
-
before do
expect(service).to receive(:installation_phase).once.and_return(phase)
end
@@ -67,7 +87,7 @@ describe Clusters::Applications::CheckInstallationProgressService do
service.execute
- expect(application).to be_installed
+ expect(application).to be_updated
expect(application.status_reason).to be_nil
end
end
@@ -83,33 +103,86 @@ describe Clusters::Applications::CheckInstallationProgressService do
it 'make the application errored' do
service.execute
- expect(application).to be_errored
- expect(application.status_reason).to eq("Installation failed. Check pod logs for install-helm for more details.")
+ expect(application).to be_update_errored
+ expect(application.status_reason).to eq('Operation failed. Check pod logs for install-helm for more details.')
end
end
- RESCHEDULE_PHASES.each { |phase| it_behaves_like 'a not yet terminated installation', phase }
+ context 'when timed out' do
+ let(:application) { create(:clusters_applications_helm, :timeouted, :updating) }
- context 'when installation raises a Kubeclient::HttpError' do
- let(:cluster) { create(:cluster, :provided_by_user, :project) }
+ before do
+ expect(service).to receive(:installation_phase).once.and_return(phase)
+ end
+
+ it 'make the application errored' do
+ expect(ClusterWaitForAppInstallationWorker).not_to receive(:perform_in)
+
+ service.execute
+
+ expect(application).to be_update_errored
+ expect(application.status_reason).to eq('Operation timed out. Check pod logs for install-helm for more details.')
+ end
+ end
+ end
+
+ context 'when application is installing' do
+ include_examples 'error logging'
+
+ RESCHEDULE_PHASES.each { |phase| it_behaves_like 'a not yet terminated installation', phase }
+ context 'when installation POD succeeded' do
+ let(:phase) { Gitlab::Kubernetes::Pod::SUCCEEDED }
before do
- application.update!(cluster: cluster)
+ expect(service).to receive(:installation_phase).once.and_return(phase)
+ end
- expect(service).to receive(:installation_phase).and_raise(Kubeclient::HttpError.new(401, 'Unauthorized', nil))
+ it 'removes the installation POD' do
+ expect(service).to receive(:remove_installation_pod).once
+
+ service.execute
end
- it 'shows the response code from the error' do
+ it 'make the application installed' do
+ expect(ClusterWaitForAppInstallationWorker).not_to receive(:perform_in)
+
+ service.execute
+
+ expect(application).to be_installed
+ expect(application.status_reason).to be_nil
+ end
+ end
+
+ context 'when installation POD failed' do
+ let(:phase) { Gitlab::Kubernetes::Pod::FAILED }
+ let(:errors) { 'test installation failed' }
+
+ before do
+ expect(service).to receive(:installation_phase).once.and_return(phase)
+ end
+
+ it 'make the application errored' do
service.execute
expect(application).to be_errored
- expect(application.status_reason).to eq('Kubernetes error: 401')
+ expect(application.status_reason).to eq('Operation failed. Check pod logs for install-helm for more details.')
+ end
+ end
+
+ context 'when timed out' do
+ let(:application) { create(:clusters_applications_helm, :timeouted) }
+
+ before do
+ expect(service).to receive(:installation_phase).once.and_return(phase)
end
- it 'should log error' do
- expect(service.send(:logger)).to receive(:error)
+ it 'make the application errored' do
+ expect(ClusterWaitForAppInstallationWorker).not_to receive(:perform_in)
service.execute
+
+ expect(application).to be_errored
+ expect(application.status_reason).to eq('Operation timed out. Check pod logs for install-helm for more details.')
end
end
end
diff --git a/spec/services/clusters/applications/create_service_spec.rb b/spec/services/clusters/applications/create_service_spec.rb
index 1a2ca23748a..3f621ed5944 100644
--- a/spec/services/clusters/applications/create_service_spec.rb
+++ b/spec/services/clusters/applications/create_service_spec.rb
@@ -13,6 +13,7 @@ describe Clusters::Applications::CreateService do
describe '#execute' do
before do
allow(ClusterInstallAppWorker).to receive(:perform_async)
+ allow(ClusterUpgradeAppWorker).to receive(:perform_async)
end
subject { service.execute(test_request) }
@@ -31,6 +32,22 @@ describe Clusters::Applications::CreateService do
subject
end
+ context 'application already installed' do
+ let!(:application) { create(:clusters_applications_helm, :installed, cluster: cluster) }
+
+ it 'does not create a new application' do
+ expect do
+ subject
+ end.not_to change(Clusters::Applications::Helm, :count)
+ end
+
+ it 'schedules an upgrade for the application' do
+ expect(Clusters::Applications::ScheduleInstallationService).to receive(:new).with(application).and_call_original
+
+ subject
+ end
+ end
+
context 'cert manager application' do
let(:params) do
{
diff --git a/spec/services/clusters/applications/schedule_installation_service_spec.rb b/spec/services/clusters/applications/schedule_installation_service_spec.rb
index 21797edd533..8380932dfaa 100644
--- a/spec/services/clusters/applications/schedule_installation_service_spec.rb
+++ b/spec/services/clusters/applications/schedule_installation_service_spec.rb
@@ -49,5 +49,29 @@ describe Clusters::Applications::ScheduleInstallationService do
it_behaves_like 'a failing service'
end
+
+ context 'when application is installed' do
+ let(:application) { create(:clusters_applications_helm, :installed) }
+
+ it 'schedules an upgrade via worker' do
+ expect(ClusterUpgradeAppWorker).to receive(:perform_async).with(application.name, application.id).once
+
+ service.execute
+
+ expect(application).to be_scheduled
+ end
+ end
+
+ context 'when application is updated' do
+ let(:application) { create(:clusters_applications_helm, :updated) }
+
+ it 'schedules an upgrade via worker' do
+ expect(ClusterUpgradeAppWorker).to receive(:perform_async).with(application.name, application.id).once
+
+ service.execute
+
+ expect(application).to be_scheduled
+ end
+ end
end
end
diff --git a/spec/services/clusters/applications/upgrade_service_spec.rb b/spec/services/clusters/applications/upgrade_service_spec.rb
new file mode 100644
index 00000000000..1822fc38dbd
--- /dev/null
+++ b/spec/services/clusters/applications/upgrade_service_spec.rb
@@ -0,0 +1,128 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::Applications::UpgradeService do
+ describe '#execute' do
+ let(:application) { create(:clusters_applications_helm, :scheduled) }
+ let!(:install_command) { application.install_command }
+ let(:service) { described_class.new(application) }
+ let(:helm_client) { instance_double(Gitlab::Kubernetes::Helm::Api) }
+
+ before do
+ allow(service).to receive(:install_command).and_return(install_command)
+ allow(service).to receive(:helm_api).and_return(helm_client)
+ end
+
+ context 'when there are no errors' do
+ before do
+ expect(helm_client).to receive(:update).with(install_command)
+ allow(ClusterWaitForAppInstallationWorker).to receive(:perform_in).and_return(nil)
+ end
+
+ it 'make the application updating' do
+ expect(application.cluster).not_to be_nil
+ service.execute
+
+ expect(application).to be_updating
+ end
+
+ it 'schedule async installation status check' do
+ expect(ClusterWaitForAppInstallationWorker).to receive(:perform_in).once
+
+ service.execute
+ end
+ end
+
+ context 'when kubernetes cluster communication fails' do
+ let(:error) { Kubeclient::HttpError.new(500, 'system failure', nil) }
+
+ before do
+ expect(helm_client).to receive(:update).with(install_command).and_raise(error)
+ end
+
+ it 'make the application errored' do
+ service.execute
+
+ expect(application).to be_update_errored
+ expect(application.status_reason).to match('Kubernetes error: 500')
+ end
+
+ it 'logs errors' do
+ expect(service.send(:logger)).to receive(:error).with(
+ {
+ exception: 'Kubeclient::HttpError',
+ message: 'system failure',
+ service: 'Clusters::Applications::UpgradeService',
+ app_id: application.id,
+ project_ids: application.cluster.project_ids,
+ group_ids: [],
+ error_code: 500
+ }
+ )
+
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
+ error,
+ extra: {
+ exception: 'Kubeclient::HttpError',
+ message: 'system failure',
+ service: 'Clusters::Applications::UpgradeService',
+ app_id: application.id,
+ project_ids: application.cluster.project_ids,
+ group_ids: [],
+ error_code: 500
+ }
+ )
+
+ service.execute
+ end
+ end
+
+ context 'a non kubernetes error happens' do
+ let(:application) { create(:clusters_applications_helm, :scheduled) }
+ let(:error) { StandardError.new('something bad happened') }
+
+ before do
+ expect(application).to receive(:make_updating!).once.and_raise(error)
+ end
+
+ it 'make the application errored' do
+ expect(helm_client).not_to receive(:update)
+
+ service.execute
+
+ expect(application).to be_update_errored
+ expect(application.status_reason).to eq("Can't start upgrade process.")
+ end
+
+ it 'logs errors' do
+ expect(service.send(:logger)).to receive(:error).with(
+ {
+ exception: 'StandardError',
+ error_code: nil,
+ message: 'something bad happened',
+ service: 'Clusters::Applications::UpgradeService',
+ app_id: application.id,
+ project_ids: application.cluster.projects.pluck(:id),
+ group_ids: []
+ }
+ )
+
+ expect(Gitlab::Sentry).to receive(:track_acceptable_exception).with(
+ error,
+ extra: {
+ exception: 'StandardError',
+ error_code: nil,
+ message: 'something bad happened',
+ service: 'Clusters::Applications::UpgradeService',
+ app_id: application.id,
+ project_ids: application.cluster.projects.pluck(:id),
+ group_ids: []
+ }
+ )
+
+ service.execute
+ end
+ end
+ end
+end
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index ef76e2311b1..931e47d3a77 100644
--- a/spec/services/issues/update_service_spec.rb
+++ b/spec/services/issues/update_service_spec.rb
@@ -471,6 +471,8 @@ describe Issues::UpdateService, :mailer do
it { expect(issue.tasks?).to eq(true) }
+ it_behaves_like 'updating a single task'
+
context 'when tasks are marked as completed' do
before do
update_issue(description: "- [x] Task 1\n- [X] Task 2")
@@ -543,76 +545,6 @@ describe Issues::UpdateService, :mailer do
end
end
- context 'when updating a single task' do
- before do
- update_issue(description: "- [ ] Task 1\n- [ ] Task 2")
- end
-
- it { expect(issue.tasks?).to eq(true) }
-
- context 'when a task is marked as completed' do
- before do
- update_issue(update_task: { index: 1, checked: true, line_source: '- [ ] Task 1', line_number: 1 })
- end
-
- it 'creates system note about task status change' do
- note1 = find_note('marked the task **Task 1** as completed')
-
- expect(note1).not_to be_nil
-
- description_notes = find_notes('description')
- expect(description_notes.length).to eq(1)
- end
- end
-
- context 'when a task is marked as incomplete' do
- before do
- update_issue(description: "- [x] Task 1\n- [X] Task 2")
- update_issue(update_task: { index: 2, checked: false, line_source: '- [X] Task 2', line_number: 2 })
- end
-
- it 'creates system note about task status change' do
- note1 = find_note('marked the task **Task 2** as incomplete')
-
- expect(note1).not_to be_nil
-
- description_notes = find_notes('description')
- expect(description_notes.length).to eq(1)
- end
- end
-
- context 'when the task position has been modified' do
- before do
- update_issue(description: "- [ ] Task 1\n- [ ] Task 3\n- [ ] Task 2")
- end
-
- it 'raises an exception' do
- expect(Note.count).to eq(2)
- expect do
- update_issue(update_task: { index: 2, checked: true, line_source: '- [ ] Task 2', line_number: 2 })
- end.to raise_error(ActiveRecord::StaleObjectError)
- expect(Note.count).to eq(2)
- end
- end
-
- context 'when the content changes but not task line number' do
- before do
- update_issue(description: "Paragraph\n\n- [ ] Task 1\n- [x] Task 2")
- update_issue(description: "Paragraph with more words\n\n- [ ] Task 1\n- [x] Task 2")
- update_issue(update_task: { index: 2, checked: false, line_source: '- [x] Task 2', line_number: 4 })
- end
-
- it 'creates system note about task status change' do
- note1 = find_note('marked the task **Task 2** as incomplete')
-
- expect(note1).not_to be_nil
-
- description_notes = find_notes('description')
- expect(description_notes.length).to eq(2)
- end
- end
- end
-
context 'updating labels' do
let(:label3) { create(:label, project: project) }
let(:result) { described_class.new(project, user, params).execute(issue).reload }
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index be5ad849ba7..20580bf14b9 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -466,6 +466,8 @@ describe MergeRequests::UpdateService, :mailer do
it { expect(@merge_request.tasks?).to eq(true) }
+ it_behaves_like 'updating a single task'
+
context 'when tasks are marked as completed' do
before do
update_merge_request({ description: "- [x] Task 1\n- [X] Task 2" })
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 54ce33dd103..d1b110b9806 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -116,7 +116,7 @@ describe Projects::CreateService, '#execute' do
def wiki_repo(project)
relative_path = ProjectWiki.new(project).disk_path + '.git'
- Gitlab::Git::Repository.new(project.repository_storage, relative_path, 'foobar')
+ Gitlab::Git::Repository.new(project.repository_storage, relative_path, 'foobar', project.full_path)
end
end
@@ -198,7 +198,7 @@ describe Projects::CreateService, '#execute' do
context 'with legacy storage' do
before do
- gitlab_shell.create_repository(repository_storage, "#{user.namespace.full_path}/existing")
+ gitlab_shell.create_repository(repository_storage, "#{user.namespace.full_path}/existing", 'group/project')
end
after do
@@ -234,7 +234,7 @@ describe Projects::CreateService, '#execute' do
end
before do
- gitlab_shell.create_repository(repository_storage, hashed_path)
+ gitlab_shell.create_repository(repository_storage, hashed_path, 'group/project')
end
after do
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index 26e8d829345..23ec29cce7b 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -119,7 +119,7 @@ describe Projects::ForkService do
let(:repository_storage_path) { Gitlab.config.repositories.storages[repository_storage].legacy_disk_path }
before do
- gitlab_shell.create_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}")
+ gitlab_shell.create_repository(repository_storage, "#{@to_user.namespace.full_path}/#{@from_project.path}", "#{@to_user.namespace.full_path}/#{@from_project.path}")
end
after do
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 766276fdba3..aae50d5307f 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -201,7 +201,7 @@ describe Projects::TransferService do
before do
group.add_owner(user)
- unless gitlab_shell.create_repository(repository_storage, "#{group.full_path}/#{project.path}")
+ unless gitlab_shell.create_repository(repository_storage, "#{group.full_path}/#{project.path}", project.full_path)
raise 'failed to add repository'
end
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 8adfc63222e..90eaea9c872 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -232,7 +232,7 @@ describe Projects::UpdateService do
let(:project) { create(:project, :legacy_storage, :repository, creator: user, namespace: user.namespace) }
before do
- gitlab_shell.create_repository(repository_storage, "#{user.namespace.full_path}/existing")
+ gitlab_shell.create_repository(repository_storage, "#{user.namespace.full_path}/existing", user.namespace.full_path)
end
after do
diff --git a/spec/services/task_list_toggle_service_spec.rb b/spec/services/task_list_toggle_service_spec.rb
index cc64dd25085..7c5480d382f 100644
--- a/spec/services/task_list_toggle_service_spec.rb
+++ b/spec/services/task_list_toggle_service_spec.rb
@@ -67,6 +67,17 @@ describe TaskListToggleService do
expect(toggler.execute).to be_falsey
end
+ it 'tolerates \r\n line endings' do
+ rn_markdown = markdown.gsub("\n", "\r\n")
+ toggler = described_class.new(rn_markdown, markdown_html,
+ toggle_as_checked: true,
+ line_source: '* [ ] Task 1', line_number: 1)
+
+ expect(toggler.execute).to be_truthy
+ expect(toggler.updated_markdown.lines[0]).to eq "* [x] Task 1\r\n"
+ expect(toggler.updated_markdown_html).to include('disabled checked> Task 1')
+ end
+
it 'returns false if markdown is nil' do
toggler = described_class.new(nil, markdown_html,
toggle_as_checked: false,
diff --git a/spec/support/shared_examples/issuable_shared_examples.rb b/spec/support/shared_examples/issuable_shared_examples.rb
index 42f3b4db23c..c3d40c5b231 100644
--- a/spec/support/shared_examples/issuable_shared_examples.rb
+++ b/spec/support/shared_examples/issuable_shared_examples.rb
@@ -36,3 +36,76 @@ shared_examples 'system notes for milestones' do
end
end
end
+
+shared_examples 'updating a single task' do
+ def update_issuable(opts)
+ issuable = try(:issue) || try(:merge_request)
+ described_class.new(project, user, opts).execute(issuable)
+ end
+
+ before do
+ update_issuable(description: "- [ ] Task 1\n- [ ] Task 2")
+ end
+
+ context 'when a task is marked as completed' do
+ before do
+ update_issuable(update_task: { index: 1, checked: true, line_source: '- [ ] Task 1', line_number: 1 })
+ end
+
+ it 'creates system note about task status change' do
+ note1 = find_note('marked the task **Task 1** as completed')
+
+ expect(note1).not_to be_nil
+
+ description_notes = find_notes('description')
+ expect(description_notes.length).to eq(1)
+ end
+ end
+
+ context 'when a task is marked as incomplete' do
+ before do
+ update_issuable(description: "- [x] Task 1\n- [X] Task 2")
+ update_issuable(update_task: { index: 2, checked: false, line_source: '- [X] Task 2', line_number: 2 })
+ end
+
+ it 'creates system note about task status change' do
+ note1 = find_note('marked the task **Task 2** as incomplete')
+
+ expect(note1).not_to be_nil
+
+ description_notes = find_notes('description')
+ expect(description_notes.length).to eq(1)
+ end
+ end
+
+ context 'when the task position has been modified' do
+ before do
+ update_issuable(description: "- [ ] Task 1\n- [ ] Task 3\n- [ ] Task 2")
+ end
+
+ it 'raises an exception' do
+ expect(Note.count).to eq(2)
+ expect do
+ update_issuable(update_task: { index: 2, checked: true, line_source: '- [ ] Task 2', line_number: 2 })
+ end.to raise_error(ActiveRecord::StaleObjectError)
+ expect(Note.count).to eq(2)
+ end
+ end
+
+ context 'when the content changes but not task line number' do
+ before do
+ update_issuable(description: "Paragraph\n\n- [ ] Task 1\n- [x] Task 2")
+ update_issuable(description: "Paragraph with more words\n\n- [ ] Task 1\n- [x] Task 2")
+ update_issuable(update_task: { index: 2, checked: false, line_source: '- [x] Task 2', line_number: 4 })
+ end
+
+ it 'creates system note about task status change' do
+ note1 = find_note('marked the task **Task 2** as incomplete')
+
+ expect(note1).not_to be_nil
+
+ description_notes = find_notes('description')
+ expect(description_notes.length).to eq(2)
+ end
+ end
+end
diff --git a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
index 554f2e747bc..0b19b50fdfc 100644
--- a/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
+++ b/spec/support/shared_examples/models/cluster_application_status_shared_examples.rb
@@ -48,6 +48,36 @@ shared_examples 'cluster application status specs' do |application_name|
expect(subject.version).to eq(subject.class.const_get(:VERSION))
end
+
+ context 'application is updating' do
+ subject { create(application_name, :updating) }
+
+ it 'is updated' do
+ subject.make_installed!
+
+ expect(subject).to be_updated
+ end
+
+ it 'updates helm version' do
+ subject.cluster.application_helm.update!(version: '1.2.3')
+
+ subject.make_installed!
+
+ subject.cluster.application_helm.reload
+
+ expect(subject.cluster.application_helm.version).to eq(Gitlab::Kubernetes::Helm::HELM_VERSION)
+ end
+
+ it 'updates the version of the application' do
+ subject.update!(version: '0.0.0')
+
+ subject.make_installed!
+
+ subject.reload
+
+ expect(subject.version).to eq(subject.class.const_get(:VERSION))
+ end
+ end
end
describe '#make_updated' do
@@ -90,6 +120,17 @@ shared_examples 'cluster application status specs' do |application_name|
expect(subject).to be_errored
expect(subject.status_reason).to eq(reason)
end
+
+ context 'application is updating' do
+ subject { create(application_name, :updating) }
+
+ it 'is update_errored' do
+ subject.make_errored(reason)
+
+ expect(subject).to be_update_errored
+ expect(subject.status_reason).to eq(reason)
+ end
+ end
end
describe '#make_scheduled' do
@@ -112,6 +153,18 @@ shared_examples 'cluster application status specs' do |application_name|
expect(subject.status_reason).to be_nil
end
end
+
+ describe 'when was updated_errored' do
+ subject { create(application_name, :update_errored) }
+
+ it 'clears #status_reason' do
+ expect(subject.status_reason).not_to be_nil
+
+ subject.make_scheduled!
+
+ expect(subject.status_reason).to be_nil
+ end
+ end
end
end
diff --git a/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb b/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb
new file mode 100644
index 00000000000..181b102e685
--- /dev/null
+++ b/spec/support/shared_examples/models/cluster_application_version_shared_examples.rb
@@ -0,0 +1,20 @@
+# frozen_string_literal: true
+
+shared_examples 'cluster application version specs' do |application_name|
+ describe 'update_available?' do
+ let(:version) { '0.0.0' }
+
+ subject { create(application_name, :installed, version: version).update_available? }
+
+ context 'version is not the same as VERSION' do
+ it { is_expected.to be_truthy }
+ end
+
+ context 'version is the same as VERSION' do
+ let(:application) { build(application_name) }
+ let(:version) { application.class.const_get(:VERSION) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+end
diff --git a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
index cb1b9e6f5fb..2a2539c80b5 100644
--- a/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
+++ b/spec/views/projects/settings/ci_cd/_autodevops_form.html.haml_spec.rb
@@ -7,56 +7,9 @@ describe 'projects/settings/ci_cd/_autodevops_form' do
assign :project, project
end
- context 'when kubernetes is not active' do
- context 'when auto devops domain is not defined' do
- it 'shows warning message' do
- render
+ it 'shows a warning message about Kubernetes cluster' do
+ render
- expect(rendered).to have_css('.auto-devops-warning-message')
- expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a domain name and a')
- expect(rendered).to have_link('Kubernetes cluster')
- end
- end
-
- context 'when auto devops domain is defined' do
- before do
- project.build_auto_devops(domain: 'example.com')
- end
-
- it 'shows warning message' do
- render
-
- expect(rendered).to have_css('.auto-devops-warning-message')
- expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a')
- expect(rendered).to have_link('Kubernetes cluster')
- end
- end
- end
-
- context 'when kubernetes is active' do
- before do
- create(:kubernetes_service, project: project)
- end
-
- context 'when auto devops domain is not defined' do
- it 'shows warning message' do
- render
-
- expect(rendered).to have_css('.auto-devops-warning-message')
- expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a domain name to work correctly.')
- end
- end
-
- context 'when auto devops domain is defined' do
- before do
- project.build_auto_devops(domain: 'example.com')
- end
-
- it 'does not show warning message' do
- render
-
- expect(rendered).not_to have_css('.auto-devops-warning-message')
- end
- end
+ expect(rendered).to have_text('You must add a Kubernetes cluster integration to this project with a domain in order for your deployment strategy to work correctly.')
end
end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 9176eb12b12..caae46a3175 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -141,11 +141,18 @@ describe PostReceive do
let(:gl_repository) { "wiki-#{project.id}" }
it 'updates project activity' do
- described_class.new.perform(gl_repository, key_id, base64_changes)
+ # Force Project#set_timestamps_for_create to initialize timestamps
+ project
- expect { project.reload }
- .to change(project, :last_activity_at)
- .and change(project, :last_repository_updated_at)
+ # MySQL drops milliseconds in the timestamps, so advance at least
+ # a second to ensure we see changes.
+ Timecop.freeze(1.second.from_now) do
+ expect do
+ described_class.new.perform(gl_repository, key_id, base64_changes)
+ project.reload
+ end.to change(project, :last_activity_at)
+ .and change(project, :last_repository_updated_at)
+ end
end
end
diff --git a/spec/workers/repository_fork_worker_spec.rb b/spec/workers/repository_fork_worker_spec.rb
index 781f91ac9ca..31bfe88d0bd 100644
--- a/spec/workers/repository_fork_worker_spec.rb
+++ b/spec/workers/repository_fork_worker_spec.rb
@@ -24,12 +24,7 @@ describe RepositoryForkWorker do
end
def expect_fork_repository
- expect(shell).to receive(:fork_repository).with(
- 'default',
- project.disk_path,
- forked_project.repository_storage,
- forked_project.disk_path
- )
+ expect(shell).to receive(:fork_repository).with(project, forked_project)
end
describe 'when a worker was reset without cleanup' do
diff --git a/vendor/project_templates/gitbook.tar.gz b/vendor/project_templates/gitbook.tar.gz
new file mode 100644
index 00000000000..73062fca038
--- /dev/null
+++ b/vendor/project_templates/gitbook.tar.gz
Binary files differ
diff --git a/vendor/project_templates/hexo.tar.gz b/vendor/project_templates/hexo.tar.gz
new file mode 100644
index 00000000000..b32c4945366
--- /dev/null
+++ b/vendor/project_templates/hexo.tar.gz
Binary files differ
diff --git a/vendor/project_templates/hugo.tar.gz b/vendor/project_templates/hugo.tar.gz
new file mode 100644
index 00000000000..4bdb03f5b2f
--- /dev/null
+++ b/vendor/project_templates/hugo.tar.gz
Binary files differ
diff --git a/vendor/project_templates/jekyll.tar.gz b/vendor/project_templates/jekyll.tar.gz
new file mode 100644
index 00000000000..ab61ddd03ea
--- /dev/null
+++ b/vendor/project_templates/jekyll.tar.gz
Binary files differ
diff --git a/vendor/project_templates/plainhtml.tar.gz b/vendor/project_templates/plainhtml.tar.gz
new file mode 100644
index 00000000000..6927ae74de8
--- /dev/null
+++ b/vendor/project_templates/plainhtml.tar.gz
Binary files differ
diff --git a/yarn.lock b/yarn.lock
index 423c7f75d47..9d0ba6640f0 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -653,10 +653,10 @@
eslint-plugin-promise "^4.0.1"
eslint-plugin-vue "^5.0.0"
-"@gitlab/svgs@^1.48.0":
- version "1.48.0"
- resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.48.0.tgz#7b2e20e357d85aa46e905e6ca51b0b4184ae2794"
- integrity sha512-9lRsfqN0W3JxopiXnTzvDY31O465jMTGNKpiOCXy7uAMfwZA6UsRsc7Pp369uKnOLR0duXUGOxOv4NGsK6AeXw==
+"@gitlab/svgs@^1.51.0":
+ version "1.51.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-1.51.0.tgz#1b608f68dfb74284401b1cbdb823440f6e8b0091"
+ integrity sha512-B1Wdhfy5ZClkHuaaCUUZyOBF8CFxxHqxGGhveRekOowtlMExa3tx+YkqNa5XPsEVMF6Aqnh8evQmmN4b+zrHVQ==
"@gitlab/ui@^2.0.2":
version "2.0.2"