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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml128
-rw-r--r--CHANGELOG.md15
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.lock4
-rw-r--r--PROCESS.md30
-rw-r--r--app/assets/javascripts/behaviors/copy_as_gfm.js7
-rw-r--r--app/assets/javascripts/deploy_keys/components/app.vue1
-rw-r--r--app/assets/javascripts/dispatcher.js41
-rw-r--r--app/assets/javascripts/fly_out_nav.js4
-rw-r--r--app/assets/javascripts/groups/index.js4
-rw-r--r--app/assets/javascripts/groups/service/groups_service.js4
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js4
-rw-r--r--app/assets/javascripts/pages/dashboard/groups/index/index.js5
-rw-r--r--app/assets/javascripts/pages/explore/groups/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/activity/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/labels/edit/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/labels/new/index.js2
-rw-r--r--app/assets/javascripts/pages/groups/milestones/show/index.js3
-rw-r--r--app/assets/javascripts/pages/groups/show/index.js3
-rw-r--r--app/assets/javascripts/pages/init_milestones_show.js9
-rw-r--r--app/assets/javascripts/pages/projects/environments/metrics/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/milestones/show/index.js3
-rw-r--r--app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue2
-rw-r--r--app/assets/javascripts/pages/projects/show/index.js4
-rw-r--r--app/assets/javascripts/pages/projects/snippets/show/index.js4
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js18
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue23
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js35
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue48
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js47
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue61
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/dependencies.js6
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/health_controller.rb3
-rw-r--r--app/finders/issues_finder.rb11
-rw-r--r--app/helpers/auto_devops_helper.rb6
-rw-r--r--app/helpers/search_helper.rb4
-rw-r--r--app/models/project.rb4
-rw-r--r--app/models/repository.rb1
-rw-r--r--app/views/ci/runner/_how_to_setup_runner.html.haml2
-rw-r--r--app/views/dashboard/activity.html.haml4
-rw-r--r--app/views/dashboard/groups/_groups.html.haml2
-rw-r--r--app/views/dashboard/groups/index.html.haml3
-rw-r--r--app/views/dashboard/projects/index.html.haml3
-rw-r--r--app/views/dashboard/projects/starred.html.haml3
-rw-r--r--app/views/explore/groups/_groups.html.haml2
-rw-r--r--app/views/explore/groups/index.html.haml3
-rw-r--r--app/views/groups/_children.html.haml5
-rw-r--r--app/views/layouts/_head.html.haml1
-rw-r--r--app/views/projects/_last_push.html.haml27
-rw-r--r--app/views/projects/activity.html.haml3
-rw-r--r--app/views/projects/blob/show.html.haml3
-rw-r--r--app/views/projects/commit/_change.html.haml3
-rw-r--r--app/views/projects/environments/metrics.html.haml1
-rw-r--r--app/views/projects/merge_requests/index.html.haml3
-rw-r--r--app/views/projects/show.html.haml4
-rw-r--r--app/views/projects/tree/show.html.haml3
-rw-r--r--app/views/search/_category.html.haml9
-rw-r--r--app/views/search/_results.html.haml5
-rw-r--r--app/views/shared/_group_form.html.haml2
-rw-r--r--changelogs/unreleased/24035-api-create-application.yml4
-rw-r--r--changelogs/unreleased/32546-cannot-copy-paste-on-ios.yml5
-rw-r--r--changelogs/unreleased/39917-revert-this-merge-request-text.yml5
-rw-r--r--changelogs/unreleased/40540-use-limit-for-global-search.yml5
-rw-r--r--changelogs/unreleased/40612-cannot-change-project-visibility-from-private-even-when-owner.yml6
-rw-r--r--changelogs/unreleased/42022-allow-users-to-request-access-not-visible-when-project-visibility-is-public.yml5
-rw-r--r--changelogs/unreleased/42053-link-to-clusters-in-auto-devops-instead-of-kubernetes-service.yml5
-rw-r--r--changelogs/unreleased/42159-utf8-uploads.yml5
-rw-r--r--changelogs/unreleased/42161-gitaly-commitservice-encoding-undefinedconversionerror-u-c124-from-utf-8-to-ascii-8bit.yml5
-rw-r--r--changelogs/unreleased/42251-explicit-timezone-for-karma.yml5
-rw-r--r--changelogs/unreleased/bvl-parent-preloading.yml5
-rw-r--r--changelogs/unreleased/dm-project-system-hooks-in-transaction.yml5
-rw-r--r--changelogs/unreleased/fix-adjust-layout-width-for-fixed-layout.yml5
-rw-r--r--changelogs/unreleased/fix-redirect-routes-schema.yml5
-rw-r--r--changelogs/unreleased/fl-mr-widget-refactor.yml5
-rw-r--r--changelogs/unreleased/issue_37143_2.yml5
-rw-r--r--changelogs/unreleased/mk-delete-orphaned-routes-before-validation.yml6
-rw-r--r--changelogs/unreleased/osw-updates-merge-status-on-api-actions.yml5
-rw-r--r--changelogs/unreleased/sh-add-gitaly-health-check.yml5
-rw-r--r--config/karma.config.js2
-rw-r--r--config/unicorn.rb.example.development13
-rw-r--r--config/webpack.config.js8
-rw-r--r--db/migrate/20180115201419_add_index_updated_at_to_issues.rb15
-rw-r--r--db/schema.rb3
-rw-r--r--doc/api/applications.md37
-rw-r--r--doc/ci/variables/README.md2
-rw-r--r--doc/ci/yaml/README.md2
-rw-r--r--doc/development/fe_guide/axios.md21
-rw-r--r--doc/raketasks/backup_restore.md24
-rw-r--r--doc/security/rack_attack.md136
-rw-r--r--doc/topics/autodevops/index.md7
-rw-r--r--features/support/db_cleaner.rb2
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/applications.rb27
-rw-r--r--lib/api/entities.rb21
-rw-r--r--lib/api/v3/projects.rb2
-rw-r--r--lib/gitlab/checks/change_access.rb38
-rw-r--r--lib/gitlab/git/blob.rb22
-rw-r--r--lib/gitlab/git/repository.rb16
-rw-r--r--lib/gitlab/git/wiki_page.rb3
-rw-r--r--lib/gitlab/gitaly_client.rb21
-rw-r--r--lib/gitlab/gitaly_client/blob_service.rb20
-rw-r--r--lib/gitlab/gitaly_client/commit_service.rb4
-rw-r--r--lib/gitlab/gitaly_client/conflicts_service.rb5
-rw-r--r--lib/gitlab/gitaly_client/health_check_service.rb19
-rw-r--r--lib/gitlab/gitaly_client/repository_service.rb17
-rw-r--r--lib/gitlab/health_checks/gitaly_check.rb53
-rw-r--r--lib/gitlab/import_export/command_line_util.rb4
-rw-r--r--lib/gitlab/import_export/repo_saver.rb2
-rw-r--r--lib/gitlab/import_export/wiki_repo_saver.rb2
-rw-r--r--lib/gitlab/project_search_results.rb2
-rw-r--r--lib/gitlab/search_results.rb66
-rw-r--r--lib/gitlab/snippet_search_results.rb2
-rw-r--r--locale/gitlab.pot4
-rw-r--r--package.json4
-rw-r--r--qa/qa.rb19
-rw-r--r--qa/qa/factory/resource/deploy_key.rb8
-rw-r--r--qa/qa/factory/resource/runner.rb42
-rw-r--r--qa/qa/page/menu/side.rb31
-rw-r--r--qa/qa/page/project/pipeline/index.rb13
-rw-r--r--qa/qa/page/project/pipeline/show.rb35
-rw-r--r--qa/qa/page/project/settings/ci_cd.rb21
-rw-r--r--qa/qa/page/project/settings/common.rb10
-rw-r--r--qa/qa/page/project/settings/deploy_keys.rb7
-rw-r--r--qa/qa/page/project/settings/runners.rb35
-rw-r--r--qa/qa/page/project/show.rb1
-rw-r--r--qa/qa/scenario/entrypoint.rb34
-rw-r--r--qa/qa/scenario/taggable.rb17
-rw-r--r--qa/qa/scenario/test/instance.rb22
-rw-r--r--qa/qa/scenario/test/integration/mattermost.rb2
-rw-r--r--qa/qa/service/omnibus.rb20
-rw-r--r--qa/qa/service/runner.rb41
-rw-r--r--qa/qa/service/shellout.rb23
-rw-r--r--qa/qa/shell/omnibus.rb39
-rw-r--r--qa/qa/specs/features/project/add_deploy_key_spec.rb12
-rw-r--r--qa/qa/specs/features/project/pipelines_spec.rb102
-rw-r--r--qa/qa/specs/features/repository/push_spec.rb5
-rw-r--r--qa/spec/factory/base_spec.rb1
-rw-r--r--qa/spec/scenario/test/instance_spec.rb (renamed from qa/spec/scenario/entrypoint_spec.rb)4
-rwxr-xr-xscripts/gitaly-test-build16
-rw-r--r--scripts/prepare_build.sh2
-rw-r--r--spec/features/global_search_spec.rb2
-rw-r--r--spec/features/issues/spam_issues_spec.rb3
-rw-r--r--spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb2
-rw-r--r--spec/javascripts/blob/notebook/index_spec.js6
-rw-r--r--spec/javascripts/boards/board_card_spec.js2
-rw-r--r--spec/javascripts/boards/board_list_spec.js2
-rw-r--r--spec/javascripts/boards/board_new_issue_spec.js2
-rw-r--r--spec/javascripts/boards/boards_store_spec.js2
-rw-r--r--spec/javascripts/boards/list_spec.js2
-rw-r--r--spec/javascripts/commit/pipelines/pipelines_spec.js5
-rw-r--r--spec/javascripts/fly_out_nav_spec.js32
-rw-r--r--spec/javascripts/helpers/class_spec_helper_spec.js20
-rw-r--r--spec/javascripts/issue_show/components/app_spec.js2
-rw-r--r--spec/javascripts/jobs/job_details_mediator_spec.js8
-rw-r--r--spec/javascripts/monitoring/dashboard_spec.js2
-rw-r--r--spec/javascripts/pipelines/pipelines_table_row_spec.js7
-rw-r--r--spec/javascripts/pipelines/pipelines_table_spec.js5
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js36
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js106
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js138
-rw-r--r--spec/javascripts/vue_shared/components/modal_spec.js2
-rw-r--r--spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js9
-rw-r--r--spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb2
-rw-r--r--spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb4
-rw-r--r--spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb4
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb2
-rw-r--r--spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb2
-rw-r--r--spec/lib/gitlab/git/blob_spec.rb43
-rw-r--r--spec/lib/gitlab/git/repository_spec.rb28
-rw-r--r--spec/lib/gitlab/gitaly_client/commit_service_spec.rb23
-rw-r--r--spec/lib/gitlab/gitaly_client/health_check_service_spec.rb41
-rw-r--r--spec/lib/gitlab/gitaly_client_spec.rb25
-rw-r--r--spec/lib/gitlab/health_checks/gitaly_check_spec.rb57
-rw-r--r--spec/lib/gitlab/search_results_spec.rb58
-rw-r--r--spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb2
-rw-r--r--spec/migrations/calculate_conv_dev_index_percentages_spec.rb2
-rw-r--r--spec/migrations/fix_wrongly_renamed_routes_spec.rb2
-rw-r--r--spec/migrations/migrate_issues_to_ghost_user_spec.rb6
-rw-r--r--spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb2
-rw-r--r--spec/migrations/migrate_user_project_view_spec.rb2
-rw-r--r--spec/migrations/remove_duplicate_mr_events_spec.rb2
-rw-r--r--spec/migrations/rename_more_reserved_project_names_spec.rb4
-rw-r--r--spec/migrations/rename_reserved_project_names_spec.rb4
-rw-r--r--spec/migrations/rename_users_with_renamed_namespace_spec.rb2
-rw-r--r--spec/migrations/update_retried_for_ci_build_spec.rb2
-rw-r--r--spec/models/concerns/avatarable_spec.rb39
-rw-r--r--spec/models/member_spec.rb4
-rw-r--r--spec/models/project_group_link_spec.rb2
-rw-r--r--spec/models/project_spec.rb17
-rw-r--r--spec/models/repository_spec.rb8
-rw-r--r--spec/models/user_spec.rb2
-rw-r--r--spec/models/wiki_page_spec.rb11
-rw-r--r--spec/requests/api/applications_spec.rb86
-rw-r--r--spec/services/issues/move_service_spec.rb4
-rw-r--r--spec/support/db_cleaner.rb26
-rw-r--r--spec/support/features/discussion_comments_shared_example.rb6
-rw-r--r--spec/uploaders/job_artifact_uploader_spec.rb4
-rw-r--r--spec/views/projects/pipelines_settings/_show.html.haml_spec.rb8
-rw-r--r--vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml10
-rw-r--r--yarn.lock16
204 files changed, 2090 insertions, 806 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index f9c5ebe7a35..349ea49fe8f 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -6,7 +6,7 @@ image: "dev.gitlab.org:5005/gitlab/gitlab-build-images:ruby-2.3.6-golang-1.9-git
- gitlab-org
.default-cache: &default-cache
- key: "ruby-235-with-yarn"
+ key: "ruby-2.3.6-with-yarn"
paths:
- vendor/ruby
- .yarn-cache/
@@ -322,69 +322,69 @@ setup-test-env:
paths:
- tmp/tests
-rspec-pg 0 26: *rspec-metadata-pg
-rspec-pg 1 26: *rspec-metadata-pg
-rspec-pg 2 26: *rspec-metadata-pg
-rspec-pg 3 26: *rspec-metadata-pg
-rspec-pg 4 26: *rspec-metadata-pg
-rspec-pg 5 26: *rspec-metadata-pg
-rspec-pg 6 26: *rspec-metadata-pg
-rspec-pg 7 26: *rspec-metadata-pg
-rspec-pg 8 26: *rspec-metadata-pg
-rspec-pg 9 26: *rspec-metadata-pg
-rspec-pg 10 26: *rspec-metadata-pg
-rspec-pg 11 26: *rspec-metadata-pg
-rspec-pg 12 26: *rspec-metadata-pg
-rspec-pg 13 26: *rspec-metadata-pg
-rspec-pg 14 26: *rspec-metadata-pg
-rspec-pg 15 26: *rspec-metadata-pg
-rspec-pg 16 26: *rspec-metadata-pg
-rspec-pg 17 26: *rspec-metadata-pg
-rspec-pg 18 26: *rspec-metadata-pg
-rspec-pg 19 26: *rspec-metadata-pg
-rspec-pg 20 26: *rspec-metadata-pg
-rspec-pg 21 26: *rspec-metadata-pg
-rspec-pg 22 26: *rspec-metadata-pg
-rspec-pg 23 26: *rspec-metadata-pg
-rspec-pg 24 26: *rspec-metadata-pg
-rspec-pg 25 26: *rspec-metadata-pg
-
-rspec-mysql 0 26: *rspec-metadata-mysql
-rspec-mysql 1 26: *rspec-metadata-mysql
-rspec-mysql 2 26: *rspec-metadata-mysql
-rspec-mysql 3 26: *rspec-metadata-mysql
-rspec-mysql 4 26: *rspec-metadata-mysql
-rspec-mysql 5 26: *rspec-metadata-mysql
-rspec-mysql 6 26: *rspec-metadata-mysql
-rspec-mysql 7 26: *rspec-metadata-mysql
-rspec-mysql 8 26: *rspec-metadata-mysql
-rspec-mysql 9 26: *rspec-metadata-mysql
-rspec-mysql 10 26: *rspec-metadata-mysql
-rspec-mysql 11 26: *rspec-metadata-mysql
-rspec-mysql 12 26: *rspec-metadata-mysql
-rspec-mysql 13 26: *rspec-metadata-mysql
-rspec-mysql 14 26: *rspec-metadata-mysql
-rspec-mysql 15 26: *rspec-metadata-mysql
-rspec-mysql 16 26: *rspec-metadata-mysql
-rspec-mysql 17 26: *rspec-metadata-mysql
-rspec-mysql 18 26: *rspec-metadata-mysql
-rspec-mysql 19 26: *rspec-metadata-mysql
-rspec-mysql 20 26: *rspec-metadata-mysql
-rspec-mysql 21 26: *rspec-metadata-mysql
-rspec-mysql 22 26: *rspec-metadata-mysql
-rspec-mysql 23 26: *rspec-metadata-mysql
-rspec-mysql 24 26: *rspec-metadata-mysql
-rspec-mysql 25 26: *rspec-metadata-mysql
-
-spinach-pg 0 4: *spinach-metadata-pg
-spinach-pg 1 4: *spinach-metadata-pg
-spinach-pg 2 4: *spinach-metadata-pg
-spinach-pg 3 4: *spinach-metadata-pg
-
-spinach-mysql 0 4: *spinach-metadata-mysql
-spinach-mysql 1 4: *spinach-metadata-mysql
-spinach-mysql 2 4: *spinach-metadata-mysql
-spinach-mysql 3 4: *spinach-metadata-mysql
+rspec-pg 0 27: *rspec-metadata-pg
+rspec-pg 1 27: *rspec-metadata-pg
+rspec-pg 2 27: *rspec-metadata-pg
+rspec-pg 3 27: *rspec-metadata-pg
+rspec-pg 4 27: *rspec-metadata-pg
+rspec-pg 5 27: *rspec-metadata-pg
+rspec-pg 6 27: *rspec-metadata-pg
+rspec-pg 7 27: *rspec-metadata-pg
+rspec-pg 8 27: *rspec-metadata-pg
+rspec-pg 9 27: *rspec-metadata-pg
+rspec-pg 10 27: *rspec-metadata-pg
+rspec-pg 11 27: *rspec-metadata-pg
+rspec-pg 12 27: *rspec-metadata-pg
+rspec-pg 13 27: *rspec-metadata-pg
+rspec-pg 14 27: *rspec-metadata-pg
+rspec-pg 15 27: *rspec-metadata-pg
+rspec-pg 16 27: *rspec-metadata-pg
+rspec-pg 17 27: *rspec-metadata-pg
+rspec-pg 18 27: *rspec-metadata-pg
+rspec-pg 19 27: *rspec-metadata-pg
+rspec-pg 20 27: *rspec-metadata-pg
+rspec-pg 21 27: *rspec-metadata-pg
+rspec-pg 22 27: *rspec-metadata-pg
+rspec-pg 23 27: *rspec-metadata-pg
+rspec-pg 24 27: *rspec-metadata-pg
+rspec-pg 25 27: *rspec-metadata-pg
+rspec-pg 26 27: *rspec-metadata-pg
+
+rspec-mysql 0 27: *rspec-metadata-mysql
+rspec-mysql 1 27: *rspec-metadata-mysql
+rspec-mysql 2 27: *rspec-metadata-mysql
+rspec-mysql 3 27: *rspec-metadata-mysql
+rspec-mysql 4 27: *rspec-metadata-mysql
+rspec-mysql 5 27: *rspec-metadata-mysql
+rspec-mysql 6 27: *rspec-metadata-mysql
+rspec-mysql 7 27: *rspec-metadata-mysql
+rspec-mysql 8 27: *rspec-metadata-mysql
+rspec-mysql 9 27: *rspec-metadata-mysql
+rspec-mysql 10 27: *rspec-metadata-mysql
+rspec-mysql 11 27: *rspec-metadata-mysql
+rspec-mysql 12 27: *rspec-metadata-mysql
+rspec-mysql 13 27: *rspec-metadata-mysql
+rspec-mysql 14 27: *rspec-metadata-mysql
+rspec-mysql 15 27: *rspec-metadata-mysql
+rspec-mysql 16 27: *rspec-metadata-mysql
+rspec-mysql 17 27: *rspec-metadata-mysql
+rspec-mysql 18 27: *rspec-metadata-mysql
+rspec-mysql 19 27: *rspec-metadata-mysql
+rspec-mysql 20 27: *rspec-metadata-mysql
+rspec-mysql 21 27: *rspec-metadata-mysql
+rspec-mysql 22 27: *rspec-metadata-mysql
+rspec-mysql 23 27: *rspec-metadata-mysql
+rspec-mysql 24 27: *rspec-metadata-mysql
+rspec-mysql 25 27: *rspec-metadata-mysql
+rspec-mysql 26 27: *rspec-metadata-mysql
+
+spinach-pg 0 3: *spinach-metadata-pg
+spinach-pg 1 3: *spinach-metadata-pg
+spinach-pg 2 3: *spinach-metadata-pg
+
+spinach-mysql 0 3: *spinach-metadata-mysql
+spinach-mysql 1 3: *spinach-metadata-mysql
+spinach-mysql 2 3: *spinach-metadata-mysql
# Static analysis jobs
.ruby-static-analysis: &ruby-static-analysis
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 77f23981c84..248c85304a9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,21 @@
documentation](doc/development/changelog.md) for instructions on adding your own
entry.
+## 10.4.1 (2018-01-24)
+
+### Fixed (4 changes)
+
+- Ensure that users can reclaim a namespace or project path that is blocked by an orphaned route. !16242
+- Correctly escape UTF-8 path elements for uploads. !16560
+- Fix issues when rendering groups and their children. !16584
+- Fix bug in which projects with forks could not change visibility settings from Private to Public. !16595
+
+### Performance (2 changes)
+
+- rework indexes on redirect_routes.
+- Remove unecessary query from labels filter.
+
+
## 10.4.0 (2018-01-22)
### Security (8 changes, 1 of them is from the community)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 7375dee5f49..31b648bd6fa 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-0.72.0
+0.73.0
diff --git a/Gemfile b/Gemfile
index a4c3db4bf5f..346182b3852 100644
--- a/Gemfile
+++ b/Gemfile
@@ -406,7 +406,7 @@ group :ed25519 do
end
# Gitaly GRPC client
-gem 'gitaly-proto', '~> 0.74.0', require: 'gitaly'
+gem 'gitaly-proto', '~> 0.76.0', require: 'gitaly'
gem 'toml-rb', '~> 0.3.15', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index d69c532b309..1cbeab8d6b5 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -285,7 +285,7 @@ GEM
po_to_json (>= 1.0.0)
rails (>= 3.2.0)
gherkin-ruby (0.3.2)
- gitaly-proto (0.74.0)
+ gitaly-proto (0.76.0)
google-protobuf (~> 3.1)
grpc (~> 1.0)
github-linguist (4.7.6)
@@ -1056,7 +1056,7 @@ DEPENDENCIES
gettext (~> 3.2.2)
gettext_i18n_rails (~> 1.8.0)
gettext_i18n_rails_js (~> 1.2.0)
- gitaly-proto (~> 0.74.0)
+ gitaly-proto (~> 0.76.0)
github-linguist (~> 4.7.0)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-markup (~> 1.6.2)
diff --git a/PROCESS.md b/PROCESS.md
index 3fcf676b302..99af3be7f14 100644
--- a/PROCESS.md
+++ b/PROCESS.md
@@ -85,7 +85,8 @@ These types of merge requests for the upcoming release need special consideratio
and a dedicated team with front-end, back-end, and UX.
* **Small features**: any other feature request.
-**Large features** must be with a maintainer **by the 1st**. This means that:
+It is strongly recommended that **large features** be with a maintainer **by the
+1st**. This means that:
* There is a merge request (even if it's WIP).
* The person (or people, if it needs a frontend and backend maintainer) who will
@@ -100,14 +101,37 @@ The maintainer can also choose to assign a reviewer to perform an initial
review, but this way the maintainer is unlikely to be surprised by receiving an
MR later in the cycle.
-**Small features** must be with a reviewer (not necessarily maintainer) **by the
-3rd**.
+It is strongly recommended that **small features** be with a reviewer (not
+necessarily a maintainer) **by the 3rd**.
Most merge requests from the community do not have a specific release
target. However, if one does and falls into either of the above categories, it's
the reviewer's responsibility to manage the above communication and assignment
on behalf of the community member.
+#### What happens if these deadlines are missed?
+
+If a small or large feature is _not_ with a maintainer or reviewer by the
+recommended date, this does _not_ mean that maintainers or reviewers will refuse
+to review or merge it, or that the feature will definitely not make it in before
+the feature freeze.
+
+However, with every day that passes without review, it will become more likely
+that the feature will slip, because maintainers and reviewers may not have
+enough time to do a thorough review, and developers may not have enough time to
+adequately address any feedback that may come back.
+
+A maintainer or reviewer may also determine that it will not be possible to
+finish the current scope of the feature in time, but that it is possible to
+reduce the scope so that something can still ship this month, with the remaining
+scope moving to the next release. The sooner this decision is made, in
+conversation with the Product Manager and developer, the more time there is to
+extract that which is now out of scope, and to finish that which remains in scope.
+
+For these reasons, it is strongly recommended to follow the guidelines above,
+to maximize the chances of your feature making it in before the feature freeze,
+and to prevent any last minute surprises.
+
### On the 7th
Merge requests should still be complete, following the
diff --git a/app/assets/javascripts/behaviors/copy_as_gfm.js b/app/assets/javascripts/behaviors/copy_as_gfm.js
index c6eca72c51b..ffe90595b5d 100644
--- a/app/assets/javascripts/behaviors/copy_as_gfm.js
+++ b/app/assets/javascripts/behaviors/copy_as_gfm.js
@@ -299,6 +299,13 @@ const gfmRules = {
export class CopyAsGFM {
constructor() {
+ // iOS currently does not support clipboardData.setData(). This bug should
+ // be fixed in iOS 12, but for now we'll disable this for all iOS browsers
+ // ref: https://trac.webkit.org/changeset/222228/webkit
+ const userAgent = (typeof navigator !== 'undefined' && navigator.userAgent) || '';
+ const isIOS = /\b(iPad|iPhone|iPod)(?=;)/.test(userAgent);
+ if (isIOS) return;
+
$(document).on('copy', '.md, .wiki', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformGFMSelection); });
$(document).on('copy', 'pre.code.highlight, .diff-content .line_content', (e) => { CopyAsGFM.copyAsGFM(e, CopyAsGFM.transformCodeSelection); });
$(document).on('paste', '.js-gfm-input', CopyAsGFM.pasteGFM);
diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue
index 7b68b19de75..5a782237b7d 100644
--- a/app/assets/javascripts/deploy_keys/components/app.vue
+++ b/app/assets/javascripts/deploy_keys/components/app.vue
@@ -87,6 +87,7 @@
<div v-else-if="hasKeys">
<keys-panel
title="Enabled deploy keys for this project"
+ class="qa-project-deploy-keys"
:keys="keys.enabled_keys"
:store="store"
:endpoint="endpoint"
diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js
index 96e5c8c890c..89fc002d4ce 100644
--- a/app/assets/javascripts/dispatcher.js
+++ b/app/assets/javascripts/dispatcher.js
@@ -1,15 +1,8 @@
/* eslint-disable func-names, space-before-function-paren, no-var, prefer-arrow-callback, wrap-iife, no-shadow, consistent-return, one-var, one-var-declaration-per-line, camelcase, default-case, no-new, quotes, no-duplicate-case, no-case-declarations, no-fallthrough, max-len */
-import Milestone from './milestone';
-import notificationsDropdown from './notifications_dropdown';
-import LineHighlighter from './line_highlighter';
import MergeRequest from './merge_request';
-import Sidebar from './right_sidebar';
import Flash from './flash';
-import BlobViewer from './blob/viewer/index';
import GfmAutoComplete from './gfm_auto_complete';
-import Star from './star';
import ZenMode from './zen_mode';
-import PerformanceBar from './performance_bar';
import initNotes from './init_notes';
import initIssuableSidebar from './init_issuable_sidebar';
import { convertPermissionToBoolean } from './lib/utils/common_utils';
@@ -68,6 +61,11 @@ import SearchAutocomplete from './search_autocomplete';
.catch(fail);
shortcut_handler = true;
break;
+ case 'projects:environments:metrics':
+ import('./pages/projects/environments/metrics')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'projects:merge_requests:index':
import('./pages/projects/merge_requests/index')
.then(callDefault)
@@ -92,9 +90,14 @@ import SearchAutocomplete from './search_autocomplete';
.catch(fail);
break;
case 'projects:milestones:show':
+ import('./pages/projects/milestones/show')
+ .then(callDefault)
+ .catch(fail);
+ break;
case 'groups:milestones:show':
- new Milestone();
- new Sidebar();
+ import('./pages/groups/milestones/show')
+ .then(callDefault)
+ .catch(fail);
break;
case 'dashboard:milestones:show':
import('./pages/dashboard/milestones/show')
@@ -530,6 +533,11 @@ import SearchAutocomplete from './search_autocomplete';
.then(callDefault)
.catch(fail);
break;
+ case 'dashboard:groups:index':
+ import('./pages/dashboard/groups/index')
+ .then(callDefault)
+ .catch(fail);
+ break;
}
switch (path[0]) {
case 'sessions':
@@ -613,23 +621,12 @@ import SearchAutocomplete from './search_autocomplete';
.then(callDefault)
.catch(fail);
break;
- case 'show':
- new Star();
- notificationsDropdown();
- break;
case 'wikis':
import('./pages/projects/wikis')
.then(callDefault)
.catch(fail);
shortcut_handler = true;
break;
- case 'snippets':
- if (path[2] === 'show') {
- new ZenMode();
- new LineHighlighter();
- new BlobViewer();
- }
- break;
}
break;
}
@@ -639,7 +636,9 @@ import SearchAutocomplete from './search_autocomplete';
}
if (document.querySelector('#peek')) {
- new PerformanceBar({ container: '#peek' });
+ import('./performance_bar')
+ .then(m => new m.default({ container: '#peek' })) // eslint-disable-line new-cap
+ .catch(fail);
}
};
diff --git a/app/assets/javascripts/fly_out_nav.js b/app/assets/javascripts/fly_out_nav.js
index abb04d77f8f..8b4f3b05ee7 100644
--- a/app/assets/javascripts/fly_out_nav.js
+++ b/app/assets/javascripts/fly_out_nav.js
@@ -118,14 +118,14 @@ export const showSubLevelItems = (el) => {
moveSubItemsToPosition(el, subItems);
};
-export const mouseEnterTopItems = (el) => {
+export const mouseEnterTopItems = (el, timeout = getHideSubItemsInterval()) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => {
if (currentOpenMenu) hideMenu(currentOpenMenu);
showSubLevelItems(el);
- }, getHideSubItemsInterval());
+ }, timeout);
};
export const mouseLeaveTopItem = (el) => {
diff --git a/app/assets/javascripts/groups/index.js b/app/assets/javascripts/groups/index.js
index 8b850765a1b..57eaac72906 100644
--- a/app/assets/javascripts/groups/index.js
+++ b/app/assets/javascripts/groups/index.js
@@ -10,7 +10,7 @@ import groupItemComponent from './components/group_item.vue';
Vue.use(Translate);
-document.addEventListener('DOMContentLoaded', () => {
+export default () => {
const el = document.getElementById('js-groups-tree');
// Don't do anything if element doesn't exist (No groups)
@@ -71,4 +71,4 @@ document.addEventListener('DOMContentLoaded', () => {
});
},
});
-});
+};
diff --git a/app/assets/javascripts/groups/service/groups_service.js b/app/assets/javascripts/groups/service/groups_service.js
index 639410384c2..b79ba291463 100644
--- a/app/assets/javascripts/groups/service/groups_service.js
+++ b/app/assets/javascripts/groups/service/groups_service.js
@@ -1,7 +1,5 @@
import Vue from 'vue';
-import VueResource from 'vue-resource';
-
-Vue.use(VueResource);
+import '../../vue_shared/vue_resource_interceptor';
export default class GroupsService {
constructor(endpoint) {
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index 104432ef5de..c3b0ef7e9ca 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -1,7 +1,7 @@
import Vue from 'vue';
import Dashboard from './components/dashboard.vue';
-document.addEventListener('DOMContentLoaded', () => new Vue({
+export default () => new Vue({
el: '#prometheus-graphs',
render: createElement => createElement(Dashboard),
-}));
+});
diff --git a/app/assets/javascripts/pages/dashboard/groups/index/index.js b/app/assets/javascripts/pages/dashboard/groups/index/index.js
new file mode 100644
index 00000000000..8a2aae706c0
--- /dev/null
+++ b/app/assets/javascripts/pages/dashboard/groups/index/index.js
@@ -0,0 +1,5 @@
+import initGroupsList from '../../../../groups';
+
+export default () => {
+ initGroupsList();
+};
diff --git a/app/assets/javascripts/pages/explore/groups/index.js b/app/assets/javascripts/pages/explore/groups/index.js
index 859b073f1cb..e59c38b8bc4 100644
--- a/app/assets/javascripts/pages/explore/groups/index.js
+++ b/app/assets/javascripts/pages/explore/groups/index.js
@@ -1,8 +1,10 @@
import GroupsList from '~/groups_list';
import Landing from '~/landing';
+import initGroupsList from '../../../groups';
export default function () {
new GroupsList(); // eslint-disable-line no-new
+ initGroupsList();
const landingElement = document.querySelector('.js-explore-groups-landing');
if (!landingElement) return;
const exploreGroupsLanding = new Landing(
diff --git a/app/assets/javascripts/pages/groups/activity/index.js b/app/assets/javascripts/pages/groups/activity/index.js
index a3bd1b0f3e2..95faf1f1e98 100644
--- a/app/assets/javascripts/pages/groups/activity/index.js
+++ b/app/assets/javascripts/pages/groups/activity/index.js
@@ -1,3 +1,3 @@
import Activities from '~/activities';
-export default new Activities();
+export default () => new Activities();
diff --git a/app/assets/javascripts/pages/groups/labels/edit/index.js b/app/assets/javascripts/pages/groups/labels/edit/index.js
index 8ff4b044ac7..72c5e4744ac 100644
--- a/app/assets/javascripts/pages/groups/labels/edit/index.js
+++ b/app/assets/javascripts/pages/groups/labels/edit/index.js
@@ -1,3 +1,3 @@
import Labels from '~/labels';
-export default new Labels();
+export default () => new Labels();
diff --git a/app/assets/javascripts/pages/groups/labels/new/index.js b/app/assets/javascripts/pages/groups/labels/new/index.js
index 8ff4b044ac7..72c5e4744ac 100644
--- a/app/assets/javascripts/pages/groups/labels/new/index.js
+++ b/app/assets/javascripts/pages/groups/labels/new/index.js
@@ -1,3 +1,3 @@
import Labels from '~/labels';
-export default new Labels();
+export default () => new Labels();
diff --git a/app/assets/javascripts/pages/groups/milestones/show/index.js b/app/assets/javascripts/pages/groups/milestones/show/index.js
new file mode 100644
index 00000000000..0c3ce848e3d
--- /dev/null
+++ b/app/assets/javascripts/pages/groups/milestones/show/index.js
@@ -0,0 +1,3 @@
+import initMilestonesShow from '~/pages/init_milestones_show';
+
+export default initMilestonesShow;
diff --git a/app/assets/javascripts/pages/groups/show/index.js b/app/assets/javascripts/pages/groups/show/index.js
index 45e11b64306..6ed0f010f15 100644
--- a/app/assets/javascripts/pages/groups/show/index.js
+++ b/app/assets/javascripts/pages/groups/show/index.js
@@ -5,6 +5,7 @@ import notificationsDropdown from '~/notifications_dropdown';
import NotificationsForm from '~/notifications_form';
import ProjectsList from '~/projects_list';
import ShortcutsNavigation from '~/shortcuts_navigation';
+import initGroupsList from '../../../groups';
export default () => {
const newGroupChildWrapper = document.querySelector('.js-new-project-subgroup');
@@ -16,4 +17,6 @@ export default () => {
if (newGroupChildWrapper) {
new NewGroupChild(newGroupChildWrapper);
}
+
+ initGroupsList();
};
diff --git a/app/assets/javascripts/pages/init_milestones_show.js b/app/assets/javascripts/pages/init_milestones_show.js
new file mode 100644
index 00000000000..7aa5be0d5b9
--- /dev/null
+++ b/app/assets/javascripts/pages/init_milestones_show.js
@@ -0,0 +1,9 @@
+/* eslint-disable no-new */
+
+import Milestone from '~/milestone';
+import Sidebar from '~/right_sidebar';
+
+export default () => {
+ new Milestone();
+ new Sidebar();
+};
diff --git a/app/assets/javascripts/pages/projects/environments/metrics/index.js b/app/assets/javascripts/pages/projects/environments/metrics/index.js
new file mode 100644
index 00000000000..f4760cb2720
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/environments/metrics/index.js
@@ -0,0 +1,3 @@
+import monitoringBundle from '~/monitoring/monitoring_bundle';
+
+export default monitoringBundle;
diff --git a/app/assets/javascripts/pages/projects/milestones/show/index.js b/app/assets/javascripts/pages/projects/milestones/show/index.js
new file mode 100644
index 00000000000..0c3ce848e3d
--- /dev/null
+++ b/app/assets/javascripts/pages/projects/milestones/show/index.js
@@ -0,0 +1,3 @@
+import initMilestonesShow from '~/pages/init_milestones_show';
+
+export default initMilestonesShow;
diff --git a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
index 380c6c3ea7d..755a34b7348 100644
--- a/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
+++ b/app/assets/javascripts/pages/projects/shared/permissions/components/settings_panel.vue
@@ -215,7 +215,7 @@
</div>
<span class="help-block">{{ visibilityLevelDescription }}</span>
<label
- v-if="visibilityLevel !== visibilityOptions.PUBLIC"
+ v-if="visibilityLevel !== visibilityOptions.PRIVATE"
class="request-access"
>
<input
diff --git a/app/assets/javascripts/pages/projects/show/index.js b/app/assets/javascripts/pages/projects/show/index.js
index 92dc1e59651..55154cdddcb 100644
--- a/app/assets/javascripts/pages/projects/show/index.js
+++ b/app/assets/javascripts/pages/projects/show/index.js
@@ -5,8 +5,12 @@ import TreeView from '~/tree';
import BlobViewer from '~/blob/viewer/index';
import Activities from '~/activities';
import { ajaxGet } from '~/lib/utils/common_utils';
+import Star from '../../../star';
+import notificationsDropdown from '../../../notifications_dropdown';
export default () => {
+ new Star(); // eslint-disable-line no-new
+ notificationsDropdown();
new ShortcutsNavigation(); // eslint-disable-line no-new
new NotificationsForm(); // eslint-disable-line no-new
new UserCallout({ // eslint-disable-line no-new
diff --git a/app/assets/javascripts/pages/projects/snippets/show/index.js b/app/assets/javascripts/pages/projects/snippets/show/index.js
index d8cf5184f8f..a3cf75c385b 100644
--- a/app/assets/javascripts/pages/projects/snippets/show/index.js
+++ b/app/assets/javascripts/pages/projects/snippets/show/index.js
@@ -1,7 +1,11 @@
import initNotes from '~/init_notes';
import ZenMode from '~/zen_mode';
+import LineHighlighter from '../../../../line_highlighter';
+import BlobViewer from '../../../../blob/viewer';
export default function () {
+ new LineHighlighter(); // eslint-disable-line no-new
+ new BlobViewer(); // eslint-disable-line no-new
initNotes();
new ZenMode(); // eslint-disable-line no-new
}
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js
deleted file mode 100644
index 09561694939..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.js
+++ /dev/null
@@ -1,18 +0,0 @@
-import statusIcon from '../mr_widget_status_icon';
-
-export default {
- name: 'MRWidgetChecking',
- components: {
- statusIcon,
- },
- template: `
- <div class="mr-widget-body media">
- <status-icon status="loading" :show-disabled-button="true" />
- <div class="media-body space-children">
- <span class="bold">
- Checking ability to merge automatically
- </span>
- </div>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue
new file mode 100644
index 00000000000..04e1766b8c7
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_checking.vue
@@ -0,0 +1,23 @@
+<script>
+ import statusIcon from '../mr_widget_status_icon';
+
+ export default {
+ name: 'MRWidgetChecking',
+ components: {
+ statusIcon,
+ },
+ };
+</script>
+<template>
+ <div class="mr-widget-body media">
+ <status-icon
+ status="loading"
+ :show-disabled-button="true"
+ />
+ <div class="media-body space-children">
+ <span class="bold">
+ {{ s__("mrWidget|Checking ability to merge automatically") }}
+ </span>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js
deleted file mode 100644
index dc19b20aa11..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.js
+++ /dev/null
@@ -1,35 +0,0 @@
-import mrWidgetAuthorTime from '../../components/mr_widget_author_time';
-import statusIcon from '../mr_widget_status_icon';
-
-export default {
- name: 'MRWidgetClosed',
- props: {
- mr: { type: Object, required: true },
- },
- components: {
- 'mr-widget-author-and-time': mrWidgetAuthorTime,
- statusIcon,
- },
- template: `
- <div class="mr-widget-body media">
- <status-icon status="warning" />
- <div class="media-body">
- <mr-widget-author-and-time
- actionText="Closed by"
- :author="mr.metrics.closedBy"
- :dateTitle="mr.metrics.closedAt"
- :dateReadable="mr.metrics.readableClosedAt"
- />
- <section class="mr-info-list">
- <p>
- The changes were not merged into
- <a
- :href="mr.targetBranchPath"
- class="label-branch">
- {{mr.targetBranch}}</a>
- </p>
- </section>
- </div>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue
new file mode 100644
index 00000000000..8c0ce43c76c
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_closed.vue
@@ -0,0 +1,48 @@
+<script>
+ import mrWidgetAuthorTime from '../../components/mr_widget_author_time';
+ import statusIcon from '../mr_widget_status_icon';
+
+ export default {
+ name: 'MRWidgetClosed',
+ components: {
+ mrWidgetAuthorTime,
+ statusIcon,
+ },
+ props: {
+ /* TODO: This is providing all store and service down when it
+ only needs metrics and targetBranch */
+ mr: {
+ type: Object,
+ required: true,
+ default: () => ({}),
+ },
+ },
+ };
+</script>
+<template>
+ <div class="mr-widget-body media">
+ <status-icon
+ status="warning"
+ />
+ <div class="media-body">
+ <mr-widget-author-time
+ :action-text="s__('mrWidget|Closed by')"
+ :author="mr.metrics.closedBy"
+ :date-title="mr.metrics.closedAt"
+ :date-readable="mr.metrics.readableClosedAt"
+ />
+
+ <section class="mr-info-list">
+ <p>
+ {{ s__("mrWidget|The changes were not merged into") }}
+ <a
+ :href="mr.targetBranchPath"
+ class="label-branch"
+ >
+ {{ mr.targetBranch }}
+ </a>
+ </p>
+ </section>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js
deleted file mode 100644
index 7a887bacfa7..00000000000
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.js
+++ /dev/null
@@ -1,47 +0,0 @@
-import statusIcon from '../mr_widget_status_icon';
-
-export default {
- name: 'MRWidgetConflicts',
- props: {
- mr: { type: Object, required: true },
- },
- components: {
- statusIcon,
- },
- template: `
- <div class="mr-widget-body media">
- <status-icon
- status="warning"
- :show-disabled-button="true" />
- <div class="media-body space-children">
- <span
- v-if="mr.shouldBeRebased"
- class="bold">
- Fast-forward merge is not possible.
- To merge this request, first rebase locally.
- </span>
- <template v-else>
- <span class="bold">
- There are merge conflicts<span v-if="!mr.canMerge">.</span>
- <span v-if="!mr.canMerge">
- Resolve these conflicts or ask someone with write access to this repository to merge it locally
- </span>
- </span>
- <a
- v-if="mr.canMerge && mr.conflictResolutionPath"
- :href="mr.conflictResolutionPath"
- class="js-resolve-conflicts-button btn btn-default btn-xs">
- Resolve conflicts
- </a>
- <a
- v-if="mr.canMerge"
- class="js-merge-locally-button btn btn-default btn-xs"
- data-toggle="modal"
- href="#modal_merge_info">
- Merge locally
- </a>
- </template>
- </div>
- </div>
- `,
-};
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
new file mode 100644
index 00000000000..13b07f82330
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/mr_widget_conflicts.vue
@@ -0,0 +1,61 @@
+<script>
+ import statusIcon from '../mr_widget_status_icon';
+
+ export default {
+ name: 'MRWidgetConflicts',
+ components: {
+ statusIcon,
+ },
+ props: {
+ /* TODO: This is providing all store and service down when it
+ only needs a few props */
+ mr: {
+ type: Object,
+ required: true,
+ default: () => ({}),
+ },
+ },
+ };
+</script>
+<template>
+ <div class="mr-widget-body media">
+ <status-icon
+ status="warning"
+ :show-disabled-button="true"
+ />
+
+ <div class="media-body space-children">
+ <span
+ v-if="mr.shouldBeRebased"
+ class="bold"
+ >
+ {{ s__(`mrWidget|Fast-forward merge is not possible.
+To merge this request, first rebase locally.`) }}
+ </span>
+ <template v-else>
+ <span class="bold">
+ {{ s__("mrWidget|There are merge conflicts") }}<span v-if="!mr.canMerge">.</span>
+ <span v-if="!mr.canMerge">
+ {{ s__(`mrWidget|Resolve these conflicts or ask someone
+ with write access to this repository to merge it locally`) }}
+ </span>
+ </span>
+ <a
+ v-if="mr.canMerge && mr.conflictResolutionPath"
+ :href="mr.conflictResolutionPath"
+ class="js-resolve-conflicts-button btn btn-default btn-xs"
+ >
+ {{ s__("mrWidget|Resolve conflicts") }}
+ </a>
+ <button
+ v-if="mr.canMerge"
+ class="js-merge-locally-button btn btn-default btn-xs"
+ data-toggle="modal"
+ data-target="#modal_merge_info"
+ >
+ {{ s__("mrWidget|Merge locally") }}
+ </button>
+ </template>
+ </div>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/dependencies.js b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
index 2dd3b2c2f98..5e8e251428a 100644
--- a/app/assets/javascripts/vue_merge_request_widget/dependencies.js
+++ b/app/assets/javascripts/vue_merge_request_widget/dependencies.js
@@ -18,11 +18,11 @@ export { default as WidgetDeployment } from './components/mr_widget_deployment';
export { default as WidgetRelatedLinks } from './components/mr_widget_related_links';
export { default as MergedState } from './components/states/mr_widget_merged';
export { default as FailedToMerge } from './components/states/mr_widget_failed_to_merge';
-export { default as ClosedState } from './components/states/mr_widget_closed';
+export { default as ClosedState } from './components/states/mr_widget_closed.vue';
export { default as MergingState } from './components/states/mr_widget_merging';
export { default as WipState } from './components/states/mr_widget_wip';
export { default as ArchivedState } from './components/states/mr_widget_archived.vue';
-export { default as ConflictsState } from './components/states/mr_widget_conflicts';
+export { default as ConflictsState } from './components/states/mr_widget_conflicts.vue';
export { default as NothingToMergeState } from './components/states/mr_widget_nothing_to_merge';
export { default as MissingBranchState } from './components/states/mr_widget_missing_branch';
export { default as NotAllowedState } from './components/states/mr_widget_not_allowed';
@@ -34,7 +34,7 @@ export { default as PipelineFailedState } from './components/states/mr_widget_pi
export { default as MergeWhenPipelineSucceedsState } from './components/states/mr_widget_merge_when_pipeline_succeeds';
export { default as RebaseState } from './components/states/mr_widget_rebase.vue';
export { default as AutoMergeFailed } from './components/states/mr_widget_auto_merge_failed.vue';
-export { default as CheckingState } from './components/states/mr_widget_checking';
+export { default as CheckingState } from './components/states/mr_widget_checking.vue';
export { default as MRWidgetStore } from './stores/mr_widget_store';
export { default as MRWidgetService } from './services/mr_widget_service';
export { default as eventHub } from './event_hub';
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index ee21d81f23e..95ad38d9230 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -147,6 +147,8 @@ class ApplicationController < ActionController::Base
format.html do
render file: Rails.root.join("public", "404"), layout: false, status: "404"
end
+ # Prevent the Rails CSRF protector from thinking a missing .js file is a JavaScript file
+ format.js { render json: '', status: :not_found, content_type: 'application/json' }
format.any { head :not_found }
end
end
diff --git a/app/controllers/health_controller.rb b/app/controllers/health_controller.rb
index a931b456a93..16abf7bab7e 100644
--- a/app/controllers/health_controller.rb
+++ b/app/controllers/health_controller.rb
@@ -8,7 +8,8 @@ class HealthController < ActionController::Base
Gitlab::HealthChecks::Redis::CacheCheck,
Gitlab::HealthChecks::Redis::QueuesCheck,
Gitlab::HealthChecks::Redis::SharedStateCheck,
- Gitlab::HealthChecks::FsShardsCheck
+ Gitlab::HealthChecks::FsShardsCheck,
+ Gitlab::HealthChecks::GitalyCheck
].freeze
def readiness
diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb
index d2275139c42..98831f5be4a 100644
--- a/app/finders/issues_finder.rb
+++ b/app/finders/issues_finder.rb
@@ -15,6 +15,7 @@
# label_name: string
# sort: string
# my_reaction_emoji: string
+# public_only: boolean
#
class IssuesFinder < IssuableFinder
CONFIDENTIAL_ACCESS_LEVEL = Gitlab::Access::REPORTER
@@ -40,7 +41,15 @@ class IssuesFinder < IssuableFinder
private
def init_collection
- with_confidentiality_access_check
+ if public_only?
+ Issue.public_only
+ else
+ with_confidentiality_access_check
+ end
+ end
+
+ def public_only?
+ params.fetch(:public_only, false)
end
def user_can_see_all_confidential_issues?
diff --git a/app/helpers/auto_devops_helper.rb b/app/helpers/auto_devops_helper.rb
index f4310ca2f06..d72457efec0 100644
--- a/app/helpers/auto_devops_helper.rb
+++ b/app/helpers/auto_devops_helper.rb
@@ -14,13 +14,13 @@ module AutoDevopsHelper
if missing_service
params = {
- kubernetes: link_to('Kubernetes service', edit_project_service_path(project, 'kubernetes'))
+ kubernetes: link_to('Kubernetes cluster', project_clusters_path(project))
}
if missing_domain
- _('Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly.') % params
+ _('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 the %{kubernetes} to work correctly.') % params
+ _('Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly.') % params
end
elsif missing_domain
_('Auto Review Apps and Auto Deploy need a domain name to work correctly.')
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index 0f9ac958f95..e6a6496871a 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -170,4 +170,8 @@ module SearchHelper
# Truncato's filtered_tags and filtered_attributes are not quite the same
sanitize(html, tags: %w(a p ol ul li pre code))
end
+
+ def limited_count(count, limit = 1000)
+ count > limit ? "#{limit}+" : count
+ end
end
diff --git a/app/models/project.rb b/app/models/project.rb
index 0570bbc8ee3..e19873f64ce 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -971,9 +971,9 @@ class Project < ActiveRecord::Base
hooks.hooks_for(hooks_scope).each do |hook|
hook.async_execute(data, hooks_scope.to_s)
end
- end
- SystemHooksService.new.execute_hooks(data, hooks_scope)
+ SystemHooksService.new.execute_hooks(data, hooks_scope)
+ end
end
def execute_services(data, hooks_scope = :push_hooks)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 73c4899cb9b..824e18bec78 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -20,6 +20,7 @@ class Repository
attr_accessor :full_path, :disk_path, :project, :is_wiki
delegate :ref_name_for_sha, to: :raw_repository
+ delegate :bundle_to_disk, to: :raw_repository
CreateTreeError = Class.new(StandardError)
diff --git a/app/views/ci/runner/_how_to_setup_runner.html.haml b/app/views/ci/runner/_how_to_setup_runner.html.haml
index b75dab0acc5..8db7727b80c 100644
--- a/app/views/ci/runner/_how_to_setup_runner.html.haml
+++ b/app/views/ci/runner/_how_to_setup_runner.html.haml
@@ -8,7 +8,7 @@
= (_("(checkout the %{link} for information on how to install it).") % { link: link }).html_safe
%li
= _("Specify the following URL during the Runner setup:")
- %code= root_url(only_path: false)
+ %code#coordinator_address= root_url(only_path: false)
%li
= _("Use the following registration token during setup:")
%code#registration_token= registration_token
diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml
index ad35d05c29a..31d4b3da4f1 100644
--- a/app/views/dashboard/activity.html.haml
+++ b/app/views/dashboard/activity.html.haml
@@ -7,10 +7,8 @@
- page_title "Activity"
- header_title "Activity", activity_dashboard_path
-.hidden-xs
- = render "projects/last_push"
-
%div{ class: container_class }
+ = render "projects/last_push"
= render 'dashboard/activity_head'
%section.activities
diff --git a/app/views/dashboard/groups/_groups.html.haml b/app/views/dashboard/groups/_groups.html.haml
index 601b6a8b1a7..db856ef7d7b 100644
--- a/app/views/dashboard/groups/_groups.html.haml
+++ b/app/views/dashboard/groups/_groups.html.haml
@@ -1,2 +1,4 @@
.js-groups-list-holder
#js-groups-tree{ data: { hide_projects: 'true', endpoint: dashboard_groups_path(format: :json), path: dashboard_groups_path, form_sel: 'form#group-filter-form', filter_sel: '.js-groups-list-filter', holder_sel: '.js-groups-list-holder', dropdown_sel: '.js-group-filter-dropdown-wrap' } }
+ .loading-container.text-center
+ = icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index 25bf08c6c12..50f39f93283 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -3,9 +3,6 @@
- header_title "Groups", dashboard_groups_path
= render 'dashboard/groups_head'
-= webpack_bundle_tag 'common_vue'
-= webpack_bundle_tag 'groups'
-
- if params[:filter].blank? && @groups.empty?
= render 'shared/groups/empty_state'
- else
diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml
index 57a4da353fe..deed774a4a5 100644
--- a/app/views/dashboard/projects/index.html.haml
+++ b/app/views/dashboard/projects/index.html.haml
@@ -7,9 +7,8 @@
- page_title "Projects"
- header_title "Projects", dashboard_projects_path
-= render "projects/last_push"
-
%div{ class: container_class }
+ = render "projects/last_push"
- if show_projects?(@projects, params)
= render 'dashboard/projects_head'
= render 'nav'
diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml
index 14f9f8cd70a..b1efe59aadc 100644
--- a/app/views/dashboard/projects/starred.html.haml
+++ b/app/views/dashboard/projects/starred.html.haml
@@ -4,9 +4,8 @@
- page_title "Starred Projects"
- header_title "Projects", dashboard_projects_path
-= render "projects/last_push"
-
%div{ class: container_class }
+ = render "projects/last_push"
= render 'dashboard/projects_head'
- if params[:filter_projects] || any_projects?(@projects)
diff --git a/app/views/explore/groups/_groups.html.haml b/app/views/explore/groups/_groups.html.haml
index 91149498248..ff57b39e947 100644
--- a/app/views/explore/groups/_groups.html.haml
+++ b/app/views/explore/groups/_groups.html.haml
@@ -1,2 +1,4 @@
.js-groups-list-holder
#js-groups-tree{ data: { hide_projects: 'true', endpoint: explore_groups_path(format: :json), path: explore_groups_path, form_sel: 'form#group-filter-form', filter_sel: '.js-groups-list-filter', holder_sel: '.js-groups-list-holder', dropdown_sel: '.js-group-filter-dropdown-wrap' } }
+ .loading-container.text-center
+ = icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index 86abdf547cc..efa8b2706da 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -2,9 +2,6 @@
- page_title "Groups"
- header_title "Groups", dashboard_groups_path
-= webpack_bundle_tag 'common_vue'
-= webpack_bundle_tag 'groups'
-
- if current_user
= render 'dashboard/groups_head'
- else
diff --git a/app/views/groups/_children.html.haml b/app/views/groups/_children.html.haml
index 3afb6b2f849..742b40784d3 100644
--- a/app/views/groups/_children.html.haml
+++ b/app/views/groups/_children.html.haml
@@ -1,5 +1,4 @@
-= webpack_bundle_tag 'common_vue'
-= webpack_bundle_tag 'groups'
-
.js-groups-list-holder
#js-groups-tree{ data: { hide_projects: 'false', group_id: group.id, endpoint: group_children_path(group, format: :json), path: group_path(group), form_sel: 'form#group-filter-form', filter_sel: '.js-groups-list-filter', holder_sel: '.js-groups-list-holder', dropdown_sel: '.js-group-filter-dropdown-wrap' } }
+ .loading-container.text-center
+ = icon('spinner spin 2x', class: 'loading-animation prepend-top-20')
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 1597621fa78..ea13a5e6d62 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -43,7 +43,6 @@
= webpack_bundle_tag "main"
= webpack_bundle_tag "raven" if current_application_settings.clientside_sentry_enabled
= webpack_bundle_tag "test" if Rails.env.test?
- = webpack_bundle_tag 'performance_bar' if performance_bar_enabled?
- if content_for?(:page_specific_javascripts)
= yield :page_specific_javascripts
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
index b68eb47c6b4..56eecece54c 100644
--- a/app/views/projects/_last_push.html.haml
+++ b/app/views/projects/_last_push.html.haml
@@ -1,19 +1,18 @@
- event = last_push_event
- if event && show_last_push_widget?(event)
- %div{ class: container_class }
- .row-content-block.top-block.hidden-xs.white
- .event-last-push
- .event-last-push-text
- %span= s_("LastPushEvent|You pushed to")
- %strong
- = link_to event.ref_name, project_commits_path(event.project, event.ref_name), class: 'ref-name'
+ .row-content-block.top-block.hidden-xs.white
+ .event-last-push
+ .event-last-push-text
+ %span= s_("LastPushEvent|You pushed to")
+ %strong
+ = link_to event.ref_name, project_commits_path(event.project, event.ref_name), class: 'ref-name'
- - if event.project != @project
- %span= s_("LastPushEvent|at")
- %strong= link_to_project event.project
+ - if event.project != @project
+ %span= s_("LastPushEvent|at")
+ %strong= link_to_project event.project
- #{time_ago_with_tooltip(event.created_at)}
+ #{time_ago_with_tooltip(event.created_at)}
- .pull-right
- = link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm" do
- #{ _('Create merge request') }
+ .pull-right
+ = link_to new_mr_path_from_push_event(event), title: _("New merge request"), class: "btn btn-info btn-sm" do
+ #{ _('Create merge request') }
diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml
index d0ab39033cf..b28a375e956 100644
--- a/app/views/projects/activity.html.haml
+++ b/app/views/projects/activity.html.haml
@@ -2,6 +2,7 @@
- page_title _("Activity")
-= render 'projects/last_push'
+%div{ class: container_class }
+ = render 'projects/last_push'
= render 'projects/activity'
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index 4d358052d43..2ed454131af 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -6,9 +6,10 @@
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'blob'
-= render 'projects/last_push'
%div{ class: container_class }
+ = render 'projects/last_push'
+
#tree-holder.tree-holder
= render 'blob', blob: @blob
diff --git a/app/views/projects/commit/_change.html.haml b/app/views/projects/commit/_change.html.haml
index d0a380516f9..93407956f56 100644
--- a/app/views/projects/commit/_change.html.haml
+++ b/app/views/projects/commit/_change.html.haml
@@ -4,6 +4,7 @@
- branch_label = s_('ChangeTypeActionLabel|Revert in branch')
- revert_merge_request = _('Revert this merge request')
- revert_commit = _('Revert this commit')
+ - description = s_('ChangeTypeAction|This will create a new commit in order to revert the existing changes.')
- title = commit.merged_merge_request(current_user) ? revert_merge_request : revert_commit
- when 'cherry-pick'
- label = s_('ChangeTypeAction|Cherry-pick')
@@ -17,6 +18,8 @@
%a.close{ href: "#", "data-dismiss" => "modal" } ×
%h3.page-title= title
.modal-body
+ - if description
+ %p.append-bottom-20= description
= form_tag [type.underscore, @project.namespace.becomes(Namespace), @project, commit], method: :post, remote: false, class: "form-horizontal js-#{type}-form js-requires-input" do
.form-group.branch
= label_tag 'start_branch', branch_label, class: 'control-label'
diff --git a/app/views/projects/environments/metrics.html.haml b/app/views/projects/environments/metrics.html.haml
index ad94113fffd..5257b42548e 100644
--- a/app/views/projects/environments/metrics.html.haml
+++ b/app/views/projects/environments/metrics.html.haml
@@ -3,7 +3,6 @@
- content_for :page_specific_javascripts do
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'common_d3'
- = webpack_bundle_tag 'monitoring'
.prometheus-container{ class: container_class }
.top-area
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 2ded7484151..640d2791dc1 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -10,7 +10,8 @@
= webpack_bundle_tag 'common_vue'
= webpack_bundle_tag 'filtered_search'
-= render 'projects/last_push'
+%div{ class: container_class }
+ = render 'projects/last_push'
- if @project.merge_requests.exists?
%div{ class: container_class }
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 7a68aa16aa4..d3e867e124c 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -7,7 +7,9 @@
= render partial: 'flash_messages', locals: { project: @project }
-= render "projects/last_push"
+%div{ class: [container_class, ("limit-container-width" unless fluid_layout)] }
+ = render "projects/last_push"
+
= render "home_panel"
- if can?(current_user, :download_code, @project)
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 709be20e00f..3b4057e56d0 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -6,7 +6,6 @@
= content_for :meta_tags do
= auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, rss_url_options), title: "#{@project.name}:#{@ref} commits")
-= render 'projects/last_push'
-
%div{ class: [(container_class), ("limit-container-width" unless fluid_layout)] }
+ = render 'projects/last_push'
= render 'projects/files', commit: @last_commit, project: @project, ref: @ref, content_url: project_tree_path(@project, @id)
diff --git a/app/views/search/_category.html.haml b/app/views/search/_category.html.haml
index 314d8e9cb25..915e648a5d3 100644
--- a/app/views/search/_category.html.haml
+++ b/app/views/search/_category.html.haml
@@ -57,25 +57,24 @@
Titles and Filenames
%span.badge
= @search_results.snippet_titles_count
-
- else
%li{ class: active_when(@scope == 'projects') }
= link_to search_filter_path(scope: 'projects') do
Projects
%span.badge
- = @search_results.projects_count
+ = limited_count(@search_results.limited_projects_count)
%li{ class: active_when(@scope == 'issues') }
= link_to search_filter_path(scope: 'issues') do
Issues
%span.badge
- = @search_results.issues_count
+ = limited_count(@search_results.limited_issues_count)
%li{ class: active_when(@scope == 'merge_requests') }
= link_to search_filter_path(scope: 'merge_requests') do
Merge requests
%span.badge
- = @search_results.merge_requests_count
+ = limited_count(@search_results.limited_merge_requests_count)
%li{ class: active_when(@scope == 'milestones') }
= link_to search_filter_path(scope: 'milestones') do
Milestones
%span.badge
- = @search_results.milestones_count
+ = limited_count(@search_results.limited_milestones_count)
diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml
index 02133d09cdf..60ef44482f0 100644
--- a/app/views/search/_results.html.haml
+++ b/app/views/search/_results.html.haml
@@ -2,7 +2,8 @@
= render partial: "search/results/empty"
- else
.row-content-block
- = search_entries_info(@search_objects, @scope, @search_term)
+ - unless @search_objects.is_a?(Kaminari::PaginatableWithoutCount)
+ = search_entries_info(@search_objects, @scope, @search_term)
- unless @show_snippets
- if @project
in project #{link_to @project.name_with_namespace, [@project.namespace.becomes(Namespace), @project]}
@@ -22,4 +23,4 @@
= render partial: "search/results/#{@scope.singularize}", collection: @search_objects
- if @scope != 'projects'
- = paginate(@search_objects, theme: 'gitlab')
+ = paginate_collection(@search_objects)
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
index d0b9e891b82..cb21f90696f 100644
--- a/app/views/shared/_group_form.html.haml
+++ b/app/views/shared/_group_form.html.haml
@@ -1,5 +1,3 @@
-- content_for :page_specific_javascripts do
- = page_specific_javascript_bundle_tag('group')
- parent = @group.parent
- group_path = root_url
- group_path << parent.full_path + '/' if parent
diff --git a/changelogs/unreleased/24035-api-create-application.yml b/changelogs/unreleased/24035-api-create-application.yml
new file mode 100644
index 00000000000..c583a020d9d
--- /dev/null
+++ b/changelogs/unreleased/24035-api-create-application.yml
@@ -0,0 +1,4 @@
+---
+title: Add application create API
+merge_request: 8160
+author: Nicolas Merelli @PNSalocin
diff --git a/changelogs/unreleased/32546-cannot-copy-paste-on-ios.yml b/changelogs/unreleased/32546-cannot-copy-paste-on-ios.yml
new file mode 100644
index 00000000000..f4c44983736
--- /dev/null
+++ b/changelogs/unreleased/32546-cannot-copy-paste-on-ios.yml
@@ -0,0 +1,5 @@
+---
+title: Fix copy/paste on iOS devices due to a bug in webkit
+merge_request: 15804
+author:
+type: fixed
diff --git a/changelogs/unreleased/39917-revert-this-merge-request-text.yml b/changelogs/unreleased/39917-revert-this-merge-request-text.yml
new file mode 100644
index 00000000000..9a27be1f9c6
--- /dev/null
+++ b/changelogs/unreleased/39917-revert-this-merge-request-text.yml
@@ -0,0 +1,5 @@
+---
+title: Changes Revert this merge request text
+merge_request: 16611
+author: Jacopo Beschi @jacopo-beschi
+type: changed
diff --git a/changelogs/unreleased/40540-use-limit-for-global-search.yml b/changelogs/unreleased/40540-use-limit-for-global-search.yml
new file mode 100644
index 00000000000..7d9612c16df
--- /dev/null
+++ b/changelogs/unreleased/40540-use-limit-for-global-search.yml
@@ -0,0 +1,5 @@
+---
+title: Optimize search queries on the search page by setting a limit for matching records.
+merge_request:
+author:
+type: performance
diff --git a/changelogs/unreleased/40612-cannot-change-project-visibility-from-private-even-when-owner.yml b/changelogs/unreleased/40612-cannot-change-project-visibility-from-private-even-when-owner.yml
deleted file mode 100644
index 96bb59d303c..00000000000
--- a/changelogs/unreleased/40612-cannot-change-project-visibility-from-private-even-when-owner.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Fix bug in which projects with forks could not change visibility settings from
- Private to Public
-merge_request: 16595
-author:
-type: fixed
diff --git a/changelogs/unreleased/42022-allow-users-to-request-access-not-visible-when-project-visibility-is-public.yml b/changelogs/unreleased/42022-allow-users-to-request-access-not-visible-when-project-visibility-is-public.yml
new file mode 100644
index 00000000000..38684cd3c44
--- /dev/null
+++ b/changelogs/unreleased/42022-allow-users-to-request-access-not-visible-when-project-visibility-is-public.yml
@@ -0,0 +1,5 @@
+---
+title: Fix missing "allow users to request access" option in public project permissions
+merge_request: 16485
+author:
+type: fixed
diff --git a/changelogs/unreleased/42053-link-to-clusters-in-auto-devops-instead-of-kubernetes-service.yml b/changelogs/unreleased/42053-link-to-clusters-in-auto-devops-instead-of-kubernetes-service.yml
new file mode 100644
index 00000000000..5cb5dc3ccd8
--- /dev/null
+++ b/changelogs/unreleased/42053-link-to-clusters-in-auto-devops-instead-of-kubernetes-service.yml
@@ -0,0 +1,5 @@
+---
+title: Link Auto DevOps settings to Clusters page
+merge_request: 16641
+author:
+type: changed
diff --git a/changelogs/unreleased/42159-utf8-uploads.yml b/changelogs/unreleased/42159-utf8-uploads.yml
deleted file mode 100644
index f6eba8f28f5..00000000000
--- a/changelogs/unreleased/42159-utf8-uploads.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Correctly escape UTF-8 path elements for uploads
-merge_request: 16560
-author:
-type: fixed
diff --git a/changelogs/unreleased/42161-gitaly-commitservice-encoding-undefinedconversionerror-u-c124-from-utf-8-to-ascii-8bit.yml b/changelogs/unreleased/42161-gitaly-commitservice-encoding-undefinedconversionerror-u-c124-from-utf-8-to-ascii-8bit.yml
new file mode 100644
index 00000000000..c64bee9126e
--- /dev/null
+++ b/changelogs/unreleased/42161-gitaly-commitservice-encoding-undefinedconversionerror-u-c124-from-utf-8-to-ascii-8bit.yml
@@ -0,0 +1,5 @@
+---
+title: Fix encoding issue when counting commit count
+merge_request: 16637
+author:
+type: fixed
diff --git a/changelogs/unreleased/42251-explicit-timezone-for-karma.yml b/changelogs/unreleased/42251-explicit-timezone-for-karma.yml
new file mode 100644
index 00000000000..25e0e774c48
--- /dev/null
+++ b/changelogs/unreleased/42251-explicit-timezone-for-karma.yml
@@ -0,0 +1,5 @@
+---
+title: Set timezone for karma to UTC
+merge_request: 16602
+author: Takuya Noguchi
+type: other
diff --git a/changelogs/unreleased/bvl-parent-preloading.yml b/changelogs/unreleased/bvl-parent-preloading.yml
deleted file mode 100644
index 97c7bbb2a2a..00000000000
--- a/changelogs/unreleased/bvl-parent-preloading.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Fix issues when rendering groups and their children
-merge_request: 16584
-author:
-type: fixed
diff --git a/changelogs/unreleased/dm-project-system-hooks-in-transaction.yml b/changelogs/unreleased/dm-project-system-hooks-in-transaction.yml
new file mode 100644
index 00000000000..f59021c0ec9
--- /dev/null
+++ b/changelogs/unreleased/dm-project-system-hooks-in-transaction.yml
@@ -0,0 +1,5 @@
+---
+title: Execute system hooks after-commit when executing project hooks
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/fix-adjust-layout-width-for-fixed-layout.yml b/changelogs/unreleased/fix-adjust-layout-width-for-fixed-layout.yml
new file mode 100644
index 00000000000..2e0f59f81e9
--- /dev/null
+++ b/changelogs/unreleased/fix-adjust-layout-width-for-fixed-layout.yml
@@ -0,0 +1,5 @@
+---
+title: Adjust layout width for fixed layout
+merge_request: 16337
+author: George Tsiolis
+type: fixed
diff --git a/changelogs/unreleased/fix-redirect-routes-schema.yml b/changelogs/unreleased/fix-redirect-routes-schema.yml
deleted file mode 100644
index ea2b916307a..00000000000
--- a/changelogs/unreleased/fix-redirect-routes-schema.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: rework indexes on redirect_routes
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/fl-mr-widget-refactor.yml b/changelogs/unreleased/fl-mr-widget-refactor.yml
new file mode 100644
index 00000000000..d59cca68409
--- /dev/null
+++ b/changelogs/unreleased/fl-mr-widget-refactor.yml
@@ -0,0 +1,5 @@
+---
+title: Refactors mr widget components into vue files and adds i18n
+merge_request:
+author:
+type: other
diff --git a/changelogs/unreleased/issue_37143_2.yml b/changelogs/unreleased/issue_37143_2.yml
deleted file mode 100644
index 38125f666b2..00000000000
--- a/changelogs/unreleased/issue_37143_2.yml
+++ /dev/null
@@ -1,5 +0,0 @@
----
-title: Remove unecessary query from labels filter
-merge_request:
-author:
-type: performance
diff --git a/changelogs/unreleased/mk-delete-orphaned-routes-before-validation.yml b/changelogs/unreleased/mk-delete-orphaned-routes-before-validation.yml
deleted file mode 100644
index 55ab318df7d..00000000000
--- a/changelogs/unreleased/mk-delete-orphaned-routes-before-validation.yml
+++ /dev/null
@@ -1,6 +0,0 @@
----
-title: Ensure that users can reclaim a namespace or project path that is blocked by
- an orphaned route
-merge_request: 16242
-author:
-type: fixed
diff --git a/changelogs/unreleased/osw-updates-merge-status-on-api-actions.yml b/changelogs/unreleased/osw-updates-merge-status-on-api-actions.yml
new file mode 100644
index 00000000000..3854985e576
--- /dev/null
+++ b/changelogs/unreleased/osw-updates-merge-status-on-api-actions.yml
@@ -0,0 +1,5 @@
+---
+title: Return more consistent values for merge_status on MR APIs
+merge_request:
+author:
+type: fixed
diff --git a/changelogs/unreleased/sh-add-gitaly-health-check.yml b/changelogs/unreleased/sh-add-gitaly-health-check.yml
new file mode 100644
index 00000000000..32c4c5362b4
--- /dev/null
+++ b/changelogs/unreleased/sh-add-gitaly-health-check.yml
@@ -0,0 +1,5 @@
+---
+title: Add a gRPC health check to ensure Gitaly is up
+merge_request:
+author:
+type: added
diff --git a/config/karma.config.js b/config/karma.config.js
index 9f018d14b8f..a101d35704e 100644
--- a/config/karma.config.js
+++ b/config/karma.config.js
@@ -18,6 +18,8 @@ webpackConfig.devtool = 'cheap-inline-source-map';
// Karma configuration
module.exports = function(config) {
+ process.env.TZ = 'Etc/UTC';
+
var progressReporter = process.env.CI ? 'mocha' : 'progress';
var karmaConfig = {
diff --git a/config/unicorn.rb.example.development b/config/unicorn.rb.example.development
index 3cd00d53a15..0df028648d1 100644
--- a/config/unicorn.rb.example.development
+++ b/config/unicorn.rb.example.development
@@ -1,2 +1,15 @@
worker_processes 2
timeout 60
+
+before_fork do |server, worker|
+ if /darwin/ =~ RUBY_PLATFORM
+ require 'fiddle'
+
+ # Dynamically load Foundation.framework, ~implicitly~ initialising
+ # the Objective-C runtime before any forking happens in Unicorn
+ #
+ # From https://bugs.ruby-lang.org/issues/14009
+ Fiddle.dlopen '/System/Library/Frameworks/Foundation.framework/Foundation'
+ end
+end
+
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 229db11acb2..783677b5b8d 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -44,9 +44,6 @@ var config = {
graphs: './graphs/graphs_bundle.js',
graphs_charts: './graphs/graphs_charts.js',
graphs_show: './graphs/graphs_show.js',
- group: './group.js',
- groups: './groups/index.js',
- groups_list: './groups_list.js',
help: './help/help.js',
how_to_merge: './how_to_merge.js',
issue_show: './issue_show/index.js',
@@ -85,7 +82,6 @@ var config = {
test: './test.js',
two_factor_auth: './two_factor_auth.js',
users: './users/index.js',
- performance_bar: './performance_bar.js',
webpack_runtime: './webpack.js',
},
@@ -119,9 +115,9 @@ var config = {
{
test: /\_worker\.js$/,
use: [
- {
+ {
loader: 'worker-loader',
- options: {
+ options: {
inline: true
}
},
diff --git a/db/migrate/20180115201419_add_index_updated_at_to_issues.rb b/db/migrate/20180115201419_add_index_updated_at_to_issues.rb
new file mode 100644
index 00000000000..a5a48fc97be
--- /dev/null
+++ b/db/migrate/20180115201419_add_index_updated_at_to_issues.rb
@@ -0,0 +1,15 @@
+class AddIndexUpdatedAtToIssues < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_index :issues, :updated_at
+ end
+
+ def down
+ remove_concurrent_index :issues, :updated_at
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index a0901833c3d..4e82a688725 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180113220114) do
+ActiveRecord::Schema.define(version: 20180115201419) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -886,6 +886,7 @@ ActiveRecord::Schema.define(version: 20180113220114) do
add_index "issues", ["relative_position"], name: "index_issues_on_relative_position", using: :btree
add_index "issues", ["state"], name: "index_issues_on_state", using: :btree
add_index "issues", ["title"], name: "index_issues_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"}
+ add_index "issues", ["updated_at"], name: "index_issues_on_updated_at", using: :btree
add_index "issues", ["updated_by_id"], name: "index_issues_on_updated_by_id", where: "(updated_by_id IS NOT NULL)", using: :btree
create_table "keys", force: :cascade do |t|
diff --git a/doc/api/applications.md b/doc/api/applications.md
new file mode 100644
index 00000000000..933867ed0bb
--- /dev/null
+++ b/doc/api/applications.md
@@ -0,0 +1,37 @@
+# Applications API
+
+> [Introduced][ce-8160] in GitLab 10.5
+
+[ce-8160]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8160
+
+## Create a application
+
+Create a application by posting a JSON payload.
+
+User must be admin to do that.
+
+Returns `200` if the request succeeds.
+
+```
+POST /applications
+```
+
+| Attribute | Type | Required | Description |
+| --------- | ---- | -------- | ----------- |
+| `name` | string | yes | The name of the application |
+| `redirect_uri` | string | yes | The redirect URI of the application |
+| `scopes` | string | yes | The scopes of the application |
+
+```bash
+curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --data "name=MyApplication&redirect_uri=http://redirect.uri&scopes=" https://gitlab.example.com/api/v3/applications
+```
+
+Example response:
+
+```json
+{
+ "application_id": "5832fc6e14300a0d962240a8144466eef4ee93ef0d218477e55f11cf12fc3737",
+ "secret": "ee1dd64b6adc89cf7e2c23099301ccc2c61b441064e9324d963c46902a85ec34",
+ "callback_url": "http://redirect.uri"
+}
+```
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 06f69938aae..598a7515b01 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -110,7 +110,7 @@ future GitLab releases.**
| `CI_BUILD_MANUAL` | `CI_JOB_MANUAL` |
| `CI_BUILD_TOKEN` | `CI_JOB_TOKEN` |
-## `.gitlab-ci.yaml` defined variables
+## `.gitlab-ci.yml` defined variables
>**Note:**
This feature requires GitLab Runner 0.5.0 or higher and GitLab CI 7.14 or higher.
diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md
index 82052cc0376..4a650303d45 100644
--- a/doc/ci/yaml/README.md
+++ b/doc/ci/yaml/README.md
@@ -1293,7 +1293,7 @@ to the CI pipeline:
```yaml
variables:
GIT_STRATEGY: clone
- GIT_CHECKOUT: false
+ GIT_CHECKOUT: "false"
script:
- git checkout master
- git merge $CI_BUILD_REF_NAME
diff --git a/doc/development/fe_guide/axios.md b/doc/development/fe_guide/axios.md
index 34fa684c5d6..0d9397c3bd5 100644
--- a/doc/development/fe_guide/axios.md
+++ b/doc/development/fe_guide/axios.md
@@ -27,10 +27,23 @@ This exported module should be used instead of directly using `axios` to ensure
});
```
-## Mock axios response on tests
+## Mock axios response in tests
-To help us mock the responses we need we use [axios-mock-adapter][axios-mock-adapter]
+To help us mock the responses we are using [axios-mock-adapter][axios-mock-adapter].
+Advantages over [`spyOn()`]:
+
+- no need to create response objects
+- does not allow call through (which we want to avoid)
+- simple API to test error cases
+- provides `replyOnce()` to allow for different responses
+
+We have also decided against using [axios interceptors] because they are not suitable for mocking.
+
+[axios interceptors]: https://github.com/axios/axios#interceptors
+[`spyOn()`]: https://jasmine.github.io/api/edge/global.html#spyOn
+
+### Example
```javascript
import axios from '~/lib/utils/axios_utils';
@@ -50,11 +63,11 @@ To help us mock the responses we need we use [axios-mock-adapter][axios-mock-ada
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
```
-### Mock poll requests on tests with axios
+### Mock poll requests in tests with axios
Because polling function requires a header object, we need to always include an object as the third argument:
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 50bb665216e..76f33b765d3 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -169,6 +169,30 @@ For Omnibus GitLab packages:
1. [Reconfigure GitLab] for the changes to take effect
+#### Digital Ocean Spaces and other S3-compatible providers
+
+Not all S3 providers are fully-compatible with the Fog library. For example,
+if you see `411 Length Required` errors after attempting to upload, you may
+need to downgrade the `aws_signature_version` value from the default value to
+2 [due to this issue](https://github.com/fog/fog-aws/issues/428).
+
+1. For example, with [Digital Ocean Spaces](https://www.digitalocean.com/products/spaces/),
+this example configuration can be used for a bucket in Amsterdam (AMS3):
+
+ ```ruby
+ gitlab_rails['backup_upload_connection'] = {
+ 'provider' => 'AWS',
+ 'region' => 'ams3',
+ 'aws_access_key_id' => 'AKIAKIAKI',
+ 'aws_secret_access_key' => 'secret123',
+ 'aws_signature_version' => 2,
+ 'endpoint' => 'https://ams3.digitaloceanspaces.com'
+ }
+ gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket'
+ ```
+
+1. [Reconfigure GitLab] for the changes to take effect
+
---
For installations from source:
diff --git a/doc/security/rack_attack.md b/doc/security/rack_attack.md
index 92066997be8..c61729581e8 100644
--- a/doc/security/rack_attack.md
+++ b/doc/security/rack_attack.md
@@ -1,25 +1,135 @@
-# Rack attack
+# Rack Attack
-To prevent abusive clients doing damage GitLab uses rack-attack gem.
+Rack Attack, also known as Rack::Attack, is [a rubygem](https://github.com/kickstarter/rack-attack)
+that is meant to protect GitLab with the ability to customize throttling and
+blocking user IPs.
+You can prevent brute-force passwords attacks, scrapers, or any other offenders
+by throttling requests from IP addresses making large volumes of requests.
+In case you find throttling is not enough to protect you against abusive clients,
+Rack Attack offers IP whitelisting, blacklisting, Fail2ban style filtering and
+tracking.
-If you installed or upgraded GitLab by following the official guides this should be enabled by default.
+By default, user sign-in, user sign-up (if enabled), and user password reset is
+limited to 6 requests per minute. After trying for 6 times, the client will
+have to wait for the next minute to be able to try again.
-If you are missing `config/initializers/rack_attack.rb` the following steps need to be taken in order to enable protection for your GitLab instance:
+If you installed or upgraded GitLab by following the [official guides](../install/README.md)
+this should be enabled by default. If your instance is not exposed to any incoming
+connections, it is recommended to disable Rack Attack.
-1. In config/application.rb find and uncomment the following line:
+For more information on how to use these options check out
+[rack-attack README](https://github.com/kickstarter/rack-attack/blob/master/README.md).
- config.middleware.use Rack::Attack
+## Settings
-1. Rename `config/initializers/rack_attack.rb.example` to `config/initializers/rack_attack.rb`.
+**Omnibus GitLab**
-1. Review the `paths_to_be_protected` and add any other path you need protecting.
+1. Open `/etc/gitlab/gitlab.rb` with you editor
+1. Add the following:
-1. Restart GitLab instance.
+ ```ruby
+ gitlab_rails['rack_attack_git_basic_auth'] = {
+ 'enabled' => true,
+ 'ip_whitelist' => ["127.0.0.1"],
+ 'maxretry' => 10,
+ 'findtime' => 60,
+ 'bantime' => 3600
+ }
+ ```
-By default, user sign-in, user sign-up(if enabled) and user password reset is limited to 6 requests per minute. After trying for 6 times, client will have to wait for the next minute to be able to try again. These settings can be found in `config/initializers/rack_attack.rb`
+3. Reconfigure GitLab:
-If you want more restrictive/relaxed throttle rule change the `limit` or `period` values. For example, more relaxed throttle rule will be if you set limit: 3 and period: 1.second(this will allow 3 requests per second). You can also add other paths to the protected list by adding to `paths_to_be_protected` variable. If you change any of these settings do not forget to restart your GitLab instance.
+ ```
+ sudo gitlab-ctl reconfigure
+ ```
-In case you find throttling is not enough to protect you against abusive clients, rack-attack gem offers IP whitelisting, blacklisting, Fail2ban style filter and tracking.
+The following settings can be configured:
-For more information on how to use these options check out [rack-attack README](https://github.com/kickstarter/rack-attack/blob/master/README.md).
+- `enabled`: By default this is set to `true`. Set this to `false` to disable Rack Attack.
+- `ip_whitelist`: Whitelist any IPs from being blocked. They must be formatted as strings within a ruby array.
+ For example, `["127.0.0.1", "127.0.0.2", "127.0.0.3"]`.
+- `maxretry`: The maximum amount of times a request can be made in the
+ specified time.
+- `findtime`: The maximum amount of time failed requests can count against an IP
+ before it's blacklisted.
+- `bantime`: The total amount of time that a blacklisted IP will be blocked in
+ seconds.
+
+**Installations from source**
+
+These settings can be found in `config/initializers/rack_attack.rb`. If you are
+missing `config/initializers/rack_attack.rb`, the following steps need to be
+taken in order to enable protection for your GitLab instance:
+
+1. In `config/application.rb` find and uncomment the following line:
+
+ ```ruby
+ config.middleware.use Rack::Attack
+ ```
+
+1. Copy `config/initializers/rack_attack.rb.example` to `config/initializers/rack_attack.rb`
+1. Open `config/initializers/rack_attack.rb`, review the
+ `paths_to_be_protected`, and add any other path you need protecting
+1. Restart GitLab:
+
+ ```sh
+ sudo service gitlab restart
+ ```
+
+If you want more restrictive/relaxed throttle rules, edit
+`config/initializers/rack_attack.rb` and change the `limit` or `period` values.
+For example, more relaxed throttle rules will be if you set
+`limit: 3` and `period: 1.seconds` (this will allow 3 requests per second).
+You can also add other paths to the protected list by adding to `paths_to_be_protected`
+variable. If you change any of these settings do not forget to restart your
+GitLab instance.
+
+## Remove blocked IPs from Rack Attack via Redis
+
+In case you want to remove a blocked IP, follow these steps:
+
+1. Find the IPs that have been blocked in the production log:
+
+ ```sh
+ grep "Rack_Attack" /var/log/gitlab/gitlab-rails/production.log
+ ```
+
+2. Since the blacklist is stored in Redis, you need to open up `redis-cli`:
+
+ ```sh
+ /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket
+ ```
+
+3. You can remove the block using the following syntax, replacing `<ip>` with
+ the actual IP that is blacklisted:
+
+ ```
+ del cache:gitlab:rack::attack:allow2ban:ban:<ip>
+ ```
+
+4. Confirm that the key with the IP no longer shows up:
+
+ ```
+ keys *rack::attack*
+ ```
+
+5. Optionally, add the IP to the whitelist to prevent it from being blacklisted
+ again (see [settings](#settings)).
+
+## Troubleshooting
+
+### Rack attack is blacklisting the load balancer
+
+Rack Attack may block your load balancer if all traffic appears to come from
+the load balancer. In that case, you will need to:
+
+1. [Configure `nginx[real_ip_trusted_addresses]`](https://docs.gitlab.com/omnibus/settings/nginx.html#configuring-gitlab-trusted_proxies-and-the-nginx-real_ip-module).
+ This will keep users' IPs from being listed as the load balancer IPs.
+2. Whitelist the load balancer's IP address(es) in the Rack Attack [settings](#settings).
+3. Reconfigure GitLab:
+
+ ```
+ sudo gitlab-ctl reconfigure
+ ```
+
+4. [Remove the block via Redis.](#remove-blocked-ips-from-rack-attack-via-redis)
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index 764ee0ca72c..144cd4c26b0 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -66,9 +66,8 @@ To make full use of Auto DevOps, you will need:
a domain configured with wildcard DNS which is gonna be used by all of your
Auto DevOps applications. [Read the specifics](#auto-devops-base-domain).
1. **Kubernetes** (needed for Auto Review Apps, Auto Deploy, and Auto Monitoring) -
- To enable deployments, you will need Kubernetes 1.5+. The [Kubernetes service][kubernetes-service]
- integration will need to be enabled for the project, or enabled as a
- [default service template](../../user/project/integrations/services_templates.md)
+ To enable deployments, you will need Kubernetes 1.5+. You need a [Kubernetes cluster][kubernetes-clusters]
+ for the project, or a Kubernetes [default service template](../../user/project/integrations/services_templates.md)
for the entire GitLab installation.
1. **A load balancer** - You can use NGINX ingress by deploying it to your
Kubernetes cluster using the
@@ -587,7 +586,7 @@ curl --data "value=true" --header "PRIVATE-TOKEN: personal_access_token" https:/
```
[ce-37115]: https://gitlab.com/gitlab-org/gitlab-ce/issues/37115
-[kubernetes-service]: ../../user/project/integrations/kubernetes.md
+[kubernetes-clusters]: ../../user/project/clusters/index.md
[docker-in-docker]: ../../docker/using_docker_build.md#use-docker-in-docker-executor
[review-app]: ../../ci/review_apps/index.md
[container-registry]: ../../user/project/container_registry.md
diff --git a/features/support/db_cleaner.rb b/features/support/db_cleaner.rb
index 8294bb1445f..31c922d23c3 100644
--- a/features/support/db_cleaner.rb
+++ b/features/support/db_cleaner.rb
@@ -1,6 +1,6 @@
require 'database_cleaner'
-DatabaseCleaner[:active_record].strategy = :truncation
+DatabaseCleaner[:active_record].strategy = :deletion
Spinach.hooks.before_scenario do
DatabaseCleaner.start
diff --git a/lib/api/api.rb b/lib/api/api.rb
index ae161efb358..f3f64244589 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -106,6 +106,7 @@ module API
# Keep in alphabetical order
mount ::API::AccessRequests
+ mount ::API::Applications
mount ::API::AwardEmoji
mount ::API::Boards
mount ::API::Branches
diff --git a/lib/api/applications.rb b/lib/api/applications.rb
new file mode 100644
index 00000000000..b122cdefe4e
--- /dev/null
+++ b/lib/api/applications.rb
@@ -0,0 +1,27 @@
+module API
+ # External applications API
+ class Applications < Grape::API
+ before { authenticated_as_admin! }
+
+ resource :applications do
+ desc 'Create a new application' do
+ detail 'This feature was introduced in GitLab 10.5'
+ success Entities::ApplicationWithSecret
+ end
+ params do
+ requires :name, type: String, desc: 'Application name'
+ requires :redirect_uri, type: String, desc: 'Application redirect URI'
+ requires :scopes, type: String, desc: 'Application scopes'
+ end
+ post do
+ application = Doorkeeper::Application.new(declared_params)
+
+ if application.save
+ present application, with: Entities::ApplicationWithSecret
+ else
+ render_validation_error! application
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 3f4b62dc1b2..5b470bd3479 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -507,7 +507,16 @@ module API
expose :work_in_progress?, as: :work_in_progress
expose :milestone, using: Entities::Milestone
expose :merge_when_pipeline_succeeds
- expose :merge_status
+
+ # Ideally we should deprecate `MergeRequest#merge_status` exposure and
+ # use `MergeRequest#mergeable?` instead (boolean).
+ # See https://gitlab.com/gitlab-org/gitlab-ce/issues/42344 for more
+ # information.
+ expose :merge_status do |merge_request|
+ # In order to avoid having a breaking change for users, we keep returning the
+ # expected values from MergeRequest#merge_status state machine.
+ merge_request.mergeable? ? 'can_be_merged' : 'cannot_be_merged'
+ end
expose :diff_head_sha, as: :sha
expose :merge_commit_sha
expose :user_notes_count
@@ -1157,5 +1166,15 @@ module API
pages_domain
end
end
+
+ class Application < Grape::Entity
+ expose :uid, as: :application_id
+ expose :redirect_uri, as: :callback_url
+ end
+
+ # Use with care, this exposes the secret
+ class ApplicationWithSecret < Application
+ expose :secret
+ end
end
end
diff --git a/lib/api/v3/projects.rb b/lib/api/v3/projects.rb
index 446f804124b..a7f0813bf74 100644
--- a/lib/api/v3/projects.rb
+++ b/lib/api/v3/projects.rb
@@ -175,7 +175,7 @@ module API
end
get "/search/:query", requirements: { query: /[^\/]+/ } do
search_service = Search::GlobalService.new(current_user, search: params[:query]).execute
- projects = search_service.objects('projects', params[:page])
+ projects = search_service.objects('projects', params[:page], false)
projects = projects.reorder(params[:order_by] => params[:sort])
present paginate(projects), with: ::API::V3::Entities::Project
diff --git a/lib/gitlab/checks/change_access.rb b/lib/gitlab/checks/change_access.rb
index ef92fc5a0a0..945d70e7a24 100644
--- a/lib/gitlab/checks/change_access.rb
+++ b/lib/gitlab/checks/change_access.rb
@@ -16,7 +16,7 @@ module Gitlab
lfs_objects_missing: 'LFS objects are missing. Ensure LFS is properly set up or try a manual "git lfs push --all".'
}.freeze
- attr_reader :user_access, :project, :skip_authorization, :protocol
+ attr_reader :user_access, :project, :skip_authorization, :protocol, :oldrev, :newrev, :ref, :branch_name, :tag_name
def initialize(
change, user_access:, project:, skip_authorization: false,
@@ -51,9 +51,9 @@ module Gitlab
end
def branch_checks
- return unless @branch_name
+ return unless branch_name
- if deletion? && @branch_name == project.default_branch
+ if deletion? && branch_name == project.default_branch
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:delete_default_branch]
end
@@ -61,7 +61,7 @@ module Gitlab
end
def protected_branch_checks
- return unless ProtectedBranch.protected?(project, @branch_name)
+ return unless ProtectedBranch.protected?(project, branch_name)
if forced_push?
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:force_push_protected_branch]
@@ -75,29 +75,29 @@ module Gitlab
end
def protected_branch_deletion_checks
- unless user_access.can_delete_branch?(@branch_name)
+ unless user_access.can_delete_branch?(branch_name)
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:non_master_delete_protected_branch]
end
- unless protocol == 'web'
+ unless updated_from_web?
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:non_web_delete_protected_branch]
end
end
def protected_branch_push_checks
if matching_merge_request?
- unless user_access.can_merge_to_branch?(@branch_name) || user_access.can_push_to_branch?(@branch_name)
+ unless user_access.can_merge_to_branch?(branch_name) || user_access.can_push_to_branch?(branch_name)
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:merge_protected_branch]
end
else
- unless user_access.can_push_to_branch?(@branch_name)
+ unless user_access.can_push_to_branch?(branch_name)
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:push_protected_branch]
end
end
end
def tag_checks
- return unless @tag_name
+ return unless tag_name
if tag_exists? && user_access.cannot_do_action?(:admin_project)
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:change_existing_tags]
@@ -107,40 +107,44 @@ module Gitlab
end
def protected_tag_checks
- return unless ProtectedTag.protected?(project, @tag_name)
+ return unless ProtectedTag.protected?(project, tag_name)
raise(GitAccess::UnauthorizedError, ERROR_MESSAGES[:update_protected_tag]) if update?
raise(GitAccess::UnauthorizedError, ERROR_MESSAGES[:delete_protected_tag]) if deletion?
- unless user_access.can_create_tag?(@tag_name)
+ unless user_access.can_create_tag?(tag_name)
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:create_protected_tag]
end
end
private
+ def updated_from_web?
+ protocol == 'web'
+ end
+
def tag_exists?
- project.repository.tag_exists?(@tag_name)
+ project.repository.tag_exists?(tag_name)
end
def forced_push?
- Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev)
+ Gitlab::Checks::ForcePush.force_push?(project, oldrev, newrev)
end
def update?
- !Gitlab::Git.blank_ref?(@oldrev) && !deletion?
+ !Gitlab::Git.blank_ref?(oldrev) && !deletion?
end
def deletion?
- Gitlab::Git.blank_ref?(@newrev)
+ Gitlab::Git.blank_ref?(newrev)
end
def matching_merge_request?
- Checks::MatchingMergeRequest.new(@newrev, @branch_name, @project).match?
+ Checks::MatchingMergeRequest.new(newrev, branch_name, project).match?
end
def lfs_objects_exist_check
- lfs_check = Checks::LfsIntegrity.new(project, @newrev)
+ lfs_check = Checks::LfsIntegrity.new(project, newrev)
if lfs_check.objects_missing?
raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:lfs_objects_missing]
diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb
index f421bf69e8f..81e46028752 100644
--- a/lib/gitlab/git/blob.rb
+++ b/lib/gitlab/git/blob.rb
@@ -34,7 +34,7 @@ module Gitlab
def raw(repository, sha)
Gitlab::GitalyClient.migrate(:git_blob_raw) do |is_enabled|
if is_enabled
- Gitlab::GitalyClient::BlobService.new(repository).get_blob(oid: sha, limit: MAX_DATA_DISPLAY_SIZE)
+ repository.gitaly_blob_client.get_blob(oid: sha, limit: MAX_DATA_DISPLAY_SIZE)
else
rugged_raw(repository, sha, limit: MAX_DATA_DISPLAY_SIZE)
end
@@ -70,11 +70,19 @@ module Gitlab
# Returns array of Gitlab::Git::Blob
# Does not guarantee blob data will be set
def batch_lfs_pointers(repository, blob_ids)
- blob_ids.lazy
- .select { |sha| possible_lfs_blob?(repository, sha) }
- .map { |sha| rugged_raw(repository, sha, limit: LFS_POINTER_MAX_SIZE) }
- .select(&:lfs_pointer?)
- .force
+ return [] if blob_ids.empty?
+
+ repository.gitaly_migrate(:batch_lfs_pointers) do |is_enabled|
+ if is_enabled
+ repository.gitaly_blob_client.batch_lfs_pointers(blob_ids)
+ else
+ blob_ids.lazy
+ .select { |sha| possible_lfs_blob?(repository, sha) }
+ .map { |sha| rugged_raw(repository, sha, limit: LFS_POINTER_MAX_SIZE) }
+ .select(&:lfs_pointer?)
+ .force
+ end
+ end
end
def binary?(data)
@@ -258,7 +266,7 @@ module Gitlab
Gitlab::GitalyClient.migrate(:git_blob_load_all_data) do |is_enabled|
@data = begin
if is_enabled
- Gitlab::GitalyClient::BlobService.new(repository).get_blob(oid: id, limit: -1).data
+ repository.gitaly_blob_client.get_blob(oid: id, limit: -1).data
else
repository.lookup(id).content
end
diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb
index d666362de0d..d7c712e75c5 100644
--- a/lib/gitlab/git/repository.rb
+++ b/lib/gitlab/git/repository.rb
@@ -1268,6 +1268,18 @@ module Gitlab
success || gitlab_projects_error
end
+ def bundle_to_disk(save_path)
+ gitaly_migrate(:bundle_to_disk) do |is_enabled|
+ if is_enabled
+ gitaly_repository_client.create_bundle(save_path)
+ else
+ run_git!(%W(bundle create #{save_path} --all))
+ end
+ end
+
+ true
+ end
+
# rubocop:disable Metrics/ParameterLists
def multi_action(
user, branch_name:, message:, actions:,
@@ -1319,6 +1331,10 @@ module Gitlab
@gitaly_remote_client ||= Gitlab::GitalyClient::RemoteService.new(self)
end
+ def gitaly_blob_client
+ @gitaly_blob_client ||= Gitlab::GitalyClient::BlobService.new(self)
+ end
+
def gitaly_conflicts_client(our_commit_oid, their_commit_oid)
Gitlab::GitalyClient::ConflictsService.new(self, our_commit_oid, their_commit_oid)
end
diff --git a/lib/gitlab/git/wiki_page.rb b/lib/gitlab/git/wiki_page.rb
index a06bac4414f..669ae11a423 100644
--- a/lib/gitlab/git/wiki_page.rb
+++ b/lib/gitlab/git/wiki_page.rb
@@ -1,7 +1,7 @@
module Gitlab
module Git
class WikiPage
- attr_reader :url_path, :title, :format, :path, :version, :raw_data, :name, :text_data, :historical
+ attr_reader :url_path, :title, :format, :path, :version, :raw_data, :name, :text_data, :historical, :formatted_data
# This class is meant to be serializable so that it can be constructed
# by Gitaly and sent over the network to GitLab.
@@ -21,6 +21,7 @@ module Gitlab
@raw_data = gollum_page.raw_data
@name = gollum_page.name
@historical = gollum_page.historical?
+ @formatted_data = gollum_page.formatted_data if gollum_page.is_a?(Gollum::Page)
@version = version
end
diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb
index 4507ea923b4..6bd256f57c7 100644
--- a/lib/gitlab/gitaly_client.rb
+++ b/lib/gitlab/gitaly_client.rb
@@ -1,6 +1,8 @@
require 'base64'
require 'gitaly'
+require 'grpc/health/v1/health_pb'
+require 'grpc/health/v1/health_services_pb'
module Gitlab
module GitalyClient
@@ -69,14 +71,27 @@ module Gitlab
@stubs ||= {}
@stubs[storage] ||= {}
@stubs[storage][name] ||= begin
- klass = Gitaly.const_get(name.to_s.camelcase.to_sym).const_get(:Stub)
- addr = address(storage)
- addr = addr.sub(%r{^tcp://}, '') if URI(addr).scheme == 'tcp'
+ klass = stub_class(name)
+ addr = stub_address(storage)
klass.new(addr, :this_channel_is_insecure)
end
end
end
+ def self.stub_class(name)
+ if name == :health_check
+ Grpc::Health::V1::Health::Stub
+ else
+ Gitaly.const_get(name.to_s.camelcase.to_sym).const_get(:Stub)
+ end
+ end
+
+ def self.stub_address(storage)
+ addr = address(storage)
+ addr = addr.sub(%r{^tcp://}, '') if URI(addr).scheme == 'tcp'
+ addr
+ end
+
def self.clear_stubs!
MUTEX.synchronize do
@stubs = nil
diff --git a/lib/gitlab/gitaly_client/blob_service.rb b/lib/gitlab/gitaly_client/blob_service.rb
index a250eb75bd4..ee36684197b 100644
--- a/lib/gitlab/gitaly_client/blob_service.rb
+++ b/lib/gitlab/gitaly_client/blob_service.rb
@@ -32,6 +32,26 @@ module Gitlab
binary: Gitlab::Git::Blob.binary?(data)
)
end
+
+ def batch_lfs_pointers(blob_ids)
+ request = Gitaly::GetLFSPointersRequest.new(
+ repository: @gitaly_repo,
+ blob_ids: blob_ids
+ )
+
+ response = GitalyClient.call(@gitaly_repo.storage_name, :blob_service, :get_lfs_pointers, request)
+
+ response.flat_map do |message|
+ message.lfs_pointers.map do |lfs_pointer|
+ Gitlab::Git::Blob.new(
+ id: lfs_pointer.oid,
+ size: lfs_pointer.size,
+ data: lfs_pointer.data,
+ binary: Gitlab::Git::Blob.binary?(lfs_pointer.data)
+ )
+ end
+ end
+ end
end
end
end
diff --git a/lib/gitlab/gitaly_client/commit_service.rb b/lib/gitlab/gitaly_client/commit_service.rb
index 2231371cfa7..33a8d3e5612 100644
--- a/lib/gitlab/gitaly_client/commit_service.rb
+++ b/lib/gitlab/gitaly_client/commit_service.rb
@@ -125,11 +125,11 @@ module Gitlab
def commit_count(ref, options = {})
request = Gitaly::CountCommitsRequest.new(
repository: @gitaly_repo,
- revision: ref
+ revision: encode_binary(ref)
)
request.after = Google::Protobuf::Timestamp.new(seconds: options[:after].to_i) if options[:after].present?
request.before = Google::Protobuf::Timestamp.new(seconds: options[:before].to_i) if options[:before].present?
- request.path = options[:path] if options[:path].present?
+ request.path = encode_binary(options[:path]) if options[:path].present?
request.max_count = options[:max_count] if options[:max_count].present?
GitalyClient.call(@repository.storage, :commit_service, :count_commits, request, timeout: GitalyClient.medium_timeout).count
diff --git a/lib/gitlab/gitaly_client/conflicts_service.rb b/lib/gitlab/gitaly_client/conflicts_service.rb
index 2565d537aff..e14734495a8 100644
--- a/lib/gitlab/gitaly_client/conflicts_service.rb
+++ b/lib/gitlab/gitaly_client/conflicts_service.rb
@@ -25,6 +25,11 @@ module Gitlab
def conflicts?
list_conflict_files.any?
+ rescue GRPC::FailedPrecondition
+ # The server raises this exception when it encounters ConflictSideMissing, which
+ # means a conflict exists but its `theirs` or `ours` data is nil due to a non-existent
+ # file in one of the trees.
+ true
end
def resolve_conflicts(target_repository, resolution, source_branch, target_branch)
diff --git a/lib/gitlab/gitaly_client/health_check_service.rb b/lib/gitlab/gitaly_client/health_check_service.rb
new file mode 100644
index 00000000000..6c1213f5e20
--- /dev/null
+++ b/lib/gitlab/gitaly_client/health_check_service.rb
@@ -0,0 +1,19 @@
+module Gitlab
+ module GitalyClient
+ class HealthCheckService
+ def initialize(storage)
+ @storage = storage
+ end
+
+ # Sends a gRPC health ping to the Gitaly server for the storage shard.
+ def check
+ request = Grpc::Health::V1::HealthCheckRequest.new
+ response = GitalyClient.call(@storage, :health_check, :check, request, timeout: GitalyClient.fast_timeout)
+
+ { success: response&.status == :SERVING }
+ rescue GRPC::BadStatus => e
+ { success: false, message: e.to_s }
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/gitaly_client/repository_service.rb b/lib/gitlab/gitaly_client/repository_service.rb
index 12016aee2a6..654a3c314f1 100644
--- a/lib/gitlab/gitaly_client/repository_service.rb
+++ b/lib/gitlab/gitaly_client/repository_service.rb
@@ -161,6 +161,23 @@ module Gitlab
return response.error.b, 1
end
end
+
+ def create_bundle(save_path)
+ request = Gitaly::CreateBundleRequest.new(repository: @gitaly_repo)
+ response = GitalyClient.call(
+ @storage,
+ :repository_service,
+ :create_bundle,
+ request,
+ timeout: GitalyClient.default_timeout
+ )
+
+ File.open(save_path, 'wb') do |f|
+ response.each do |message|
+ f.write(message.data)
+ end
+ end
+ end
end
end
end
diff --git a/lib/gitlab/health_checks/gitaly_check.rb b/lib/gitlab/health_checks/gitaly_check.rb
new file mode 100644
index 00000000000..11416c002e3
--- /dev/null
+++ b/lib/gitlab/health_checks/gitaly_check.rb
@@ -0,0 +1,53 @@
+module Gitlab
+ module HealthChecks
+ class GitalyCheck
+ extend BaseAbstractCheck
+
+ METRIC_PREFIX = 'gitaly_health_check'.freeze
+
+ class << self
+ def readiness
+ repository_storages.map do |storage_name|
+ check(storage_name)
+ end
+ end
+
+ def metrics
+ repository_storages.flat_map do |storage_name|
+ result, elapsed = with_timing { check(storage_name) }
+ labels = { shard: storage_name }
+
+ [
+ metric("#{metric_prefix}_success", successful?(result) ? 1 : 0, **labels),
+ metric("#{metric_prefix}_latency_seconds", elapsed, **labels)
+ ].flatten
+ end
+ end
+
+ def check(storage_name)
+ serv = Gitlab::GitalyClient::HealthCheckService.new(storage_name)
+ result = serv.check
+ HealthChecks::Result.new(result[:success], result[:message], shard: storage_name)
+ end
+
+ private
+
+ def metric_prefix
+ METRIC_PREFIX
+ end
+
+ def successful?(result)
+ result[:success]
+ end
+
+ def repository_storages
+ storages.keys
+ end
+
+ def storages
+ Gitlab.config.repositories.storages
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb
index dd5d35feab9..25399f307f2 100644
--- a/lib/gitlab/import_export/command_line_util.rb
+++ b/lib/gitlab/import_export/command_line_util.rb
@@ -11,10 +11,6 @@ module Gitlab
untar_with_options(archive: archive, dir: dir, options: 'zxf')
end
- def git_bundle(repo_path:, bundle_path:)
- execute(%W(#{git_bin_path} --git-dir=#{repo_path} bundle create #{bundle_path} --all))
- end
-
def git_clone_bundle(repo_path:, bundle_path:)
execute(%W(#{git_bin_path} clone --bare -- #{bundle_path} #{repo_path}))
Gitlab::Git::Repository.create_hooks(repo_path, File.expand_path(Gitlab.config.gitlab_shell.hooks_path))
diff --git a/lib/gitlab/import_export/repo_saver.rb b/lib/gitlab/import_export/repo_saver.rb
index a7028a32570..695462c7dd2 100644
--- a/lib/gitlab/import_export/repo_saver.rb
+++ b/lib/gitlab/import_export/repo_saver.rb
@@ -21,7 +21,7 @@ module Gitlab
def bundle_to_disk
mkdir_p(@shared.export_path)
- git_bundle(repo_path: path_to_repo, bundle_path: @full_path)
+ @project.repository.bundle_to_disk(@full_path)
rescue => e
@shared.error(e)
false
diff --git a/lib/gitlab/import_export/wiki_repo_saver.rb b/lib/gitlab/import_export/wiki_repo_saver.rb
index 1e6722a7bba..5fa2e101e29 100644
--- a/lib/gitlab/import_export/wiki_repo_saver.rb
+++ b/lib/gitlab/import_export/wiki_repo_saver.rb
@@ -10,7 +10,7 @@ module Gitlab
def bundle_to_disk(full_path)
mkdir_p(@shared.export_path)
- git_bundle(repo_path: path_to_repo, bundle_path: full_path)
+ @wiki.repository.bundle_to_disk(full_path)
rescue => e
@shared.error(e)
false
diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb
index 7771b15069b..4823f703ba4 100644
--- a/lib/gitlab/project_search_results.rb
+++ b/lib/gitlab/project_search_results.rb
@@ -20,7 +20,7 @@ module Gitlab
when 'commits'
Kaminari.paginate_array(commits).page(page).per(per_page)
else
- super
+ super(scope, page, false)
end
end
diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb
index 70b639501fd..7362514167f 100644
--- a/lib/gitlab/search_results.rb
+++ b/lib/gitlab/search_results.rb
@@ -40,19 +40,21 @@ module Gitlab
@default_project_filter = default_project_filter
end
- def objects(scope, page = nil)
- case scope
- when 'projects'
- projects.page(page).per(per_page)
- when 'issues'
- issues.page(page).per(per_page)
- when 'merge_requests'
- merge_requests.page(page).per(per_page)
- when 'milestones'
- milestones.page(page).per(per_page)
- else
- Kaminari.paginate_array([]).page(page).per(per_page)
- end
+ def objects(scope, page = nil, without_count = true)
+ collection = case scope
+ when 'projects'
+ projects.page(page).per(per_page)
+ when 'issues'
+ issues.page(page).per(per_page)
+ when 'merge_requests'
+ merge_requests.page(page).per(per_page)
+ when 'milestones'
+ milestones.page(page).per(per_page)
+ else
+ Kaminari.paginate_array([]).page(page).per(per_page)
+ end
+
+ without_count ? collection.without_count : collection
end
def projects_count
@@ -71,18 +73,46 @@ module Gitlab
@milestones_count ||= milestones.count
end
+ def limited_projects_count
+ @limited_projects_count ||= projects.limit(count_limit).count
+ end
+
+ def limited_issues_count
+ return @limited_issues_count if @limited_issues_count
+
+ # By default getting limited count (e.g. 1000+) is fast on issuable
+ # collections except for issues, where filtering both not confidential
+ # and confidential issues user has access to, is too complex.
+ # It's faster to try to fetch all public issues first, then only
+ # if necessary try to fetch all issues.
+ sum = issues(public_only: true).limit(count_limit).count
+ @limited_issues_count = sum < count_limit ? issues.limit(count_limit).count : sum
+ end
+
+ def limited_merge_requests_count
+ @limited_merge_requests_count ||= merge_requests.limit(count_limit).count
+ end
+
+ def limited_milestones_count
+ @limited_milestones_count ||= milestones.limit(count_limit).count
+ end
+
def single_commit_result?
false
end
+ def count_limit
+ 1001
+ end
+
private
def projects
limit_projects.search(query)
end
- def issues
- issues = IssuesFinder.new(current_user).execute
+ def issues(finder_params = {})
+ issues = IssuesFinder.new(current_user, finder_params).execute
unless default_project_filter
issues = issues.where(project_id: project_ids_relation)
end
@@ -94,13 +124,13 @@ module Gitlab
issues.full_search(query)
end
- issues.order('updated_at DESC')
+ issues.reorder('updated_at DESC')
end
def milestones
milestones = Milestone.where(project_id: project_ids_relation)
milestones = milestones.search(query)
- milestones.order('updated_at DESC')
+ milestones.reorder('updated_at DESC')
end
def merge_requests
@@ -116,7 +146,7 @@ module Gitlab
merge_requests.full_search(query)
end
- merge_requests.order('updated_at DESC')
+ merge_requests.reorder('updated_at DESC')
end
def default_scope
diff --git a/lib/gitlab/snippet_search_results.rb b/lib/gitlab/snippet_search_results.rb
index b85f70e450e..4f86b3e8f73 100644
--- a/lib/gitlab/snippet_search_results.rb
+++ b/lib/gitlab/snippet_search_results.rb
@@ -16,7 +16,7 @@ module Gitlab
when 'snippet_blobs'
snippet_blobs.page(page).per(per_page)
else
- super
+ super(scope, nil, false)
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 3ebc7859232..74d76caf47d 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -186,13 +186,13 @@ msgstr ""
msgid "Author"
msgstr ""
-msgid "Auto Review Apps and Auto Deploy need a domain name and the %{kubernetes} to work correctly."
+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 Review Apps and Auto Deploy need the %{kubernetes} to work correctly."
+msgid "Auto Review Apps and Auto Deploy need a %{kubernetes} to work correctly."
msgstr ""
msgid "AutoDevOps|Auto DevOps (Beta)"
diff --git a/package.json b/package.json
index 5c9849515ee..c68cf648932 100644
--- a/package.json
+++ b/package.json
@@ -61,7 +61,7 @@
"pikaday": "^1.6.1",
"prismjs": "^1.6.0",
"raphael": "^2.2.7",
- "raven-js": "^3.14.0",
+ "raven-js": "^3.22.1",
"raw-loader": "^0.5.1",
"react-dev-utils": "^0.5.2",
"sanitize-html": "^1.16.1",
@@ -100,7 +100,7 @@
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-vue": "^4.0.1",
"istanbul": "^0.4.5",
- "jasmine-core": "^2.6.3",
+ "jasmine-core": "^2.9.0",
"jasmine-jquery": "^2.1.1",
"karma": "^2.0.0",
"karma-chrome-launcher": "^2.2.0",
diff --git a/qa/qa.rb b/qa/qa.rb
index fa2cabe0e46..ac51bb445f2 100644
--- a/qa/qa.rb
+++ b/qa/qa.rb
@@ -28,6 +28,7 @@ module QA
autoload :Group, 'qa/factory/resource/group'
autoload :Project, 'qa/factory/resource/project'
autoload :DeployKey, 'qa/factory/resource/deploy_key'
+ autoload :Runner, 'qa/factory/resource/runner'
autoload :PersonalAccessToken, 'qa/factory/resource/personal_access_token'
end
@@ -49,7 +50,7 @@ module QA
#
autoload :Bootable, 'qa/scenario/bootable'
autoload :Actable, 'qa/scenario/actable'
- autoload :Entrypoint, 'qa/scenario/entrypoint'
+ autoload :Taggable, 'qa/scenario/taggable'
autoload :Template, 'qa/scenario/template'
##
@@ -108,7 +109,14 @@ module QA
module Settings
autoload :Common, 'qa/page/project/settings/common'
autoload :Repository, 'qa/page/project/settings/repository'
+ autoload :CICD, 'qa/page/project/settings/ci_cd'
autoload :DeployKeys, 'qa/page/project/settings/deploy_keys'
+ autoload :Runners, 'qa/page/project/settings/runners'
+ end
+
+ module Pipeline
+ autoload :Index, 'qa/page/project/pipeline/index'
+ autoload :Show, 'qa/page/project/pipeline/show'
end
end
@@ -134,10 +142,13 @@ module QA
end
##
- # Classes describing shell interaction with GitLab
+ # Classes describing services being part of GitLab and how we can interact
+ # with these services, like through the shell.
#
- module Shell
- autoload :Omnibus, 'qa/shell/omnibus'
+ module Service
+ autoload :Shellout, 'qa/service/shellout'
+ autoload :Omnibus, 'qa/service/omnibus'
+ autoload :Runner, 'qa/service/runner'
end
##
diff --git a/qa/qa/factory/resource/deploy_key.rb b/qa/qa/factory/resource/deploy_key.rb
index 7c58e70bcc4..25d2af6e321 100644
--- a/qa/qa/factory/resource/deploy_key.rb
+++ b/qa/qa/factory/resource/deploy_key.rb
@@ -4,6 +4,12 @@ module QA
class DeployKey < Factory::Base
attr_accessor :title, :key
+ product :title do
+ Page::Project::Settings::Repository.act do
+ expand_deploy_keys(&:key_title)
+ end
+ end
+
dependency Factory::Resource::Project, as: :project do |project|
project.name = 'project-to-deploy'
project.description = 'project for adding deploy key test'
@@ -13,7 +19,7 @@ module QA
project.visit!
Page::Menu::Side.act do
- click_repository_setting
+ click_repository_settings
end
Page::Project::Settings::Repository.perform do |setting|
diff --git a/qa/qa/factory/resource/runner.rb b/qa/qa/factory/resource/runner.rb
new file mode 100644
index 00000000000..5f37f8ac2e9
--- /dev/null
+++ b/qa/qa/factory/resource/runner.rb
@@ -0,0 +1,42 @@
+require 'securerandom'
+
+module QA
+ module Factory
+ module Resource
+ class Runner < Factory::Base
+ attr_writer :name, :tags
+
+ dependency Factory::Resource::Project, as: :project do |project|
+ project.name = 'project-with-ci-cd'
+ project.description = 'Project with CI/CD Pipelines'
+ end
+
+ def name
+ @name || "qa-runner-#{SecureRandom.hex(4)}"
+ end
+
+ def tags
+ @tags || %w[qa e2e]
+ end
+
+ def fabricate!
+ project.visit!
+
+ Page::Menu::Side.act { click_ci_cd_settings }
+
+ Service::Runner.new(name).tap do |runner|
+ Page::Project::Settings::CICD.perform do |settings|
+ settings.expand_runners_settings do |runners|
+ runner.pull
+ runner.token = runners.registration_token
+ runner.address = runners.coordinator_address
+ runner.tags = tags
+ runner.register!
+ end
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/menu/side.rb b/qa/qa/page/menu/side.rb
index 1df4e0c2429..7f0f924c5e8 100644
--- a/qa/qa/page/menu/side.rb
+++ b/qa/qa/page/menu/side.rb
@@ -5,18 +5,35 @@ module QA
view 'app/views/layouts/nav/sidebar/_project.html.haml' do
element :settings_item
element :repository_link, "title: 'Repository'"
+ element :pipelines_settings_link, "title: 'CI / CD'"
element :top_level_items, '.sidebar-top-level-items'
end
- def click_repository_setting
- hover_setting do
- click_link('Repository')
+ def click_repository_settings
+ hover_settings do
+ within_submenu do
+ click_link('Repository')
+ end
+ end
+ end
+
+ def click_ci_cd_settings
+ hover_settings do
+ within_submenu do
+ click_link('CI / CD')
+ end
+ end
+ end
+
+ def click_ci_cd_pipelines
+ within_sidebar do
+ click_link('CI / CD')
end
end
private
- def hover_setting
+ def hover_settings
within_sidebar do
find('.qa-settings-item').hover
@@ -29,6 +46,12 @@ module QA
yield
end
end
+
+ def within_submenu
+ page.within('.fly-out-list') do
+ yield
+ end
+ end
end
end
end
diff --git a/qa/qa/page/project/pipeline/index.rb b/qa/qa/page/project/pipeline/index.rb
new file mode 100644
index 00000000000..32c108393b9
--- /dev/null
+++ b/qa/qa/page/project/pipeline/index.rb
@@ -0,0 +1,13 @@
+module QA::Page
+ module Project::Pipeline
+ class Index < QA::Page::Base
+ view 'app/assets/javascripts/pipelines/components/pipeline_url.vue' do
+ element :pipeline_link, 'class="js-pipeline-url-link"'
+ end
+
+ def go_to_latest_pipeline
+ first('.js-pipeline-url-link').click
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/pipeline/show.rb b/qa/qa/page/project/pipeline/show.rb
new file mode 100644
index 00000000000..0835173f1cd
--- /dev/null
+++ b/qa/qa/page/project/pipeline/show.rb
@@ -0,0 +1,35 @@
+module QA::Page
+ module Project::Pipeline
+ class Show < QA::Page::Base
+ view 'app/assets/javascripts/vue_shared/components/header_ci_component.vue' do
+ element :pipeline_header, /header class.*ci-header-container.*/
+ end
+
+ view 'app/assets/javascripts/pipelines/components/graph/graph_component.vue' do
+ element :pipeline_graph, /class.*pipeline-graph.*/
+ end
+
+ view 'app/assets/javascripts/pipelines/components/graph/job_component.vue' do
+ element :job_component, /class.*ci-job-component.*/
+ end
+
+ view 'app/assets/javascripts/vue_shared/components/ci_icon.vue' do
+ element :status_icon, 'ci-status-icon-${status}'
+ end
+
+ def running?
+ within('.ci-header-container') do
+ return page.has_content?('running')
+ end
+ end
+
+ def has_build?(name, status: :success)
+ within('.pipeline-graph') do
+ within('.ci-job-component', text: name) do
+ return has_selector?(".ci-status-icon-#{status}")
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/settings/ci_cd.rb b/qa/qa/page/project/settings/ci_cd.rb
new file mode 100644
index 00000000000..5270dde7411
--- /dev/null
+++ b/qa/qa/page/project/settings/ci_cd.rb
@@ -0,0 +1,21 @@
+module QA
+ module Page
+ module Project
+ module Settings
+ class CICD < Page::Base
+ include Common
+
+ view 'app/views/projects/settings/ci_cd/show.html.haml' do
+ element :runners_settings, 'Runners settings'
+ end
+
+ def expand_runners_settings(&block)
+ expand_section('Runners settings') do
+ Settings::Runners.perform(&block)
+ end
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/settings/common.rb b/qa/qa/page/project/settings/common.rb
index b4ef07e1540..1357bf031d5 100644
--- a/qa/qa/page/project/settings/common.rb
+++ b/qa/qa/page/project/settings/common.rb
@@ -10,6 +10,16 @@ module QA
yield
end
end
+
+ def expand_section(name)
+ page.within('#content-body') do
+ page.within('section', text: name) do
+ click_button 'Expand'
+
+ yield
+ end
+ end
+ end
end
end
end
diff --git a/qa/qa/page/project/settings/deploy_keys.rb b/qa/qa/page/project/settings/deploy_keys.rb
index bf42767c707..f9e40bf4252 100644
--- a/qa/qa/page/project/settings/deploy_keys.rb
+++ b/qa/qa/page/project/settings/deploy_keys.rb
@@ -10,6 +10,7 @@ module QA
view 'app/assets/javascripts/deploy_keys/components/app.vue' do
element :deploy_keys_section, /class=".*deploy\-keys.*"/
+ element :project_deploy_keys, 'class="qa-project-deploy-keys"'
end
view 'app/assets/javascripts/deploy_keys/components/key.vue' do
@@ -29,9 +30,9 @@ module QA
click_on 'Add key'
end
- def has_key_title?(title)
- page.within('.deploy-keys') do
- page.find('.title', text: title)
+ def key_title
+ page.within('.qa-project-deploy-keys') do
+ page.find('.title').text
end
end
end
diff --git a/qa/qa/page/project/settings/runners.rb b/qa/qa/page/project/settings/runners.rb
new file mode 100644
index 00000000000..b41668c94cd
--- /dev/null
+++ b/qa/qa/page/project/settings/runners.rb
@@ -0,0 +1,35 @@
+module QA
+ module Page
+ module Project
+ module Settings
+ class Runners < Page::Base
+ view 'app/views/ci/runner/_how_to_setup_runner.html.haml' do
+ element :registration_token, '%code#registration_token'
+ element :coordinator_address, '%code#coordinator_address'
+ end
+
+ ##
+ # TODO, phase-out CSS classes added in Ruby helpers.
+ #
+ view 'app/helpers/runners_helper.rb' do
+ # rubocop:disable Lint/InterpolationCheck
+ element :runner_status, 'runner-status-#{status}'
+ # rubocop:enable Lint/InterpolationCheck
+ end
+
+ def registration_token
+ find('code#registration_token').text
+ end
+
+ def coordinator_address
+ find('code#coordinator_address').text
+ end
+
+ def has_online_runner?
+ page.has_css?('.runner-status-online')
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/page/project/show.rb b/qa/qa/page/project/show.rb
index c8af5ba6280..5e66e40a0b5 100644
--- a/qa/qa/page/project/show.rb
+++ b/qa/qa/page/project/show.rb
@@ -33,6 +33,7 @@ module QA
def wait_for_push
sleep 5
+ refresh
end
end
end
diff --git a/qa/qa/scenario/entrypoint.rb b/qa/qa/scenario/entrypoint.rb
deleted file mode 100644
index ae099fd911e..00000000000
--- a/qa/qa/scenario/entrypoint.rb
+++ /dev/null
@@ -1,34 +0,0 @@
-module QA
- module Scenario
- ##
- # Base class for running the suite against any GitLab instance,
- # including staging and on-premises installation.
- #
- class Entrypoint < Template
- include Bootable
-
- def perform(address, *files)
- Runtime::Scenario.define(:gitlab_address, address)
-
- ##
- # Perform before hooks, which are different for CE and EE
- #
- Runtime::Release.perform_before_hooks
-
- Specs::Runner.perform do |specs|
- specs.tty = true
- specs.tags = self.class.get_tags
- specs.files = files.any? ? files : 'qa/specs/features'
- end
- end
-
- def self.tags(*tags)
- @tags = tags
- end
-
- def self.get_tags
- @tags
- end
- end
- end
-end
diff --git a/qa/qa/scenario/taggable.rb b/qa/qa/scenario/taggable.rb
new file mode 100644
index 00000000000..b1f24d742e0
--- /dev/null
+++ b/qa/qa/scenario/taggable.rb
@@ -0,0 +1,17 @@
+module QA
+ module Scenario
+ module Taggable
+ # rubocop:disable Gitlab/ModuleWithInstanceVariables
+
+ def tags(*tags)
+ @tags = tags
+ end
+
+ def focus
+ @tags.to_a
+ end
+
+ # rubocop:enable Gitlab/ModuleWithInstanceVariables
+ end
+ end
+end
diff --git a/qa/qa/scenario/test/instance.rb b/qa/qa/scenario/test/instance.rb
index e2a1f6bf2bd..993bbd723a3 100644
--- a/qa/qa/scenario/test/instance.rb
+++ b/qa/qa/scenario/test/instance.rb
@@ -2,11 +2,29 @@ module QA
module Scenario
module Test
##
- # Run test suite against any GitLab instance,
+ # Base class for running the suite against any GitLab instance,
# including staging and on-premises installation.
#
- class Instance < Entrypoint
+ class Instance < Template
+ include Bootable
+ extend Taggable
+
tags :core
+
+ def perform(address, *files)
+ Runtime::Scenario.define(:gitlab_address, address)
+
+ ##
+ # Perform before hooks, which are different for CE and EE
+ #
+ Runtime::Release.perform_before_hooks
+
+ Specs::Runner.perform do |specs|
+ specs.tty = true
+ specs.tags = self.class.focus
+ specs.files = files.any? ? files : 'qa/specs/features'
+ end
+ end
end
end
end
diff --git a/qa/qa/scenario/test/integration/mattermost.rb b/qa/qa/scenario/test/integration/mattermost.rb
index 7d0702afdb1..d939f52ab16 100644
--- a/qa/qa/scenario/test/integration/mattermost.rb
+++ b/qa/qa/scenario/test/integration/mattermost.rb
@@ -6,7 +6,7 @@ module QA
# Run test suite against any GitLab instance where mattermost is enabled,
# including staging and on-premises installation.
#
- class Mattermost < Scenario::Entrypoint
+ class Mattermost < Test::Instance
tags :core, :mattermost
def perform(address, mattermost, *files)
diff --git a/qa/qa/service/omnibus.rb b/qa/qa/service/omnibus.rb
new file mode 100644
index 00000000000..b5c06874e5c
--- /dev/null
+++ b/qa/qa/service/omnibus.rb
@@ -0,0 +1,20 @@
+module QA
+ module Service
+ class Omnibus
+ include Scenario::Actable
+ include Service::Shellout
+
+ def initialize(container)
+ @name = container
+ end
+
+ def gitlab_ctl(command, input: nil)
+ if input.nil?
+ shell "docker exec #{@name} gitlab-ctl #{command}"
+ else
+ shell "docker exec #{@name} bash -c '#{input} | gitlab-ctl #{command}'"
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/service/runner.rb b/qa/qa/service/runner.rb
new file mode 100644
index 00000000000..d0ee33c69f2
--- /dev/null
+++ b/qa/qa/service/runner.rb
@@ -0,0 +1,41 @@
+require 'securerandom'
+
+module QA
+ module Service
+ class Runner
+ include Scenario::Actable
+ include Service::Shellout
+
+ attr_accessor :token, :address, :tags, :image
+
+ def initialize(name)
+ @image = 'gitlab/gitlab-runner:alpine'
+ @name = name || "qa-runner-#{SecureRandom.hex(4)}"
+ @network = Runtime::Scenario.attributes[:network] || 'test'
+ @tags = %w[qa test]
+ end
+
+ def pull
+ shell "docker pull #{@image}"
+ end
+
+ def register!
+ shell <<~CMD.tr("\n", ' ')
+ docker run -d --rm --entrypoint=/bin/sh
+ --network #{@network} --name #{@name}
+ -e CI_SERVER_URL=#{@address}
+ -e REGISTER_NON_INTERACTIVE=true
+ -e REGISTRATION_TOKEN=#{@token}
+ -e RUNNER_EXECUTOR=shell
+ -e RUNNER_TAG_LIST=#{@tags.join(',')}
+ -e RUNNER_NAME=#{@name}
+ #{@image} -c 'gitlab-runner register && gitlab-runner run'
+ CMD
+ end
+
+ def remove!
+ shell "docker rm -f #{@name}"
+ end
+ end
+ end
+end
diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb
new file mode 100644
index 00000000000..898febde63c
--- /dev/null
+++ b/qa/qa/service/shellout.rb
@@ -0,0 +1,23 @@
+require 'open3'
+
+module QA
+ module Service
+ module Shellout
+ ##
+ # TODO, make it possible to use generic QA framework classes
+ # as a library - gitlab-org/gitlab-qa#94
+ #
+ def shell(command)
+ puts "Executing `#{command}`"
+
+ Open3.popen2e(command) do |_in, out, wait|
+ out.each { |line| puts line }
+
+ if wait.value.exited? && wait.value.exitstatus.nonzero?
+ raise "Command `#{command}` failed!"
+ end
+ end
+ end
+ end
+ end
+end
diff --git a/qa/qa/shell/omnibus.rb b/qa/qa/shell/omnibus.rb
deleted file mode 100644
index 6b3628d3109..00000000000
--- a/qa/qa/shell/omnibus.rb
+++ /dev/null
@@ -1,39 +0,0 @@
-require 'open3'
-
-module QA
- module Shell
- class Omnibus
- include Scenario::Actable
-
- def initialize(container)
- @name = container
- end
-
- def gitlab_ctl(command, input: nil)
- if input.nil?
- shell "docker exec #{@name} gitlab-ctl #{command}"
- else
- shell "docker exec #{@name} bash -c '#{input} | gitlab-ctl #{command}'"
- end
- end
-
- private
-
- ##
- # TODO, make it possible to use generic QA framework classes
- # as a library - gitlab-org/gitlab-qa#94
- #
- def shell(command)
- puts "Executing `#{command}`"
-
- Open3.popen2e(command) do |_in, out, wait|
- out.each { |line| puts line }
-
- if wait.value.exited? && wait.value.exitstatus.nonzero?
- raise "Docker command `#{command}` failed!"
- end
- end
- end
- end
- end
-end
diff --git a/qa/qa/specs/features/project/add_deploy_key_spec.rb b/qa/qa/specs/features/project/add_deploy_key_spec.rb
index 43a85213501..7a123e539e1 100644
--- a/qa/qa/specs/features/project/add_deploy_key_spec.rb
+++ b/qa/qa/specs/features/project/add_deploy_key_spec.rb
@@ -7,16 +7,12 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.act { sign_in_using_credentials }
- Factory::Resource::DeployKey.fabricate! do |deploy_key|
- deploy_key.title = deploy_key_title
- deploy_key.key = deploy_key_value
+ deploy_key = Factory::Resource::DeployKey.fabricate! do |resource|
+ resource.title = deploy_key_title
+ resource.key = deploy_key_value
end
- Page::Project::Settings::Repository.perform do |setting|
- setting.expand_deploy_keys do |page|
- expect(page).to have_key_title(deploy_key_title)
- end
- end
+ expect(deploy_key.title).to eq(deploy_key_title)
end
end
end
diff --git a/qa/qa/specs/features/project/pipelines_spec.rb b/qa/qa/specs/features/project/pipelines_spec.rb
new file mode 100644
index 00000000000..1bb7730e06c
--- /dev/null
+++ b/qa/qa/specs/features/project/pipelines_spec.rb
@@ -0,0 +1,102 @@
+module QA
+ feature 'CI/CD Pipelines', :core, :docker do
+ let(:executor) { "qa-runner-#{Time.now.to_i}" }
+
+ after do
+ Service::Runner.new(executor).remove!
+ end
+
+ scenario 'user registers a new specific runner' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.act { sign_in_using_credentials }
+
+ Factory::Resource::Runner.fabricate! do |runner|
+ runner.name = executor
+ end
+
+ Page::Project::Settings::CICD.perform do |settings|
+ sleep 5 # Runner should register within 5 seconds
+ settings.refresh
+
+ settings.expand_runners_settings do |page|
+ expect(page).to have_content(executor)
+ expect(page).to have_online_runner
+ end
+ end
+ end
+
+ scenario 'users creates a new pipeline' do
+ Runtime::Browser.visit(:gitlab, Page::Main::Login)
+ Page::Main::Login.act { sign_in_using_credentials }
+
+ project = Factory::Resource::Project.fabricate! do |project|
+ project.name = 'project-with-pipelines'
+ project.description = 'Project with CI/CD Pipelines.'
+ end
+
+ Factory::Resource::Runner.fabricate! do |runner|
+ runner.project = project
+ runner.name = executor
+ runner.tags = %w[qa test]
+ end
+
+ Factory::Repository::Push.fabricate! do |push|
+ push.project = project
+ push.file_name = '.gitlab-ci.yml'
+ push.commit_message = 'Add .gitlab-ci.yml'
+ push.file_content = <<~EOF
+ test-success:
+ tags:
+ - qa
+ - test
+ script: echo 'OK'
+
+ test-failure:
+ tags:
+ - qa
+ - test
+ script:
+ - echo 'FAILURE'
+ - exit 1
+
+ test-tags:
+ tags:
+ - qa
+ - docker
+ script: echo 'NOOP'
+
+ test-artifacts:
+ tags:
+ - qa
+ - test
+ script: echo "CONTENTS" > my-artifacts/artifact.txt
+ artifacts:
+ paths:
+ - my-artifacts/
+ EOF
+ end
+
+ Page::Project::Show.act { wait_for_push }
+
+ expect(page).to have_content('Add .gitlab-ci.yml')
+
+ Page::Menu::Side.act { click_ci_cd_pipelines }
+
+ expect(page).to have_content('All 1')
+ expect(page).to have_content('Add .gitlab-ci.yml')
+
+ puts 'Waiting for the runner to process the pipeline'
+ sleep 15 # Runner should process all jobs within 15 seconds.
+
+ Page::Project::Pipeline::Index.act { go_to_latest_pipeline }
+
+ Page::Project::Pipeline::Show.perform do |pipeline|
+ expect(pipeline).to be_running
+ expect(pipeline).to have_build('test-success', status: :success)
+ expect(pipeline).to have_build('test-failure', status: :failed)
+ expect(pipeline).to have_build('test-tags', status: :pending)
+ expect(pipeline).to have_build('test-artifacts', status: :failed)
+ end
+ end
+ end
+end
diff --git a/qa/qa/specs/features/repository/push_spec.rb b/qa/qa/specs/features/repository/push_spec.rb
index 4f6ffe14c9f..51d9c2c7fd2 100644
--- a/qa/qa/specs/features/repository/push_spec.rb
+++ b/qa/qa/specs/features/repository/push_spec.rb
@@ -11,10 +11,7 @@ module QA
push.commit_message = 'Add README.md'
end
- Page::Project::Show.act do
- wait_for_push
- refresh
- end
+ Page::Project::Show.act { wait_for_push }
expect(page).to have_content('README.md')
expect(page).to have_content('This is a test project')
diff --git a/qa/spec/factory/base_spec.rb b/qa/spec/factory/base_spec.rb
index 90dd58e20fd..c5663049be8 100644
--- a/qa/spec/factory/base_spec.rb
+++ b/qa/spec/factory/base_spec.rb
@@ -19,7 +19,6 @@ describe QA::Factory::Base do
it 'returns fabrication product' do
allow(subject).to receive(:new).and_return(factory)
- allow(factory).to receive(:fabricate!).and_return('something')
result = subject.fabricate!('something')
diff --git a/qa/spec/scenario/entrypoint_spec.rb b/qa/spec/scenario/test/instance_spec.rb
index aec79dcea04..1824db54c9b 100644
--- a/qa/spec/scenario/entrypoint_spec.rb
+++ b/qa/spec/scenario/test/instance_spec.rb
@@ -1,6 +1,6 @@
-describe QA::Scenario::Entrypoint do
+describe QA::Scenario::Test::Instance do
subject do
- Class.new(QA::Scenario::Entrypoint) do
+ Class.new(described_class) do
tags :rspec
end
end
diff --git a/scripts/gitaly-test-build b/scripts/gitaly-test-build
index 95d9fe0f176..b42ae2a2595 100755
--- a/scripts/gitaly-test-build
+++ b/scripts/gitaly-test-build
@@ -9,11 +9,21 @@ require 'fileutils'
# called 'bundle install' using a different Gemfile, as happens with
# gitlab-ce and gitaly.
-dir = 'tmp/tests/gitaly'
+tmp_tests_gitaly_dir = File.expand_path('../tmp/tests/gitaly', __dir__)
-abort 'gitaly build failed' unless system('make', chdir: dir)
+# Use the top-level bundle vendor folder so that we don't reinstall gems twice
+bundle_vendor_path = File.expand_path('../vendor', __dir__)
+
+env = {
+ # This ensure the `clean` config set in `scripts/prepare_build.sh` isn't taken into account
+ 'BUNDLE_IGNORE_CONFIG' => 'true',
+ 'BUNDLE_GEMFILE' => File.join(tmp_tests_gitaly_dir, 'ruby', 'Gemfile'),
+ 'BUNDLE_FLAGS' => "--jobs=4 --path=#{bundle_vendor_path} --retry=3"
+}
+
+abort 'gitaly build failed' unless system(env, 'make', chdir: tmp_tests_gitaly_dir)
# Make the 'gitaly' executable look newer than 'GITALY_SERVER_VERSION'.
# Without this a gitaly executable created in the setup-test-env job
# will look stale compared to GITALY_SERVER_VERSION.
-FileUtils.touch(File.join(dir, 'gitaly'), mtime: Time.now + (1 << 24))
+FileUtils.touch(File.join(tmp_tests_gitaly_dir, 'gitaly'), mtime: Time.now + (1 << 24))
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index ea406aadf39..206d62dbc78 100644
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -3,7 +3,7 @@
export SETUP_DB=${SETUP_DB:-true}
export CREATE_DB_USER=${CREATE_DB_USER:-$SETUP_DB}
export USE_BUNDLE_INSTALL=${USE_BUNDLE_INSTALL:-true}
-export BUNDLE_INSTALL_FLAGS="--without production --jobs $(nproc) --path vendor --retry 3 --quiet"
+export BUNDLE_INSTALL_FLAGS="--without=production --jobs=$(nproc) --path=vendor --retry=3 --quiet"
if [ "$USE_BUNDLE_INSTALL" != "false" ]; then
bundle install --clean $BUNDLE_INSTALL_FLAGS && bundle check
diff --git a/spec/features/global_search_spec.rb b/spec/features/global_search_spec.rb
index 4f575613848..f8c4db1403c 100644
--- a/spec/features/global_search_spec.rb
+++ b/spec/features/global_search_spec.rb
@@ -22,7 +22,7 @@ feature 'Global search' do
click_button "Go"
select_filter("Issues")
- expect(page).to have_selector('.gl-pagination .page', count: 2)
+ expect(page).to have_selector('.gl-pagination .next')
end
end
end
diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb
index 53706ef84bc..c7cfd01f588 100644
--- a/spec/features/issues/spam_issues_spec.rb
+++ b/spec/features/issues/spam_issues_spec.rb
@@ -34,6 +34,9 @@ describe 'New issue', :js do
click_button 'Submit issue'
+ # reCAPTCHA alerts when it can't contact the server, so just accept it and move on
+ page.driver.browser.switch_to.alert.accept
+
# it is impossible to test recaptcha automatically and there is no possibility to fill in recaptcha
# recaptcha verification is skipped in test environment and it always returns true
expect(page).not_to have_content('issue title')
diff --git a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
index 590210d44ef..3e83a549682 100644
--- a/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
+++ b/spec/features/merge_request/user_resolves_diff_notes_and_discussions_resolve_spec.rb
@@ -108,7 +108,7 @@ describe 'Merge request > User resolves diff notes and discussions', :js do
it 'shows resolved discussion when toggled' do
find(".timeline-content .discussion[data-discussion-id='#{note.discussion_id}'] .discussion-toggle-button").click
- expect(page.find(".timeline-content #note_#{note.noteable_id}")).to be_visible
+ expect(page.find(".timeline-content #note_#{note.id}")).to be_visible
end
end
diff --git a/spec/javascripts/blob/notebook/index_spec.js b/spec/javascripts/blob/notebook/index_spec.js
index df1b2c9960b..a143fc827d5 100644
--- a/spec/javascripts/blob/notebook/index_spec.js
+++ b/spec/javascripts/blob/notebook/index_spec.js
@@ -45,7 +45,7 @@ describe('iPython notebook renderer', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('does not show loading icon', () => {
@@ -96,7 +96,7 @@ describe('iPython notebook renderer', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('does not show loading icon', () => {
@@ -127,7 +127,7 @@ describe('iPython notebook renderer', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('does not show loading icon', () => {
diff --git a/spec/javascripts/boards/board_card_spec.js b/spec/javascripts/boards/board_card_spec.js
index 4e73fa1fe87..80a598e63bd 100644
--- a/spec/javascripts/boards/board_card_spec.js
+++ b/spec/javascripts/boards/board_card_spec.js
@@ -55,7 +55,7 @@ describe('Board card', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('returns false when detailIssue is empty', () => {
diff --git a/spec/javascripts/boards/board_list_spec.js b/spec/javascripts/boards/board_list_spec.js
index 9ffdac9be97..a5fcb10b9dd 100644
--- a/spec/javascripts/boards/board_list_spec.js
+++ b/spec/javascripts/boards/board_list_spec.js
@@ -60,7 +60,7 @@ describe('Board list component', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('renders component', () => {
diff --git a/spec/javascripts/boards/board_new_issue_spec.js b/spec/javascripts/boards/board_new_issue_spec.js
index c62c537841c..e204985f039 100644
--- a/spec/javascripts/boards/board_new_issue_spec.js
+++ b/spec/javascripts/boards/board_new_issue_spec.js
@@ -58,7 +58,7 @@ describe('Issue boards new issue form', () => {
afterEach(() => {
vm.$destroy();
- mock.reset();
+ mock.restore();
});
it('calls submit if submit button is clicked', (done) => {
diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js
index 49fb20f4c84..8411f4dd8a6 100644
--- a/spec/javascripts/boards/boards_store_spec.js
+++ b/spec/javascripts/boards/boards_store_spec.js
@@ -35,7 +35,7 @@ describe('Store', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('starts with a blank state', () => {
diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js
index e5e7b48228b..34964b20b05 100644
--- a/spec/javascripts/boards/list_spec.js
+++ b/spec/javascripts/boards/list_spec.js
@@ -30,7 +30,7 @@ describe('List model', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('gets issues when created', (done) => {
diff --git a/spec/javascripts/commit/pipelines/pipelines_spec.js b/spec/javascripts/commit/pipelines/pipelines_spec.js
index d62c2966a8b..0afe09d87bc 100644
--- a/spec/javascripts/commit/pipelines/pipelines_spec.js
+++ b/spec/javascripts/commit/pipelines/pipelines_spec.js
@@ -10,9 +10,10 @@ describe('Pipelines table in Commits and Merge requests', () => {
preloadFixtures(jsonFixtureName);
beforeEach(() => {
- PipelinesTable = Vue.extend(pipelinesTable);
const pipelines = getJSONFixture(jsonFixtureName).pipelines;
- pipeline = pipelines.find(p => p.id === 1);
+
+ PipelinesTable = Vue.extend(pipelinesTable);
+ pipeline = pipelines.find(p => p.user !== null && p.commit !== null);
});
describe('successful request', () => {
diff --git a/spec/javascripts/fly_out_nav_spec.js b/spec/javascripts/fly_out_nav_spec.js
index a3fa07d5bc2..eb9330f5e5b 100644
--- a/spec/javascripts/fly_out_nav_spec.js
+++ b/spec/javascripts/fly_out_nav_spec.js
@@ -167,30 +167,26 @@ describe('Fly out sidebar navigation', () => {
describe('mouseEnterTopItems', () => {
beforeEach(() => {
- jasmine.clock().install();
-
el.innerHTML = '<div class="sidebar-sub-level-items" style="position: absolute; top: 0; left: 100px; height: 200px;"></div>';
});
- afterEach(() => {
- jasmine.clock().uninstall();
- });
-
- it('shows sub-items after 0ms if no menu is open', () => {
+ it('shows sub-items after 0ms if no menu is open', (done) => {
mouseEnterTopItems(el);
expect(
getHideSubItemsInterval(),
).toBe(0);
- jasmine.clock().tick(0);
+ setTimeout(() => {
+ expect(
+ el.querySelector('.sidebar-sub-level-items').style.display,
+ ).toBe('block');
- expect(
- el.querySelector('.sidebar-sub-level-items').style.display,
- ).toBe('block');
+ done();
+ });
});
- it('shows sub-items after 300ms if a menu is currently open', () => {
+ it('shows sub-items after 300ms if a menu is currently open', (done) => {
documentMouseMove({
clientX: el.getBoundingClientRect().left,
clientY: el.getBoundingClientRect().top,
@@ -203,17 +199,19 @@ describe('Fly out sidebar navigation', () => {
clientY: el.getBoundingClientRect().top + 10,
});
- mouseEnterTopItems(el);
+ mouseEnterTopItems(el, 0);
expect(
getHideSubItemsInterval(),
).toBe(300);
- jasmine.clock().tick(300);
+ setTimeout(() => {
+ expect(
+ el.querySelector('.sidebar-sub-level-items').style.display,
+ ).toBe('block');
- expect(
- el.querySelector('.sidebar-sub-level-items').style.display,
- ).toBe('block');
+ done();
+ });
});
});
diff --git a/spec/javascripts/helpers/class_spec_helper_spec.js b/spec/javascripts/helpers/class_spec_helper_spec.js
index 686b8eaed31..1415ffb7eb3 100644
--- a/spec/javascripts/helpers/class_spec_helper_spec.js
+++ b/spec/javascripts/helpers/class_spec_helper_spec.js
@@ -3,7 +3,7 @@
import './class_spec_helper';
describe('ClassSpecHelper', () => {
- describe('itShouldBeAStaticMethod', function () {
+ describe('itShouldBeAStaticMethod', () => {
beforeEach(() => {
class TestClass {
instanceMethod() { this.prop = 'val'; }
@@ -14,23 +14,5 @@ describe('ClassSpecHelper', () => {
});
ClassSpecHelper.itShouldBeAStaticMethod(ClassSpecHelper, 'itShouldBeAStaticMethod');
-
- it('should have a defined spec', () => {
- expect(ClassSpecHelper.itShouldBeAStaticMethod(this.TestClass, 'staticMethod').description).toBe('should be a static method');
- });
-
- it('should pass for a static method', () => {
- const spec = ClassSpecHelper.itShouldBeAStaticMethod(this.TestClass, 'staticMethod');
- expect(spec.status()).toBe('passed');
- });
-
- it('should fail for an instance method', (done) => {
- const spec = ClassSpecHelper.itShouldBeAStaticMethod(this.TestClass, 'instanceMethod');
- spec.resultCallback = (result) => {
- expect(result.status).toBe('failed');
- done();
- };
- spec.execute();
- });
});
});
diff --git a/spec/javascripts/issue_show/components/app_spec.js b/spec/javascripts/issue_show/components/app_spec.js
index 9280db072b3..1c9f48028f2 100644
--- a/spec/javascripts/issue_show/components/app_spec.js
+++ b/spec/javascripts/issue_show/components/app_spec.js
@@ -59,7 +59,7 @@ describe('Issuable output', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
realtimeRequestCount = 0;
vm.poll.stop();
diff --git a/spec/javascripts/jobs/job_details_mediator_spec.js b/spec/javascripts/jobs/job_details_mediator_spec.js
index 3069a0cd60e..ca5c9cf87e4 100644
--- a/spec/javascripts/jobs/job_details_mediator_spec.js
+++ b/spec/javascripts/jobs/job_details_mediator_spec.js
@@ -12,6 +12,10 @@ describe('JobMediator', () => {
mock = new MockAdapter(axios);
});
+ afterEach(() => {
+ mock.restore();
+ });
+
it('should set defaults', () => {
expect(mediator.store).toBeDefined();
expect(mediator.service).toBeDefined();
@@ -24,10 +28,6 @@ describe('JobMediator', () => {
mock.onGet().reply(200, job, {});
});
- afterEach(() => {
- mock.restore();
- });
-
it('should store received data', (done) => {
mediator.fetchJob();
setTimeout(() => {
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js
index 9885b8a790f..eb8f6bbe50d 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/dashboard_spec.js
@@ -38,7 +38,7 @@ describe('Dashboard', () => {
});
afterEach(() => {
- mock.reset();
+ mock.restore();
});
it('shows up a loading state', (done) => {
diff --git a/spec/javascripts/pipelines/pipelines_table_row_spec.js b/spec/javascripts/pipelines/pipelines_table_row_spec.js
index a9126d2f4e9..b3cbf9aba48 100644
--- a/spec/javascripts/pipelines/pipelines_table_row_spec.js
+++ b/spec/javascripts/pipelines/pipelines_table_row_spec.js
@@ -24,9 +24,10 @@ describe('Pipelines Table Row', () => {
beforeEach(() => {
const pipelines = getJSONFixture(jsonFixtureName).pipelines;
- pipeline = pipelines.find(p => p.id === 1);
- pipelineWithoutAuthor = pipelines.find(p => p.id === 2);
- pipelineWithoutCommit = pipelines.find(p => p.id === 3);
+
+ pipeline = pipelines.find(p => p.user !== null && p.commit !== null);
+ pipelineWithoutAuthor = pipelines.find(p => p.user == null && p.commit !== null);
+ pipelineWithoutCommit = pipelines.find(p => p.user == null && p.commit == null);
});
afterEach(() => {
diff --git a/spec/javascripts/pipelines/pipelines_table_spec.js b/spec/javascripts/pipelines/pipelines_table_spec.js
index ca2f9163313..4fc3c08145e 100644
--- a/spec/javascripts/pipelines/pipelines_table_spec.js
+++ b/spec/javascripts/pipelines/pipelines_table_spec.js
@@ -11,9 +11,10 @@ describe('Pipelines Table', () => {
preloadFixtures(jsonFixtureName);
beforeEach(() => {
- PipelinesTableComponent = Vue.extend(pipelinesTableComp);
const pipelines = getJSONFixture(jsonFixtureName).pipelines;
- pipeline = pipelines.find(p => p.id === 1);
+
+ PipelinesTableComponent = Vue.extend(pipelinesTableComp);
+ pipeline = pipelines.find(p => p.user !== null && p.commit !== null);
});
describe('table', () => {
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
index 6b7aa935ad3..658cadddb81 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_checking_spec.js
@@ -1,19 +1,29 @@
import Vue from 'vue';
-import checkingComponent from '~/vue_merge_request_widget/components/states/mr_widget_checking';
+import checkingComponent from '~/vue_merge_request_widget/components/states/mr_widget_checking.vue';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('MRWidgetChecking', () => {
- describe('template', () => {
- it('should have correct elements', () => {
- const Component = Vue.extend(checkingComponent);
- const el = new Component({
- el: document.createElement('div'),
- }).$el;
+ let Component;
+ let vm;
- expect(el.classList.contains('mr-widget-body')).toBeTruthy();
- expect(el.querySelector('button').classList.contains('btn-success')).toBeTruthy();
- expect(el.querySelector('button').disabled).toBeTruthy();
- expect(el.innerText).toContain('Checking ability to merge automatically');
- expect(el.querySelector('i')).toBeDefined();
- });
+ beforeEach(() => {
+ Component = Vue.extend(checkingComponent);
+ vm = mountComponent(Component);
+ });
+
+ afterEach(() => {
+ vm.$destroy();
+ });
+
+ it('renders disabled button', () => {
+ expect(vm.$el.querySelector('button').getAttribute('disabled')).toEqual('disabled');
+ });
+
+ it('renders loading icon', () => {
+ expect(vm.$el.querySelector('.mr-widget-icon i').classList).toContain('fa-spinner');
+ });
+
+ it('renders information about merging', () => {
+ expect(vm.$el.querySelector('.media-body').textContent.trim()).toEqual('Checking ability to merge automatically');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js
index 1bf97bbf093..51a34739ee9 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_closed_spec.js
@@ -1,74 +1,58 @@
import Vue from 'vue';
-import closedComponent from '~/vue_merge_request_widget/components/states/mr_widget_closed';
-
-const mr = {
- targetBranch: 'good-branch',
- targetBranchPath: '/good-branch',
- metrics: {
- mergedBy: {},
- mergedAt: 'mergedUpdatedAt',
- closedBy: {
- name: 'Fatih Acet',
- username: 'fatihacet',
- },
- closedAt: 'closedEventUpdatedAt',
- readableMergedAt: '',
- readableClosedAt: '',
- },
- updatedAt: 'mrUpdatedAt',
- closedAt: '1 day ago',
-};
-
-const createComponent = () => {
- const Component = Vue.extend(closedComponent);
-
- return new Component({
- el: document.createElement('div'),
- propsData: { mr },
- });
-};
+import closedComponent from '~/vue_merge_request_widget/components/states/mr_widget_closed.vue';
+import mountComponent from '../../../helpers/vue_mount_component_helper';
describe('MRWidgetClosed', () => {
- describe('props', () => {
- it('should have props', () => {
- const mrProp = closedComponent.props.mr;
-
- expect(mrProp.type instanceof Object).toBeTruthy();
- expect(mrProp.required).toBeTruthy();
- });
+ let vm;
+
+ beforeEach(() => {
+ const Component = Vue.extend(closedComponent);
+ vm = mountComponent(Component, { mr: {
+ metrics: {
+ mergedBy: {},
+ closedBy: {
+ name: 'Administrator',
+ username: 'root',
+ webUrl: 'http://localhost:3000/root',
+ avatarUrl: 'http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ },
+ mergedAt: 'Jan 24, 2018 1:02pm GMT+0000',
+ closedAt: 'Jan 24, 2018 1:02pm GMT+0000',
+ readableMergedAt: '',
+ readableClosedAt: 'less than a minute ago',
+ },
+ targetBranchPath: '/twitter/flight/commits/so_long_jquery',
+ targetBranch: 'so_long_jquery',
+ } });
});
- describe('components', () => {
- it('should have components added', () => {
- expect(closedComponent.components['mr-widget-author-and-time']).toBeDefined();
- });
+ afterEach(() => {
+ vm.$destroy();
});
- describe('template', () => {
- let vm;
- let el;
+ it('renders warning icon', () => {
+ expect(vm.$el.querySelector('.js-ci-status-icon-warning')).not.toBeNull();
+ });
- beforeEach(() => {
- vm = createComponent();
- el = vm.$el;
- });
+ it('renders closed by information with author and time', () => {
+ expect(
+ vm.$el.querySelector('.js-mr-widget-author').textContent.trim().replace(/\s\s+/g, ' '),
+ ).toContain(
+ 'Closed by Administrator less than a minute ago',
+ );
+ });
- afterEach(() => {
- vm.$destroy();
- });
+ it('links to the user that closed the MR', () => {
+ expect(vm.$el.querySelector('.author-link').getAttribute('href')).toEqual('http://localhost:3000/root');
+ });
- it('should have correct elements', () => {
- expect(el.querySelector('h4').textContent).toContain('Closed by');
- expect(el.querySelector('h4').textContent).toContain(mr.metrics.closedBy.name);
- expect(el.textContent).toContain('The changes were not merged into');
- expect(el.querySelector('.label-branch').getAttribute('href')).toEqual(mr.targetBranchPath);
- expect(el.querySelector('.label-branch').textContent).toContain(mr.targetBranch);
- });
+ it('renders information about the changes not being merged', () => {
+ expect(
+ vm.$el.querySelector('.mr-info-list').textContent.trim().replace(/\s\s+/g, ' '),
+ ).toContain('The changes were not merged into so_long_jquery');
+ });
- it('should use closedEvent updatedAt as tooltip title', () => {
- expect(
- el.querySelector('time').getAttribute('title'),
- ).toBe('closedEventUpdatedAt');
- });
+ it('renders link for target branch', () => {
+ expect(vm.$el.querySelector('.label-branch').getAttribute('href')).toEqual('/twitter/flight/commits/so_long_jquery');
});
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
index 5d4c7ec09dc..a7d69fdcdb9 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_conflicts_spec.js
@@ -1,105 +1,85 @@
import Vue from 'vue';
-import conflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts';
+import conflictsComponent from '~/vue_merge_request_widget/components/states/mr_widget_conflicts.vue';
import mountComponent from '../../../helpers/vue_mount_component_helper';
-const ConflictsComponent = Vue.extend(conflictsComponent);
-const path = '/conflicts';
-
describe('MRWidgetConflicts', () => {
- describe('props', () => {
- it('should have props', () => {
- const { mr } = conflictsComponent.props;
+ let Component;
+ let vm;
+ const path = '/conflicts';
- expect(mr.type instanceof Object).toBeTruthy();
- expect(mr.required).toBeTruthy();
- });
+ beforeEach(() => {
+ Component = Vue.extend(conflictsComponent);
});
- describe('template', () => {
- describe('when allowed to merge', () => {
- let vm;
-
- beforeEach(() => {
- vm = mountComponent(ConflictsComponent, {
- mr: {
- canMerge: true,
- conflictResolutionPath: path,
- },
- });
- });
-
- afterEach(() => {
- vm.$destroy();
- });
-
- it('should tell you about conflicts without bothering other people', () => {
- expect(vm.$el.textContent).toContain('There are merge conflicts');
- expect(vm.$el.textContent).not.toContain('ask someone with write access');
- });
-
- it('should allow you to resolve the conflicts', () => {
- const resolveButton = vm.$el.querySelector('.js-resolve-conflicts-button');
+ afterEach(() => {
+ vm.$destroy();
+ });
- expect(resolveButton.textContent).toContain('Resolve conflicts');
- expect(resolveButton.getAttribute('href')).toEqual(path);
+ describe('when allowed to merge', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ mr: {
+ canMerge: true,
+ conflictResolutionPath: path,
+ },
});
+ });
- it('should have merge buttons', () => {
- const mergeButton = vm.$el.querySelector('.js-disabled-merge-button');
- const mergeLocallyButton = vm.$el.querySelector('.js-merge-locally-button');
-
- expect(mergeButton.textContent).toContain('Merge');
- expect(mergeButton.disabled).toBeTruthy();
- expect(mergeButton.classList.contains('btn-success')).toEqual(true);
- expect(mergeLocallyButton.textContent).toContain('Merge locally');
- });
+ it('should tell you about conflicts without bothering other people', () => {
+ expect(vm.$el.textContent).toContain('There are merge conflicts');
+ expect(vm.$el.textContent).not.toContain('ask someone with write access');
});
- describe('when user does not have permission to merge', () => {
- let vm;
+ it('should allow you to resolve the conflicts', () => {
+ const resolveButton = vm.$el.querySelector('.js-resolve-conflicts-button');
- beforeEach(() => {
- vm = mountComponent(ConflictsComponent, {
- mr: {
- canMerge: false,
- },
- });
- });
+ expect(resolveButton.textContent).toContain('Resolve conflicts');
+ expect(resolveButton.getAttribute('href')).toEqual(path);
+ });
- afterEach(() => {
- vm.$destroy();
- });
+ it('should have merge buttons', () => {
+ const mergeButton = vm.$el.querySelector('.js-disabled-merge-button');
+ const mergeLocallyButton = vm.$el.querySelector('.js-merge-locally-button');
- it('should show proper message', () => {
- expect(vm.$el.textContent).toContain('ask someone with write access');
- });
+ expect(mergeButton.textContent).toContain('Merge');
+ expect(mergeButton.disabled).toBeTruthy();
+ expect(mergeButton.classList.contains('btn-success')).toEqual(true);
+ expect(mergeLocallyButton.textContent).toContain('Merge locally');
+ });
+ });
- it('should not have action buttons', () => {
- expect(vm.$el.querySelector('.js-disabled-merge-button')).toBeDefined();
- expect(vm.$el.querySelector('.js-resolve-conflicts-button')).toBeNull();
- expect(vm.$el.querySelector('.js-merge-locally-button')).toBeNull();
+ describe('when user does not have permission to merge', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ mr: {
+ canMerge: false,
+ },
});
});
- describe('when fast-forward or semi-linear merge enabled', () => {
- let vm;
+ it('should show proper message', () => {
+ expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain('ask someone with write access');
+ });
- beforeEach(() => {
- vm = mountComponent(ConflictsComponent, {
- mr: {
- shouldBeRebased: true,
- },
- });
- });
+ it('should not have action buttons', () => {
+ expect(vm.$el.querySelector('.js-disabled-merge-button')).toBeDefined();
+ expect(vm.$el.querySelector('.js-resolve-conflicts-button')).toBeNull();
+ expect(vm.$el.querySelector('.js-merge-locally-button')).toBeNull();
+ });
+ });
- afterEach(() => {
- vm.$destroy();
+ describe('when fast-forward or semi-linear merge enabled', () => {
+ beforeEach(() => {
+ vm = mountComponent(Component, {
+ mr: {
+ shouldBeRebased: true,
+ },
});
+ });
- it('should tell you to rebase locally', () => {
- expect(vm.$el.textContent).toContain('Fast-forward merge is not possible.');
- expect(vm.$el.textContent).toContain('To merge this request, first rebase locally');
- });
+ it('should tell you to rebase locally', () => {
+ expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain('Fast-forward merge is not possible.');
+ expect(vm.$el.textContent.trim().replace(/\s\s+/g, ' ')).toContain('To merge this request, first rebase locally');
});
});
});
diff --git a/spec/javascripts/vue_shared/components/modal_spec.js b/spec/javascripts/vue_shared/components/modal_spec.js
index fe75a86cac8..a5f9c75be4e 100644
--- a/spec/javascripts/vue_shared/components/modal_spec.js
+++ b/spec/javascripts/vue_shared/components/modal_spec.js
@@ -25,7 +25,7 @@ describe('Modal', () => {
});
describe('with id', () => {
- it('does not render a primary button', () => {
+ describe('does not render a primary button', () => {
beforeEach(() => {
vm = mountComponent(modalComponent, {
id: 'my-modal',
diff --git a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
index 20363e78094..2de108da2ac 100644
--- a/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
+++ b/spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
@@ -21,22 +21,21 @@ describe('collapsedGroupedDatePicker', () => {
});
});
- it('toggleCollapse events', () => {
- const toggleCollapse = jasmine.createSpy();
-
+ describe('toggleCollapse events', () => {
beforeEach((done) => {
+ spyOn(vm, 'toggleSidebar');
vm.minDate = new Date('07/17/2016');
Vue.nextTick(done);
});
it('should emit when sidebar is toggled', () => {
vm.$el.querySelector('.gutter-toggle').click();
- expect(toggleCollapse).toHaveBeenCalled();
+ expect(vm.toggleSidebar).toHaveBeenCalled();
});
it('should emit when collapsed-calendar-icon is clicked', () => {
vm.$el.querySelector('.sidebar-collapsed-icon').click();
- expect(toggleCollapse).toHaveBeenCalled();
+ expect(vm.toggleSidebar).toHaveBeenCalled();
});
});
diff --git a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
index d21183b668b..c8df6dd2118 100644
--- a/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
+++ b/spec/lib/gitlab/background_migration/deserialize_merge_request_diffs_and_commits_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :truncate, :migration, schema: 20171114162227 do
+describe Gitlab::BackgroundMigration::DeserializeMergeRequestDiffsAndCommits, :migration, schema: 20171114162227 do
let(:merge_request_diffs) { table(:merge_request_diffs) }
let(:merge_requests) { table(:merge_requests) }
diff --git a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb
index 7b5a00c6111..021e1d14b18 100644
--- a/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb
+++ b/spec/lib/gitlab/background_migration/migrate_system_uploads_to_new_folder_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder do
+describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder, :delete do
let(:migration) { described_class.new }
before do
@@ -8,7 +8,7 @@ describe Gitlab::BackgroundMigration::MigrateSystemUploadsToNewFolder do
end
describe '#perform' do
- it 'renames the path of system-uploads', :truncate do
+ it 'renames the path of system-uploads' do
upload = create(:upload, model: create(:project), path: 'uploads/system/project/avatar.jpg')
migration.perform('uploads/system/', 'uploads/-/system/')
diff --git a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
index e99257e3481..4cdb679c97f 100644
--- a/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
+++ b/spec/lib/gitlab/background_migration/populate_merge_request_metrics_with_events_data_spec.rb
@@ -7,6 +7,10 @@ describe Gitlab::BackgroundMigration::PopulateMergeRequestMetricsWithEventsData,
.to receive(:commits_count=).and_return(nil)
end
+ after do
+ [MergeRequest, MergeRequestDiff].each(&:reset_column_information)
+ end
+
describe '#perform' do
let(:mr_with_event) { create(:merge_request) }
let!(:merged_event) { create(:event, :merged, target: mr_with_event) }
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
index 596cc435bd9..cc7cb3f23fd 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_base_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :truncate do
+describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameBase, :delete do
let(:migration) { FakeRenameReservedPathMigrationV1.new }
let(:subject) { described_class.new(['the-path'], migration) }
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
index 1143182531f..f31475dbd71 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_namespaces_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :truncate do
+describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameNamespaces, :delete do
let(:migration) { FakeRenameReservedPathMigrationV1.new }
let(:subject) { described_class.new(['the-path'], migration) }
let(:namespace) { create(:group, name: 'the-path') }
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
index e850b5cd6a4..0958144643b 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1/rename_projects_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :truncate do
+describe Gitlab::Database::RenameReservedPathsMigration::V1::RenameProjects, :delete do
let(:migration) { FakeRenameReservedPathMigrationV1.new }
let(:subject) { described_class.new(['the-path'], migration) }
let(:project) do
diff --git a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
index 7695b95dc57..1d31f96159c 100644
--- a/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
+++ b/spec/lib/gitlab/database/rename_reserved_paths_migration/v1_spec.rb
@@ -13,7 +13,7 @@ shared_examples 'renames child namespaces' do |type|
end
end
-describe Gitlab::Database::RenameReservedPathsMigration::V1, :truncate do
+describe Gitlab::Database::RenameReservedPathsMigration::V1, :delete do
let(:subject) { FakeRenameReservedPathMigrationV1.new }
before do
diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb
index 8706c89c147..168207552ff 100644
--- a/spec/lib/gitlab/git/blob_spec.rb
+++ b/spec/lib/gitlab/git/blob_spec.rb
@@ -260,29 +260,42 @@ describe Gitlab::Git::Blob, seed_helper: true do
)
end
- it 'returns a list of Gitlab::Git::Blob' do
- blobs = described_class.batch_lfs_pointers(repository, [lfs_blob.id])
+ shared_examples 'fetching batch of LFS pointers' do
+ it 'returns a list of Gitlab::Git::Blob' do
+ blobs = described_class.batch_lfs_pointers(repository, [lfs_blob.id])
- expect(blobs.count).to eq(1)
- expect(blobs).to all( be_a(Gitlab::Git::Blob) )
- end
+ expect(blobs.count).to eq(1)
+ expect(blobs).to all( be_a(Gitlab::Git::Blob) )
+ end
- it 'silently ignores tree objects' do
- blobs = described_class.batch_lfs_pointers(repository, [tree_object.oid])
+ it 'silently ignores tree objects' do
+ blobs = described_class.batch_lfs_pointers(repository, [tree_object.oid])
- expect(blobs).to eq([])
- end
+ expect(blobs).to eq([])
+ end
+
+ it 'silently ignores non lfs objects' do
+ blobs = described_class.batch_lfs_pointers(repository, [non_lfs_blob.id])
- it 'silently ignores non lfs objects' do
- blobs = described_class.batch_lfs_pointers(repository, [non_lfs_blob.id])
+ expect(blobs).to eq([])
+ end
+
+ it 'avoids loading large blobs into memory' do
+ # This line could call `lookup` on `repository`, so do here before mocking.
+ non_lfs_blob_id = non_lfs_blob.id
+
+ expect(repository).not_to receive(:lookup)
- expect(blobs).to eq([])
+ described_class.batch_lfs_pointers(repository, [non_lfs_blob_id])
+ end
end
- it 'avoids loading large blobs into memory' do
- expect(repository).not_to receive(:lookup)
+ context 'when Gitaly batch_lfs_pointers is enabled' do
+ it_behaves_like 'fetching batch of LFS pointers'
+ end
- described_class.batch_lfs_pointers(repository, [non_lfs_blob.id])
+ context 'when Gitaly batch_lfs_pointers is disabled', :disable_gitaly do
+ it_behaves_like 'fetching batch of LFS pointers'
end
end
diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb
index aec7cde6df8..36ca3980de9 100644
--- a/spec/lib/gitlab/git/repository_spec.rb
+++ b/spec/lib/gitlab/git/repository_spec.rb
@@ -1926,6 +1926,34 @@ describe Gitlab::Git::Repository, seed_helper: true do
it { expect(subject.repository_relative_path).to eq(repository.relative_path) }
end
+ describe '#bundle_to_disk' do
+ shared_examples 'bundling to disk' do
+ let(:save_path) { File.join(Dir.tmpdir, "repo-#{SecureRandom.hex}.bundle") }
+
+ after do
+ FileUtils.rm_rf(save_path)
+ end
+
+ it 'saves a bundle to disk' do
+ repository.bundle_to_disk(save_path)
+
+ success = system(
+ *%W(#{Gitlab.config.git.bin_path} -C #{repository.path} bundle verify #{save_path}),
+ [:out, :err] => '/dev/null'
+ )
+ expect(success).to be true
+ end
+ end
+
+ context 'when Gitaly bundle_to_disk feature is enabled' do
+ it_behaves_like 'bundling to disk'
+ end
+
+ context 'when Gitaly bundle_to_disk feature is disabled', :disable_gitaly do
+ it_behaves_like 'bundling to disk'
+ end
+ end
+
context 'gitlab_projects commands' do
let(:gitlab_projects) { repository.gitlab_projects }
let(:timeout) { Gitlab.config.gitlab_shell.git_timeout }
diff --git a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
index b2275119a04..3722a91c050 100644
--- a/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
+++ b/spec/lib/gitlab/gitaly_client/commit_service_spec.rb
@@ -131,6 +131,29 @@ describe Gitlab::GitalyClient::CommitService do
end
end
+ describe '#commit_count' do
+ before do
+ expect_any_instance_of(Gitaly::CommitService::Stub)
+ .to receive(:count_commits)
+ .with(gitaly_request_with_path(storage_name, relative_path),
+ kind_of(Hash))
+ .and_return([])
+ end
+
+ it 'sends a commit_count message' do
+ client.commit_count(revision)
+ end
+
+ context 'with UTF-8 params strings' do
+ let(:revision) { "branch\u011F" }
+ let(:path) { "foo/\u011F.txt" }
+
+ it 'handles string encodings correctly' do
+ client.commit_count(revision, path: path)
+ end
+ end
+ end
+
describe '#find_commit' do
let(:revision) { '4b825dc642cb6eb9a060e54bf8d69288fbee4904' }
it 'sends an RPC request' do
diff --git a/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb b/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb
new file mode 100644
index 00000000000..2c7e5eb5787
--- /dev/null
+++ b/spec/lib/gitlab/gitaly_client/health_check_service_spec.rb
@@ -0,0 +1,41 @@
+require 'spec_helper'
+
+describe Gitlab::GitalyClient::HealthCheckService do
+ let(:project) { create(:project) }
+ let(:storage_name) { project.repository_storage }
+
+ subject { described_class.new(storage_name) }
+
+ describe '#check' do
+ it 'successfully sends a health check request' do
+ expect(Gitlab::GitalyClient).to receive(:call).with(
+ storage_name,
+ :health_check,
+ :check,
+ instance_of(Grpc::Health::V1::HealthCheckRequest),
+ timeout: Gitlab::GitalyClient.fast_timeout).and_call_original
+
+ expect(subject.check).to eq({ success: true })
+ end
+
+ it 'receives an unsuccessful health check request' do
+ expect_any_instance_of(Grpc::Health::V1::Health::Stub)
+ .to receive(:check)
+ .and_return(double(status: false))
+
+ expect(subject.check).to eq({ success: false })
+ end
+
+ it 'gracefully handles gRPC error' do
+ expect(Gitlab::GitalyClient).to receive(:call).with(
+ storage_name,
+ :health_check,
+ :check,
+ instance_of(Grpc::Health::V1::HealthCheckRequest),
+ timeout: Gitlab::GitalyClient.fast_timeout)
+ .and_raise(GRPC::Unavailable.new('Connection refused'))
+
+ expect(subject.check).to eq({ success: false, message: '14:Connection refused' })
+ end
+ end
+end
diff --git a/spec/lib/gitlab/gitaly_client_spec.rb b/spec/lib/gitlab/gitaly_client_spec.rb
index 309b7338ef0..81bcd8c28ed 100644
--- a/spec/lib/gitlab/gitaly_client_spec.rb
+++ b/spec/lib/gitlab/gitaly_client_spec.rb
@@ -3,6 +3,31 @@ require 'spec_helper'
# We stub Gitaly in `spec/support/gitaly.rb` for other tests. We don't want
# those stubs while testing the GitalyClient itself.
describe Gitlab::GitalyClient, skip_gitaly_mock: true do
+ describe '.stub_class' do
+ it 'returns the gRPC health check stub' do
+ expect(described_class.stub_class(:health_check)).to eq(::Grpc::Health::V1::Health::Stub)
+ end
+
+ it 'returns a Gitaly stub' do
+ expect(described_class.stub_class(:ref_service)).to eq(::Gitaly::RefService::Stub)
+ end
+ end
+
+ describe '.stub_address' do
+ it 'returns the same result after being called multiple times' do
+ address = 'localhost:9876'
+ prefixed_address = "tcp://#{address}"
+
+ allow(Gitlab.config.repositories).to receive(:storages).and_return({
+ 'default' => { 'gitaly_address' => prefixed_address }
+ })
+
+ 2.times do
+ expect(described_class.stub_address('default')).to eq('localhost:9876')
+ end
+ end
+ end
+
describe '.stub' do
# Notice that this is referring to gRPC "stubs", not rspec stubs
before do
diff --git a/spec/lib/gitlab/health_checks/gitaly_check_spec.rb b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
new file mode 100644
index 00000000000..724beefff69
--- /dev/null
+++ b/spec/lib/gitlab/health_checks/gitaly_check_spec.rb
@@ -0,0 +1,57 @@
+require 'spec_helper'
+
+describe Gitlab::HealthChecks::GitalyCheck do
+ let(:result_class) { Gitlab::HealthChecks::Result }
+ let(:repository_storages) { ['default'] }
+
+ before do
+ allow(described_class).to receive(:repository_storages) { repository_storages }
+ end
+
+ describe '#readiness' do
+ subject { described_class.readiness }
+
+ before do
+ expect(Gitlab::GitalyClient::HealthCheckService).to receive(:new).and_return(gitaly_check)
+ end
+
+ context 'Gitaly server is up' do
+ let(:gitaly_check) { double(check: { success: true }) }
+
+ it { is_expected.to eq([result_class.new(true, nil, shard: 'default')]) }
+ end
+
+ context 'Gitaly server is down' do
+ let(:gitaly_check) { double(check: { success: false, message: 'Connection refused' }) }
+
+ it { is_expected.to eq([result_class.new(false, 'Connection refused', shard: 'default')]) }
+ end
+ end
+
+ describe '#metrics' do
+ subject { described_class.metrics }
+
+ before do
+ expect(Gitlab::GitalyClient::HealthCheckService).to receive(:new).and_return(gitaly_check)
+ end
+
+ context 'Gitaly server is up' do
+ let(:gitaly_check) { double(check: { success: true }) }
+
+ it 'provides metrics' do
+ expect(subject).to all(have_attributes(labels: { shard: 'default' }))
+ expect(subject).to include(an_object_having_attributes(name: 'gitaly_health_check_success', value: 1))
+ expect(subject).to include(an_object_having_attributes(name: 'gitaly_health_check_latency_seconds', value: be >= 0))
+ end
+ end
+
+ context 'Gitaly server is down' do
+ let(:gitaly_check) { double(check: { success: false, message: 'Connection refused' }) }
+
+ it 'provides metrics' do
+ expect(subject).to include(an_object_having_attributes(name: 'gitaly_health_check_success', value: 0))
+ expect(subject).to include(an_object_having_attributes(name: 'gitaly_health_check_latency_seconds', value: be >= 0))
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/search_results_spec.rb b/spec/lib/gitlab/search_results_spec.rb
index b5a9ac570e6..17b48b3d062 100644
--- a/spec/lib/gitlab/search_results_spec.rb
+++ b/spec/lib/gitlab/search_results_spec.rb
@@ -19,6 +19,12 @@ describe Gitlab::SearchResults do
project.add_developer(user)
end
+ describe '#objects' do
+ it 'returns without_page collection by default' do
+ expect(results.objects('projects')).to be_kind_of(Kaminari::PaginatableWithoutCount)
+ end
+ end
+
describe '#projects_count' do
it 'returns the total amount of projects' do
expect(results.projects_count).to eq(1)
@@ -43,6 +49,58 @@ describe Gitlab::SearchResults do
end
end
+ context "when count_limit is lower than total amount" do
+ before do
+ allow(results).to receive(:count_limit).and_return(1)
+ end
+
+ describe '#limited_projects_count' do
+ it 'returns the limited amount of projects' do
+ create(:project, name: 'foo2')
+
+ expect(results.limited_projects_count).to eq(1)
+ end
+ end
+
+ describe '#limited_merge_requests_count' do
+ it 'returns the limited amount of merge requests' do
+ create(:merge_request, :simple, source_project: project, title: 'foo2')
+
+ expect(results.limited_merge_requests_count).to eq(1)
+ end
+ end
+
+ describe '#limited_milestones_count' do
+ it 'returns the limited amount of milestones' do
+ create(:milestone, project: project, title: 'foo2')
+
+ expect(results.limited_milestones_count).to eq(1)
+ end
+ end
+
+ describe '#limited_issues_count' do
+ it 'runs single SQL query to get the limited amount of issues' do
+ create(:milestone, project: project, title: 'foo2')
+
+ expect(results).to receive(:issues).with(public_only: true).and_call_original
+ expect(results).not_to receive(:issues).with(no_args).and_call_original
+
+ expect(results.limited_issues_count).to eq(1)
+ end
+ end
+ end
+
+ context "when count_limit is higher than total amount" do
+ describe '#limited_issues_count' do
+ it 'runs multiple queries to get the limited amount of issues' do
+ expect(results).to receive(:issues).with(public_only: true).and_call_original
+ expect(results).to receive(:issues).with(no_args).and_call_original
+
+ expect(results.limited_issues_count).to eq(1)
+ end
+ end
+ end
+
it 'includes merge requests from source and target projects' do
forked_project = fork_project(project, user)
merge_request_2 = create(:merge_request, target_project: project, source_project: forked_project, title: 'foo')
diff --git a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
index 84c2e9f7e52..63defcb39bf 100644
--- a/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
+++ b/spec/migrations/add_head_pipeline_for_each_merge_request_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170508170547_add_head_pipeline_for_each_merge_request.rb')
-describe AddHeadPipelineForEachMergeRequest, :truncate do
+describe AddHeadPipelineForEachMergeRequest, :delete do
include ProjectForksHelper
let(:migration) { described_class.new }
diff --git a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
index 597d8eab51c..f3a46025376 100644
--- a/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
+++ b/spec/migrations/calculate_conv_dev_index_percentages_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170803090603_calculate_conv_dev_index_percentages.rb')
-describe CalculateConvDevIndexPercentages, truncate: true do
+describe CalculateConvDevIndexPercentages, :delete do
let(:migration) { described_class.new }
let!(:conv_dev_index) do
create(:conversational_development_index_metric,
diff --git a/spec/migrations/fix_wrongly_renamed_routes_spec.rb b/spec/migrations/fix_wrongly_renamed_routes_spec.rb
index 78f8ab7512d..543cf55f076 100644
--- a/spec/migrations/fix_wrongly_renamed_routes_spec.rb
+++ b/spec/migrations/fix_wrongly_renamed_routes_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170518231126_fix_wrongly_renamed_routes.rb')
-describe FixWronglyRenamedRoutes, :truncate, :migration do
+describe FixWronglyRenamedRoutes, :migration do
let(:subject) { described_class.new }
let(:namespaces_table) { table(:namespaces) }
let(:projects_table) { table(:projects) }
diff --git a/spec/migrations/migrate_issues_to_ghost_user_spec.rb b/spec/migrations/migrate_issues_to_ghost_user_spec.rb
index cfd4021fbac..ff0d44e1ed2 100644
--- a/spec/migrations/migrate_issues_to_ghost_user_spec.rb
+++ b/spec/migrations/migrate_issues_to_ghost_user_spec.rb
@@ -8,10 +8,10 @@ describe MigrateIssuesToGhostUser, :migration do
let(:users) { table(:users) }
before do
- projects.create!(name: 'gitlab')
+ project = projects.create!(name: 'gitlab')
user = users.create(email: 'test@example.com')
- issues.create(title: 'Issue 1', author_id: nil, project_id: 1)
- issues.create(title: 'Issue 2', author_id: user.id, project_id: 1)
+ issues.create(title: 'Issue 1', author_id: nil, project_id: project.id)
+ issues.create(title: 'Issue 2', author_id: user.id, project_id: project.id)
end
context 'when ghost user exists' do
diff --git a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
index 063829be546..a17c9c72bde 100644
--- a/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
+++ b/spec/migrations/migrate_user_activities_to_users_last_activity_on_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170324160416_migrate_user_activities_to_users_last_activity_on.rb')
-describe MigrateUserActivitiesToUsersLastActivityOn, :clean_gitlab_redis_shared_state, :truncate do
+describe MigrateUserActivitiesToUsersLastActivityOn, :clean_gitlab_redis_shared_state, :delete do
let(:migration) { described_class.new }
let!(:user_active_1) { create(:user) }
let!(:user_active_2) { create(:user) }
diff --git a/spec/migrations/migrate_user_project_view_spec.rb b/spec/migrations/migrate_user_project_view_spec.rb
index 5e16769d63a..31d16e17d7b 100644
--- a/spec/migrations/migrate_user_project_view_spec.rb
+++ b/spec/migrations/migrate_user_project_view_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170406142253_migrate_user_project_view.rb')
-describe MigrateUserProjectView, :truncate do
+describe MigrateUserProjectView, :delete do
let(:migration) { described_class.new }
let!(:user) { create(:user, project_view: 'readme') }
diff --git a/spec/migrations/remove_duplicate_mr_events_spec.rb b/spec/migrations/remove_duplicate_mr_events_spec.rb
index e393374028f..e51872239ad 100644
--- a/spec/migrations/remove_duplicate_mr_events_spec.rb
+++ b/spec/migrations/remove_duplicate_mr_events_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170815060945_remove_duplicate_mr_events.rb')
-describe RemoveDuplicateMrEvents, truncate: true do
+describe RemoveDuplicateMrEvents, :delete do
let(:migration) { described_class.new }
describe '#up' do
diff --git a/spec/migrations/rename_more_reserved_project_names_spec.rb b/spec/migrations/rename_more_reserved_project_names_spec.rb
index ae3a4cb9b29..75310075cc5 100644
--- a/spec/migrations/rename_more_reserved_project_names_spec.rb
+++ b/spec/migrations/rename_more_reserved_project_names_spec.rb
@@ -5,8 +5,8 @@ require Rails.root.join('db', 'post_migrate', '20170313133418_rename_more_reserv
# This migration uses multiple threads, and thus different transactions. This
# means data created in this spec may not be visible to some threads. To work
-# around this we use the TRUNCATE cleaning strategy.
-describe RenameMoreReservedProjectNames, truncate: true do
+# around this we use the DELETE cleaning strategy.
+describe RenameMoreReservedProjectNames, :delete do
let(:migration) { described_class.new }
let!(:project) { create(:project) }
diff --git a/spec/migrations/rename_reserved_project_names_spec.rb b/spec/migrations/rename_reserved_project_names_spec.rb
index 462f4c08d63..e6555b1fe6b 100644
--- a/spec/migrations/rename_reserved_project_names_spec.rb
+++ b/spec/migrations/rename_reserved_project_names_spec.rb
@@ -5,8 +5,8 @@ require Rails.root.join('db', 'post_migrate', '20161221153951_rename_reserved_pr
# This migration uses multiple threads, and thus different transactions. This
# means data created in this spec may not be visible to some threads. To work
-# around this we use the TRUNCATE cleaning strategy.
-describe RenameReservedProjectNames, truncate: true do
+# around this we use the DELETE cleaning strategy.
+describe RenameReservedProjectNames, :delete do
let(:migration) { described_class.new }
let!(:project) { create(:project) }
diff --git a/spec/migrations/rename_users_with_renamed_namespace_spec.rb b/spec/migrations/rename_users_with_renamed_namespace_spec.rb
index 1e9aab3d9a1..cbc0ebeb44d 100644
--- a/spec/migrations/rename_users_with_renamed_namespace_spec.rb
+++ b/spec/migrations/rename_users_with_renamed_namespace_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170518200835_rename_users_with_renamed_namespace.rb')
-describe RenameUsersWithRenamedNamespace, truncate: true do
+describe RenameUsersWithRenamedNamespace, :delete do
it 'renames a user that had their namespace renamed to the namespace path' do
other_user = create(:user, username: 'kodingu')
other_user1 = create(:user, username: 'api0')
diff --git a/spec/migrations/update_retried_for_ci_build_spec.rb b/spec/migrations/update_retried_for_ci_build_spec.rb
index 3742b4dafe5..ccb77766b84 100644
--- a/spec/migrations/update_retried_for_ci_build_spec.rb
+++ b/spec/migrations/update_retried_for_ci_build_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
require Rails.root.join('db', 'post_migrate', '20170503004427_update_retried_for_ci_build.rb')
-describe UpdateRetriedForCiBuild, truncate: true do
+describe UpdateRetriedForCiBuild, :delete do
let(:pipeline) { create(:ci_pipeline) }
let!(:build_old) { create(:ci_build, pipeline: pipeline, name: 'test') }
let!(:build_new) { create(:ci_build, pipeline: pipeline, name: 'test') }
diff --git a/spec/models/concerns/avatarable_spec.rb b/spec/models/concerns/avatarable_spec.rb
index cbdc438be0b..3696e6f62fd 100644
--- a/spec/models/concerns/avatarable_spec.rb
+++ b/spec/models/concerns/avatarable_spec.rb
@@ -1,11 +1,11 @@
require 'spec_helper'
describe Avatarable do
- subject { create(:project, avatar: fixture_file_upload(File.join(Rails.root, 'spec/fixtures/dk.png'))) }
+ set(:project) { create(:project, avatar: fixture_file_upload(File.join(Rails.root, 'spec/fixtures/dk.png'))) }
let(:gitlab_host) { "https://gitlab.example.com" }
let(:relative_url_root) { "/gitlab" }
- let(:asset_host) { "https://gitlab-assets.example.com" }
+ let(:asset_host) { 'https://gitlab-assets.example.com' }
before do
stub_config_setting(base_url: gitlab_host)
@@ -15,29 +15,32 @@ describe Avatarable do
describe '#avatar_path' do
using RSpec::Parameterized::TableSyntax
- where(:has_asset_host, :visibility_level, :only_path, :avatar_path) do
- true | Project::PRIVATE | true | [gitlab_host, relative_url_root, subject.avatar.url]
- true | Project::PRIVATE | false | [gitlab_host, relative_url_root, subject.avatar.url]
- true | Project::INTERNAL | true | [gitlab_host, relative_url_root, subject.avatar.url]
- true | Project::INTERNAL | false | [gitlab_host, relative_url_root, subject.avatar.url]
- true | Project::PUBLIC | true | [subject.avatar.url]
- true | Project::PUBLIC | false | [asset_host, subject.avatar.url]
- false | Project::PRIVATE | true | [relative_url_root, subject.avatar.url]
- false | Project::PRIVATE | false | [gitlab_host, relative_url_root, subject.avatar.url]
- false | Project::INTERNAL | true | [relative_url_root, subject.avatar.url]
- false | Project::INTERNAL | false | [gitlab_host, relative_url_root, subject.avatar.url]
- false | Project::PUBLIC | true | [relative_url_root, subject.avatar.url]
- false | Project::PUBLIC | false | [gitlab_host, relative_url_root, subject.avatar.url]
+ where(:has_asset_host, :visibility_level, :only_path, :avatar_path_prefix) do
+ true | Project::PRIVATE | true | [gitlab_host, relative_url_root]
+ true | Project::PRIVATE | false | [gitlab_host, relative_url_root]
+ true | Project::INTERNAL | true | [gitlab_host, relative_url_root]
+ true | Project::INTERNAL | false | [gitlab_host, relative_url_root]
+ true | Project::PUBLIC | true | []
+ true | Project::PUBLIC | false | [asset_host]
+ false | Project::PRIVATE | true | [relative_url_root]
+ false | Project::PRIVATE | false | [gitlab_host, relative_url_root]
+ false | Project::INTERNAL | true | [relative_url_root]
+ false | Project::INTERNAL | false | [gitlab_host, relative_url_root]
+ false | Project::PUBLIC | true | [relative_url_root]
+ false | Project::PUBLIC | false | [gitlab_host, relative_url_root]
end
with_them do
before do
- allow(ActionController::Base).to receive(:asset_host).and_return(has_asset_host ? asset_host : nil)
- subject.visibility_level = visibility_level
+ allow(ActionController::Base).to receive(:asset_host) { has_asset_host && asset_host }
+
+ project.visibility_level = visibility_level
end
+ let(:avatar_path) { (avatar_path_prefix + [project.avatar.url]).join }
+
it 'returns the expected avatar path' do
- expect(subject.avatar_path(only_path: only_path)).to eq(avatar_path.join)
+ expect(project.avatar_path(only_path: only_path)).to eq(avatar_path)
end
end
end
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 6aa0e7f49c3..c64cdf8f812 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -488,7 +488,7 @@ describe Member do
member.accept_invite!(user)
end
- it "refreshes user's authorized projects", :truncate do
+ it "refreshes user's authorized projects", :delete do
project = member.source
expect(user.authorized_projects).not_to include(project)
@@ -523,7 +523,7 @@ describe Member do
end
end
- describe "destroying a record", :truncate do
+ describe "destroying a record", :delete do
it "refreshes user's authorized projects" do
project = create(:project, :private)
user = create(:user)
diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb
index 41e2ab20d69..1fccf92627a 100644
--- a/spec/models/project_group_link_spec.rb
+++ b/spec/models/project_group_link_spec.rb
@@ -30,7 +30,7 @@ describe ProjectGroupLink do
end
end
- describe "destroying a record", :truncate do
+ describe "destroying a record", :delete do
it "refreshes group users' authorized projects" do
project = create(:project, :private)
group = create(:group)
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 4d10df410ab..31dcb543cbd 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -3228,5 +3228,22 @@ describe Project do
project = build(:project)
project.execute_hooks({ data: 'data' }, :merge_request_hooks)
end
+
+ it 'executes the system hooks when inside a transaction' do
+ allow_any_instance_of(WebHookService).to receive(:execute)
+
+ create(:system_hook, merge_requests_events: true)
+
+ project = build(:project)
+
+ # Ideally, we'd test that `WebHookWorker.jobs.size` increased by 1,
+ # but since the entire spec run takes place in a transaction, we never
+ # actually get to the `after_commit` hook that queues these jobs.
+ expect do
+ project.transaction do
+ project.execute_hooks({ data: 'data' }, :merge_request_hooks)
+ end
+ end.not_to raise_error # Sidekiq::Worker::EnqueueFromTransactionError
+ end
end
end
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index baaa9e3ef44..8f406253f39 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -365,12 +365,18 @@ describe Repository do
it { is_expected.to be_truthy }
end
- context 'non-mergeable branches' do
+ context 'non-mergeable branches without conflict sides missing' do
subject { repository.can_be_merged?('bb5206fee213d983da88c47f9cf4cc6caf9c66dc', 'feature') }
it { is_expected.to be_falsey }
end
+ context 'non-mergeable branches with conflict sides missing' do
+ subject { repository.can_be_merged?('conflict-missing-side', 'conflict-start') }
+
+ it { is_expected.to be_falsey }
+ end
+
context 'non merged branch' do
subject { repository.merged_to_root_ref?('fix') }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 762cec9b95e..594f23718da 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -1569,7 +1569,7 @@ describe User do
it { is_expected.to eq([private_group]) }
end
- describe '#authorized_projects', :truncate do
+ describe '#authorized_projects', :delete do
context 'with a minimum access level' do
it 'includes projects for which the user is an owner' do
user = create(:user)
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index ea75434e399..cc9d79da708 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -386,6 +386,17 @@ describe WikiPage do
end
end
+ describe '#formatted_content' do
+ it 'returns processed content of the page', :disable_gitaly do
+ subject.create({ title: "RDoc", content: "*bold*", format: "rdoc" })
+ page = wiki.find_page('RDoc')
+
+ expect(page.formatted_content).to eq("\n<p><strong>bold</strong></p>\n")
+
+ destroy_page('RDoc')
+ end
+ end
+
private
def remove_temp_repo(path)
diff --git a/spec/requests/api/applications_spec.rb b/spec/requests/api/applications_spec.rb
new file mode 100644
index 00000000000..f56bc932f40
--- /dev/null
+++ b/spec/requests/api/applications_spec.rb
@@ -0,0 +1,86 @@
+require 'spec_helper'
+
+describe API::Applications, :api do
+ include ApiHelpers
+
+ let(:admin_user) { create(:user, admin: true) }
+ let(:user) { create(:user, admin: false) }
+
+ describe 'POST /applications' do
+ context 'authenticated and authorized user' do
+ it 'creates and returns an OAuth application' do
+ expect do
+ post api('/applications', admin_user), name: 'application_name', redirect_uri: 'http://application.url', scopes: ''
+ end.to change { Doorkeeper::Application.count }.by 1
+
+ application = Doorkeeper::Application.find_by(name: 'application_name', redirect_uri: 'http://application.url')
+
+ expect(response).to have_http_status 201
+ expect(json_response).to be_a Hash
+ expect(json_response['application_id']).to eq application.uid
+ expect(json_response['secret']).to eq application.secret
+ expect(json_response['callback_url']).to eq application.redirect_uri
+ end
+
+ it 'does not allow creating an application with the wrong redirect_uri format' do
+ expect do
+ post api('/applications', admin_user), name: 'application_name', redirect_uri: 'wrong_url_format', scopes: ''
+ end.not_to change { Doorkeeper::Application.count }
+
+ expect(response).to have_http_status 400
+ expect(json_response).to be_a Hash
+ expect(json_response['message']['redirect_uri'][0]).to eq('must be an absolute URI.')
+ end
+
+ it 'does not allow creating an application without a name' do
+ expect do
+ post api('/applications', admin_user), redirect_uri: 'http://application.url', scopes: ''
+ end.not_to change { Doorkeeper::Application.count }
+
+ expect(response).to have_http_status 400
+ expect(json_response).to be_a Hash
+ expect(json_response['error']).to eq('name is missing')
+ end
+
+ it 'does not allow creating an application without a redirect_uri' do
+ expect do
+ post api('/applications', admin_user), name: 'application_name', scopes: ''
+ end.not_to change { Doorkeeper::Application.count }
+
+ expect(response).to have_http_status 400
+ expect(json_response).to be_a Hash
+ expect(json_response['error']).to eq('redirect_uri is missing')
+ end
+
+ it 'does not allow creating an application without scopes' do
+ expect do
+ post api('/applications', admin_user), name: 'application_name', redirect_uri: 'http://application.url'
+ end.not_to change { Doorkeeper::Application.count }
+
+ expect(response).to have_http_status 400
+ expect(json_response).to be_a Hash
+ expect(json_response['error']).to eq('scopes is missing')
+ end
+ end
+
+ context 'authorized user without authorization' do
+ it 'does not create application' do
+ expect do
+ post api('/applications', user), name: 'application_name', redirect_uri: 'http://application.url', scopes: ''
+ end.not_to change { Doorkeeper::Application.count }
+
+ expect(response).to have_http_status 403
+ end
+ end
+
+ context 'non-authenticated user' do
+ it 'does not create application' do
+ expect do
+ post api('/applications'), name: 'application_name', redirect_uri: 'http://application.url'
+ end.not_to change { Doorkeeper::Application.count }
+
+ expect(response).to have_http_status 401
+ end
+ end
+ end
+end
diff --git a/spec/services/issues/move_service_spec.rb b/spec/services/issues/move_service_spec.rb
index f3c98fa5416..388c9d63c7b 100644
--- a/spec/services/issues/move_service_spec.rb
+++ b/spec/services/issues/move_service_spec.rb
@@ -297,9 +297,11 @@ describe Issues::MoveService do
end
context 'project issue hooks' do
- let(:hook) { create(:project_hook, project: old_project, issues_events: true) }
+ let!(:hook) { create(:project_hook, project: old_project, issues_events: true) }
it 'executes project issue hooks' do
+ allow_any_instance_of(WebHookService).to receive(:execute)
+
# Ideally, we'd test that `WebHookWorker.jobs.size` increased by 1,
# but since the entire spec run takes place in a transaction, we never
# actually get to the `after_commit` hook that queues these jobs.
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index edaee03ea6c..1809ae1d141 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -1,10 +1,26 @@
+require 'database_cleaner/active_record/deletion'
+
+module FakeInformationSchema
+ # Work around a bug in DatabaseCleaner when using the deletion strategy:
+ # https://github.com/DatabaseCleaner/database_cleaner/issues/347
+ #
+ # On MySQL, if the information schema is said to exist, we use an inaccurate
+ # row count leading to some tables not being cleaned when they should
+ def information_schema_exists?(_connection)
+ false
+ end
+end
+
+DatabaseCleaner::ActiveRecord::Deletion.prepend(FakeInformationSchema)
+
RSpec.configure do |config|
+ # Ensure all sequences are reset at the start of the suite run
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
end
config.append_after(:context) do
- DatabaseCleaner.clean_with(:truncation, cache_tables: false)
+ DatabaseCleaner.clean_with(:deletion, cache_tables: false)
end
config.before(:each) do
@@ -12,15 +28,15 @@ RSpec.configure do |config|
end
config.before(:each, :js) do
- DatabaseCleaner.strategy = :truncation
+ DatabaseCleaner.strategy = :deletion
end
- config.before(:each, :truncate) do
- DatabaseCleaner.strategy = :truncation
+ config.before(:each, :delete) do
+ DatabaseCleaner.strategy = :deletion
end
config.before(:each, :migration) do
- DatabaseCleaner.strategy = :truncation, { cache_tables: false }
+ DatabaseCleaner.strategy = :deletion, { cache_tables: false }
end
config.before(:each) do
diff --git a/spec/support/features/discussion_comments_shared_example.rb b/spec/support/features/discussion_comments_shared_example.rb
index fa94aa2ae3d..c8662d41769 100644
--- a/spec/support/features/discussion_comments_shared_example.rb
+++ b/spec/support/features/discussion_comments_shared_example.rb
@@ -143,15 +143,17 @@ shared_examples 'discussion comments' do |resource_name|
end
if resource_name == 'merge request'
+ let(:note_id) { find("#{comments_selector} .note", match: :first)['data-note-id'] }
+
it 'shows resolved discussion when toggled' do
click_button "Resolve discussion"
- expect(page).to have_selector('.note-row-1', visible: true)
+ expect(page).to have_selector(".note-row-#{note_id}", visible: true)
refresh
click_button "Toggle discussion"
- expect(page).to have_selector('.note-row-1', visible: true)
+ expect(page).to have_selector(".note-row-#{note_id}", visible: true)
end
end
end
diff --git a/spec/uploaders/job_artifact_uploader_spec.rb b/spec/uploaders/job_artifact_uploader_spec.rb
index 14fd5f3600f..98a4373e9d0 100644
--- a/spec/uploaders/job_artifact_uploader_spec.rb
+++ b/spec/uploaders/job_artifact_uploader_spec.rb
@@ -8,7 +8,7 @@ describe JobArtifactUploader do
describe '#store_dir' do
subject { uploader.store_dir }
- let(:path) { "#{job_artifact.created_at.utc.strftime('%Y_%m_%d')}/#{job_artifact.project_id}/#{job_artifact.id}" }
+ let(:path) { "#{job_artifact.created_at.utc.strftime('%Y_%m_%d')}/#{job_artifact.job_id}/#{job_artifact.id}" }
context 'when using local storage' do
it { is_expected.to start_with(local_path) }
@@ -45,7 +45,7 @@ describe JobArtifactUploader do
it { is_expected.to start_with(local_path) }
it { is_expected.to include("/#{job_artifact.created_at.utc.strftime('%Y_%m_%d')}/") }
- it { is_expected.to include("/#{job_artifact.project_id}/") }
+ it { is_expected.to include("/#{job_artifact.job_id}/#{job_artifact.id}/") }
it { is_expected.to end_with("ci_build_artifacts.zip") }
end
end
diff --git a/spec/views/projects/pipelines_settings/_show.html.haml_spec.rb b/spec/views/projects/pipelines_settings/_show.html.haml_spec.rb
index 95f0be49412..7b300150874 100644
--- a/spec/views/projects/pipelines_settings/_show.html.haml_spec.rb
+++ b/spec/views/projects/pipelines_settings/_show.html.haml_spec.rb
@@ -13,8 +13,8 @@ describe 'projects/pipelines_settings/_show' do
render
expect(rendered).to have_css('.settings-message')
- expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a domain name and the')
- expect(rendered).to have_link('Kubernetes service')
+ 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
@@ -27,8 +27,8 @@ describe 'projects/pipelines_settings/_show' do
render
expect(rendered).to have_css('.settings-message')
- expect(rendered).to have_text('Auto Review Apps and Auto Deploy need the')
- expect(rendered).to have_link('Kubernetes service')
+ expect(rendered).to have_text('Auto Review Apps and Auto Deploy need a')
+ expect(rendered).to have_link('Kubernetes cluster')
end
end
end
diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
index 5ebad58e171..a7cd2bc972c 100644
--- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
+++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml
@@ -345,12 +345,6 @@ production:
replicas="$new_replicas"
fi
- if [[ "$CI_PROJECT_VISIBILITY" != "public" ]]; then
- secret_name='gitlab-registry'
- else
- secret_name=''
- fi
-
helm upgrade --install \
--wait \
--set service.enabled="$service_enabled" \
@@ -358,7 +352,6 @@ production:
--set image.repository="$CI_APPLICATION_REPOSITORY" \
--set image.tag="$CI_APPLICATION_TAG" \
--set image.pullPolicy=IfNotPresent \
- --set image.secrets[0].name="$secret_name" \
--set application.track="$track" \
--set application.database_url="$DATABASE_URL" \
--set service.url="$CI_ENVIRONMENT_URL" \
@@ -488,9 +481,6 @@ production:
function create_secret() {
echo "Create secret..."
- if [[ "$CI_PROJECT_VISIBILITY" == "public" ]]; then
- return
- fi
kubectl create secret -n "$KUBE_NAMESPACE" \
docker-registry gitlab-registry \
diff --git a/yarn.lock b/yarn.lock
index dac3a0f473d..d10a4372a40 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4185,9 +4185,9 @@ isurl@^1.0.0-alpha5:
has-to-string-tag-x "^1.2.0"
is-object "^1.0.1"
-jasmine-core@^2.6.3:
- version "2.6.3"
- resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.6.3.tgz#45072950e4a42b1e322fe55c001100a465d77815"
+jasmine-core@^2.9.0:
+ version "2.9.0"
+ resolved "https://registry.yarnpkg.com/jasmine-core/-/jasmine-core-2.9.0.tgz#bfbb56defcd30789adec5a3fbba8504233289c72"
jasmine-jquery@^2.1.1:
version "2.1.1"
@@ -4269,7 +4269,7 @@ json-stable-stringify@~0.0.0:
dependencies:
jsonify "~0.0.0"
-json-stringify-safe@5.0.x, json-stringify-safe@^5.0.1, json-stringify-safe@~5.0.1:
+json-stringify-safe@5.0.x, json-stringify-safe@~5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
@@ -6067,11 +6067,9 @@ raphael@^2.2.7:
dependencies:
eve-raphael "0.5.0"
-raven-js@^3.14.0:
- version "3.14.0"
- resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.14.0.tgz#94dda81d975fdc4a42f193db437cf70021d654e0"
- dependencies:
- json-stringify-safe "^5.0.1"
+raven-js@^3.22.1:
+ version "3.22.1"
+ resolved "https://registry.yarnpkg.com/raven-js/-/raven-js-3.22.1.tgz#1117f00dfefaa427ef6e1a7d50bbb1fb998a24da"
raw-body@2:
version "2.3.2"